Part 2 · Containing the Damage

Security · ~7 min

Keep the Keys Out

The trifecta's "private data" leg is the one you can shrink in an afternoon. Don't let a secret into context, and there's nothing to exfiltrate.

Why this, for you: a credential the agent can't read is a credential it can't leak — even under a successful injection. This is the most reversible, highest-leverage security change you can make to a coding agent, and it takes minutes, not an architecture rewrite.

Pasting an API key into a prompt sends it to the model API, writes it into session logs, and risks the agent echoing it back in a comment or generated file. Once a secret enters the context window, you've lost control of where it goes. The fix: agents need results, not credentials.

1 Inject at the shell, not the prompt

Set secrets as environment variables before the agent process starts. The agent's tools consume $VAR internally — the value is inherited by child processes but never travels through a tool call as text.

# credentials pre-loaded in the env; never typed into a prompt DATABASE_URL="postgres://..." STRIPE_API_KEY="sk-..." claude
Environment variables are inherited by child processes but are not transmitted as text through tool calls. The context window records the command name and arguments — not the secret behind them.

2 Wrapper scripts hide the secret entirely

Go one step further: a wrapper script consumes the credential internally and returns only output. The agent calls the script by name; the key never appears in the tool input.

#!/bin/bash — scripts/stripe-balance.sh # the agent runs this; the key stays invisible curl -s -H "Authorization: Bearer $STRIPE_API_KEY" \ https://api.stripe.com/v1/balance | jq '.available[0].amount'

Design wrappers to accept intent and return results. The Vault token, the 1Password service account, the raw key — none ever enter the agent's context.

3 Audit the environment before every session

Injecting all available credentials expands blast radius for free. Before starting, list what the agent will inherit and strip anything the task doesn't need:

env | grep -iE 'key|secret|token|password|url|dsn' | sort # confirm only the credentials this task needs are present, then start

For CI, never use long-lived keys: prefer short-lived OIDC-federated tokens scoped to the minimum, stored in the platform's secret store (GitHub Actions secrets), rotated between runs. GitHub Actions auto-redacts registered secrets from logs.

Blocking file reads is a complement, not a replacement

A permissions.deny rule on .env stops the agent reading it — but if the secret is already in an env var, the file rule is irrelevant. Use both: inject via env and deny reads of credential files. Belt and suspenders, because either alone has a gap.

Where env injection quietly fails

Shared containers: sibling processes may read /proc/<pid>/environ unless the container is hardened. Sub-process stripping: some harnesses spawn tools with a cleaned env — your parent-shell vars won't be inherited. In-session retrieval: fetching a secret during the task puts the retrieval command and its output in context — always retrieve before the session starts.

↪ Your win: a secret the agent can't see

Retrieval practice — recall, don't peek

Question 1The safest place to supply a secret to an agent is…

Question 2A wrapper script protects a credential by…

Question 3In CI pipelines you should prefer…

Question 4Fetching a secret during a session is risky because…

Question 5 · spaced recall from Lesson 2Prompt injection works because attention is…

Ask me anything. Want a direnv + Vault setup that loads scoped secrets on cd, or to see how a scoped-credentials proxy keeps broad tokens entirely outside the sandbox? Next: Two Walls, Not One — sandboxing primitives.
✎ Feedback