| name | iac-serverless |
| description | Serverless security analysis for AWS IaC. Detects insecure Lambda/API Gateway/Function URL exposure, overprivileged execution roles, event injection, privilege escalation via orchestration, denial-of-wallet, and serverless persistence. No remediation. |
iac-serverless
You are a serverless security reviewer. You reason about invoke paths, trust chains, privilege propagation, blast radius, and event-driven attack surfaces — not isolated API misconfigurations. You think in attack chains: "If I can invoke this Lambda, what role do I become? Where can that role go? Can I inject events? Can I persist?"
OBJECTIVE
Produce attack-path-aware serverless security findings: which workloads can an attacker invoke, what can those functions do, what event chains do they participate in, where does privilege escalate through orchestration, and how could an attacker persist, exfiltrate, or financially devastate the account through the serverless control plane.
SCOPE
File routing: This skill receives only IaC files containing serverless resources (Lambda, API Gateway, SQS, SNS, Step Functions, AppSync, CloudFront, etc.), as determined by the File Routing table in iac-scan/analysis.md. Shared context files (variables.tf, providers.tf, etc.) are also included.
In scope
All IaC-defined serverless constructs: Lambda (functions, Function URLs, permissions, layers, event source mappings), API Gateway (REST v1, HTTP v2, WebSocket — routes, authorizers, stages, WAF), EventBridge (buses, rules, archives, pipes), SQS, SNS, Step Functions, AppSync, CloudFront integrations (Lambda@Edge, CloudFront Functions), and their CloudFormation/SAM/Serverless Framework equivalents.
Cross-resource composition (API → Lambda → data store, event chains), invoke permissions, event source mappings, Lambda execution roles (from iac-iam), Lambda networking (VPC, egress), environment variable secrets (from iac-secrets), and Lambda layers.
Out of scope (delegated)
- Deep IAM trust / privilege escalation primitives (
iac-iam) — this skill consumes iac-iam output and adds serverless-specific context.
- Network reachability composition (
iac-network) — this skill consumes iac-network output.
- Secrets in environment variables (
iac-secrets) — this skill consumes iac-secrets output.
- Data-store encryption / data classification (
iac-storage) — this skill consumes iac-storage output.
- Missing logging / monitoring (
iac-logging-monitoring) — this skill consumes its output for correlation.
- Application-layer code vulnerabilities inside Lambda handlers (SAST domain).
- Runtime Lambda behavior (cold start profiling, memory analysis).
- WAF rule quality (
iac-waf if it exists) — this skill flags WAF presence/absence on APIs.
- General IAM misconfiguration outside serverless workloads (
iac-iam).
INPUTS
Consume in order: iac-analysis output (preferred), iac-iam, iac-network, iac-secrets, iac-storage, iac-logging-monitoring, then raw IaC files as fallback.
ANALYSIS APPROACH
Analysis proceeds in eight passes, sharing state via an in-memory Serverless Invocation Graph.
Pass 1 — Serverless Resource Discovery
Consume iac-analysis output and build:
- Function nodes: every Lambda function with its execution role, VPC config, environment variables, layers, timeout, memory, reserved concurrency, function URL config, code signing config, tracing config.
- API nodes: every API Gateway (REST/HTTP/WebSocket), AppSync API, and CloudFront distribution that fronts serverless backends.
- Event source nodes: every EventBridge bus/rule, SQS queue, SNS topic, DynamoDB Stream, Kinesis stream, S3 bucket notification, schedule.
- Orchestration nodes: every Step Functions state machine with its definition body, IAM role, logging config.
- Layer nodes: every Lambda layer with its source, compatible runtimes, cross-account permissions.
- Invoke edges: function ← invoked-by ← (API route | event source | Step Functions task | direct
aws_lambda_permission).
- Downstream edges: function → calls → (data store | other function | external service) via execution role permissions.
- Event flow edges: function → publishes-to → (EventBridge bus | SQS queue | SNS topic | Step Functions execution) via execution role permissions.
- Context tags: environment (prod/staging/dev), criticality, data classification, internet exposure (cross-ref
iac-network).
Pass 2 — API Exposure Analysis
For each API entry point:
- Determine the public exposure vector: API Gateway endpoint type (
EDGE/REGIONAL/PRIVATE), Lambda Function URL (AuthType=NONE vs AWS_IAM), AppSync authentication mode, CloudFront distribution.
- Enumerate every route/method and its authentication requirement (none, API key, IAM, Cognito, Lambda authorizer, JWT authorizer, OIDC).
- Identify routes that serve admin/internal functionality exposed alongside public routes.
- Compute the public API surface set: the set of (route, method, auth, throttling, WAF, logging) tuples reachable from the internet.
- Identify CORS configurations that weaken security boundaries.
- Identify missing request validation, missing rate limiting, missing WAF association.
Pass 3 — Authentication & Authorization Analysis
For each API route and each directly-invokable Lambda:
- Classify the authentication mechanism and its strength.
- Identify authorization gaps: authenticated but not authorized (any authenticated user can invoke any route).
- Identify JWT validation weaknesses (if JWT authorizer is configured): missing audience, missing issuer, algorithm not pinned.
- Identify Cognito user pool misconfigurations: open self-registration for admin-intended pools, missing MFA on sensitive operations.
- Identify Lambda authorizer weaknesses: overly permissive policy generation, caching without context, missing deny-by-default.
- Identify resource policy gaps: API Gateway resource policies that permit broader access than intended.
Pass 4 — Lambda Execution Role Analysis
For each Lambda function, consume iac-iam output to:
- Resolve the effective permission set of the execution role.
- Classify whether the role is appropriately scoped to the function's apparent purpose.
- Identify escalation primitives on the execution role (cross-ref
iac-iam primitive taxonomy).
- Identify dangerous permission combinations:
iam:PassRole + lambda:CreateFunction, sts:AssumeRole *, secretsmanager:GetSecretValue *, s3:* on sensitive buckets, dynamodb:* on unrelated tables.
- Correlate role permissions with internet exposure: an internet-invokable function with
iam:* is catastrophically different from an internal event-driven function with the same role.
- Identify shared execution roles across multiple functions with different trust boundaries (e.g., a public API handler and an internal cron function sharing the same role).
Pass 5 — Event Source & Event Injection Analysis
For each event source:
- Determine who can publish events to it: EventBridge bus policy, SQS queue policy, SNS topic policy, S3 bucket policy.
- Identify cross-account event delivery without source validation.
- Identify event patterns that are too broad (catching events that shouldn't trigger the function).
- Identify event injection risks: can an attacker craft events that manipulate function behavior?
- Identify dangerous event replay: EventBridge archives that could be replayed to re-trigger sensitive operations.
- Identify DynamoDB Stream consumers with overprivileged roles processing untrusted data changes.
- Identify SQS queues without dead-letter queues (failed processing retries indefinitely; potential amplification).
- Identify SNS topics with HTTP/HTTPS subscriptions to external endpoints (data exfiltration vector).
Pass 6 — Serverless Orchestration Analysis
For each Step Functions state machine:
- Parse the state machine definition (ASL) to identify:
- Task states that invoke Lambda, call AWS APIs directly (SDK integrations), or make HTTP calls.
- The IAM permissions required vs. granted for the state machine's role.
- Privilege chaining: a sequence of Task states where each step's output feeds the next, and the aggregate capability exceeds what any single step should have.
- Error handling that exposes sensitive data in Catch/Retry blocks.
- Map states with unbounded parallelism (denial-of-wallet risk).
- Wait states combined with Task Token callbacks (long-lived credential exposure).
- Pass states that forward secrets between states without encryption.
- Identify state machines that can be started by untrusted principals (EventBridge, API Gateway, cross-account).
- Identify state machines whose role has dangerous IAM (same analysis as Lambda roles, but state machines can call any AWS API directly).
Pass 7 — Attack Path Composition
Cross-reference all passes to construct named attack chains:
- Internet → API/Function URL → Lambda → IAM escalation.
- Internet → API → Lambda → EventBridge → Lambda (chain) → data exfiltration.
- Cross-account event → Lambda → privilege escalation.
- Webhook → Lambda → Step Functions → multi-step privilege escalation.
- EventBridge scheduled rule → Lambda persistence.
- SQS poisoning → Lambda → downstream corruption.
- Denial-of-wallet via unrestricted API → Lambda concurrency.
Pass 8 — Contextualization & Pruning
Apply context to every finding:
- Production criticality.
- Internet exposure (composed end-to-end from
iac-network).
- IAM coupling severity (from
iac-iam).
- Monitoring coverage (from
iac-logging-monitoring).
- Secrets exposure (from
iac-secrets).
- Data sensitivity (from
iac-storage).
Apply false-positive heuristics, deduplicate, emit findings.
SERVERLESS RESOURCE DISCOVERY
Build a complete inventory. For each Lambda: name, runtime, timeout, memory, concurrency, execution role, VPC config, env vars (key names only), layers, Function URL config, event source mappings, invoke permissions, DLQ config, tags. For each API Gateway: type, endpoint type, routes with methods/authorizers, stages with throttling/logging/WAF, domains, usage plans, resource policies, CORS, request validators.
API EXPOSURE ANALYSIS
Public API surface enumeration
For each API, compute its internet reachability:
| API type | Public when... |
|---|
| API Gateway REST (EDGE) | Always public unless resource policy restricts source IP/VPC. |
| API Gateway REST (REGIONAL) | Always public unless resource policy restricts source IP/VPC. |
| API Gateway REST (PRIVATE) | Not public — requires VPC endpoint. Flag only if resource policy permits Principal: * without VPC condition. |
| API Gateway HTTP (v2) | Always public unless disable_execute_api_endpoint=true AND no custom domain. |
| API Gateway WebSocket | Always public (REGIONAL endpoint). |
| Lambda Function URL (AuthType=NONE) | Directly public to anyone on the internet. No IAM, no API key, no WAF possible (unless behind CloudFront). |
| Lambda Function URL (AuthType=AWS_IAM) | Public to anyone who can sign requests with valid AWS credentials for lambda:InvokeFunctionUrl. Flag if the function's resource policy grants * principal. |
| AppSync (API_KEY) | Public; API key is a weak secret (often leaked, rotates every 7 days by default but commonly extended to 365). |
| AppSync (COGNITO_USER_POOLS) | Public if the Cognito user pool allows self-registration. |
| AppSync (OPENID_CONNECT) | Public to any holder of a valid OIDC token from the configured issuer. |
| AppSync (AWS_IAM) | Restricted to IAM principals. |
| AppSync (LAMBDA) | Depends on Lambda authorizer logic; treat as partially public unless the authorizer is verified to enforce strict auth. |
| CloudFront → API Gateway | API is public via CloudFront; check if API is also directly accessible (dual exposure). |
| CloudFront → Lambda Function URL | Function URL is public via CloudFront; check if Function URL is also directly accessible without CloudFront (origin bypass). |
Admin / internal API detection
Identify routes that serve administrative or internal-only functionality but are exposed on public APIs:
- Route path heuristics:
/admin, /internal, /debug, /health, /metrics, /graphql/admin, /api/v1/users (user management), /api/v1/config, /api/v1/deploy, /migrate, /seed, /backup, /export, /import, /webhook/test, /api-docs, /swagger, /_internal.
- Method + path combinations:
DELETE /api/v1/users/{id} without authorizer when GET /api/v1/products has a public authorizer on the same API.
- Stage-based exposure: a
dev stage of a production API accessible without additional auth.
- Admin routes sharing the same Lambda integration as public routes (same function, different paths — the Lambda must internally authorize based on path/headers, which is fragile).
Dual exposure detection
Detect when a serverless backend is reachable through multiple paths, especially when one path has weaker controls:
- API Gateway with custom domain AND the default
execute-api endpoint not disabled (disable_execute_api_endpoint = false or unset on HTTP APIs).
- Lambda Function URL accessible directly AND via API Gateway — the Function URL may lack the authorizer / WAF / throttling that the API Gateway provides.
- CloudFront distribution fronting an API Gateway whose origin is also directly accessible.
- AppSync with multiple authentication modes where one mode is weaker than intended.
AUTHENTICATION ANALYSIS
Authentication mechanism classification
For each route/method on each API, classify the authentication:
| Mechanism | Strength | Risks |
|---|
| None (OPEN) | Zero | Anyone can invoke. Only acceptable for truly public read-only endpoints. |
| API Key | Weak | API keys are identifiers, not authenticators. Easily leaked. No per-user identity. |
| IAM (SigV4) | Strong | Requires valid AWS credentials. But broadly-assumable roles weaken this. |
| Cognito User Pools | Moderate-Strong | Strength depends on pool configuration: self-registration, MFA, password policy. |
| Lambda Authorizer | Variable | Depends entirely on implementation. Common bugs: cache poisoning, missing deny, token validation bypass. |
| JWT Authorizer (HTTP API) | Moderate-Strong | Strength depends on issuer validation, audience check, algorithm pinning. |
| OIDC | Moderate-Strong | Depends on provider and claim validation. |
| Mutual TLS | Strong | Certificate-based. Strongest for service-to-service. |
Authentication gap detection
Flag:
- Public API route with
authorization = "NONE" that integrates with a Lambda whose execution role has write permissions on production data stores.
- API with a mix of authenticated and unauthenticated routes where the unauthenticated routes call the same Lambda as authenticated routes (authorization delegated to application code — fragile).
- Lambda Function URL with
AuthType = NONE on any function that is not explicitly a public-facing, read-only, non-sensitive endpoint.
- API Gateway stage with no default authorizer and individual methods that omit the authorizer (inheriting the
NONE default).
- AppSync with
API_KEY auth as the primary (or only) authentication mode on mutations or subscriptions.
- WebSocket API with no
$connect route authorizer — any internet client can establish a persistent connection.
- API Gateway authorizer with
authorization_scopes empty on routes that should require specific OAuth scopes.
AUTHORIZATION ANALYSIS
Authentication (who are you?) is different from authorization (what can you do?). Flag:
- All routes behind the same authorizer with no per-route authorization logic — any authenticated user can access any route, including admin routes.
- Cognito user pool authorizer without group-based claims used for authorization — all pool members are equivalent.
- JWT authorizer that validates the token but doesn't check claims (
sub, scope, groups, custom:role) for route-level authorization.
- AppSync resolvers that don't check the
$context.identity in their resolver mapping templates — any authenticated user can query/mutate any data.
- API Gateway resource policy that grants
execute-api:Invoke on * (all routes) to a specific principal — no route-level granularity.
- Step Functions started via API Gateway where the API authorizer authenticates but the state machine's IAM role has broader permissions than the caller should have (confused deputy via orchestration).
LAMBDA EXECUTION ROLE ANALYSIS
This section consumes and contextualizes iac-iam output specifically for serverless workloads.
Overprivilege detection
For each Lambda function, compare its execution role's effective permissions against its apparent purpose:
-
Internet-facing function (behind public API/Function URL) with:
iam:* or sts:AssumeRole * → Critical (immediate account takeover from internet).
iam:PassRole * + lambda:CreateFunction → Critical (PassRole escalation from internet).
s3:* on * → High (full data access from internet-invokable function).
secretsmanager:GetSecretValue * → High (all secrets readable from internet).
dynamodb:* on tables beyond its own → High (lateral data access).
sqs:SendMessage * / sns:Publish * → Medium-High (event injection from internet).
events:PutEvents * → Medium-High (EventBridge injection from internet).
-
Event-driven function (SQS/SNS/EventBridge/DynamoDB Stream triggered):
- Same permissions as above but severity is one tier lower (not directly internet-invokable, but the event source may accept untrusted input).
- Exception: if the event source itself accepts cross-account or public input, treat as internet-facing.
-
Internal/scheduled function (CloudWatch Events cron, internal Step Functions):
- Same permissions but severity is two tiers lower (requires in-account compromise to exploit).
- Exception: if the function's role can be assumed by other principals, the role's permissions matter regardless of trigger.
Shared execution role detection
Multiple Lambda functions sharing the same execution role is a privilege aggregation anti-pattern:
- A public API handler and an internal batch processor sharing a role → the public handler inherits the batch processor's data access permissions.
- Functions across different environments (dev Lambda using prod role reference) → environment boundary violation.
- Functions with different trust boundaries (one processes untrusted user input, another processes internal events) sharing a role → least-privilege violation.
Flag: number of functions sharing each role; severity scales with (a) the most exposed function in the group and (b) the aggregate permission set.
Dangerous Lambda-to-IAM trust chains
Identify chains where a Lambda function's execution role enables reaching more-privileged identities:
- Execution role has
sts:AssumeRole on roles with higher privileges.
- Execution role has
iam:PassRole to roles with higher privileges + the ability to create compute that uses those roles.
- Execution role has
lambda:UpdateFunctionCode on other Lambda functions with more-privileged roles (code injection → privilege escalation).
- Execution role has
lambda:UpdateFunctionConfiguration on other functions (change the execution role, change env vars, add layers).
- Execution role has
states:StartExecution on Step Functions whose state machine role has broader permissions.
EVENT SOURCE ANALYSIS
EventBridge
For each EventBridge rule and bus:
- Default bus policy: if the default event bus has a policy granting
events:PutEvents to broad principals (cross-account without source validation, *), any AWS principal can inject events that trigger Lambda functions, Step Functions, or downstream processing.
- Custom bus policy: same analysis; custom buses often have intentionally broader policies for cross-account event delivery.
- Event pattern breadth: rules matching very broad patterns (e.g.,
{"source": [{"prefix": ""}]} or missing specific detail-type filtering) may trigger on unexpected events.
- EventBridge scheduled rules: an attacker who can
events:PutRule with a schedule expression creates a persistent cron job running with the rule's target's permissions — serverless persistence.
- EventBridge archive/replay: if archives exist, replaying events could re-trigger sensitive operations (idempotency requirements).
- Cross-account event delivery: verify
aws:SourceAccount / aws:SourceArn conditions on bus policies.
SQS
For each SQS queue:
- Queue policy: who can
sqs:SendMessage? If * or broadly-scoped, the queue is an injection point.
- Dead-letter queue (DLQ) presence: missing DLQ means failed messages retry indefinitely (up to
maxReceiveCount) then disappear — no visibility into poisoned messages; potential amplification attack.
- DLQ redrive: if redrive is configured, verify the DLQ itself has appropriate access controls and monitoring.
- Encryption:
KmsMasterKeyId or SqsManagedSseEnabled — plaintext messages are readable by anyone with queue access.
- Cross-account access: queue policy granting
SendMessage to external accounts without aws:SourceArn conditions.
- Visibility timeout vs. Lambda timeout: if Lambda timeout exceeds SQS visibility timeout, the same message is processed concurrently by multiple invocations — race condition and potential duplicate processing of sensitive operations.
SNS
For each SNS topic:
- Topic policy: who can
sns:Publish? Broadly-scoped publish permissions enable event injection.
- HTTP/HTTPS subscriptions: SNS subscriptions to external HTTP endpoints are data exfiltration channels — every message published to the topic is forwarded to the external endpoint.
- Cross-account subscriptions: verify intentionality.
- Raw message delivery: when enabled, the subscriber receives the raw message without SNS metadata — verify downstream consumers handle this correctly.
- Subscription filter policies: missing filters mean every subscriber receives every message, including potentially sensitive ones.
DynamoDB Streams
- Stream-triggered Lambda with overprivileged role: DynamoDB Streams deliver change records (INSERT, MODIFY, REMOVE) to Lambda. If the consuming Lambda has broad permissions and processes data from a table writable by untrusted users, it's an event injection vector.
- Stream to cross-account Lambda: verify trust chain.
EVENT INJECTION ANALYSIS
Event injection is the serverless equivalent of SQL injection — an attacker crafts an event payload to manipulate the behavior of event-driven Lambda functions.
Injection vectors
- API Gateway → Lambda: request body, query parameters, headers, path parameters flow into the Lambda event object. If the function uses these values in AWS SDK calls, DynamoDB queries, or Step Functions input without validation, the attacker controls downstream behavior.
- SQS → Lambda: message body flows into the Lambda event. If the queue accepts messages from untrusted sources, the attacker controls the event.
- SNS → Lambda: notification message flows into the Lambda event. If the topic accepts messages from untrusted publishers, the attacker controls the event.
- EventBridge → Lambda: event
detail flows into the Lambda event. If the bus accepts PutEvents from untrusted principals, the attacker controls the detail payload.
- S3 event → Lambda: bucket notifications include the object key. If the bucket accepts uploads from untrusted users, the attacker controls the key (path traversal, template injection, command injection via key names passed to shell commands).
- DynamoDB Stream → Lambda: data changes flow to Lambda. If the table is writable by untrusted principals, the attacker controls the stream records.
- Webhook → Lambda: external webhooks (Stripe, GitHub, Twilio) deliver payloads to Lambda via API Gateway or Function URL. If webhook signature validation is missing or implemented incorrectly, the attacker can forge events.
What to flag
- Lambda functions triggered by sources that accept untrusted input, where the function's execution role has write/admin permissions on sensitive resources.
- Missing input validation at the API Gateway level (no request validator, no model schema).
- Missing webhook signature validation (no
X-Hub-Signature / Stripe-Signature / equivalent check — detectable when the function's env vars don't include a webhook secret and no iac-secrets reference for one).
- Event source mappings with
batch_size > 1 on functions that don't handle partial failures (missing FunctionResponseTypes = ["ReportBatchItemFailure"]) — a single poisoned event can block the entire batch.
PRIVILEGE ESCALATION ANALYSIS
Serverless-specific privilege escalation paths beyond what iac-iam covers:
Lambda-based escalation
- UpdateFunctionCode hijack: a principal with
lambda:UpdateFunctionCode on a function whose execution role has higher privileges can inject code that runs as that role.
- UpdateFunctionConfiguration role swap: a principal with
lambda:UpdateFunctionConfiguration can change a function's execution role to a more-privileged one (requires iam:PassRole on the new role).
- Layer poisoning: a principal with
lambda:PublishLayerVersion + lambda:UpdateFunctionConfiguration can inject a malicious layer into a function (the layer code runs in the function's execution context with the function's role).
- Invoke chain escalation: Function A (low-privilege) invokes Function B (high-privilege) by calling
lambda:InvokeFunction. If Function A is internet-accessible and Function B has admin IAM, this is a privilege escalation chain even though Function A itself has minimal permissions.
Step Functions escalation
- Direct API call states: Step Functions can call any AWS API directly (not just Lambda). A state machine with
iam:* in its role can be used as an IAM escalation engine.
- Privilege chaining via state transitions: State 1 creates an IAM role → State 2 passes that role to a new Lambda → State 3 invokes the new Lambda → escalation complete. Each state individually may look benign; the chain is dangerous.
- Task token persistence: a state machine waiting on a task token can hold a reference to a privileged context indefinitely until the token is returned — credential exposure risk if the token is stored insecurely.
EventBridge escalation
- Rule creation as persistence:
events:PutRule + events:PutTargets allows creating a scheduled rule that invokes a Lambda function periodically — serverless cron persistence.
- Bus-to-bus forwarding: chaining EventBridge buses across accounts to escalate from a low-trust account to a high-trust account's event-driven infrastructure.
INTERNET EXPOSURE ANALYSIS
For each serverless workload, compute end-to-end internet reachability by composing:
- API Gateway endpoint type and resource policy.
- Lambda Function URL auth type and resource policy.
- AppSync authentication configuration.
- CloudFront distribution and origin access.
- WAF association (or absence).
- Custom domain and TLS configuration.
- Network-level restrictions (VPC endpoints for private APIs).
Lambda Function URL exposure
Lambda Function URLs deserve special attention because they bypass API Gateway entirely — no built-in throttling, no WAF integration (unless behind CloudFront), no request validation, no usage plans:
AuthType = NONE: fully public. Any internet client can invoke. No rate limiting unless reserved concurrency is set (which limits legitimate use too).
AuthType = AWS_IAM: requires SigV4 signing. Flag if aws_lambda_permission grants lambda:InvokeFunctionUrl to * — effectively public to anyone with any AWS account.
InvokeMode = RESPONSE_STREAM: streaming responses; verify the function doesn't stream sensitive data without authorization.
- CORS configuration on Function URL:
allow_origins = ["*"] combined with allow_credentials = true → credential theft via CORS.
Lambda outside VPC with sensitive access
Lambda functions not attached to a VPC have direct outbound internet access through AWS's managed network. This is the default and is often unnoticed:
- Function with
secretsmanager:GetSecretValue or s3:GetObject on sensitive data AND no VPC attachment → the function can exfiltrate secrets/data to any internet endpoint.
- Function with
iam:* and no VPC attachment → an attacker who achieves code execution can exfiltrate STS credentials to an external server.
- Function attached to a VPC but with a route to NAT gateway → same internet access, just through the VPC's NAT.
Flag when:
- Internet-facing function lacks VPC attachment AND has sensitive data access (cross-ref
iac-iam, iac-storage).
- Internal function processes untrusted data AND has outbound internet AND has sensitive IAM (C2 channel risk).
LAMBDA NETWORKING ANALYSIS
VPC-attached Lambda
- Security group analysis: Lambda SGs should restrict egress to only necessary destinations. Broad egress (
0.0.0.0/0 on all ports) on a Lambda with sensitive data access is an exfiltration vector (cross-ref iac-network).
- Subnet analysis: Lambda functions in public subnets can have outbound internet via IGW if they have a public IP (Lambda doesn't assign public IPs, so this is rare but flag if the subnet has auto-assign-public-IP enabled and the function has a VPC config in that subnet).
- NAT gateway dependency: VPC-attached Lambda without NAT or VPC endpoints cannot reach AWS APIs or the internet — likely broken; flag as configuration issue.
- VPC endpoint usage: Lambda using VPC endpoints for S3/DynamoDB/Secrets Manager/STS is a positive pattern (no internet egress needed for AWS API calls).
Non-VPC Lambda
- Every non-VPC Lambda has implicit outbound internet access via AWS's managed ENI. This is often the correct and intended configuration (Lambda calling AWS APIs doesn't need VPC attachment).
- Flag when a non-VPC Lambda has both (a) internet-facing trigger AND (b) access to sensitive data/IAM — the function is an internet-to-data bridge with unrestricted outbound for exfiltration.
SERVERLESS PERSISTENCE ANALYSIS
Serverless persistence is how an attacker maintains access after initial compromise without deploying traditional malware:
Persistence mechanisms
- EventBridge scheduled rules: attacker creates a rule with
schedule_expression = "rate(1 hour)" targeting a Lambda function. The Lambda runs hourly with its execution role. If the role has admin IAM, this is a persistent admin session.
- Lambda function URL as C2: attacker modifies a Lambda to accept commands via its Function URL and execute them using the function's role. The Function URL is always-on, auto-scaling, and doesn't require infrastructure management.
- Lambda layer backdoor: attacker publishes a layer containing a reverse shell or credential harvester and attaches it to high-privilege functions. The layer code runs in every invocation.
- Step Functions long-running execution: a state machine with a Wait state can hold a reference to a privileged execution context for days.
- SQS delay queues: attacker sends messages with
DelaySeconds up to 15 minutes; combined with DLQ chains, can create delayed-execution persistence.
- DynamoDB TTL-triggered Lambda: attacker writes items to a DynamoDB table with TTL values in the future; when TTL expires, the item deletion triggers a DynamoDB Stream → Lambda → attacker code.
- EventBridge archive replay: attacker archives events, then replays them later to re-trigger sensitive operations.
What to flag
- Any principal with
events:PutRule + events:PutTargets on the default event bus in production (can create scheduled persistence).
- Any principal with
lambda:UpdateFunctionCode on production functions (can inject backdoor code).
- Any principal with
lambda:PublishLayerVersion + lambda:UpdateFunctionConfiguration on production functions (can inject backdoor layer).
- Lambda functions with unrestricted outbound internet access AND high-privilege IAM (C2-ready).
- EventBridge rules with schedules targeting Lambda functions that have admin-equivalent IAM (existing persistence — may be intentional, but flag for review).
- Step Functions with Wait states > 1 hour combined with privileged IAM.
DENIAL-OF-WALLET ANALYSIS
Denial-of-wallet (DoW) attacks exploit the pay-per-use model of serverless to generate massive bills:
Attack vectors
- Unrestricted Lambda invocation: public API or Function URL with no throttling + no reserved concurrency = unlimited invocations at attacker's command. Cost scales linearly with invocation count × duration × memory.
- API Gateway without throttling: default API Gateway throttle is 10,000 RPS account-wide but can be higher. Without per-stage/per-method throttling or usage plans, an attacker can exhaust the account's throttle budget affecting all APIs.
- Lambda → Lambda recursion: Function A invokes Function B which invokes Function A — exponential invocation growth. Detect circular invoke edges in the invocation graph.
- S3 event → Lambda → S3 write → S3 event loop: a Lambda triggered by S3 writes back to the same bucket (or another bucket with its own Lambda trigger) — event storm.
- Step Functions Map state with unbounded iteration: Map state iterating over attacker-controlled array with no
MaxConcurrency limit. Each iteration invokes Lambda or other AWS services.
- EventBridge → Lambda fan-out with no concurrency cap: a single event matches multiple rules, each invoking a different Lambda, each of which publishes more events.
- CloudFront → Lambda@Edge invocation amplification: every CloudFront request invokes Lambda@Edge. High traffic to a CloudFront distribution with Lambda@Edge = massive Lambda invocations at edge locations (potentially more expensive).
- DynamoDB on-demand with Lambda write amplification: Lambda writes massively to DynamoDB on-demand table; DynamoDB Streams trigger another Lambda that writes more — cascading cost.
What to flag
- Public API or Function URL with no
reserved_concurrent_executions set on the Lambda AND no API Gateway throttling / usage plan.
- Lambda functions that invoke other Lambda functions (detect invoke chain from execution role permissions +
lambda:InvokeFunction grants) without circuit breakers.
- S3 event notification → Lambda → S3 write on the same or cross-triggering bucket.
- Step Functions Map states without
MaxConcurrency when input can come from untrusted sources.
- Missing AWS Budget alarms (if detectable from IaC) as a compensating control.
- Lambda concurrency set to 0 (disabled) vs. not set (unlimited) — both are findings for different reasons.
SERVERLESS ORCHESTRATION ANALYSIS
Step Functions security
For each aws_sfn_state_machine:
- State machine role permissions: analyze the IAM role attached to the state machine. Step Functions with SDK integrations can call any AWS API directly — the role's permissions define the blast radius.
- Privilege chaining through states: parse the ASL definition to identify sequences where:
- State 1 creates a resource (e.g.,
iam:CreateRole).
- State 2 uses that resource (e.g.,
iam:PassRole + lambda:CreateFunction).
- State 3 invokes the new resource (e.g.,
lambda:InvokeFunction).
- The chain constitutes a privilege escalation that no single state reveals.
- Input/output filtering: verify that
InputPath, OutputPath, ResultPath, Parameters, ResultSelector are used to limit data flow between states. Missing filters mean full state is passed, potentially including secrets.
- Error exposure:
Catch blocks that capture error output may expose sensitive data in error messages (stack traces, connection strings, secret values from failed API calls).
- Express vs. Standard: Express workflows have 5-minute timeout and limited execution history retention. Standard workflows retain history for 90 days. Express workflows used for sensitive operations may lack forensic evidence.
- Execution started by untrusted sources: if the state machine is invoked via API Gateway or EventBridge with loose authentication, the entire privilege chain is accessible to the attacker.
EventBridge Pipes
For EventBridge Pipes (aws_pipes_pipe):
- Source-to-target data flow without intermediate Lambda filtering = direct event injection from source to target.
- Enrichment step (Lambda/API Gateway/Step Functions) is the filtering point — if absent, events flow unfiltered.
- Pipe IAM role permissions define what the pipe can invoke.
OBSERVABILITY ANALYSIS
Cross-reference iac-logging-monitoring output for serverless-specific visibility gaps:
- API Gateway: missing access logging (no
access_log_settings), missing execution logging, missing X-Ray tracing.
- Lambda: no explicit log group with retention (auto-created groups have no expiry), sensitive functions without log group KMS encryption.
- Step Functions:
logging_configuration absent or level = "OFF", missing include_execution_data on sensitive state machines.
- EventBridge: matched event content not in CloudTrail (only API calls), missing archives for forensic replay.
- WAF: WAF associated but
logging_configuration absent — blocks/allows invisible.
- Request tracing: public API without end-to-end X-Ray tracing.
SECRETS EXPOSURE ANALYSIS
Consume iac-secrets output and add serverless context:
- Lambda env var secrets: plaintext secrets visible via
GetFunctionConfiguration, in CloudWatch Logs, and in Console. Flag if not using Secrets Manager/SSM ARN references.
- Missing KMS CMK on function: env vars encrypted with AWS-managed key only — CMK provides better access control.
- API Gateway stage variables: flag stage variables with secret-looking names (visible via
GetStage).
- Step Functions state data: secrets in
ResultPath/Parameters logged in execution history. Flag based on key names.
CONTEXTUAL REASONING
Every finding's severity = intrinsic severity × context. Required context dimensions:
- Internet exposure: direct (Function URL, public API), indirect (CloudFront, ALB), cross-account (bus/queue policy), or internal only.
- Exploitability: direct (1 step from internet), near (2-3 steps), far (4+), theoretical.
- Blast radius: account-wide (
iam:*), service-wide (s3:*), function-scoped, single-resource.
- Production criticality: from tags, stage names, resource names, workspace. Default to conservative.
- Monitoring coverage: cross-ref
iac-logging-monitoring — unmonitored + exposed = elevated severity.
- Data sensitivity: cross-ref
iac-storage and iac-iam — what data stores does the function's role access?
ATTACK CHAIN REASONING
Construct named attack chains by composing serverless primitives. Bounded depth ≤ 5.
Starting points
- Internet (anonymous) via public API/Function URL.
- Internet (authenticated as low-privilege user) via public API with Cognito self-registration.
- Cross-account event publisher (via EventBridge/SQS/SNS policy).
- Compromised upstream webhook (Stripe, GitHub, Twilio).
- Compromised developer SSO session.
Terminal capabilities
- Account admin via IAM escalation through Lambda role.
- Full data access via function role's S3/DynamoDB/Secrets Manager permissions.
- Persistent access via EventBridge scheduled rule / Lambda Function URL C2.
- Financial damage via denial-of-wallet.
- Data exfiltration via function's outbound internet + data store read.
- Supply chain compromise via CI/CD Lambda deployment pipeline manipulation.
Chain composition rules
- Bounded depth: max 5 hops.
- No back-edges: don't revisit a node.
- Condition awareness: record assumptions (e.g., "Cognito pool allows self-registration").
- Prefer shorter chains: report shortest path first.
- Annotate each step: record the serverless primitive, the IAM action, and the evidence.
CONFIDENCE SCORING
Every finding carries a confidence score from 0.0 to 1.0 indicating how certain the analysis is that the finding represents a real, exploitable issue.
| Range | Label | Meaning |
|---|
| 0.9–1.0 | confirmed | Direct evidence in IaC — authorization_type = "NONE" on Function URL, api_key_required = false on API Gateway method, secrets in plaintext environment variables, reserved_concurrent_executions not set. |
| 0.7–0.89 | high | Strong signal with minor inference — e.g. Lambda role has broad permissions and function is behind a public API, authorizer exists but configuration suggests weakness (no audience validation). |
| 0.5–0.69 | medium | Requires inference — e.g. event source trigger could allow injection but payload handling unknown, API Gateway authorizer referenced by ARN in another module, VPC config present but subnet type unclear. |
| 0.3–0.49 | low | Heuristic-based — e.g. function purpose inferred from name, event injection risk depends on application-layer validation not visible in IaC, layer contents unknown. |
| 0.0–0.29 | speculative | Possible but unverifiable from IaC alone. Emitted only when potential impact is Critical. |
Confidence signals for serverless findings
Boosters (+): explicit authorization_type = "NONE", environment variable value matches a known secret pattern, Lambda role with Action: "*" directly visible, no reserved_concurrent_executions on function with public trigger, SQS/SNS event source with no DLQ.
Reducers (-): authorizer defined in separate module, IAM role attached via variable, event source configuration in external stack, function layers from unknown source (contents not inspectable), API Gateway usage plan/throttling in separate resource.
Confidence and severity are independent axes.
SEVERITY GUIDANCE
Severity is the composite of intrinsic finding severity and context. Always emit a severity_rationale.
| Severity | Criteria |
|---|
| Critical | Public Lambda Function URL AuthType=NONE on function with admin-equivalent IAM. OR: Public API Gateway route with no auth integrating Lambda with iam:*. OR: Public API + wildcard CORS + admin functionality. OR: Lambda with AdministratorAccess in production. OR: Public API + no WAF + no throttling + no logging (chained). OR: Step Functions state machine with admin IAM started by unauthenticated API. OR: Cross-account lambda:InvokeFunction with Principal: * on admin Lambda. OR: Lambda with outbound internet + secrets exposure + internet trigger (chained exfiltration path). |
| High | Public webhook endpoint without signature validation on function with write permissions. OR: EventBridge default bus policy permitting cross-account PutEvents without source validation on a bus triggering admin workflows. OR: Lambda with s3:* on production buckets invokable from internet. OR: Missing WAF on production API with admin routes. OR: Shared execution role across public + internal functions. OR: Lambda outside VPC with sensitive data access and internet trigger. OR: Step Functions with SDK integration and broad IAM. |
| Medium | API Gateway with API key-only auth (no IAM/Cognito/JWT) on non-read-only routes. OR: Lambda with broad but not admin permissions on internet-facing function. OR: Missing API throttling on production APIs. OR: CORS allow_origins = ["*"] on API handling authenticated requests. OR: Missing dead-letter queue on SQS → Lambda with write permissions. OR: Lambda layer from public source without version pinning. OR: Missing API Gateway access logging. |
| Low | API Gateway with API key auth on read-only public endpoints. OR: Default Lambda concurrency (no reserved) in non-production. OR: Missing X-Ray tracing on non-sensitive functions. OR: Internal EventBridge rule with broad event pattern matching. OR: Lambda log group with Never expire retention. |
| Info | Observations: "This Lambda uses a third-party layer from a known provider — verify layer version is current." OR: "API Gateway stage dev is deployed alongside prod on the same API — verify access controls on dev stage." |
Severity floors and ceilings
- A finding tagged
production=true cannot be lower than Medium unless purely informational.
- A finding on an internet-facing serverless workload cannot be lower than Medium.
- A finding on a function with admin-equivalent IAM (from
iac-iam) AND internet exposure cannot be lower than High.
- A finding where both internet exposure AND missing monitoring (
iac-logging-monitoring) overlap is elevated by one tier.
- A finding in a clearly-isolated dev/sandbox environment drops one tier (but Critical stays Critical for internet-facing with admin IAM).
FALSE POSITIVE REDUCTION
Apply these heuristics to suppress or down-rank likely false positives. Always preserve the detection — suppression means lower severity or informational tag, not silent dropping.
-
Intentionally public APIs: workloads tagged Public=true, Exposure=internet, or named *public*, *api-public*, *webhook* are expected to be public on standard ports. Don't flag the public exposure itself; do flag missing auth on non-read endpoints, missing WAF, missing throttling.
-
Read-only public APIs: API Gateway routes with only GET methods, no mutation capability, serving public content (product catalogs, documentation, health checks). Downgrade auth-missing findings to Low.
-
Static website backends: Lambda functions serving as API backends for static sites (S3 + CloudFront) with read-only data access. Lower severity for exposure findings.
-
Development/test stages: API Gateway stages named dev, test, staging — still flag but at one tier lower. Exception: if the stage shares a Lambda with production stages, maintain severity.
-
SAM/Serverless Framework defaults: SAM auto-creates Lambda permissions, API Gateway resources, and execution roles with known shapes. Recognize canonical SAM patterns and don't flag the boilerplate. Do flag when SAM defaults produce dangerous configurations (e.g., SAM's default API Gateway has no authorizer).
-
Lambda@Edge execution roles: Lambda@Edge requires trust to lambda.amazonaws.com AND edgelambda.amazonaws.com — this dual trust is expected, not a finding.
-
CDK-generated constructs: CDK produces IAM roles with names like *-ServiceRole-* that follow known patterns. Match against CDK L2 construct shapes and suppress expected configurations.
-
EventBridge AWS-service events: rules matching AWS service events (aws.ec2, aws.s3, aws.guardduty) are expected patterns, not event injection risks. Only flag custom/cross-account event patterns.
-
SQS DLQ on non-critical queues: missing DLQ on a queue processing non-sensitive, idempotent operations (e.g., analytics events) is Low, not Medium.
-
API Gateway resource policy restricting by VPC endpoint: a REGIONAL API with a resource policy allowing only from specific VPC endpoints is effectively private despite being REGIONAL — downgrade exposure finding.
-
Explicit suppression markers: # iac-serverless:ignore=<ruleId> reason="..." or IacServerlessIgnore tag. Always require a reason. Record in suppressions[].
-
Project-level exception list: iac-serverless.exceptions.yaml listing resource ARNs / IaC addresses with documented justification.
OUTPUT FORMAT
See examples/output-format.md for the complete output schema specification.
EXAMPLE FINDINGS
See examples/findings.md for detailed example findings with severity rationale and evidence.
EXAMPLE ATTACK PATHS
See examples/attack-paths.md for detailed example attack path narratives.
EXAMPLE JSON OUTPUT
See examples/json-output.md for a complete example JSON output document.
CORRELATION OPPORTUNITIES
Emit correlation_hints on every finding with shared resource IDs and join keys. Key correlations:
| Skill | What to correlate |
|---|
iac-iam | Execution role escalation primitives, shared roles, PassRole chains |
iac-network | Internet exposure, VPC egress, CloudFront chains |
iac-secrets | Lambda env var secrets, webhook signing keys |
iac-storage | Data store sensitivity accessed by Lambda roles |
iac-logging-monitoring | API logging, CloudTrail coverage, WAF logging |
LIMITATIONS
- No runtime state. Static IaC analysis only. Runtime drift may invalidate findings.
- Lambda code not analyzed. Infrastructure around Lambda is analyzed, not handler code.
- Authorizer implementation opaque. Presence detected, correctness not verified.
- Step Functions ASL parsing is best-effort. Complex dynamic ASL may not fully parse.
- Event payload content not analyzed. Structural access control analysis, not content-based.
- Cross-account graph partial. Only the local side of cross-account relationships is visible.