Keep secrets from Claude Code, Codex and Gemini CLI
By Maria Otworowska,
If you use more than one coding agent, you have more than one set of rules to get right. We checked what each one does with your keys by default against its docs and source code on October 4, 2026.
The short version
| Claude Code | Codex CLI | Gemini CLI | |
|---|---|---|---|
| Reads project files without asking | Yes | Yes, even in read-only mode | Yes |
| Ignore file | .claudeignore has no effect | None | .geminiignore, for tools that support it |
| Block a file | Read(./.env) deny rule | Beta permission profile | .geminiignore or policy rules |
| Your shell's variables reach its commands | Yes | Yes, secrets included | Yes, plus the project's .env |
| Global instructions file | ~/.claude/CLAUDE.md | ~/.codex/AGENTS.md | ~/.gemini/GEMINI.md |
Keys leak to agents in two ways. They read a file, like .env. Or the keys already sit in the environment the agent runs in, so every command it starts inherits them. Most advice covers the first and forgets the second.
Claude Code
Claude Code reads files in your working directory without asking. A .claudeignore file does nothing, according to its permissions docs. Read deny rules block its file tools and commands like cat, but not grep -r or a script that opens the file itself. We covered this in detail in Can Claude Code read your .env file?
Its sandbox doesn't help with the second leak. The sandboxing docs say sandboxed commands inherit Claude Code's environment, "including any secrets in its environment".
Codex CLI
Codex reads files in your workspace without asking, and its security docs say it can still read files in the read-only sandbox. The sandbox limits writes and network, not reads.
There is no .codexignore. The long-running request for one, openai/codex#2847, was closed in June 2026 in favor of a beta feature called permission profiles, which can deny reads with a rule like "**/*.env" = "deny". The permissions docs say profiles are under active development and can't be combined with sandbox_mode, and one user in that issue reports the Codex app on Windows didn't enforce it.
The bigger surprise is the environment. Codex passes your whole shell environment to the commands it runs, and the filter for names containing KEY, SECRET or TOKEN is off by default. The config reference and the source code agree on this. So an OPENAI_API_KEY exported in your ~/.zshrc reaches every command Codex starts. To turn the filter on, add this to ~/.codex/config.toml:
[shell_environment_policy]
inherit = "core"
ignore_default_excludes = falseinherit = "core" passes only basic variables like PATH and HOME, and ignore_default_excludes = false drops names containing KEY, SECRET or TOKEN. Something like DB_PASSWORD still gets through, so add your own exclude patterns for those. That's the setup the advanced config docs recommend.
Gemini CLI
Gemini CLI goes a step further: it loads your .env itself. Its configuration docs say it automatically loads environment variables from a .env file, searching up from the project and then trying ~/.env. The values go into Gemini CLI's own process, so every shell command it runs inherits them. In a folder you haven't marked as trusted, it loads only its own login variables.
To stop it loading generic .env files from your project, add this to ~/.gemini/settings.json (merged with whatever is already there) and restart Gemini CLI:
{
"advanced": {
"ignoreLocalEnv": true
}
}This doesn't cover every file it loads: keep application keys out of .gemini/.env, ~/.gemini/.env and ~/.env as well.
.geminiignore exists and uses gitignore syntax. Its docs say it excludes files from "tools that support this feature". The file-reading tool does honor it. The shell tool's docs don't mention it, so don't count on it stopping cat .env.
Its sandbox is off by default, and on macOS the default sandbox profile still allows broad file reads.
The setup that works for all three
Since every agent handles this differently, fix it below the agents:
- No keys in files inside your project. No .env, no config.local.json with real values. An agent can't read a file that isn't there, whatever its ignore rules say.
- No keys exported from your shell profile. An export in ~/.zshrc puts the key in every program started from that shell, every agent included. Profiles also end up in public dotfiles repos: a study of 124,230 of them found that 73.6% leaked potentially sensitive information.
- Keys go to one command at a time. Store them outside the project and inject them only into the command that needs them:
$ accio myapp npm test$ accio myapp npm run dev
If the agent itself starts without your keys and runs your app through accio, the app and its child processes get the keys and the agent's own environment doesn't, so they don't spread to every other command it runs. That limits accidents. It doesn't stop an agent that deliberately runs accio to get a value. 1Password's op run, doppler run and infisical run use the same pattern. The example above is accio, the command that comes with fidelius, our Mac app that keeps keys by project in your iCloud Keychain.
Tell all three the same thing
Each agent reads a global instructions file in your home folder. (Codex reads AGENTS.override.md instead if one exists, and its folder moves if you set CODEX_HOME.) Put the same section in all three, so whichever agent you open knows how to run your project without asking for keys. A minimal version:
## Secrets
API keys are not in .env files. Run commands that need them
with: accio <project> <command>
List the variable names (never values) with: accio list <project>
Never print, echo or log secret values.
If accio doesn't recognize you as an agent, prefix each
accio command with FIDELIUS_MASK=1.fidelius writes a fuller version for you: Setup > Add to My Agents Automatically… adds it to ~/.claude/CLAUDE.md, ~/.codex/AGENTS.md and ~/.gemini/GEMINI.md, shows you the change first, and keeps the old version so you can undo it. For any other agent, it gives you a prompt to paste.
When Claude Code or Codex runs accio, key values in the output come back as <concealed by fidelius: NAME>. Gemini CLI isn't detected automatically, so the instructions ask it to set FIDELIUS_MASK=1 on its accio commands. Masking catches accidental prints. It doesn't stop an agent that sets out to get a value, which is why the steps above matter more than any one agent's settings.
Checklist
- Claude Code: add Read(./.env) and Read(./.env.*) deny rules, and delete any .claudeignore.
- Codex: set inherit = "core" and ignore_default_excludes = false under [shell_environment_policy].
- Gemini CLI: set advanced.ignoreLocalEnv to true, restart it, keep keys out of .gemini/.env and ~/.env, and turn on environment variable redaction if you want it.
- Remove key exports from ~/.zshrc and ~/.bash_profile.
- Move keys out of the project and inject them per command.
- Put the same secrets section in all three global instructions files.