Claude Code

Secrets, credentials, and what never enters context

Claude Code

Four routes get a secret into the transcript, and a Read deny rule closes two of them. Only the sandbox stops a script that opens the file itself.

Applies to
Claude Fable 5.1 Claude Opus 5 Claude Sonnet 5 Claude Haiku 4.5
Last verified
Reviewed by
Timothy Fehr

What never goes in a prompt covers what you type. An agent has three other ways to put a credential in front of the model, and none of them involves you typing it.

Once something enters the context window it is re-sent to the API on every following turn and sits in the session transcript on disk. There is no un-reading it.

The four routes in

A file read. The direct one. Claude reads .env, a kubeconfig, a private key, because it was looking for configuration and that is where configuration lives.

Command output. Anything a command prints enters context in full. env, git log -p over a commit that once contained a key, a test run whose fixtures carry real tokens, a debug log. Anthropic's own walkthrough shows a test run costing 1,200 tokens of context — all of it whatever the tests printed.

The environment block. Loaded at startup: working directory, platform, shell, OS version, and whether this is a git repo. Git branch, status and recent commits load as a separate block at the end of the system prompt. Branch names and commit subjects are rarely sensitive and occasionally are.

Your own ! commands. Run a shell command with the ! prefix and "the command and its output both enter context as part of your message". Handy for grounding, and it is you doing the pasting.

Deny rules, and exactly what they cover

Add a Read deny rule for the paths that should never be opened:

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Read(~/.ssh/**)"]
  }
}

Rules evaluate deny, then ask, then allow, and the first match wins. Specificity does not change the order, so a deny rule cannot carry an allowlist exception.

Two things the docs are explicit about, both worth knowing before you rely on this.

A Read deny rule also blocks Edit and Write on the same path, including creating a file there. Convenient, and occasionally surprising.

The rules apply to Claude's file tools and to file commands Claude Code recognises in Bash — cat, head, tail, sed. That coverage is better than most people assume.

The read-only commands that never prompt

Claude Code treats a fixed set of Bash commands as read-only and runs them without a prompt in every mode: ls, cat, echo, pwd, head, tail, grep, find, wc, which, diff, stat, du, cd, and read-only forms of git. The set is not configurable.

cat and grep are on that list. They are covered by a Read deny rule where one exists, and where one does not, reading a secrets file needs no approval. The docs' own remedy is to add an ask or deny rule for the command.

The habits that do the most

Keep secrets out of the working directory where you can, since the strongest control is the file not being there.

Prefer a secret manager or an injected environment variable over a file the agent could plausibly open while looking for config.

Assume anything a command prints has been read. When you ask for a test run whose fixtures hold real credentials, that is a disclosure decision you have already made.

And treat a leak as a leak. A credential that reached context was transmitted to the API and written to a local transcript. Rotate it; deleting the conversation does not un-send it.

Try this

In a repo you work in, run grep -rl "SECRET\|TOKEN\|PASSWORD\|BEGIN.*PRIVATE KEY" . --exclude-dir=.git and look at the list. Every one of those paths is somewhere Claude could plausibly read while investigating. Write a Read deny rule for the ones that should never be opened.

What goes wrong

Assuming .gitignore protects it. It keeps a file out of git and does nothing about a file read. The two mechanisms are unrelated.

Relying on a deny rule against a script. The rule covers the file tools and recognised bash file commands. A program that opens the file goes around it.

Forgetting command output is context. Most accidental exposure arrives this way rather than through a deliberate read, because nobody thinks of a test run as a disclosure.

Rotating nothing after a near-miss. "It only went to the model" still means it left the machine and is in a transcript on disk.

How to check it worked

Ask Claude to read one of the paths you denied. A refusal confirms the rule matches. Then run a command that would print the same secret — a script that loads it, not cat — and see it appear anyway. That contrast is the shape of what you have and have not protected, and it is the reason the sandbox exists for anything that genuinely must not be read.

Sources

  1. Configure permissions — Claude Code Docs Tier 1 2026-09-11
  2. Explore the context window — Claude Code Docs Tier 1 2026-09-04