# How to keep secrets away from a coding agent

Keeping secrets away from a coding agent means the project's keys stay outside its sandbox, where a credential proxy adds them to requests that need them.

Last updated September 29, 2026, 9 min read

## Learning objectives

After reading this article you will be able to:

-   Explain how coding agents leak secrets
-   Apply a setup that keeps broad keys outside the sandbox
-   Compare credential proxies, scoped tokens, and scanning

## Related content

-   [What is a sandbox for AI agents?](https://specstory.com/learning/environments/ai-sandbox)
-   [What is least privilege for AI agents?](https://specstory.com/learning/environments/least-privilege-for-ai-agents)
-   [What is prompt injection in coding agents?](https://specstory.com/learning/environments/prompt-injection-in-coding-agents)
-   [What is the difference between Docker, dev containers, and VMs?](https://specstory.com/learning/environments/docker-vs-devcontainer-vs-vm)

## Key points

-   A coding agent can leak any key it can read, so keep broad keys out of its sandbox.
-   A credential proxy outside the sandbox adds the key to each request, so the agent never holds it.
-   Narrow tokens limit what a leaked key can do, and secret scanning catches keys that reach the code.

## How do you keep secrets away from a coding agent?

To keep secrets away from a [coding agent](https://specstory.com/learning/ai-coding/coding-agent), run it in a [sandbox](https://specstory.com/learning/environments/ai-sandbox) that holds none of the user's keys. A credential proxy outside the sandbox adds keys to the agent's API requests. The agent gets its own narrow tokens that expire with the task, and [secret scanning](https://specstory.com/learning/glossary#secret-scanning) checks what it writes.

An agent's commands can read any key its user can read. Coding agents leak secrets by four routes:

-   **Into the context.** The agent reads a `.env` file, so the key goes to the [model provider](https://specstory.com/learning/environments/what-leaves-your-machine) and into the [session history](https://specstory.com/learning/ai-coding/ai-coding-session-history).
-   **Into command output.** A command, e.g. `env`, prints the key into the context and the logs.
-   **Into code.** The agent writes the key into a file or browser code. Exposed keys are a common [bug in AI-generated code](https://specstory.com/learning/verification/vibe-coding-bugs).
-   **Out to an attacker.** [Prompt injection](https://specstory.com/learning/environments/prompt-injection-in-coding-agents) tells the agent to send the key to an outside host.

[METR disclosed](https://metr.org/blog/2026-08-31-security-update/) that a dashboard whose authentication silently failed open let an attacker extract an API key and run up about $600,000 in model usage over three weeks. The attacker prompted an agent to reveal its model provider API key. Keeping keys away is one part of making [AI-generated code secure](https://specstory.com/learning/verification/ai-generated-code-security).

## What do you need before you start?

The setup needs four things:

-   **A list of the keys on the machine.** Keys sit in `.env` files, shell variables, and files in the home folder, e.g. `~/.aws/credentials`.
-   **A sandbox for the agent.** A container, a [dev container](https://specstory.com/learning/environments/docker-vs-devcontainer-vs-vm), or a virtual machine runs the agent's commands away from the host's files.
-   **Test keys.** Each outside service has a test mode or staging key with no access to production data.
-   **A way to revoke each key.** The team knows how to revoke and replace each one.

## How do you keep secrets away from a coding agent step by step?

Here is an illustrative example. Acme Co. sells furniture online. A developer at Acme asks a coding agent to "Let customers edit their delivery address during checkout." The checkout's end-to-end tests call a payment provider's test API with a key.

### 1\. List the keys the agent could reach

On the laptop, the developer lists the names of secret variables, not their values:

```text
$ env | cut -s -d= -f1 | grep -Ei 'key|token|secret'
GH_TOKEN
NPM_TOKEN
PAYMENTS_KEY
```

An agent started from this shell inherits all three, and `~/.aws/credentials` holds a cloud key.

### 2\. Start the agent in a sandbox without them

The developer starts the agent in a dev container with a fresh clone. The `.env` file is in `.gitignore`, so the clone has none. The container mounts no home folder, gets no SSH agent or Git credentials from the editor, and sets none of the three variables. The agent's model provider key stays out of its commands' environment.

### 3\. Route the payment API through a credential proxy

A credential proxy runs in its own container. It holds the payment provider's test key and forwards only `POST` requests to `api.payments.example.com`. The agent's container sets `PAYMENTS_API_URL=http://payments-proxy:8080`. It can reach only the proxy, the package registry, the code host, and the model's API, a form of [egress control](https://specstory.com/learning/environments/egress-control).

### 4\. Give the agent its own code host token

The agent pushes branches with a token limited to `acme/web-store` that expires after 1 hour, following [least privilege](https://specstory.com/learning/environments/least-privilege-for-ai-agents).

### 5\. Scan commits before and after they leave

A secret check runs in a [pre-commit hook](https://specstory.com/learning/ci-cd/pre-commit-hook) and again in [GitHub Actions](https://specstory.com/learning/ci-cd/github-actions-checks) on each pull request. The second check catches a commit made with `--no-verify`, which skips the hook.

### 6\. Run the task and read the output

The agent edits the checkout and runs the end-to-end tests, whose payment call goes through the proxy. The address saved, but the cart emptied. While it debugs, the agent prints its payment settings, which hold no key:

```text
$ env | grep PAYMENTS
PAYMENTS_API_URL=http://payments-proxy:8080
```

The transcript holds the same line. This example is simplified. A real project would route each outside service through a proxy, e.g. the email provider.

## What is a credential proxy?

A credential proxy is a service between an agent and an API that attaches the real key to each request, so the key stays out of the agent's environment. A request moves through it in four steps:

1.  The agent's code sends a request without the key to the proxy.
2.  The proxy checks the host and can check the path and method.
3.  The proxy adds the real key, usually in the `Authorization` header, and forwards the request over HTTPS.
4.  The response returns to the agent's code through the proxy.

Diagram: How a credential proxy keeps the key outside the sandbox

The key stays with the proxy. The agent's code can call the allowed API through the proxy, but no command in the sandbox can read, print, or commit the key.

HTTPS hides the request from the proxy unless the agent's code calls the proxy's own address, as Acme's does, or trusts the proxy's certificate.

Some coding agents build one in. Claude Code's sandbox can [mask an environment variable](https://code.claude.com/docs/en/sandboxing). Sandboxed commands get a placeholder, and its proxy swaps in the real value only for the hosts in `injectHosts`:

```json
{
  "sandbox": {
    "enabled": true, "allowUnsandboxedCommands": false,
    "network": {
      "tlsTerminate": {},
      "allowedDomains": ["api.payments.example.com"]
    },
    "credentials": {
      "envVars": [
        { "name": "PAYMENTS_KEY", "mode": "mask",
          "injectHosts": ["api.payments.example.com"] }
      ]
    }
  }
}
```

Claude Code ignores `mask` entries in a repository's settings files, so the block goes in user settings. A project change cannot redirect the key. Setting `allowUnsandboxedCommands` to false blocks retries outside the sandbox, where the real key shows.

Four controls cover different gaps:

| Control | What the agent holds | What it stops | What it misses |
| --- | --- | --- | --- |
| Credential proxy | No key, or a placeholder | Reading, printing, or committing the key | Misuse of the key through allowed requests |
| Narrow token that expires | A real key with limited access | Use beyond one resource or after it expires | A leak inside its scope and lifetime |
| Secret scanning | Any key it was given | Known key formats in files and commits | Keys no rule matches and files it does not scan |
| Secrets manager | The real key, once a program fetches it | Keys stored in files and code | A leak after a program reads the key |

## What are common mistakes?

These mistakes let a key leak or be misused:

-   **Relying on the agent's deny rules.** Claude Code's `Read(./.env)` deny rule blocks its file tools and recognized commands, e.g. `cat`. [Its permissions documentation](https://code.claude.com/docs/en/permissions) states that it does not cover a script that opens the file itself.
-   **Forgetting the agent's own key.** The agent's commands can often read its model provider key. Give it a separate key with a spend alert.
-   **Putting a key in a browser variable.** Next.js copies each `NEXT_PUBLIC_` variable that browser code reads into the client JavaScript bundle. When browser code gets `undefined` for a server variable, an agent can add the prefix, which clears the error and publishes the key.
-   **Deleting a leaked key instead of revoking it.** Git history keeps the old commit. OWASP's [Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html) orders the response as revocation, rotation, and then deletion.
-   **Sharing a transcript without scanning it.** A transcript keeps whatever a tool returned, including a printed key. Most scans check commits, so scan transcript files before sharing them.
-   **Putting a broad key behind the proxy.** The proxy hides the key, not what it can do. An injected instruction can still send requests through the proxy, so use a test key with narrow access.

## How do you check that it worked?

The setup works when these checks pass from inside the sandbox:

-   The output of `env` shows the proxy's address or a placeholder, and no real key except the agent's own tokens.
-   Reading a secret file, e.g. `cat ~/.aws/credentials`, fails because the file is not there.
-   A `POST` through the proxy to the payment API succeeds, and a `GET` through it fails.
-   A request straight to the API, without the proxy, fails because egress control blocks it.
-   A test commit that holds a fake key fails the pre-commit hook, and the same commit, made with `--no-verify`, fails in GitHub Actions.

From the host, search the transcripts and the diff for each real key's value with `grep -rF` and expect no match. Passing checks cover only the listed keys, not a key that the list missed.

## FAQs

### Can a coding agent read a .env file?

A coding agent can read a .env file whenever the file sits in a folder its commands can reach. Deny rules in the agent's settings block its file tools and some commands, but a script that opens the file itself can still read it.

### What should you do if an agent leaked a key?

After an agent leaks a key, revoke it first, then issue a replacement and update the places that use it. Remove the old key from the code and transcripts last. Deleting the key without revoking it leaves a usable copy in Git history.

### Do secret scanners catch keys in agent transcripts?

Secret scanners catch keys in agent transcripts only when someone runs them on the transcript files, because most scans check commits. A scanner looks mainly for known key formats, so a key with no known pattern can pass.

### Is a credential proxy the same as a secrets manager?

A credential proxy is not the same as a secrets manager. A secrets manager hands stored keys to the programs allowed to fetch them, so those programs hold the key. A credential proxy uses the key for the agent without handing it over.

---

Source: [How coding agents leak secrets, and the fix | SpecStory](https://specstory.com/learning/environments/agent-secrets)
