Give each Lambda function its own execution role, start with the basic logging permissions, and add only the specific actions and resources the code calls. Then tighten it using access activity: IAM Access Analyzer can read CloudTrail logs and draft a policy containing only what the function actually used.
What the role is and why it matters
The execution role is an IAM role that grants the function permission to reach AWS services and resources. Lambda assumes it for you on every invocation and hands the function temporary credentials through environment variables. Anyone who can run code inside your function, whether through a vulnerable dependency or an injection bug, can use those credentials.
That makes the role the blast radius. A function that only writes one item to one table should not be able to read every bucket in the account.
Start small
When you create a function in the console, Lambda creates a role with minimal permissions. It carries the AWSLambdaBasicExecutionRole managed policy, which allows writing logs to CloudWatch Logs. Anything beyond that is your decision.
The role's trust policy must name the Lambda service principal, lambda.amazonaws.com, as a trusted service. That is the only principal that needs to assume it. The AWS documentation also advises against calling sts:AssumeRole yourself in function code to assume the execution role.
The usual drift
During development it is common to grant wide permissions to get things working. The AWS documentation says to adjust the policy to only the required permissions before publishing to production. In practice the wide policy ships because nobody owns the cleanup.
Common patterns to look for:
- A wildcard in the Action field for a whole service.
- A wildcard in the Resource field where one ARN would do.
- One shared role reused by many functions, so the union of everyone's needs applies to each.
- Managed policies attached for convenience that include write or delete.
Tighten with real usage
Run the function in a realistic environment for a while. Then use IAM Access Analyzer's policy generation. It reviews your CloudTrail logs over a date range you choose and produces a policy template with only the permissions used in that window.
Treat the output as a draft. Rarely used code paths, such as an error handler that writes to a dead letter queue, may not appear in a short window. Check the generated policy against the code, add the missing rare actions deliberately, and then attach it as a managed policy.
A review routine that fits a pull request
- List the AWS API calls the function code makes.
- Confirm each one appears in the role, scoped to named resources.
- Confirm nothing appears in the role that is not in the list.
- Confirm the role is not shared with another function.
- Re-run the access analysis after a few weeks of production traffic.
If you manage roles in infrastructure as code, a policy gate on pull requests can flag new wildcard statements before they merge.
Quick check
Should several functions share one execution role?
Usually not. A shared role grants every function the combined permissions of all of them, so one compromised function reaches everything. One role per function keeps the damage contained and makes reviews simpler.
Is the basic execution role enough for a function that calls other services?
No. It covers writing logs. Anything else, such as reading a queue or writing to a table, needs its own scoped statement.
If you want help keeping this in check, Safeguard can enforce policies and gates on changes before they merge, and its free scan at gold.safeguard.sh/scan covers the dependencies you package with a function.
Sources
- Defining Lambda function permissions with an execution role, AWS documentation
- Using source function ARN to control function access behavior, AWS documentation
Last reviewed: 19 October 2026.