Production Hardening & IaC
VPC, KMS, least privilege, and infrastructure as code
- Explain AgentCore's defense-in-depth model layer by layer: microVM isolation, inbound auth, the outbound token vault, Cedar Policy, Guardrails, and auditability
- Articulate why a sandbox is strong on cross-tenant isolation but NOT an airtight data-egress boundary, and what compensating controls to add
- Configure GA networking — VPC mode, AWS PrivateLink, and resource tagging — while verifying which mode each service actually supports
- Author a least-privilege execution role and trust policy instead of relying on the broad auto-generated dev role, keeping secrets in the token vault / Secrets Manager and out of images
- Deploy AgentCore repeatably with the new CLI's CDK→CloudFormation path, and reason about CloudFormation/CDK/Terraform for reviewable infrastructure
AgentCore gives you a strong default security posture — per-session microVM isolation, SigV4/JWT inbound auth, a KMS-encrypted outbound token vault, Cedar Policy governance, Bedrock Guardrails, and durable auditable logs — but it is defense in depth, not a single airtight wall. This lesson teaches that layered model honestly (sandboxes resist cross-tenant attacks far better than they bound data egress), then walks the production engineering you own: VPC/PrivateLink networking, least-privilege execution roles, KMS keys, secrets handling, and repeatable infrastructure via CDK→CloudFormation and Terraform.
- 1Defense in depth: six layers, not one wall
- 2The honest caveat: isolation is not an egress boundary
- 3GA networking: VPC, PrivateLink, tagging — and verifying scope
- 4Least-privilege execution roles (not the dev default)
- 5Infrastructure as code: CDK→CloudFormation and Terraform
- 6Guardrails, Cedar governance, and auditable logs
Defense in depth: six layers, not one wall
AgentCore's security is not a single boundary — it is a stack of independent layers, each of which has to be defeated for an attacker to win. Internalize the layers as a checklist; a hardened deployment uses all of them.
| Layer | What it does | The control |
|---|---|---|
| Isolation | Each user session runs in its own dedicated Firecracker microVM (isolated CPU, memory, filesystem). On session end the microVM is torn down and memory sanitized — no cross-session contamination. | Built in to Runtime, Browser, Code Interpreter |
| Inbound auth | Who may invoke the runtime/gateway. SigV4 (IAM) or JWT/OIDC (Cognito, Entra ID, Okta, Auth0…). InvokeAgentRuntimeForUser delegates an opaque user id — lock it down. | Configured per runtime/gateway |
| Outbound auth + token vault | Downstream credentials (OAuth tokens, API keys) held in a KMS-encrypted vault with strict per-(agent identity + user id) isolation — "zero token sharing." | AgentCore Identity, @requires_access_token / @requires_api_key |
| Governance | The Cedar Policy engine intercepts every tool call in ENFORCE or LOG mode; gateway interceptors and namespace-scoped IAM on Memory add more chokepoints. | Policy, Gateway, Memory IAM |
| Guardrails | Bedrock Guardrails — content filters, denied topics, PII detection/redaction, contextual grounding — applied natively or model-agnostically. | Bedrock Guardrails |
| Auditability | APPLICATION_LOGS capture full request/response payloads plus trace/session IDs, durable to CloudWatch Logs / S3 / Data Firehose; CloudTrail records control-plane calls. | Observability + CloudTrail |
The headline layer is session isolation. Agents are non-deterministic, hold stateful reasoning context, and perform privileged tool operations on a user's behalf — so AWS gives every session its own microVM and destroys it on termination (idle timeout and max compute lifetime defaults — commonly 15 min and 8 hours as of writing; these are quotas, so confirm the current values on the live limits page). Critically, AWS does not map sessions to users: "your client backend should maintain the relationship between users and their session IDs." Owning that mapping — and per-user session caps — is your job, not the platform's.
The outbound layer is the one engineers most often get wrong by reflex: they hardcode an API key or stuff it into a prompt. AgentCore Identity exists precisely so you never do that. Tokens live in the vault, encrypted with KMS, and are pulled at call time by a decorator — never embedded in code, config, or the LLM context window.
Key insight
The decorator is the whole point of the token vault
@requires_access_token (OAuth) and @requires_api_key (static keys, e.g. OpenAI) fetch the credential from the vault at invocation time and hand it to your function. The credential is scoped to the specific (agent + user) pair, so user A's Slack token can never leak into user B's session. If you find yourself writing os.environ["SOME_API_KEY"] inside an agent, that's the anti-pattern this layer eliminates.
Note
Firecracker is ecosystem-sourced
The AgentCore developer guide says "dedicated microVM." AWS blogs and the ecosystem name it Firecracker. Treat the specific hypervisor name as ecosystem-sourced rather than a guaranteed contract — what AWS commits to is per-session isolation with teardown and memory sanitization.
The honest caveat: isolation is not an egress boundary
Here is the senior-engineer truth that marketing slides skip: AgentCore sandboxes are strong on cross-tenant isolation but are NOT airtight data-egress boundaries. The microVM keeps tenant A's data away from tenant B extremely well. It does not, by itself, guarantee that a compromised or prompt-injected agent inside a sandbox cannot smuggle data out.
The concrete leak everyone misses is DNS. The Code Interpreter SANDBOX network mode is often described as "no internet," but the dossier is precise: SANDBOX still permits DNS (A/AAAA) resolution. Teach it as "no general internet egress, S3 reachable via the execution role" — not "airtight network jail." DNS resolution alone is a viable covert channel.
# networkConfiguration on a Code Interpreter session
# SANDBOX -> isolated; no general public egress; S3 reachable via execution role; DNS still resolves
# PUBLIC -> full public internet egress
{
"networkConfiguration": { "networkMode": "SANDBOX" }
}This is not hypothetical. Published external research demonstrates real exfiltration paths out of the Code Interpreter sandbox:
- BeyondTrust — DNS-based command-and-control out of
SANDBOX. - Sonrai — credential exfiltration via the metadata service (
169.254.169.254) plus a "global S3" covert channel. - Palo Alto Unit 42 — a network-isolation bypass.
The correct takeaway is not "the sandbox is broken" — cross-tenant isolation holds. The takeaway is that isolation is one layer, and you must combine it with the others: least-privilege execution roles (so an escaped agent has nothing worth stealing), no sensitive secrets baked into the role, and active monitoring of egress. The platform raises the floor; it does not remove your obligation to assume the agent can be subverted.
Watch out
SANDBOX ≠ no network
Do not design a control whose only assumption is "the agent in SANDBOX mode cannot phone home." It can resolve DNS, and documented research has used that and the instance metadata endpoint to exfiltrate. Verify the current remediation status on the Code Interpreter session-characteristics page before relying on any specific mitigation — this area is [VOLATILE].
Tip
Assume breach, then minimize blast radius
Combine isolation with least privilege and monitoring. If the execution role grants only the one S3 prefix the agent needs and no Secrets Manager wildcard, a successful escape yields very little. Pair that with APPLICATION_LOGS and CloudTrail so the attempt is at least auditable.
GA networking: VPC, PrivateLink, tagging — and verifying scope
At GA, all AgentCore services gained VPC, AWS PrivateLink, AWS CloudFormation, and resource tagging support [VOLATILE — confirm scope per service]. "All services" is the headline, but the per-service reality is uneven, so verify before you assume parity.
Runtime supports VPC mode. Configure it through the starter toolkit with --vpc / --subnets / --security-groups, so the runtime's egress flows through your VPC's routing, NAT, and security groups:
# Starter-toolkit configure with VPC networking
agentcore configure \
--vpc \
--subnets subnet-0a1b2c3d,subnet-4e5f6a7b \
--security-groups sg-0123456789abcdef0Network egress to customer VPCs is billed at roughly $0.006/GB in commercial regions [VOLATILE — verify the live pricing page]. Treat that number as a concept (VPC egress is metered), not a timeless fact.
The tools do NOT all match Runtime. The dossier is explicit about the gaps:
| Service | Network modes documented | VPC mode? |
|---|---|---|
| Runtime | VPC mode via --vpc/--subnets/--security-groups | Yes |
| Browser | Documents only PUBLIC network mode | Not documented |
| Code Interpreter | SANDBOX / PUBLIC only | Not documented (UNVERIFIED — do not assume Runtime parity) |
So if your compliance story depends on "all agent traffic stays inside the VPC," that holds for the Runtime but is not confirmed for the Browser or Code Interpreter tools — design accordingly and read the live docs.
Resource tagging is the cheap governance win. Tag every AgentCore resource at creation for cost allocation, attribution, and tag-based IAM/Policy conditions. Tagging is supported across the services at GA, and IaC is the right place to enforce it (every resource gets the same baseline tags automatically).
Watch out
Don't promise VPC isolation you can't deliver
Runtime VPC mode is documented; Browser is documented as PUBLIC only, and Code Interpreter only as SANDBOX/PUBLIC with VPC mode UNVERIFIED. If an auditor asks 'does all agent egress stay in our VPC?', the honest answer covers Runtime but flags the tools. Verify the current scope per service on the live AWS docs — this is [VOLATILE].
Least-privilege execution roles (not the dev default)
AgentCore uses two roles you must understand: the execution role the agent assumes at runtime, and the trust policy that lets the service assume it. AWS itself warns that auto-generated dev policies are broad and not production-grade — the security research above specifically calls out the Starter Toolkit's default role as granting full S3 read + Secrets Manager, exactly the kind of standing credential an escaped agent would love.
Scope the execution role to only what the agent actually does. The dossier's abbreviated action list:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Logs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents",
"logs:DescribeLogGroups", "logs:DescribeLogStreams"
],
"Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/bedrock-agentcore/runtimes/*"
},
{
"Sid": "Tracing",
"Effect": "Allow",
"Action": [
"xray:PutTraceSegments", "xray:PutTelemetryRecords",
"xray:GetSamplingRules", "xray:GetSamplingTargets"
],
"Resource": "*"
},
{
"Sid": "Metrics",
"Effect": "Allow",
"Action": "cloudwatch:PutMetricData",
"Resource": "*",
"Condition": { "StringEquals": { "cloudwatch:namespace": "bedrock-agentcore" } }
},
{
"Sid": "WorkloadTokens",
"Effect": "Allow",
"Action": "bedrock-agentcore:GetWorkloadAccessToken*",
"Resource": "arn:aws:bedrock-agentcore:us-east-1:123456789012:workload-identity-directory/default/workload-identity/myAgent-*"
},
{
"Sid": "Model",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": "*"
}
]
}Notes that separate a production role from the dev default:
- ECR actions are container-only.
ecr:BatchGetImage,ecr:GetDownloadUrlForLayer,ecr:GetAuthorizationTokenbelong on the role only when you deploy via Container/ECR. A CodeZip deploy that carries ECR perms is over-broad — and a container deploy missing them silently fails to pull the image. - Workload-token access is scoped by agent name (
workload-identity-directory/default/workload-identity/<agentName>-*), not a wildcard. cloudwatch:PutMetricDatais namespace-conditioned tobedrock-agentcore.- Drop
bedrock:InvokeModelentirely if the agent never calls a Bedrock model.
The trust policy uses confused-deputy guards — principal bedrock-agentcore.amazonaws.com, sts:AssumeRole, conditioned on aws:SourceAccount and aws:SourceArn:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "bedrock-agentcore.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "aws:SourceAccount": "123456789012" },
"ArnLike": { "aws:SourceArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:*" }
}
}]
}Secrets stay out of the role and out of the image. The role is for AWS permissions; downstream credentials belong in the token vault (fetched via @requires_access_token / @requires_api_key) or in Secrets Manager — never baked into a container image, a CodeZip bundle, or an environment variable in the deployment. Encrypt at rest with KMS CMKs where the service supports them (the token vault encrypts with service-managed KMS or a CMK; Memory and other stores accept CMKs too) so you control rotation and key access.
For the developer/caller identity, the managed policy BedrockAgentCoreFullAccess is the quickest way to get going, but it is a developer convenience, not a production runtime grant — keep it off the execution role.
Watch out
The auto-generated dev role is the documented attack surface
The Sonrai research achieved credential exfil partly because the Starter Toolkit's default execution role carried full S3 read plus Secrets Manager. In production, never ship that role: scope S3 to specific prefixes, drop Secrets Manager unless the agent truly needs it, and prefer the token vault for downstream credentials.
Tip
Match ECR perms to your build mode
CodeZip deploys (zip → S3) need no ECR actions; Container deploys (ARM64 → ECR) need exactly the three ECR read actions. A role/build-mode mismatch is a listed deploy gotcha — over-broad on CodeZip, broken pull on Container.
Infrastructure as code: CDK→CloudFormation and Terraform
Repeatable, reviewable infrastructure is the difference between a demo and a production service. AgentCore is built for this: at GA every service gained CloudFormation support, and the new AgentCore CLI (@aws/agentcore) deploys via CDK → CloudFormation under the hood.
That means agentcore deploy is not magic — it synthesizes a CDK app and applies a CloudFormation stack, which is exactly what you want for production: a diffable template, a change set you can review, and a clean teardown.
npm install -g @aws/agentcore
# Scaffold a production-shaped agent (HTTP protocol, Bedrock model, no managed memory)
agentcore create --name MyAgent --framework Strands --protocol HTTP \
--model-provider Bedrock --memory none
cd MyAgent
agentcore dev # hot-reload server + inspector on :8080 (local)
agentcore deploy # package -> CDK synth -> CloudFormation deploy
agentcore status
agentcore logs
agentcore traces list
agentcore remove all && agentcore deploy # full teardown then redeployPrerequisites are CDK-shaped. The new CLI needs Node 20+, Python 3.10+, AWS credentials, and — critically — cdk bootstrap to have been run in the target account/region. "CDK not bootstrapped" is a documented deploy gotcha. The caller also needs CloudFormation/CDK permissions in addition to the AgentCore ones.
Why IaC, beyond repeatability:
- Reviewable least privilege. The execution-role JSON above lives in your template, so a reviewer can see — in a PR — that you didn't grant
s3:*. Drift is caught by the next deploy. - Enforced tagging. Apply a baseline tag set at the stack level so every AgentCore resource is tagged for cost allocation and tag-based governance, with no human remembering to do it.
- Reproducible environments. The same template stands up dev, staging, and prod with parameterized account/region/VPC inputs.
CloudFormation, CDK, or Terraform? Pick the one your org already standardizes on:
- CDK — what the new CLI emits; ideal if you want to extend the generated app in TypeScript/Python.
- CloudFormation — the lingua franca underneath; review the synthesized template directly, or hand-author for tight control.
- Terraform — fully viable since the resources are CloudFormation-backed; use it when Terraform is your house standard for multi-account/multi-cloud estates.
Whatever you choose, the win is the same: the security posture (roles, KMS keys, VPC config, tags) is code in version control, not console clicks.
Tip
agentcore deploy IS CloudFormation
Because agentcore deploy synthesizes CDK and applies a CloudFormation stack, you get change sets, drift detection, and agentcore remove all for teardown for free. Treat the generated stack as a reviewable artifact: read the synthesized template in CI before it deploys.
Note
Don't confuse the two agentcore commands
There are two CLIs: the new @aws/agentcore (CDK→CloudFormation, agentcore create/dev/deploy) and the older Starter Toolkit (configure/launch/invoke/status, where Python is always .launch()). Confusing the two — or launch vs deploy — is a listed gotcha. Match the command set to the tool you actually installed.
Guardrails, Cedar governance, and auditable logs
The remaining layers turn a secured deployment into a governed, observable one.
Cedar Policy governs tool calls. The Policy engine intercepts every tool call in one of two modes: ENFORCE (block disallowed calls) or LOG (record what would be blocked without blocking). The progression for a new policy is to roll out in LOG, watch the audit trail for false positives, then flip to ENFORCE. Gateway interceptors and namespace-scoped IAM on Memory add further chokepoints.
Bedrock Guardrails span models and frameworks — content filters, denied topics, word filters, PII detection/redaction, and contextual grounding. Two ways to apply them:
# 1) Native (Bedrock model): attach the guardrail to a Strands BedrockModel
from strands.models import BedrockModel
model = BedrockModel(
model_id="global.anthropic.claude-sonnet-4-5-20250929-v1:0", # [VOLATILE] verify current default
guardrail_id="gr-abc123",
guardrail_version="1",
guardrail_trace="enabled",
)
# Detect a firing: response.stop_reason == "guardrail_intervened"
# 2) Model-agnostic (imperative): moderate text without invoking a Bedrock FM
# Works with OpenAI / Gemini / self-hosted models
resp = bedrock_runtime.apply_guardrail(
guardrailIdentifier="gr-abc123",
guardrailVersion="1",
source="INPUT", # or "OUTPUT"
content=[{"text": {"text": user_input}}],
)The ApplyGuardrail API is what lets one guardrail policy cover a fleet that mixes Bedrock, OpenAI, and self-hosted models. A useful rollout trick mirrors Cedar's LOG mode: run guardrails in shadow mode (Strands Hooks on MessageAddedEvent / AfterInvocationEvent log would-be blocks) before you enforce.
Auditability is durable and detailed. APPLICATION_LOGS capture full request/response payloads plus trace and session IDs, and route to CloudWatch Logs, S3, or Data Firehose (cross-account monitoring supported); CloudTrail records control-plane API calls. That richness is a double-edged sword:
APPLICATION_LOGS -> full request + response payloads + trace/session IDs
-> CloudWatch Logs / S3 / Data Firehose (durable, cross-account)Watch out
Full payload logging will capture your secrets and PII
Because APPLICATION_LOGS capture complete request/response payloads, they will faithfully record any PII or secret that flows through the agent. Filter PII and secrets BEFORE emission — do not rely on after-the-fact log scrubbing. This is the auditability layer's sharpest edge.
Tip
Roll out enforcement in observe-first mode
Both Cedar Policy (LOG mode) and Guardrails (shadow mode via Strands Hooks) let you watch what would be blocked before you block it. Use that window to catch false positives against real traffic, then promote to ENFORCE — far safer than flipping enforcement on blind.
Try it: Harden an agent: least-privilege role, verified networking, and a reviewable stack
Goal: take an agent from the broad dev default to a production-hardened, IaC-defined deployment — and prove the egress caveat to yourself.
-
Inspect the dev default. Deploy a throwaway agent with the Starter Toolkit and read its auto-generated execution role in the IAM console. Note any broad grants (e.g. wide S3 read, Secrets Manager). Write down which permissions the agent actually needs versus what it was given.
-
Author a least-privilege role in IaC. In a CloudFormation template (or CDK/Terraform), define an execution role scoped to:
logs:*on/aws/bedrock-agentcore/runtimes/*, the four X-Ray actions,cloudwatch:PutMetricDataconditioned oncloudwatch:namespace = bedrock-agentcore,bedrock-agentcore:GetWorkloadAccessToken*scoped toworkload-identity-directory/default/workload-identity/<yourAgent>-*, andbedrock:InvokeModelonly if the agent calls Bedrock. Add the trust policy withaws:SourceAccountandaws:SourceArnconfused-deputy guards. If you deploy via CodeZip, deliberately OMIT the three ECR actions and note why. -
Move a secret into the vault. Take any downstream API key the agent uses (e.g. an OpenAI key) out of code/env and register it as a credential provider, then fetch it with
@requires_api_key(or@requires_access_tokenfor OAuth). Confirm the key no longer appears in your image, your stack, or any prompt. -
Deploy as a reviewable stack. Run
cdk bootstrap(once per account/region), scaffold withagentcore create --name MyAgent --framework Strands --protocol HTTP --model-provider Bedrock --memory none, thenagentcore deploy. Find the resulting CloudFormation stack in the console and read the synthesized template — confirm your scoped role and baseline tags are present. -
Verify the networking scope. Configure Runtime VPC mode (
agentcore configure --vpc --subnets ... --security-groups ...). Then check the live docs for the Browser and Code Interpreter tools and record which network modes each documents — confirm for yourself that VPC-only egress is NOT guaranteed for the tools. -
Prove the caveat (read-only). Without attempting anything malicious, reason through the BeyondTrust/Sonrai/Unit 42 findings: given your least-privilege role from step 2, what would an escaped agent in a SANDBOX be able to reach? Write three sentences on why scoping the role and enabling
APPLICATION_LOGS+ CloudTrail (with PII/secret filtering before emission) shrinks the blast radius.
Key takeaways
- 1Security is defense in depth across six layers: per-session Firecracker microVM isolation, SigV4/JWT inbound auth, a KMS-encrypted outbound token vault with per-(agent+user) isolation, Cedar Policy governance, Bedrock Guardrails, and durable auditable logs. Use all of them.
- 2Sandboxes are strong on cross-tenant isolation but NOT airtight data-egress boundaries — Code Interpreter SANDBOX mode still resolves DNS, and BeyondTrust/Sonrai/Unit 42 have published real exfil paths. Combine isolation with least privilege and monitoring; assume breach.
- 3At GA all services gained VPC, PrivateLink, CloudFormation, and tagging — but verify scope: Runtime supports VPC mode (`--vpc`/`--subnets`/`--security-groups`), while Browser documents PUBLIC only and Code Interpreter only SANDBOX/PUBLIC (VPC unverified).
- 4Never ship the broad auto-generated dev role (full S3 + Secrets Manager). Scope the execution role to logs/X-Ray/CloudWatch/workload-tokens/model, make ECR actions container-only, scope workload-token access by agent name, and use the confused-deputy trust policy (aws:SourceAccount + aws:SourceArn).
- 5Keep secrets out of roles and images: downstream credentials live in the token vault (`@requires_access_token` / `@requires_api_key`) or Secrets Manager, encrypted with KMS CMKs you control.
- 6Deploy repeatably: the new `@aws/agentcore` CLI runs CDK→CloudFormation (needs `cdk bootstrap`), so `agentcore deploy` produces a reviewable stack. CDK/CloudFormation/Terraform all work — pick your house standard and keep roles, KMS, VPC, and tags in version control.
- 7Roll out enforcement observe-first: Cedar Policy in LOG mode and Guardrails in shadow mode let you catch false positives against real traffic before flipping to ENFORCE; and filter PII/secrets before APPLICATION_LOGS emission because they capture full payloads.
Quiz
Lock in what you learned
Check your understanding
0 / 4 answered
1.A teammate claims that running the Code Interpreter in SANDBOX network mode means a compromised agent 'has no network access and cannot exfiltrate anything.' What is the accurate correction?
2.You're writing a CloudFormation template for an AgentCore agent that calls a Bedrock model and is deployed via CodeZip (not a container). Which execution-role choice is correct?
3.Where should a downstream OAuth token (e.g. the user's Slack token) live so it is never exposed to code, images, or the LLM context?
4.Your org standardizes on Terraform, but a colleague says AgentCore 'can only be deployed with the agentcore CLI's CDK path.' What's the correct picture of AgentCore IaC?
Go deeper
Hand-picked sources to keep learning
Execution-role actions, trust policy, and caller permissions — the source for the least-privilege role in this lesson. Verify the action list against this live page.
The CDK→CloudFormation deploy path: agentcore create/dev/deploy, prerequisites including cdk bootstrap.
SANDBOX vs PUBLIC network modes and the DNS-egress nuance. Re-check current remediation status — this area is volatile.
SigV4/JWT inbound, the workload-token flow, and the token vault — the auth layers of defense in depth.
GA announcement; source for the VPC/PrivateLink/CloudFormation/tagging-at-GA claim. Confirm per-service scope here.
Deep dive on the token vault, per-(agent+user) isolation, and KMS encryption for outbound credentials.