Security Engineering

Credential Boundaries: What an Agent Should Never Be Able to See

David Guzenburg/ / 8 min read

'Use environment variables, not literals' addresses where a secret is stored. It says nothing about what happens once the agent can read it — which is the part that matters.

credentialsleast privilegesecretsaudit

The credential question, asked properly

Most discussions of agent credentials stop at "use environment variables, not literals". That is necessary and nowhere near sufficient, because it addresses only where a secret is stored, not what happens once the agent can use it.

Three separate questions matter, and they have different answers:

  1. Which credentials can the agent see?
  2. Which can it use without seeing?
  3. What can each one reach if the session is steered by someone else?

Seeing versus using

The distinction is the most useful idea here and is routinely missed.

An agent that can read DATABASE_URL from the environment holds a value that can appear in its output — in a log line, an error message, a commit, a summary. An agent that can run ./query.sh, where the script reads the credential and the agent never sees it, can achieve the same task without ever holding the secret.

PatternAgent sees secretLeak paths
Secret in env, agent reads envYesLogs, output, commits, tool calls, transcripts
Secret in env, wrapper script uses itNoOnly what the wrapper prints
Secret in a vault, agent has vault tokenYes, on fetchSame as the first, plus the token itself
Broker holds secret, agent calls brokerNoOnly what the broker returns

Rows two and four are structurally better, and the improvement is not marginal. A secret the agent never holds cannot be exfiltrated through the agent, regardless of how convincingly it is instructed to do so.

#!/bin/bash
# The agent calls: ./query.sh "SELECT count(*) FROM orders"
# It never sees DATABASE_URL.
set -euo pipefail

case "$1" in
  *[Dd][Rr][Oo][Pp]*|*[Dd][Ee][Ll][Ee][Tt][Ee]*|*[Tt][Rr][Uu][Nn][Cc]*)
    echo "Refused: destructive statement." >&2; exit 1 ;;
esac

psql "$DATABASE_URL" -c "$1" 2>&1 | head -100

Two benefits, not one. The agent never holds the credential, and the wrapper is a natural place to put constraints — here a refusal on destructive statements and a cap on output size. Both would be impossible if the agent talked to the database directly.

Where to put policy

Every constraint you can express in a wrapper is a constraint enforced by code rather than requested of a model. When choosing between "tell the agent not to" and "make the wrapper refuse", the wrapper wins every time.

Scope, not convenience

The second question is what each credential reaches. The default in most setups is a token with far more scope than the task needs, because that is the token that already existed.

That last point is worth insisting on. An agent operating under a developer's personal token produces an audit trail that is actively misleading — every action attributed to a person who may not have been at their desk.

What must never be in context

Some things should never reach the window under any circumstances, regardless of how the session is structured.

NeverWhy
Production database credentialsBlast radius is unbounded
Cloud root or org-admin keysCan grant themselves anything else
Signing keys, release credentialsEnables supply-chain compromise downstream
Customer PII beyond the immediate taskRegulatory exposure, no upside
Other people's credentialsDestroys attribution entirely

The paths a secret actually escapes through

Worth enumerating, because the obvious one — the agent printing a credential in its reply — is neither the most likely nor the most damaging.

PathHow it happensPersistence
Written into a committed fileAgent adds a config with a real value while debuggingPermanent, in git history
Sent as a tool argumentPassed to a third-party MCP server as a parameterIn that server's logs, outside your control
Echoed in an errorA failed command prints the full connection stringIn the transcript, and in CI logs if it ran there
Included in a summaryAgent quotes an env dump when explaining a failureTranscript, and anywhere the summary is pasted
Committed to a test fixtureA real key used to make a test passPermanent

The second row is the one that should worry you most and gets the least attention. A credential passed as an argument to a third-party server has left your infrastructure entirely. You cannot scan for it, you cannot rotate it out of their logs, and you will not know it happened. This is the concrete reason the wrapper pattern matters: a secret the agent never holds cannot be passed as an argument to anything.

The blast radius question

The third question — what can each credential reach if the session is steered by someone else — is the one to actually sit down and answer, credential by credential. Assume the agent is doing exactly what an attacker wants and ask what that gets them.

CredentialWorst realistic outcomeAcceptable?
Read-only staging DBTest data disclosedUsually yes
Repo write, protected mainBad code in a PR, caught in reviewYes, if review is real
Repo write, no protectionDirect push to main, deployedNo
Cloud credential with IAM rightsPrivilege escalation to anythingNo
Package registry publish tokenMalicious release to your usersNo

Row two is the interesting one, because it is the whole argument for branch protection stated in security terms. With protected branches and required review, a fully steered agent still cannot ship code unilaterally — it can only produce a pull request a human must approve. Without them, the same credential is a direct path to production.

Detecting a leak after the fact

Assume something will end up in a transcript eventually, and arrange to find out. Two mechanisms, both cheap:

Canary credentials. Place a credential that does nothing except alert when used. If it is ever exercised, something read it out of a place it should not have.

Scan transcripts. If your tooling retains session logs, run the same secret-scanning rules over them that you run over commits. The patterns already exist; pointing them at a second corpus is nearly free.

#!/bin/bash
# Point existing secret-scanning rules at agent session logs.
gitleaks detect --no-git --source ./agent-logs \
  --report-path leaks.json --redact

Most teams scan their commits and have never scanned a transcript, despite the transcript being the place a secret is most likely to appear first.

Takeaway

Separate seeing a credential from using one — a wrapper script the agent calls is strictly better than a secret it can read, and gives you a place to enforce policy in code. Scope every credential to read-only, non-production and short-lived by default, give agents their own identity, and scan transcripts with the same rules you point at commits.

Keep reading
Codex vs Claude

What the Agent Inherits: Sessions You Are Already Logged Into

Credential discussions focus on secrets in files. On a developer machine most access is an authenticated session an agent can simply use: cloud CLIs, the current kubectl context, a forwarded SSH agent. An inventory script and what to make absent by default.

Security Engineering

Gating Irreversible Actions: Controls That Don't Depend on the Model

Sorting agent actions by reversibility and visibility, why the strongest control is simply not granting the credential, and the limits of confirmation prompts.

Security Engineering

Hardening the Local Toolchain: Prompt Injection on a Developer Machine

Where untrusted text enters an agent's context on your own laptop, four controls ordered by return, and the session split that turns a possible compromise into a contained one.

Codex vs Claude

Below the Prompt: What a Kernel Sandbox Actually Constrains

Seatbelt, bwrap and seccomp enforce policy that injected text cannot argue with. What a profile can express, why a workspace-write policy still permits everything inside your repository including .git/hooks, and a self-test that proves the policy is on.

← Shell Execution and Blast Radius: What an Agent Can Actually Reach  ·  Prompt Injection Through Tool Descriptions and Tool Output →

All security engineering articles  ·  Every article