← ALL DOCS
DOCS · SECURITY REVIEWERS

Security overview

This document is for security reviewers. FunctionPilot can read, invoke and change Lambda functions across your AWS accounts, so its controls make sure that each person can do exactly what an administrator granted them, that AWS itself enforces it, and that every action can be traced to a person. Everything described here runs in your own AWS account.

Threat model

ThreatMitigation
A user reaches functions no one granted themTwo independent layers: grants checked by FunctionPilot on every request, and AWS credentials that a per-request session policy limits to the functions and actions the grants allow (Permissions)
A user reads another function's logsFunctionPilot finds the log group from the function's own configuration; the browser never names one, and the session policy allows log actions on that one log group only
Someone impersonates another userFederated sign-in; FunctionPilot trusts only email addresses the user can't choose, and binds the bootstrap admin to an immutable user ID (Identity and sign-in)
A stolen token is used from elsewhereOne-hour ID tokens, revocable 8-hour refresh tokens, and an optional IP allowlist checked on every request
A shared test leaks secretsSaved tests are private by default. Making one public shows a warning that everyone who can invoke the function can read its payload and results; only the owner or an administrator can change or delete it
Environment variables leak secretsValues are hidden unless the person may see them (people with edit, everyone who can view, or administrators only, as an administrator chooses); names are always shown
Actions can't be tracedAn application audit log, plus the user's email as the SourceIdentity of every assumed-role session, so CloudTrail in every account names the person
A third party assumes a spoke role ("confused deputy")Spoke roles trust only the hub's API role, and require your external ID and a source identity
FunctionPilot is used to change its own stack, or to deploy codeConnection roles have no IAM, CloudFormation, DynamoDB or S3 permissions. Lambda changes are limited to configuration, versions, aliases and concurrency; changing code and deleting functions are not offered
Data leaves your accountNo vendor service is in the data path; the stack never contacts the vendor at runtime (What stays in your account)

Out of scope: a malicious administrator of the AWS account (they can change any IAM policy), and the security of your identity provider and of users' devices.

Controls

PrincipleControl
Strong identity on every requestEvery API route requires a Cognito ID token, validated by the API Gateway JWT authorizer (issuer and audience). Administration routes check the role on each call
Least privilegeEach component has its own role with scoped permissions (IAM summary). Users act only through roles under the IAM path /functionpilot/, narrowed per request
Context-aware accessIP allowlist checked on every request (Business and above); outside it, every request except the sign-in check is refused
Short-lived credentialsAssumed-role sessions last 15 minutes. Code download links are short-lived presigned URLs
Strong authenticationYour identity provider's MFA applies. Cognito-only users must use TOTP MFA and passwords of at least 14 characters
Hardened portalStrict Content Security Policy, HSTS and other headers (Portal hardening)
AuditabilityApplication audit log, API access logs, and CloudTrail in every account with the user's source identity (Audit)

Identity and sign-in

FunctionPilot uses an Amazon Cognito user pool in your account, federated to IAM Identity Center, a SAML 2.0 IdP or an OIDC IdP, or with Cognito-managed users. The browser signs in with the authorisation code flow and PKCE.

Roles, grants, saved tests and the bootstrap admin are keyed on the email claim, so someone who could pick their own email could take over another person's access. FunctionPilot therefore accepts the email only when the user can't choose it, and refuses everyone else on every request:

How the user signs inEmail trusted when
SAML: IAM Identity Center, Entra ID, OktaAlways: it is asserted by your directory and signed by the IdP
OIDCThe IdP says it is verified (email_verified, or xms_edov for Entra ID)
Cognito user pool userIt is marked verified; users can't change their own email

The sign-in provider is read from the signed token. BootstrapAdminEmail is bound to the first trusted sign-in's immutable user ID with a conditional write; from then on only that user is the bootstrap admin, even if someone else later presents the same email.

Roles are Administrator and User, set per user email or per group. Groups come from the Cognito user pool groups and from your IdP's groups attribute. See Identity providers.

Permissions

What a user can do with a function is the intersection of:

  1. The connection's IAM role, set by the owner of each account: ReadOnly, Operate or Full (LocalAccessLevel on the hub stack, AccessLevel on each spoke stack).
  2. Grants: user or group, connection (or all), Regions (or all), function name patterns (* and ?), and the permissions view, logs, invoke, edit and manage. Administrators hold every permission, still bounded by the role.

Grants are enforced twice. FunctionPilot checks them on every request, and for every AWS call it assumes the connection's role with:

  • SourceIdentity = the user's email, fixed for the session and visible in CloudTrail;
  • a role session name derived from the user's email;
  • for non-administrators, a session policy that allows only the actions of their permissions, on the function ARNs their patterns match in that account and Region, and log actions on the one log group the request needs. If the policy would exceed the AWS size limit, the request is refused rather than widened.

So even if FunctionPilot's own check had a bug, AWS would refuse anything outside the grants.

A few read APIs have no resource-level permissions (lambda:ListFunctions, cloudwatch:GetMetricData, tag:GetResources). For those, FunctionPilot filters the results by the caller's grants: a user never sees a function they have no grant on, although the AWS layer alone would allow listing names and metrics in that Region. The same filtering applies to everything served from the shared cache.

Code download needs edit; the session policy denies lambda:GetFunction (which returns a code download link) on every other function.

Personal AWS access

On the Team plan and above, administrators can let people act with their own AWS permissions instead of grants. Then AWS alone decides what they can do: no grants and no session policy apply, and CloudTrail records the person's own identity. See Personal AWS access.

  • IAM Identity Center: the person approves FunctionPilot in their AWS access portal (the OAuth device authorization flow) and picks accounts and permission sets. FunctionPilot keeps their Identity Center access token for at most its lifetime (up to 8 hours) to get one-hour role credentials. People who sign in through Identity Center while this is on never get grants.
  • Access keys: an IAM user's keys are exchanged for temporary credentials with sts:GetSessionToken (up to 12 hours, with MFA when the user's policies require it). The secret access key is used once and never stored or logged. Root user keys are refused.

