How to keep API keys and .env files away from AI coding agents
Deny rules for Claude Code, Codex and Cursor, plus masking, scoped tokens and the steps to take after a secret reaches an agent.
For Developers and security teams
To keep API keys out of AI coding agents, do three things in order: deny the agent access to secret files, remove secrets from the environment its commands inherit, and make whatever is left short-lived and narrow. A deny rule on .env is the quickest start, but it is not enough alone, because agents can reach secrets through shell commands and environment variables.
How do secrets reach an AI coding agent?
There are four routes. The agent reads a file such as .env or ~/.aws/credentials. A command it runs inherits environment variables holding tokens. You paste a key into a prompt, or a command prints one. Or a secret already sits in code or config in the repository. OWASP's secure coding guidance for AI adds a fifth warning: do not open .env files or private keys in an IDE while an AI assistant is active, and do not assume .gitignore stops an AI tool, since it only affects git.
Each route needs its own control, so the sections below take them in turn. Most guidance covers only the first.
How do you stop Claude Code reading .env?
Use Read deny rules. In .claude/settings.json, ~/.claude/settings.json or managed settings:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Read(./config/credentials.json)"
]
}
}
This blocks reads and also excludes matching files from search results. It covers Claude's built-in file tools, the file commands Claude Code recognizes in Bash (cat, head, tail, sed, tee) and redirection targets. It does not cover grep -r pattern . or a Python or Node script that opens the file itself. A .claudeignore file has no effect, so move any entries from one into deny rules.
For a block that applies to every process Claude starts, turn on the sandbox and deny files there too. By default sandboxed commands can read most of the machine, including ~/.ssh and ~/.aws/credentials, and there is no built-in credential list. Fill it in:
{
"sandbox": {
"enabled": true,
"credentials": {
"files": [
{ "path": "~/.aws/credentials", "mode": "deny" },
{ "path": "~/.ssh", "mode": "deny" }
],
"envVars": [
{ "name": "GITHUB_TOKEN", "mode": "deny" },
{ "name": "NPM_TOKEN", "mode": "deny" }
]
}
}
}
Environment variables with mode deny are unset before each sandboxed command. Claude Code also offers "mode": "mask": the command and its logs never hold the real value, but requests still authenticate because the sandbox's proxy substitutes the real credential for approved hosts. That suits tokens a command needs to use but never needs to see. Masking has two conditions that are easy to miss: the proxy has to terminate TLS (network.tlsTerminate), and Claude Code only honors mask entries from user settings, managed settings or --settings, never from a repository's own .claude/settings.json. Without TLS termination nothing leaks, but authentication quietly fails, and claude doctor warns about it.
Setting CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 removes credentials from the environments of the subprocesses Claude Code starts, including Bash commands, hooks and stdio MCP servers. It recognizes them by variable name or value. Read the exceptions before you rely on it: by design it leaves GitHub tokens and proxy settings in place, so a GITHUB_TOKEN still needs a deny or mask entry. The full policy for a team is in the Claude Code security guide.
How do you do the same in Codex?
Codex scrubs less than you might expect. shell_environment_policy.ignore_default_excludes defaults to true, so variables whose names contain KEY, SECRET or TOKEN are not removed automatically. Set it to false and filter what remains:
[shell_environment_policy]
inherit = "core"
ignore_default_excludes = false
[shell_environment_policy.filters]
"AWS_*" = "exclude"
"AZURE_*" = "exclude"
For files, a permission profile can deny reads with "**/*.env" = "deny" under :workspace_roots. On Linux, WSL and Windows the glob is expanded when each sandboxed process starts, so a long-running process will not see an .env created afterward. Administrators can enforce [permissions.filesystem] deny_read entries such as "/**/*.env" and "~/.ssh" in requirements.toml. More in the Codex security guide.
How do you do the same in Cursor?
Add patterns to .cursorignore or, better, to the global ignore list in user settings, which is empty by default. Cursor suggests **/.env, **/.env.*, **/credentials.json, **/*.key, **/*.pem and **/id_rsa. Cursor already ignores .env* through its default list.
Know the gap. Cursor's documentation says the terminal and MCP tools the agent uses cannot be blocked from code governed by .cursorignore. So pair the ignore file with the sandbox, set read access to Workspace so commands see less of the machine, and keep real secrets out of the project folder. See the Cursor security guide.
Where should secrets live instead?
Not in the project tree. OWASP's guidance is to keep secrets in environment variables set outside the agent's context, vault services or encrypted stores, and never in files inside the project where AI tools can read them. In practice:
- Keep a secret manager or your cloud's identity system as the source, and inject at runtime for the process that needs it
- Give the agent a development account, a sandbox project or a read-only key, never the production credential
- Give each tool its own token. OWASP advises scoped, per-server credentials and against sharing tokens across tools
- Prefer tokens that expire. OWASP recommends ephemeral, short-lived tokens over long-lived personal access tokens
If an agent needs to call an API, a masked or proxied credential is better than a readable one. The agent can then use the access without ever holding the value that an injected instruction could repeat. This ties into the broader limits on prompt injection.
What if a secret reached the agent?
Assume it left your machine.
- Revoke or rotate the credential immediately. GitHub's guidance on sensitive data says that for a secret this is the first step, and that once it is revoked, rewriting history may not be warranted.
- Look at the provider's audit logs for use of that key since it was exposed.
- Find the route: a file the agent read, a variable it inherited, a prompt someone pasted, or a command's output.
- Close that route with the controls above, and check whether the secret also reached logs, transcripts or telemetry.
- If it was committed, rotate first and rewrite history only if you need to, keeping in mind that GitHub lists many side effects of doing so.
How do you test that the rules work?
Put a dummy file named .env in a scratch repository with a fake value such as FAKE_KEY=not-a-real-secret. Ask each agent to read it directly, then to show it with cat, then to find it with grep -r FAKE_KEY ., and finally to export the variable in a script. Write down which attempts were blocked. The gaps in the lists above will show up as the attempts that succeed. Run the same test again after tool updates, since the settings and defaults change often.
Where checking prompts fits
Deny rules cannot see a key a developer pastes into a prompt or one a command prints. Rechazo, which is in waitlist stage, checks prompts before they leave the company and redacts or blocks credentials it finds, which is the gap left after the settings above.
What to do this week
- Add
Readdeny rules (or the equivalent for each tool) for.env, key files and cloud credential files. - Enable the sandbox and list credential files and token variables in it.
- Remove production credentials from developer machines used with agents, and issue scoped, expiring tokens.
- Scan repositories and shell profiles for secrets an agent could read.
- Write the rotation steps for your top five credentials so the response is a checklist, not a debate.
Frequently asked questions
- How do I stop Claude Code from reading my .env file?
- Add Read deny rules to your settings, such as Read(./.env) and Read(./.env.*), in ~/.claude/settings.json, the project's .claude/settings.json, or managed settings. That blocks Claude's file tools and the shell file commands Claude Code recognizes. It does not stop a script that opens the file itself, so also turn on the sandbox and deny the file there.
- Do deny rules keep secrets out of prompts?
- They keep the agent from reading listed files, which is the main way secrets enter a prompt. They do not catch a key you paste yourself, a secret in a command's output, or one that sits in an environment variable. For those you need environment scrubbing or masking, and ideally a check on prompts before they leave your machine.
- What should I do if an AI agent saw my API key?
- Treat the key as exposed. Revoke or rotate it first, then check the provider's logs for use you did not make. GitHub's guidance says that revoking or rotating a secret is the first step, and that rewriting repository history may not be needed once it is dead. Then fix the path that let the agent see it.
- Are short-lived tokens better than API keys for coding agents?
- Yes. A token that expires and is limited to one task or repository has little value if it ends up in a prompt, a log or an injected command. OWASP's guidance for agents is to prefer ephemeral, short-lived credentials over long-lived personal access tokens and to avoid sharing tokens across tools.