Stop AI coding tools from reading your API keys and secrets: settings for Claude Code, Codex and Gemini CLI
Settings that keep .env files and credentials away from Claude Code, Codex and Gemini CLI, what to do after a leak, and how to keep secrets out of the repository altogether.
Contents
A coding agent reads .env or config/secrets.yml whenever it decides that is needed for the task. What it reads is sent to the API and kept in logs and history. "It is in .gitignore, so it is fine" is a misconception: what Git tracks and what the agent reads are two different things.
This article gives per-tool settings that keep secrets away from Claude Code, Codex and Gemini CLI, and the tool-independent fix that actually works.
KEY POINT
What you will learn
- Blocking reads of
.envand similar files in each of the three tools - Leak paths through MCP servers, hooks and CI, and how to close them
- Keeping secrets out of the repository, and what to do after a leak
What can leak
| Path | Examples |
|---|---|
| File reads | .env, *.pem, credentials.json, ~/.aws/credentials |
| Command output | env, printenv, cat ~/.ssh/id_rsa, docker inspect |
| Git history | Secrets committed in the past show up in git log -p |
| MCP servers | Tokens passed to a server, and whatever the server returns |
| Context files | URLs or passwords accidentally written into CLAUDE.md |
Claude Code: deny rules
deny in .claude/settings.json always beats allow and cannot be overridden by personal settings, so put it in the shared file:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./**/*.pem)",
"Read(./**/*.key)",
"Read(./secrets/**)",
"Read(~/.aws/**)",
"Read(~/.ssh/**)",
"Bash(env)",
"Bash(printenv:*)",
"Bash(cat ~/.aws:*)",
"Bash(cat ~/.ssh:*)"
]
}
}
Bash deny rules match the beginning of the command string, so alternative spellings slip past them. Denying Read on the files is the reliable part. The full rule syntax is in Claude Code permissions in settings.json.
A PreToolUse hook can add a second check on the path being read:
#!/usr/bin/env bash
# .claude/hooks/guard-secrets.sh
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
*.env|*.env.*|*.pem|*.key|*/secrets/*|*/.aws/*|*/.ssh/*)
echo "Reading secret files is blocked: $file" >&2
exit 2 ;;
esac
exit 0
Codex: rely on placement, not the sandbox
Codex's sandbox restricts writes and network access, but it does not restrict reading files inside the workspace, and there is no per-file read deny. Two things help:
- Keep secrets outside the workspace. Put
.envin a location such as~/.config/myapp/.envand have the application load it by path. - Say it in AGENTS.md. "Do not read
.envor anything undersecrets/; use.env.examplefor the variable names." It is instruction-based and therefore not a guarantee, but it reduces accidental reads.
Even read-only mode can read. Placement is the real control; see Codex CLI approval modes versus sandbox modes.
Gemini CLI: ignore files and GEMINI.md
Gemini CLI can respect .gitignore when exploring files (see the fileFiltering settings for your version), and it reads a .geminiignore file:
# .geminiignore
.env
.env.*
secrets/
*.pem
*.key
Ignore rules apply to file discovery (search and listing) and may not block a read of an explicitly named path. State the rule in GEMINI.md as well, and combine it with placement.
Tool settings are a safety net, not a wall
In all three tools, settings make secrets harder to read, not impossible. The reliable control is not having the secret in a readable place at all.
The real fix: keep secrets out of the repository
- Commit a
.env.examplewith variable names only, and point the AI at it. - Use a secrets manager (1Password CLI, AWS Secrets Manager, Doppler) that injects values at run time. A file that does not exist cannot be read.
- direnv and environment variables:
.envrcis read by the shell, not by the agent, unless it runscaton it. Deny theenvcommand anyway. - Clean Git history: secrets committed in the past are visible through
git log -p. Revoke them, then remove them from history if needed.
MCP servers
Tokens passed to a server through env reach the server process, not the model directly. But whatever the server returns (API responses) does reach the model. Make sure no tool returns configuration that includes tokens, and never write tokens into configuration files; see Adding MCP servers to Claude Code.
CI and cloud runs
In GitHub Actions or Codex cloud, secrets and environment variables are available inside the container, and the agent can read them by running env.
- Pass the agent only the variables it needs
- Never give the CI agent production credentials; use test values
- Do not register production values in cloud environment settings either
After a leak
Sent data cannot be recalled. Do this, in order:
- Revoke and reissue the key, token or password
- Check the service's logs for misuse between the leak and the revocation
- Find which setting was missing and fix the deny rule or the file placement
- If the secret was in Git history, remove it from history in addition to revoking it
Do not skip rotation because "only I saw it". How long the data is retained depends on your plan and contract.
Summary
.gitignoredoes not stop the agent from reading files; each tool needs its own exclusions- Claude Code:
denywithRead(...); Codex: placement plus AGENTS.md; Gemini CLI:.geminiignoreplus GEMINI.md - The real fix is keeping secrets out of readable locations:
.env.exampleand a secrets manager - After a leak, revoking and reissuing is the only reliable remedy
FAQ
- Are files listed in .gitignore hidden from the AI?
- Not necessarily. .gitignore only controls what Git tracks; it does not stop an agent from reading a file. Each tool needs its own exclusion settings.
- What should I do if the AI already read a secret?
- Revoke and reissue the key or token. Data that has been sent cannot be recalled, so rotation is the only reliable remedy.
- Can secrets leak through MCP servers?
- Yes. Environment variables passed to a server, and anything the server returns, can reach the model. Pass tokens via environment variables and check that servers never return secrets in their output.
Primary sources
This article was drafted by AI from official documentation and reviewed by the site operator before publishing. Found a mistake? Let us know via the contact page.