aws-samples / aws-samples/sample-autonomous-cloud-coding-agents

fix(bootstrap): least-privilege policies deny all nested-stack resources — default-on Agent Registry cannot deploy without AdministratorAccess

Open
#865 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
143
Forks
46
Avg merge
3d 9h
Merged PRs (30d)
20

Description

**Component:** cdk (bootstrap policies)

## Describe the bug

The least-privilege bootstrap policies scope every resource ARN to the **parent stack's** name prefix (`backgroundagent-dev-*`). Resources created inside **nested** stacks do not carry that prefix in their generated physical names, so the CFN execution role is denied on all of them.

The Agent Registry is **default-on** (`enableAgentRegistry !== false`, `cdk/src/stacks/agent.ts:115-128`) and contributes two nested stacks — `AgentRegistryStack` and `RegistryApi`. So on a clean account with default context, `mise //cdk:deploy` **cannot succeed** with the repo's own least-privilege bootstrap.

This is not one missing action; it is a whole class of denials.

## Expected behavior

A default-context deploy (Agent Registry enabled) succeeds against the least-privilege bootstrap, with **no** `AdministratorAccess` attached to `cdk-hnb659fds-cfn-exec-role-*`.

## Current behavior

Two distinct denials, hit in sequence:

**1. `iam:PassRole` on the exec role itself.** Creating any nested stack makes CloudFormation pass the execution role to itself. No statement permits this:

```
User: arn:aws:sts:::assumed-role/cdk-hnb659fds-cfn-exec-role--us-east-1/AWSCloudFormation
is not authorized to perform: iam:PassRole on resource:
arn:aws:iam:::role/cdk-hnb659fds-cfn-exec-role--us-east-1
because no identity-based policy allows the iam:PassRole action
```

The existing `IAMPassRole` statement scopes to `arn:aws:iam::*:role/backgroundagent-dev-*` and conditions on `iam:PassedToService`, which does not cover this case.

**2. Resource-name mismatch inside nested stacks.** After granting the above, the next failure:

```
User: ...AWSCloudFormation is not authorized to perform: states:CreateStateMachine on resource:
arn:aws:states:us-east-1::stateMachine:AgentRegistryProviderwaiterstatemachineE27177B2-arZJxYWqV4wX
because no identity-based policy allows the states:CreateStateMachine action
```

The action **is** granted; the `StepFunctions` statement scopes it to `arn:aws:states:*:*:stateMachine:backgroundagent-dev-*`. The waiter state machine is named `AgentRegistryProviderwaiterstatemachine…` — no parent-stack prefix — so the ARN never matches.

The same prefix assumption appears across the generated policies (SNS, CloudFront, Lambda, IAM roles/policies …), so fixing only `states:*` would just surface the next resource type. Patching one action at a time is not viable — each cycle is an ~8-minute deploy.

## Reproduction steps

1. Fresh account; bootstrap with the least-privilege template (see #864 for the size workaround needed to do this at all).
2. Do **not** attach `AdministratorAccess` to the cfn-exec-role.
3. `export MISE_EXPERIMENTAL=1 && mise run //cdk:deploy -- --require-approval never` with default context.
4. Deploy reaches changeset creation, then fails on `iam:PassRole`. Grant that, redeploy, and it fails on `states:CreateStateMachine` inside the nested stack.

## Possible solution

1. **Allow the exec role to pass itself**, scoped to exactly that role ARN, for nested-stack creation.
2. **Stop assuming the parent-stack prefix.** Nested-stack physical names are not prefixed. Either:
- widen the affected `Resource` patterns to also admit nested-stack-generated names, or
- drive scoping off a stable tag/condition (e.g. `aws:ResourceTag/aws:cloudformation:stack-id`) rather than a name prefix, or
- give nested-stack constructs explicit physical names carrying the prefix, so the existing scoping holds.

The tag-condition route is the most robust — it stays genuinely least-privilege without depending on CDK's name generation, which is not a stable contract.

3. **Add coverage.** A synth/policy test asserting every resource ARN in the synthesized template (including nested templates) is authorized by the generated policies would catch this class before deploy. Note `cdk/test/bootstrap/synth-coverage.test.ts` already exists and did not catch it.

**Workaround used to get unblocked:** attached `AdministratorAccess` to the cfn-exec-role plus an inline `iam:PassRole` policy. That defeats the purpose of the custom bootstrap and should not be the documented path.

## Environment

- Node: v22.23.2 (mise) · mise: 2026.7.0 macos-arm64 · Region: us-east-1
- Bootstrap: `BootstrapVariant = "ABCA: Least-Privilege Bootstrap"`, version 32, `ComputeTypes=agentcore`
- Commit: `6e865f2d`
- Related: #120 (least-privilege bootstrap policies as code — this is a gap in that design), #350 (prior stale-policy AccessDenied), #864 (blocks reaching this step at all)

Contributor guide

Open the contributing guide

Research direction

Start with cdk/src/stacks/agent.ts:115-128 and cdk/test/bootstrap/synth-coverage.test.ts, then reproduce the default-context deploy using the least-privilege bootstrap and no AdministratorAccess. Trace the generated policies and nested templates to cover the exec role PassRole case and nested-stack resource names. Done means the default Agent Registry deployment succeeds and the cfn-exec role has no AdministratorAccess, with synth coverage preventing regressions.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
cloud, devops, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.