Tokens and temporary credentials are encrypted with a KMS key under an encryption context naming the user, and FunctionPilot's API role may use the key only with that context. Everything is deleted when the person disconnects or the credentials expire. Cached listings and saved tests on a personal connection are shared only by people with the same permission set in the same account, or the same IAM user. If a permission set grants different access per person through session-tag ABAC, use grants instead.

IAM summary

RoleAssumed byPermissions
API role (<NamePrefix>-api-<region>)The API LambdaIts own DynamoDB tables; sts:AssumeRole, sts:SetSourceIdentity and sts:TagSession on roles under /functionpilot/ only; License Manager reads; its log group; KMS through DynamoDB, and encrypt or decrypt on the personal access key only with a FunctionPilot encryption context
Local role (/functionpilot/<NamePrefix>-local-<region>)The API role only, with a source identityLambda reads, CloudWatch GetMetricData, CloudWatch Logs reads and queries. Operate adds lambda:InvokeFunction; Full adds UpdateFunctionConfiguration, PublishVersion, alias and reserved concurrency actions
Spoke role (/functionpilot/<RoleName>, other accounts)The API role only, with your external ID and a source identityAs the local role, by its AccessLevel
Update notifier roleA daily Lambda (only with UpdateChecks = Enabled)Its settings and audit items, and publishing to its SNS topic
Portal deployer roleA CloudFormation custom resourceDeploy one Amplify app from one file in the artifacts bucket
Installer rolesECS (a one-off task) and a LambdaRun the installer task, read licences, pull the image, write the artifacts bucket

Data flows

FlowPathProtection
Sign-inBrowser, Cognito managed login and your IdPTLS; SAML signatures or OIDC; authorisation code flow with PKCE
APIBrowser to API Gateway to the API LambdaTLS 1.2 or later; a JWT on every call; CORS limited to the portal; throttled
AWS callsAPI Lambda to STS, then Lambda, CloudWatch and CloudWatch Logs in any connected accountTLS; per-request, per-user scoped STS credentials with a source identity
Code downloadBrowser to a presigned Lambda code URLOnly with edit; audited
LicenceInstaller and API to AWS License Manager in the hub accountTLS; a read of the account's licences
Code installAWS Marketplace registry to the installer task to the artifacts bucketImage pulled by digest; SHA-256 manifest; private, TLS-only bucket
Update checkDaily read of the public release manifest (optional)A plain HTTPS GET of a public file; nothing is sent

The shared cache

CloudWatch charges per metric returned and per GB scanned, so FunctionPilot keeps a shared cache in a second DynamoDB table: function lists, metrics, findings and Logs Insights results, fetched for a whole Region and shared by every user. Every read from it is filtered by the caller's permissions exactly as a live call would be, and Insights results are only served to callers with logs on that function. The cache never holds environment variable values, resource policies, test payloads or raw log events. It is reachable only by the API role, expires on its own, and can be rebuilt at any time.

What stays in your account

Everything: identities, connections and external IDs, grants, saved tests with their payloads and results, the cache and the audit log. Cloud Certainty runs no service in the data path and receives no telemetry. The vendor's public bucket (templates and the release manifest) and the AWS Marketplace registry are read-only distribution points used at install, at upgrade and, if you leave it on, by the daily update check. Links to cloudcertainty.com in the portal are plain links.

Encryption

  • In transit: TLS everywhere; the artifacts bucket denies non-TLS requests; HSTS on the portal.
  • At rest: DynamoDB, CloudWatch Logs, the SNS topic and the artifacts bucket use your KMS key when you set KmsKeyArn, otherwise AWS owned keys and SSE-S3. Personal access secrets always use a customer managed KMS key (yours, or one the stack creates).

Portal hardening

A strict Content-Security-Policy (no inline or third-party script, frame-ancestors 'none', AWS endpoints only for sign-in and the API), Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy: no-referrer, X-Frame-Options: DENY and a restrictive Permissions-Policy. Tokens are kept in session storage only. The web app ships without source maps.

Audit

Every administrative change, connection and grant change, configuration change, version and alias change, concurrency change, code download, invocation (without the payload), saved test change and personal access connection is audited with the actor, source IP and target. Refused changes and invocations are audited too. Events are kept for your plan's audit days (30 to 3,650) in the table, and are also written to the API's CloudWatch log group for your SIEM. CloudTrail in each account records every AWS call with the person's email as the source identity.

Licensing

FunctionPilot reads its AWS Marketplace licence from AWS License Manager in the hub account, at install and in the API. Nothing about usage is sent anywhere. Without a licence the installer stops and the portal says a licence is required. If License Manager is unreachable, or an active licence disappears (for example while you move between plans), the last active plan keeps working for 72 hours.

Shared responsibility

  • Keep BootstrapAdminEmail on a protected identity with MFA, and grant the Administrator role to a second person.
  • Set connection roles to the lowest access level that works: ReadOnly for production accounts unless teams need to test there.
  • Prefer grants to groups, with function name prefixes per team (orders-*).
  • Treat public saved tests like shared documentation: no real customer data or secrets in payloads.
  • Forward CloudTrail and FunctionPilot's log groups to your SIEM.

Something unclear or missing? Email [email protected].

Tell us what you have. We'll make it better and cheaper.

Send a short description of your current setup. We will tell you what we would improve, what it would cost, and where the savings are. A considered reply from an engineer, not a sales sequence.

TELL US ABOUT YOUR SETUP →