AWS Builder Center
Credential Management in an AgentCore Code Sandbox

Credential Management in an AgentCore Code Sandbox

Short-lived, scoped credentials for agent-run code in an AgentCore sandbox — and the difference between hiding a secret from the model and from the code.

Give an autonomous agent a code sandbox and sooner or later it needs a credential. It has to  git push , call a partner API, read an object from S3, write a report somewhere. The moment you hand it one, you've made a security decision — and most of the time it's made implicitly, by whoever wrote the setup script.
There's one distinction that, once you see it, changes how you design the whole thing:
Keeping a secret out of the model's context is not the same as keeping it out of the sandbox.
Those are two different problems with two different solutions, and conflating them is how teams end up believing a credential is "hidden from the agent" when any line of code the agent writes can read it. This article walks through both — using the  Amazon Bedrock AgentCore Code Interpreter  as the setting and a GitHub write token as the worked example.

The uncomfortable starting fact

AWS is refreshingly blunt about this in its own guidance: treat all code running in a Code Interpreter session as potentially untrusted — the model-generated code, the libraries it imports, the shell commands, the data-driven branches. The execution role's temporary AWS credentials are reachable from inside the microVM through the instance metadata service, so any code that can run in that sandbox can use that role's permissions ( Understanding Credentials Management in AgentCore ).
The same holds for anything you seed into the session. Write a token to  ~/.git-credentials , export it as  $GH_TOKEN , drop it in a file — the agent's next shell command can  cat  it. That isn't a flaw in AgentCore; it's the nature of running arbitrary code. It just means you have to be precise about what each control actually buys you.
So before seeding any credential, ask two separate questions:
  1. Can the model repeat it? — Could the secret end up in the prompt, the reasoning context, a tool result, a log line, or an error message the model then sees and echoes back? This is the exfiltration-by-the-model risk, and it's the one prompt injection loves.
  1. Can arbitrary code read it? — Is the raw credential present inside the sandbox at all, where any generated code can pick it up and use it?
You can fully solve (1) and still be wide open on (2). The patterns below are really about choosing how far you push on each.

A ladder of patterns, strongest first

Diagram 1

Strongest — don't put the secret in the sandbox at all. For anything high-impact (production database writes, payments, signing keys, PII exports, IAM or KMS changes), don't give the sandbox a credential. Expose a narrow internal API — or an  AgentCore Gateway  target — that authenticates the request, authorizes the specific action server-side, validates inputs, and returns only the result. The privileged credential lives behind that boundary, and the sandbox gets a capability, not a key. If a prompt injection convinces the agent to do something ugly, it's still bounded by what that one API permits.
Middle — let AgentCore Identity broker outbound credentials. When the sandbox needs to call a third-party service (Google, Slack, Salesforce, a custom OAuth API, or an API-key-only endpoint), reach for  AgentCore Identity . You store the client secret or API key in  AWS Secrets Manager , register a credential provider that references it, and the agent exchanges its short-lived workload identity token for a scoped access token at execution time. Your config holds the provider name, never the raw key. Prefer user-delegated OAuth with minimal scopes for user data, machine-to-machine credentials only for service-owned data, and a separate provider per integration and environment —  github-readonly-prod , not one shared "SaaS credentials" bucket. This is also the  Well-Architected Agentic AI Lens  recommendation: OAuth over static keys wherever the service supports it.
In code, the SDK hands you the credential through a decorator — the provider name goes in, the resolved key comes out as a parameter you attach to the outbound call ( Obtain API key ):
Python
from bedrock_agentcore.identity.auth import requires_api_key

@requires_api_key(provider_name="github-readonly-prod") # the provider name, never the key
async def list_repos(*, api_key: str):
# api_key is resolved from the vault at call time and injected here
async with httpx.AsyncClient() as client:
resp = await client.get(
"https://api.github.com/user/repos",
headers={"Authorization": f"Bearer {api_key}"}, # the real token goes on the wire
)
return resp.json()
# use it immediately — don't log, persist, or return it to the model
Be clear-eyed about what this buys: the long-lived secret stays in the vault and out of your source, env, and image, and it's fetched just-in-time and centrally rotatable. But the resolved key does materialize in the function to make the call — so in this middle path the sandbox code holds a usable credential for the duration of the request. That's the line between this and the strongest rung, where the sandbox never holds one at all.
Pragmatic — seed a scoped, short-lived credential into the session. Sometimes the tool genuinely needs the raw credential in hand —  git  and  gh  want a token in the credential store, not a capability behind an API. That's fine, as long as you're honest that the code can read it, and you bound the blast radius with three things: narrow scope, short lifetime, and tight control over who can trigger the run. The rest of this article is a worked example of doing exactly that well.

Worked example: a GitHub write token for a coding agent

Picture an autonomous agent that acts on a GitHub issue — it needs to  git clone , commit, push, and open a PR on one repo, for the duration of one run. The tooling needs a real token on disk. Here's the shape that keeps it tight.

Mint in trusted server-side code, never in the sandbox

The only long-lived secret is a GitHub App private key, and it never leaves your trusted backend (a Lambda, in this example). The key signs a short-lived App JWT, which is used once to request a scoped installation token:
Diagram 2

