This check flags Lambda functions that have no IAM execution role attached. Without one, the function cannot write logs, reach other AWS services, or run reliably, and it usually signals a broken or misconfigured deployment. Fix it by attaching a least-privilege execution role with aws lambda update-function-configuration.
Every Lambda function needs an identity to act on your behalf inside AWS. That identity is the execution role: an IAM role the Lambda service assumes when your function runs. When the role is missing, the function loses its ability to talk to CloudWatch Logs, read from S3, publish to SNS, or do anything else that requires AWS permissions. This check catches functions where the execution role is absent entirely, which is almost always a sign of a deployment gone wrong or a manual edit that left the function in a broken state.
It is a small misconfiguration with outsized consequences. A function with no role is either non-functional, silently failing, or about to fail the moment it touches another service.
What this check detects
The lambda_norole check inspects each Lambda function's configuration and looks at the Role attribute. If that attribute is empty, null, or otherwise unset, the function is flagged.
Under normal conditions, AWS does not let you create a function without a role through the standard APIs, so an empty role almost never appears by accident in steady state. It tends to show up in a few specific situations:
- A failed or partially applied infrastructure-as-code deployment where the role reference resolved to an empty string
- Functions imported or migrated between accounts where the role ARN was dropped
- Manual tinkering in the console or CLI that left the configuration inconsistent
- Drift between your IaC state and what actually exists in the account
Note: The execution role is different from the resource-based policy that controls who can invoke the function. The execution role governs what the function can do once it is running. Both matter, but this check is only about the former.
Why it matters
A Lambda function without an execution role is broken in ways that range from annoying to dangerous, depending on what the function was supposed to do.
You lose all observability
Lambda writes its logs to CloudWatch Logs using permissions granted by the execution role. With no role, there are no log streams. When the function errors out, you get nothing in CloudWatch to tell you why. Incident response on a function you cannot observe is guesswork.
Anything that touches another service fails
Most real-world functions read from a queue, write to a database, fetch a secret, or publish an event. Every one of those calls needs IAM permissions delivered through the execution role. Without it, those calls return AccessDenied and the function fails at runtime, often after it has already been triggered and consumed part of a payload.
It points to a deeper deployment problem
Because AWS normally requires a role at creation time, an empty role usually means something upstream is broken. Your pipeline may be applying partial changes, your IaC state may have drifted, or a role was deleted out from under a live function. That underlying issue can affect more than just this one function.
Warning: If a role was deleted while still referenced by a function, every invocation will throw an error like The role defined for the function cannot be assumed by Lambda. Recreating the role with the same name is not always enough, since the trust relationship and the role's unique ID can change.
How to fix it
The fix is to attach a properly scoped execution role. Do not reach for AdministratorAccess here, even temporarily. Grant only what the function actually needs.
Step 1: Create a least-privilege execution role
First, define a trust policy so the Lambda service can assume the role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
Save it as trust-policy.json, then create the role:
aws iam create-role \
--role-name my-function-exec-role \
--assume-role-policy-document file://trust-policy.json
Step 2: Attach the basic logging permissions
At minimum, the function needs to write to CloudWatch Logs. AWS provides a managed policy for exactly this:
aws iam attach-role-policy \
--role-name my-function-exec-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
If your function reads from SQS, DynamoDB streams, or a VPC, add the relevant scoped permissions as inline or customer-managed policies rather than broad managed ones.
Step 3: Attach the role to the function
Danger: Updating the configuration of a live, production function takes effect immediately and affects the next invocation. Confirm the role and its policies are correct before running this against production. An over-scoped role here can grant the function far more access than it should have.
aws lambda update-function-configuration \
--function-name my-function \
--role arn:aws:iam::123456789012:role/my-function-exec-role
Step 4: Verify
aws lambda get-function-configuration \
--function-name my-function \
--query 'Role' \
--output text
You should see the full role ARN. Then trigger a test invocation and confirm log streams appear in the function's CloudWatch Logs group.
Tip: If you manage functions with infrastructure-as-code, do not patch this in the console. Fix it in the source template and redeploy, otherwise the next deployment will revert your change and reintroduce the finding. A console hotfix is fine for stopping the bleeding, but the IaC must be the source of truth.
Fixing it in infrastructure-as-code
Most teams should never hit this check if their IaC defines roles correctly. Here is the role and function wired together in Terraform:
resource "aws_iam_role" "lambda_exec" {
name = "my-function-exec-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy_attachment" "basic_logs" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_lambda_function" "my_function" {
function_name = "my-function"
role = aws_iam_role.lambda_exec.arn
handler = "index.handler"
runtime = "python3.12"
filename = "function.zip"
}
The key line is role = aws_iam_role.lambda_exec.arn. If that ever resolves to an empty interpolation, for example because of a renamed or destroyed resource, you can end up with the misconfiguration this check catches. Use explicit references, not string concatenation, to build role ARNs.
The same applies in the AWS SAM template, where Role or an inline Policies block must be present:
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: python3.12
Policies:
- AWSLambdaBasicExecutionRole
How to prevent it from recurring
A one-off fix solves today's finding. Preventing it means catching the problem before it ships.
Scan IaC in the pipeline
Add a policy-as-code step to your CI pipeline that rejects any Lambda definition without a resolvable role. With checkov:
checkov -d ./infra --framework terraform
For custom rules, Open Policy Agent (Conftest) lets you write a Rego check that fails the build when a function has no role:
package main
deny[msg] {
resource := input.resource.aws_lambda_function[name]
not resource.role
msg := sprintf("Lambda '%s' has no execution role", [name])
}
Detect drift continuously
IaC scanning only covers what is in your repository. Functions modified out of band, or roles deleted by another process, will not show up until something breaks. Continuous scanning of the live account is what closes that gap. Lensix runs the lambda_norole check across your AWS accounts on a schedule and surfaces any function where the role has gone missing, regardless of how it got that way.
Protect roles from accidental deletion
Use a permissions boundary or an SCP that prevents deletion of execution roles tagged as in-use. This stops the common failure mode where someone cleaning up IAM removes a role that a live function still depends on.
Best practices
- One role per function. Sharing a single role across many functions makes least privilege impossible and turns one compromise into many. Give each function its own role scoped to its own needs.
- Start from
AWSLambdaBasicExecutionRole, then add narrowly. Logging permissions are the baseline. Everything beyond that should be a specific, scoped statement, ideally restricted to named resource ARNs. - Never use wildcards on actions and resources together. A role with
"Action": "*"on"Resource": "*"defeats the purpose of having a role at all. - Manage roles in the same IaC as the function. Keeping the role and function in one module ensures they are created, updated, and destroyed together, which prevents the dangling-reference problem.
- Review with IAM Access Analyzer. It will flag overly permissive policies and unused permissions so you can tighten roles over time.
A missing execution role is rarely the whole problem. It is a signal. When this check fires, fix the function, then ask why the role went missing in the first place, because the answer usually points to a gap in your deployment process worth closing.

