IAM

Every IAM identity is created by one of the two stacks. Neither imports an existing role.

iam.Role.fromRoleArn() is banned in CDK. A role imported by ARN has a policy defined outside this repository, which means a permission change never appears in a pull request. CDK owns every identity it attaches, so the blast radius of the runtime is reviewable in one file.

Roles

Role Created by Assumed by

agentcore-RuntimeRole

CDK

bedrock-agentcore.amazonaws.com — the AgentCore runtime

MemoryExecutionRole

CDK

bedrock-agentcore.amazonaws.com — AgentCore Memory

Five *FunctionRole roles

SAM

lambda.amazonaws.com

Neither CDK role sets an explicit roleName, so CloudFormation generates physical names such as AgentCoreStack-agentcoreRuntimeRole3DA72390-og2sjxN8EWYw. That name changes if the role is replaced, which is why agentcore/.bedrock_agentcore.yaml pins the ARN and has to be refreshed after a stack recreate.

No AWS managed policies are attached to either CDK role. Every permission is an inline statement.

MemoryExecutionRole

One statement, in an inline policy named MemoryPolicy:

Actions Resources

bedrock:InvokeModel, bedrock:InvokeModelWithResponseStream

*

AgentCore Memory assumes this role to run its own extraction strategies — the fact extractor, the preference extractor, and the summariser each invoke a model to condense a session. See AgentCore Memory.

agentcore-RuntimeRole

Inline policy RuntimeAccessPolicy, ten statements:

Sid Actions Resources

ECRImageAccess

ecr:BatchGetImage, ecr:GetDownloadUrlForLayer

arn:aws:ecr:us-east-1:625663093496:repository/*

ECRTokenAccess

ecr:GetAuthorizationToken

* — the action does not support resource scoping

CloudWatchLogsManage

logs:CreateLogGroup, DescribeLogGroups, DescribeLogStreams

arn:aws:logs:us-east-1:625663093496:log-group:*

CloudWatchLogsWrite

logs:CreateLogStream, PutLogEvents

/aws/bedrock-agentcore/runtimes/ and the stack’s own log group, both :log-stream:

XRayTracing

xray:PutTraceSegments, PutTelemetryRecords, GetSamplingRules, GetSamplingTargets

*

CloudWatchMetrics

cloudwatch:PutMetricData

*, conditioned on cloudwatch:namespace equalling bedrock-agentcore

AgentCoreWorkloadIdentity

bedrock-agentcore:GetWorkloadAccessToken, …ForJWT, …ForUserId

The default workload-identity directory and …/workload-identity/agentName-*

BedrockModelInvocation

bedrock:InvokeModel, InvokeModelWithResponseStream

arn:aws:bedrock:*::foundation-model/ and arn:aws:bedrock:us-east-1:625663093496:

MarketplaceModelAccess

aws-marketplace:Subscribe, Unsubscribe, ViewSubscriptions

*

SecretsManagerRead

secretsmanager:GetSecretValue, DescribeSecret

[props.secretArn] when set, otherwise …:secret:*

Plus a memory statement added through addToPolicy:

Actions Resources

bedrock-agentcore:CreateEvent, ListEvents, GetEvent, RetrieveMemoryRecords, ListMemoryRecords, GetMemory

[memory.attrMemoryArn]

Three of these deserve comment.

BedrockModelInvocation spans all regions

The foundation-model resource is arn:aws:bedrock:*::foundation-model/*, with a wildcard region. That is required, not sloppy: the us. model ids are cross-region inference profiles, and the underlying invocation is dispatched to whichever region serves the request. Pinning it to us-east-1 would break every model in AgentModelIds.

CloudWatchMetrics is conditioned, not scoped

cloudwatch:PutMetricData cannot be restricted by resource, so the policy restricts it by condition instead — the runtime can only publish into the bedrock-agentcore namespace. This is the correct pattern for actions with no resource-level control, and it is the only place the stack uses it.

MarketplaceModelAccess grants Subscribe

The runtime can subscribe the account to a Marketplace model offering, which has a billing consequence. It is present because first use of certain Bedrock models requires an implicit subscription. It is the widest grant in the policy.

SAM function roles

SAM generates one role per function from its Policies block. The policies in use:

Policy Effect

SQSPollerPolicy: { QueueName: … }

ReceiveMessage, DeleteMessage, GetQueueAttributes on one named queue

SQSSendMessagePolicy: { QueueName: … }

SendMessage on one named queue

Inline secretsmanager:GetSecretValue

Scoped to AgentcoreSecret by !Ref

Inline secretsmanager:PutSecretValue

enrichment-scraper only

Inline bedrock:InvokeModel, InvokeModelWithResponseStream

Resource "*" — enrichment-scorer and email-agent only

The SAM policy templates are resource-scoped by construction: a function that sends to CrmSyncQueue cannot receive from it, and cannot touch EnrichmentPipelineQueue at all. The producer/consumer topology in Queues and dead-letter queues is enforced by IAM, not just by convention.

The Bedrock statements use Resource: "*" rather than naming model ARNs. Same reason as the runtime role: cross-region inference profiles resolve to region-specific ARNs that cannot be enumerated up front. Narrowing it would need a wildcard on the region regardless.

Three things the functions notably do not have IAM permission for, because those systems do not use IAM: PostgreSQL (a connection string in the secret), HubSpot (an OAuth token or private app token), and BigQuery (a Google service-account JSON). Losing the AWS role does not protect any of them — see credentials.

Where the model is wide

Worth knowing, and each is a deliberate trade rather than an oversight:

Grant Reason

bedrock:InvokeModel on *

Cross-region inference profiles

aws-marketplace:Subscribe

First-use model subscription; has a billing effect

PutSecretValue on the whole shared secret

The scraper rotates its SmartScout session token; the API cannot scope to one JSON key

logs:CreateLogGroup on log-group:*

The runtime creates its own per-session log groups

The corresponding narrow points are real too: every queue permission names one queue, secret reads name one secret, and memory actions name one memory ARN.

Reviewing a change

  1. cdk diff prints an IAM change summary and, without --require-approval never, prompts before applying it. Read the summary — it is the only IAM review in the deploy path.

  2. sam deploy requires CAPABILITY_IAM, which is set in samconfig.toml and therefore never prompts. Inspect the changeset when a Policies block changed.

  3. Prefer a SAM policy template over an inline statement. SQSPollerPolicy cannot be accidentally over-scoped; a hand-written statement can.

  4. If an action supports no resource scoping, add a Condition instead of accepting *.