Skip to main content
cd ../config-traps
risk/register/lambda-updatefunctioncode-bypasses-the-passrole-check.html
AWS Lambda IAMhigh severityAWS Lambda

Lambda UpdateFunctionCode Bypasses the PassRole Check

Severity
high
Reviewed
22 Jul 2026
Remediation
~20 minutes
Overview

A CI/CD deploy role in AWS Lambda holds lambda:UpdateFunctionCode and lambda:InvokeFunction against a function whose execution role already carries broad S3, DynamoDB and KMS access. IAM's PassRole check never fires because the role isn't being reassigned, so anyone who can push new code inherits that role's full permission set the moment it runs.

Operational summary

At a glance

Symptom
A principal without any PassRole permission overwrites an existing function's deployment package and invokes it, inheriting the already-attached execution role's permissions.
Likely cause
iam:PassRole is correctly scoped with a PassedToService condition, but lambda:UpdateFunctionCode and lambda:InvokeFunction remain unscoped on functions holding privileged execution roles.
Impact
Deploy identities with only code-update rights silently inherit the full permission set of any execution role attached to functions they can update, bypassing PassRole entirely.
Verification signal
Re-run the detection test and a controlled negative-path test.
Safe correction
Attach permissions boundaries to every Lambda execution role and scope UpdateFunctionCode and InvokeFunction by resource tag, independent of PassRole conditions.
Rollback or recovery
Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.

The Trap

Lambda execution roles created via the AWSLambdaBasicExecutionRole managed policy look tightly scoped — CloudWatch Logs write access and nothing else — while the IAM policy governing the deploy pipeline carries iam:PassRole on Resource “*” restricted with a Condition StringEquals iam:PassedToService lambda.amazonaws.com. Reviewers sign this off because PassRole is conditioned and the execution role itself is minimal on paper. The trap is that the same deploy pipeline role also holds lambda:UpdateFunctionCode20150331v2 and lambda:InvokeFunction against existing functions, and neither of those API calls triggers a PassRole evaluation at all.

The Default State

Terraform modules and Serverless Framework pipelines routinely grant one deployment role broad lambda:Update* and lambda:Invoke* permissions across an entire account, because scoping by function name or tag is treated as pipeline friction. Meanwhile, individual functions accumulate execution roles far wider than AWSLambdaBasicExecutionRole over their lifetime — a data-ingestion function ends up with s3:GetObject, dynamodb:PutItem and kms:Decrypt bolted on ad hoc, none of which get re-reviewed once the function is live and passing tests.

The Blast Radius

Any principal with lambda:UpdateFunctionCode on that function can overwrite its deployment package with arbitrary code, then call lambda:InvokeFunction, or simply wait for its EventBridge or S3 trigger, and execute that code under the existing execution role. AWS never re-checks iam:PassRole here because the ExecutionRole parameter isn’t part of either API call — the role stays attached, only the code changes underneath it. A developer with narrow, apparently harmless deploy permissions on one low-risk function silently inherits KMS decrypt and DynamoDB write access on data the security review never associated with their access level. CloudTrail shows only UpdateFunctionCode20150331v2 and Invoke events; there is no AssumeRole or PassRole entry to flag the escalation.

The Lead Mechanic Fix

Attach a permissions boundary to every Lambda execution role so its effective privilege is capped regardless of what code runs inside it: aws iam put-role-permissions-boundary –role-name –permissions-boundary arn:aws:iam:::policy/LambdaExecBoundary. Separate the “who can pass a role” question from “who can change code” — scope lambda:UpdateFunctionCode* and lambda:InvokeFunction with a Condition on aws:ResourceTag/Sensitivity, and deny both actions account-wide via SCP unless the caller’s own tag matches the function’s tag. Route UpdateFunctionCode20150331v2 calls through an EventBridge rule keyed on the API name, alerting whenever a function tagged Sensitivity=high receives a code update from outside the release pipeline’s assumed-role session.

03

Apply the safer control

Before you change production

Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.

Control to implement

Attach permissions boundaries to every Lambda execution role and scope UpdateFunctionCode and InvokeFunction by resource tag, independent of PassRole conditions.

Validate the vendor-specific syntax in official documentation before applying it.

04

Verify, roll back or escalate

Verify

Re-run the detection test and a controlled negative-path test. Confirm the unsafe behaviour is blocked while approved traffic still succeeds.

Rollback

Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.

Escalate

Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.

After remediation

Further reading stays below the corrective workflow and is selected by platform, category and shared technical keywords.

Discover more

Connected KBY resources