1 / 6 · The small role
agent-runner, allowed
s3:GetObject
sqs:SendMessage
lambda:InvokeFunction
lambda:CreateFunction
iam:PassRole
Resource: *
This role is not an administrator. Read it line by line and nothing is alarming.
It can create a function. It can pass a role to a service. Both are ordinary.
Read the two lines together instead.
It is not an administrator. It can become one.
2 / 6 · The move
agent.log
02:10:04CreateFunction helper, role=pipeline-exec
02:10:09Invoke helper
pipeline-exec, trusted by Lambda
s3:*
dynamodb:*
iam:*
0 DENIED
Create a function. Hand it the strongest role that Lambda is allowed to use.
Invoke it. Two calls, both allowed, because both were granted.
Nothing was exploited. There is no vulnerability here to patch.
The function runs as that role, and the agent holds the function.
3 / 6 · The search
CloudTrail · event history
searchEventName = PassRole
No events found
CreateFunction · requestParameters
namehelper
rolearn:aws:iam::ACCOUNT:role/pipeline-exec
So look it up. Search the trail for the moment the role was passed.
There is nothing to find.
AWS's own words: PassRole is not an API call. It is a permission, so no event is written for it.
The evidence is inside the call that used it. You have to know to look there.
4 / 6 · The fix
agent-runner, iam:PassRole
Resource: *
Resource: arn:aws:iam::ACCOUNT:role/agent-task-*
Condition: iam:PassedToService = lambda.amazonaws.com
tag the role instead
PASS DENIED
Name the roles that may be passed. A wildcard here is the whole account.
Bound it further by the one service they may be passed to.
Not by tagging the role. AWS says that does not have reliable results.
Two lines of policy, and the move in scene two is refused.
5 / 6 · What it does not fix
agent-task-runner, allowed
s3:GetObject, s3:PutObject
dynamodb:Query, dynamodb:PutItem
bedrock:InvokeModel
secretsmanager:GetSecretValue
sqs:SendMessage
Naming the roles closes one path. It does not make the named role small.
The agent still reaches everything the role you named can do.
And roles already handed to running functions keep what they have.
Least privilege bounds reach. It cannot read intent.
6 / 6 · Two commands
terminal · read-only
$aws iam get-account-authorization-details --filter Role > roles.json
$grep -c '"iam:PassRole"' roles.json
abacross.com/readiness
Flags an agent role that may pass any role, and names what it does not read.
Two read-only commands. Nothing leaves your machine.
Every role that can pass a role. If the answer is a wildcard, you have scene one.
The free readiness check now flags it, and says what it does not read.