aws / aws/aws-cdk

(bedrock-agentcore-alpha): AgentRuntimeArtifact.fromAsset grants ECR pull to only the first Runtime when the same artifact is shared

Open
#37,663 1 comment 2 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
2d 3h
Merged PRs (30d)
83

Description

### Describe the bug

`AgentRuntimeArtifact.fromAsset(directory)` returns an `AssetImage` whose `bind(scope, runtime)` method grants ECR pull permission to `runtime.role` exactly once — guarded by `this.bound = true`. When the same artifact instance is passed to two `agentcore.Runtime` constructs, the **second runtime's execution role never receives ECR pull permissions**, and its microVM fails to pull the container image at invocation time.

### Expected behaviour

Either:

1. `bind()` grants ECR pull to each runtime it is bound to (e.g., track `boundRoles: Set` and call `grantPull` once per unique role), **or**
2. Throw a clear error at synth time if the same `AssetImage` instance is bound to more than one runtime, so the user can `fromAsset` twice.

### Actual behaviour

- First bound runtime: ECR pull statements present in its `AWS::IAM::Policy` resource.
- Second bound runtime: missing `ecr:BatchGetImage`, `ecr:GetDownloadUrlForLayer`, `ecr:BatchCheckLayerAvailability` for the asset repo.
- At invocation time, the second runtime's microVM logs in CloudWatch:
```
Failed to pull image: .dkr.ecr..amazonaws.com/cdk-hnb659fds-container-assets-...:
Message: failed to resolve image: pull access denied, repository does not exist or may require authorization: authorization failed: no basic auth credentials
```
- Client calls to the second runtime's `/invocations` endpoint return **HTTP 424 Failed Dependency** repeatedly.

### Minimal reproduction

```ts
import * as cdk from 'aws-cdk-lib';
import * as agentcore from '@aws-cdk/aws-bedrock-agentcore-alpha';
import { Template } from 'aws-cdk-lib/assertions';
import * as path from 'path';

const app = new cdk.App();
const stack = new cdk.Stack(app, 'ReproStack');

const artifact = agentcore.AgentRuntimeArtifact.fromAsset(path.join(__dirname, 'agent'));

new agentcore.Runtime(stack, 'RuntimeA', {
runtimeName: 'runtime_a',
agentRuntimeArtifact: artifact, // gets ECR pull grants
});

new agentcore.Runtime(stack, 'RuntimeB', {
runtimeName: 'runtime_b',
agentRuntimeArtifact: artifact, // does NOT get ECR pull grants
});

const tpl = Template.fromStack(stack).toJSON();
// Inspect policies: only one will carry the ECR actions.
```

### Root cause

`packages/@aws-cdk/aws-bedrock-agentcore-alpha/lib/runtime/runtime-artifact.ts`:

```ts
class AssetImage extends AgentRuntimeArtifact {
asset;
bound = false;

bind(scope, runtime) {
if (!this.asset) {
this.asset = new assets.DockerImageAsset(scope, 'AgentRuntimeArtifact', {
directory: this.directory,
...this.options,
});
}
if (!this.bound) { // <-- guards the grant against double-calls,
this.asset.repository.grantPull(runtime.role); // but skips ALL subsequent runtimes
this.bound = true;
}
}
}
```

`S3Image.bind` has the same shape (`this.bound` single-shot guard), so the same latent issue exists for S3-backed artifacts shared across runtimes.

The `!this.bound` guard is almost certainly there to avoid duplicate `grantPull` calls on the **same** runtime if `bind()` were called twice during the same runtime's synth. A stricter guard on `(runtime.role.roleArn)` identity would preserve that protection while supporting shared artifacts.

### Workaround

Call `fromAsset` twice, once per runtime. `DockerImageAsset` dedupes on asset hash so only one image is published to ECR:

```ts
const artifactA = agentcore.AgentRuntimeArtifact.fromAsset(runnerPath);
const artifactB = agentcore.AgentRuntimeArtifact.fromAsset(runnerPath);

new agentcore.Runtime(stack, 'RuntimeA', { ..., agentRuntimeArtifact: artifactA });
new agentcore.Runtime(stack, 'RuntimeB', { ..., agentRuntimeArtifact: artifactB });
```

This works but is surprising — nothing in the `AgentRuntimeArtifact.fromAsset` API docs suggests the returned instance is single-use.

### Suggested fix

Track bound roles explicitly and grant idempotently per role:

```ts
class AssetImage extends AgentRuntimeArtifact {
private asset?: assets.DockerImageAsset;
private boundRoles = new Set();

bind(scope: Construct, runtime: IRuntime): void {
if (!this.asset) {
this.asset = new assets.DockerImageAsset(scope, 'AgentRuntimeArtifact', {
directory: this.directory,
...this.options,
});
}
const roleKey = runtime.role.roleArn;
if (!this.boundRoles.has(roleKey)) {
this.asset.repository.grantPull(runtime.role);
this.boundRoles.add(roleKey);
}
}
}
```

(Same shape for `S3Image`.)

Alternatively — or in addition — throw a validation error in `bind()` the second time if the library author wants to keep artifact instances single-use by design; either behaviour is an improvement over the silent failure.

### Environment

- `@aws-cdk/aws-bedrock-agentcore-alpha`: `2.238.0-alpha.0`
- `aws-cdk-lib`: `2.238.0` (per the alpha tag)
- Node: 20.x–24.x
- OS: macOS 15 (Darwin 25), also reproduced in Lambda-synth environments
- Region: us-east-1

### Additional context

Affects anyone deploying more than one Runtime against the same container image — a common pattern for dev/prod runtime splits, or for different authorizers (IAM vs Cognito JWT) fronting the same agent. The failure mode (424 after image pull denied) is remote from the root cause, which makes initial triage expensive.

Happy to submit a PR if the maintainers prefer.

Contributor guide

Open the contributing guide

Research direction

Read packages/@aws-cdk/aws-bedrock-agentcore-alpha/lib/runtime/runtime-artifact.ts, focusing on AssetImage.bind and the analogous S3Image.bind guard. Run the two-Runtime reproduction from the issue and inspect synthesized IAM policies; done means shared artifacts grant pull access to each distinct runtime role without duplicating grants for the same role.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.