Two details carry a lot of weight here. First, the JWT is deliberately tiny —  exp  nine minutes out (under GitHub's ten-minute cap),  iat  backdated sixty seconds to tolerate clock drift — and it's discarded the instant the installation token comes back. Second, the installation token is scoped down at issuance, which is the single most important line of the whole flow:
JSON
{
"repository_ids": [123456789],
"permissions": {
"contents": "write",
"pull_requests": "write",
"issues": "write"
}
}
Leave those fields off and GitHub hands back a token with every repo and every permission the installation has. With them, a leaked token can touch one repository and do three things to it, for about an hour. GitHub's own  best-practices guide  is explicit: downscope both  repositories  and  permissions  on every mint, and keep the App's registered permissions minimal to begin with — a token can only ever be narrower than the App.

Seed it out of band, not through the prompt

The token travels into the session once, inside the TLS-encrypted body of a setup call that runs a shell command in the same session the agent will use — it sets  git config --global credential.helper store , writes  ~/.git-credentials  with an  x-access-token:<token>@github.com  entry at mode  600 , and runs  gh auth login --with-token . The agent's later tool calls simply find  git  and  gh  already authenticated. The token is never part of the model prompt, so there's no path for the model to read it out of its own context and repeat it.
That solves question (1). It does not solve question (2) — and this is the part worth saying out loud.

What "the model never sees it" actually means

It's easy to overstate this, so here's the honest framing.
What's true: the token never enters the model's prompt or reasoning context, is never written to a log in cleartext, and is redacted from error output and status comments (a simple  redact()  that turns  x-access-token:…@  into  x-access-token:***@  on anything headed for a log or a caller). It can't be exfiltrated by the model repeating it, and it won't leak through your own logging.
What's not true: the token is not cryptographically hidden from the agent. It's on disk at  ~/.git-credentials  and, if you also export it, in  $GH_TOKEN . An agent that runs  cat ~/.git-credentials  reads it. As AWS puts it for AgentCore Identity tokens too — once a credential is injected into code, treat it as exposed to that process.
So the design goal is precise: authenticated  git / gh  without the token flowing through the model's context, bounded by a one-hour single-repo token — not hard isolation of the credential from the code. What makes that acceptable is the combination of controls, not any one of them:
  • Narrow scope + short life — one repo, three permissions, ~1 hour.
  • Identity gating on the trigger — only act on a mention from someone whose  author_association  is  OWNER ,  MEMBER , or  COLLABORATOR . The person who can make the agent wield a repo-write token already has comparable access to that repo themselves, so the token grants no new reach.
  • Webhook signature verification — HMAC-SHA256 over the raw body, keyed by a secret, so the trigger itself can't be forged.
Redaction is defense-in-depth on top of those, not the primary control. If you want the stronger posture, that's the top rung of the ladder: don't give the sandbox the token — put the git operations behind an internal API and let the sandbox request a push, not hold a key.

Revoke when the job is done

GitHub installation tokens expire on their own after about an hour, and it's tempting to stop there. But GitHub specifically recommends revoking a token after a high-risk, one-off, privileged job handed to a short-lived worker — which is exactly an autonomous agent run. One call closes the window:
http
DELETE /installation/token
Authorization: Bearer <installation-token>
Make it best-effort — a failed revoke should log a warning, not fail the run — and it shrinks the post-run exposure from ~1 hour down to seconds. It's the highest-value hardening step most teams skip.

Don't forget the execution role

All of the above is about a credential you seed in. The sandbox also carries the credential you didn't think about: its execution role. A few rules keep it from becoming the soft underbelly:
  • No  secretsmanager:GetSecretValue  by default. A sandbox role with  GetSecretValue  on  *  — or even on one secret — means arbitrary code can read, print, and exfiltrate that secret. Let Secrets Manager be the system of record and have AgentCore Identity reference secrets; keep the read permission off the sandbox role.
  • Least privilege, per trust boundary. A dedicated role per interpreter, environment, and tenant —  ci-reporting-readonly-prod , not one shared "agent role." Scope it to the exact actions, resources, and prefixes the sandbox needs (e.g.  s3:GetObject  on one input prefix,  s3:PutObject  on one output prefix) and nothing more. AWS recommends the execution role hold equal or fewer privileges than the principals allowed to invoke it.
  • Separate the invocation plane from the execution plane. Who may create interpreters and choose execution roles is a different permission from what code inside one can do. Don't let an app role create arbitrary interpreters.
  • Pair network egress with credential access. A sandbox holding anything useful shouldn't also have unrestricted  0.0.0.0/0  egress — that's the exfiltration path. Use  VPC mode  with private subnets and VPC endpoints for private resources, and keep public internet access off unless the task truly needs it.

The checklist

Before you seed a credential into an agent sandbox, run it past these:
  1. Does the sandbox actually need the raw credential, or can a narrow API hold it instead?
  1. Is it scoped to the minimum resources and permissions at issuance?
  1. Is it short-lived, and do you revoke it when the privileged job finishes?
  1. Does it stay out of the model's context — prompts, tool results, memory, traces?
  1. Is it redacted from logs and error output as defense-in-depth?
  1. Is the trigger authenticated and identity-gated, so only someone with comparable access can set it off?
  1. Does the execution role avoid broad  GetSecretValue  and follow least privilege?
  1. Is network egress constrained so a leaked secret can't easily leave?
Get the first three right and most of the risk is already bounded. The rest is what turns a working pattern into one you'd defend in a review.

Related reading

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article