aws / aws/aws-cdk

(bedrockagentcore): Gateway L2 construct adds redundant unconditioned sts:AssumeRole trust statement

Open
#38,780 2 comments 0 reactions 0 assignees View on GitHub
@aws-cdk/aws-bedrock-agentcore-alpha bug effort/medium p2
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
1d 19h
Merged PRs (30d)
74

Description

### Describe the bug

`aws_bedrockagentcore.Gateway` (the L2 construct for `AWS::BedrockAgentCore::Gateway`) auto-generates a service-role trust policy (`AssumeRolePolicyDocument`) containing **two** statements for the `bedrock-agentcore.amazonaws.com` principal:

1. An **unconditioned** statement — no `aws:SourceAccount`/`aws:SourceArn` condition at all.
2. A correctly-scoped statement, restricting the same principal via `aws:SourceAccount`/`aws:SourceArn` (`ArnLike`) to this account and this specific Gateway's ARN pattern.

Statement 2 alone is sufficient and correct. Statement 1 is redundant and is a classic confused-deputy gap: without any condition, any AWS resource that can name this specific role's ARN and assumes the `bedrock-agentcore.amazonaws.com` service principal (in principle, a different Gateway/AgentCore resource anywhere) could assume this role — nothing in that statement restricts the calling resource to this Gateway or even to this account.

Confirmed live via `iam:GetRole` against a deployed Gateway (not just synth output):

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "bedrock-agentcore.amazonaws.com" },
"Action": "sts:AssumeRole"
},
{
"Effect": "Allow",
"Principal": { "Service": "bedrock-agentcore.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "aws:SourceAccount": "" },
"ArnLike": { "aws:SourceArn": "arn:aws:bedrock-agentcore:::gateway/*" }
}
}
]
}
```

This appears to be a default-behavior quirk of the L2 construct's own role-generation logic — no prop on `agentcore.Gateway(...)`/`GatewayAuthorizer` exposes control over the generated trust policy's statement set, so there's no supported way to opt out of the unconditioned statement.

### Expected Behavior

Only the correctly-scoped (conditioned) `sts:AssumeRole` statement should be present. The unconditioned statement should not be generated at all.

### Current Behavior

Both statements are generated; the unconditioned one is a real, exploitable confused-deputy gap on the resulting IAM role.

### Reproduction Steps

```python
from aws_cdk import Stack, aws_bedrockagentcore as agentcore
from constructs import Construct

class ReproStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
self.gateway = agentcore.Gateway(
self,
"MyGateway",
gateway_name="my-gateway",
protocol_configuration=agentcore.GatewayProtocol.mcp(),
authorizer_configuration=agentcore.GatewayAuthorizer.using_aws_iam(),
)
```

Deploy this stack, then inspect the generated Gateway service role's `AssumeRolePolicyDocument` via `aws iam get-role --role-name ` (not just `cdk synth` — the behavior was confirmed against a real deployed role, though it should also be visible in the synthesized `AWS::IAM::Role` resource's `AssumeRolePolicyDocument` property).

### Possible Solution

Remove the unconditioned statement from the construct's internally-generated trust policy, keeping only the `aws:SourceAccount`/`aws:SourceArn`-conditioned one. This mirrors AWS's own documented CloudFormation reference pattern for `bedrock-agentcore.amazonaws.com` service roles (a single, conditioned statement), and is the same fix already applied for a related class of overly-permissive-trust-policy bug in CDK Pipelines (#25356).

### Additional Information/Context

Found during a live security audit of a personal project's `aws_bedrockagentcore.Gateway` deployment. Worked around locally by overriding the role's `AssumeRolePolicyDocument` post-construction (via the L1 `CfnRole` escape hatch, `gateway.role.node.default_child`) to the single, correctly-scoped statement — but this is a local, reproducible-on-every-deploy patch, not a real fix, and is fragile against any future change to how the construct assembles the trust policy.

### CDK CLI Version

2.1126.0 (build a90d578)

### Framework Version

aws-cdk-lib 2.268.0

### Node.js Version

v22.22.1

### OS

macOS

### Language

Python

### Language Version

Python 3.12

### Other information

No response

Contributor guide

Open the contributing guide

Research direction

Start with the aws_bedrockagentcore.Gateway L2 construct and its internally generated service role, then inspect the synthesized AWS::IAM::Role AssumeRolePolicyDocument and confirm the deployed result with `aws iam get-role`. Trace how GatewayAuthorizer.using_aws_iam() assembles the trust policy. Done means the role contains only the conditioned bedrock-agentcore.amazonaws.com statement with aws:SourceAccount and aws:SourceArn restrictions.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
infrastructure, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.