(bedrockagentcore): Gateway L2 construct adds redundant unconditioned sts:AssumeRole trust statement
- 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
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