Safeguard
Cloud Security

AWS Lambda Security: A Deep Dive Into the Function Attack Surface

An attack-surface walkthrough of AWS Lambda security — execution roles, resource-based policies, environment-variable secrets, function URLs, and dependency layers — with IAM policy and CLI examples.

Marcus Chen
Cloud Security Engineer
5 min read

AWS Lambda strips away the server, but it leaves you with a surprisingly rich attack surface: the execution role a function assumes, the resource-based policy that decides who can invoke it, the environment variables it reads, the function URL it might expose, and the dependency layers it bundles. Each of these is a place where a small mistake becomes an account-wide problem. Because Lambda functions are ephemeral — a cold start can complete in under a second and the execution environment is recycled shortly after — traditional agent-based detection often can't attach in time. That makes getting the configuration right before deploy far more valuable than trying to catch abuse at runtime. Let's walk the surface piece by piece.

The Execution Role: Your Biggest Lever

Every Lambda function assumes an IAM execution role, and that role is what an attacker who achieves code execution inherits. The two failure modes are identical to the rest of IAM: wildcard actions and wildcard resources.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["dynamodb:GetItem", "dynamodb:Query"],
    "Resource": "arn:aws:dynamodb:us-east-1:111122223333:table/Orders"
  }]
}

That policy grants exactly what a read path needs — two actions on one table. Compare it to "Action": "dynamodb:*" on "Resource": "*", which turns a single vulnerable dependency into full database access. Give each function its own role rather than sharing one across a service, and use IAM Access Analyzer's policy generation against CloudTrail history to strip permissions a role has never actually used.

Resource-Based Policies and Function URLs

A separate policy — the resource-based policy — controls who and what can invoke the function. Misconfigure it and an unintended service or account triggers your code. Audit it directly:

# Who is allowed to invoke this function?
aws lambda get-policy --function-name process-orders \
  --query Policy --output text | python -m json.tool

Function URLs deserve special caution. A function URL with AuthType: NONE is a public HTTPS endpoint hitting your code with no IAM check in front of it — convenient for a webhook, dangerous for anything that touches sensitive data. Prefer AWS_IAM auth, or front the function with API Gateway so you get request validation, throttling, and WAF integration.

Secrets Do Not Belong in Environment Variables

Lambda environment variables are convenient and visible in plaintext to anyone holding lambda:GetFunctionConfiguration. They're also captured in CloudFormation templates and Terraform state, so a leaked IaC repo leaks the credentials too. Pull secrets from AWS Secrets Manager or SSM Parameter Store at invocation time, cache them for the container's lifetime, and let KMS handle encryption.

aws secretsmanager get-secret-value \
  --secret-id prod/orders/db --query SecretString --output text

If you must use environment variables for non-secret config, at least confirm they aren't holding tokens left over from a prototype.

Third-Party Layers Are Unverified Code

Lambda layers are a convenient way to share dependencies and native binaries across functions, but a layer you pull from a public ARN is code running inside your function's execution context with your function's permissions. Treat a third-party layer with the same scrutiny as any other dependency: pin it to a specific version ARN, confirm its publisher, and scan its contents. A popular monitoring or utility layer that gets compromised upstream would run in every function that references it — the same blast-radius problem as a poisoned package, delivered through a different channel. Prefer building your own layers from scanned, pinned dependencies over trusting opaque published ones.

Dependency Layers Are Frozen Code

A Lambda deployment package or layer is a snapshot of your dependency tree at build time, and it runs unchanged until the next deploy — potentially for months. That persistence is exactly why serverless workloads tend to accumulate unpatched CVEs: nothing rebuilds them on a schedule. Generate an SBOM at build, scan it in CI, and fail the pipeline on high-severity findings before the artifact reaches Lambda. Reachability matters here — most of a bundle's transitive packages are never called, so knowing which vulnerable package is actually on a code path tells you what to fix first. That analysis is what software composition analysis provides in the pipeline.

Lambda Hardening Checklist

  • One scoped execution role per function; no * actions or resources
  • IAM Access Analyzer used to remove unused permissions
  • Resource-based policy reviewed; no unintended invoke grants
  • Function URLs use AWS_IAM auth or sit behind API Gateway
  • Secrets read from Secrets Manager/SSM, not environment variables
  • Event payloads validated at the top of the handler
  • Dependency bundle scanned in CI; build fails on high severity
  • Reserved concurrency set to cap abuse and runaway cost

How Safeguard Helps

Lambda security is decided in two artifacts: the IAM and function configuration in your Terraform or SAM templates, and the dependency bundle in your build. Safeguard covers both. Its infrastructure-as-code scanning flags wildcard execution-role permissions, public AuthType: NONE function URLs, and secrets sitting in environment variables in the pull request that introduces them — with the finding tied to the exact line of HCL. Its software composition analysis generates an SBOM for every function bundle and runs reachability analysis to tell you whether a flagged CVE is actually invoked in your code path, cutting the noise that keeps teams chasing unreachable vulnerabilities. Griffin, Safeguard's AI triage engine, combines those two signals — an internet-facing function URL plus a reachable critical CVE ranks far above the same CVE behind a private trigger. All of it runs through the Safeguard CLI so you can make it a merge gate rather than a post-deploy cleanup. See how the full platform compares on the comparison overview.

Lock down your Lambda functions before they deploy — get started free or read the documentation.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.