Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Securing AWS Lambda execution roles by implementing least-privilege IAM policies, applying permission boundaries, restricting resource-based policies, using IAM Access Analyzer to validate permissions, and enforcing role scoping through SCPs.
.claude/skills/securing-aws-lambda-execution-roles/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-20 | ✗→✓ | ▲ Improved | — | — |
Do not use for securing Lambda function invocation (use resource-based policies and API Gateway authorizers), for Lambda code security (use SAST tools), or for Lambda network security (use VPC configuration and security groups).
Enumerate all Lambda functions and their associated IAM roles to identify over-privileged functions.
bash# List all Lambda functions with their execution roles aws lambda list-functions \ --query 'Functions[*].[FunctionName,Role]' --output table # For each function, analyze attached policies for func in $(aws lambda list-functions --query 'Functions[*].FunctionName' --output text); do role_arn=$(aws lambda get-function-configuration --function-name "$func" --query 'Role' --output text) role_name=$(echo "$role_arn" | awk -F'/' '{print $NF}') echo "=== $func -> $role_name ===" # Check for AWS managed policies (often too broad) aws iam list-attached-role-policies --role-name "$role_name" \ --query 'AttachedPolicies[*].[PolicyName,PolicyArn]' --output table # Check inline policies for policy in $(aws iam list-role-policies --role-name "$role_name" --query 'PolicyNames' --output text); do echo " Inline: $policy" aws iam get-role-policy --role-name "$role_name" --policy-name "$policy" \ --query 'PolicyDocument' --output json done done
Use CloudTrail and IAM Access Analyzer to determine which API actions the function actually uses.
bash# Query CloudTrail for actual API calls made by a Lambda execution role aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=Username,AttributeValue=LAMBDA_ROLE_NAME \ --start-time 2026-01-23T00:00:00Z \ --end-time 2026-02-23T00:00:00Z \ --query 'Events[*].[EventTime,EventName,EventSource]' \ --output table | sort -k2 | uniq -f1 # Use IAM Access Analyzer policy generation (based on CloudTrail activity) aws accessanalyzer start-policy-generation \ --policy-generation-details '{ "principalArn": "arn:aws:iam::ACCOUNT:role/lambda-execution-role", "cloudTrailDetails": { "trailArn": "arn:aws:cloudtrail:us-east-1:ACCOUNT:trail/management-trail", "startTime": "2026-01-23T00:00:00Z", "endTime": "2026-02-23T00:00:00Z" } }' # Check the generated policy aws accessanalyzer get-generated-policy \ --job-id JOB_ID \ --query 'generatedPolicyResult.generatedPolicies[*].policy'
Build scoped IAM policies that grant only the specific actions and resources each function needs.
bash# Example: Scoped policy for a function that reads from S3 and writes to DynamoDB cat > lambda-scoped-policy.json << 'EOF' { "Version": "2012-10-17", "Statement": [ { "Sid": "ReadInputBucket", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::input-data-bucket", "arn:aws:s3:::input-data-bucket/*" ] }, { "Sid": "WriteDynamoDB", "Effect": "Allow", "Action": [ "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:BatchWriteItem" ], "Resource": "arn:aws:dynamodb:us-east-1:ACCOUNT:table/results-table" }, { "Sid": "CloudWatchLogs", "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:us-east-1:ACCOUNT:log-group:/aws/lambda/my-function:*" } ] } EOF # Create the policy aws iam create-policy \ --policy-name lambda-my-function-policy \ --policy-document file://lambda-scoped-policy.json # Create execution role with scoped trust policy cat > lambda-trust-policy.json << 'EOF' { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "aws:SourceAccount": "ACCOUNT_ID" } } }] } EOF aws iam create-role \ --role-name lambda-my-function-role \ --assume-role-policy-document file://lambda-trust-policy.json aws iam attach-role-policy \ --role-name lambda-my-function-role \ --policy-arn arn:aws:iam::ACCOUNT:policy/lambda-my-function-policy
Implement permission boundaries to set maximum permissions for Lambda execution roles.
bash# Create a permission boundary that caps Lambda role capabilities cat > lambda-permission-boundary.json << 'EOF' { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowedServices", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:ListBucket", "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:Query", "dynamodb:UpdateItem", "sqs:SendMessage", "sqs:ReceiveMessage", "sqs:DeleteMessage", "sns:Publish", "secretsmanager:GetSecretValue", "kms:Decrypt", "kms:GenerateDataKey", "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "xray:PutTraceSegments", "xray:PutTelemetryRecords" ], "Resource": "*" }, { "Sid": "DenyPrivilegeEscalation", "Effect": "Deny", "Action": [ "iam:CreateUser", "iam:CreateRole", "iam:CreatePolicy", "iam:AttachRolePolicy", "iam:AttachUserPolicy", "iam:PutRolePolicy", "iam:PutUserPolicy", "iam:CreateAccessKey", "iam:PassRole", "lambda:CreateFunction", "lambda:UpdateFunctionConfiguration", "sts:AssumeRole" ], "Resource": "*" } ] } EOF # Create and apply the boundary aws iam create-policy \ --policy-name lambda-permission-boundary \ --policy-document file://lambda-permission-boundary.json aws iam put-role-permissions-boundary \ --role-name lambda-my-function-role \ --permissions-boundary arn:aws:iam::ACCOUNT:policy/lambda-permission-boundary
Use Access Analyzer to validate policies for security best practices.
bash# Validate the scoped policy aws accessanalyzer validate-policy \ --policy-document file://lambda-scoped-policy.json \ --policy-type IDENTITY_POLICY \ --query 'findings[*].[findingType,issueCode,learnMoreLink]' --output table # Check for unused access aws accessanalyzer check-no-new-access \ --new-policy-document file://lambda-scoped-policy.json \ --existing-policy-document file://old-broad-policy.json \ --policy-type IDENTITY_POLICY # Verify the permission boundary effectiveness aws iam simulate-principal-policy \ --policy-source-arn arn:aws:iam::ACCOUNT:role/lambda-my-function-role \ --action-names iam:CreateUser iam:PassRole s3:GetObject dynamodb:PutItem \ --query 'EvaluationResults[*].[EvalActionName,EvalDecision]' --output table
Apply Service Control Policies to prevent Lambda functions from using overly broad roles.
bash# SCP to deny Lambda functions using AdministratorAccess cat > scp-deny-lambda-admin.json << 'EOF' { "Version": "2012-10-17", "Statement": [{ "Sid": "DenyLambdaAdminRole", "Effect": "Deny", "Action": "lambda:CreateFunction", "Resource": "*", "Condition": { "ForAnyValue:StringLike": { "lambda:FunctionArn": "*" }, "ArnLike": { "iam:PassedToService": "lambda.amazonaws.com" } } }, { "Sid": "RequirePermissionBoundary", "Effect": "Deny", "Action": [ "iam:CreateRole", "iam:AttachRolePolicy", "iam:PutRolePolicy" ], "Resource": "arn:aws:iam::*:role/lambda-*", "Condition": { "StringNotEquals": { "iam:PermissionsBoundary": "arn:aws:iam::*:policy/lambda-permission-boundary" } } }] } EOF aws organizations create-policy \ --name "lambda-role-guardrails" \ --type SERVICE_CONTROL_POLICY \ --content file://scp-deny-lambda-admin.json
| Term | Definition | |------|------------| | Execution Role | IAM role assumed by Lambda during function execution that defines all AWS API actions the function can perform | | Least Privilege | Security principle of granting only the minimum permissions required for a function to perform its intended operations | | Permission Boundary | IAM policy that sets the maximum permissions an execution role can have, even if identity policies grant broader access | | IAM Access Analyzer | AWS service that generates least-privilege policies based on actual CloudTrail usage and validates policies for security issues | | Resource-Scoped Policy | IAM policy that specifies exact resource ARNs rather than wildcards, limiting access to only the specific resources needed | | Confused Deputy Prevention | Adding aws:SourceAccount or aws:SourceArn conditions to trust policies to prevent cross-account role assumption attacks |
Context: A security audit finds 12 Lambda functions using a shared execution role with AdministratorAccess. The team needs to scope each function to minimum required permissions without breaking production.
Approach:
Pitfalls: Some Lambda functions may have infrequent code paths that only trigger monthly (batch jobs, error handlers). A 30-day observation window may miss rare API calls. Review the function code alongside CloudTrail data to identify all potential API calls. Use Access Analyzer's policy validation rather than relying solely on generated policies.
Lambda Execution Role Security Report
========================================
Account: 123456789012
Review Date: 2026-02-23
Functions Audited: 34
ROLE PERMISSION SUMMARY:
Functions with AdministratorAccess: 3 (CRITICAL)
Functions with PowerUserAccess: 5 (HIGH)
Functions with wildcard actions: 12 (MEDIUM)
Functions with scoped policies: 14 (OK)
REMEDIATION PROGRESS:
[x] payment-processor: Scoped to DynamoDB + S3 + KMS (3 actions)
[x] order-notification: Scoped to SNS + SES (2 actions)
[ ] data-pipeline: Generating policy from 30-day CloudTrail data
[ ] image-resizer: Awaiting staging validation
PERMISSION BOUNDARY STATUS:
Functions with boundary applied: 14 / 34
Functions without boundary: 20 / 34
POLICY VALIDATION RESULTS:
Policies with security warnings: 4
Policies with errors: 0
Policies with suggestions: 12| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.