Keep secrets away from AI coding agents
AI coding agents read files and run commands with your permissions. How to keep .env values out of their reach in Claude Code, Cursor and GitHub Copilot.
Short answer
Assume an agent can read anything your account can read and run. Keep real values out of the project directory, inject them only into the program that needs them, add deny rules for env files in each agent, sandbox its shell, and make sure any key it could see is a narrow development key you can reissue.
On this page
Assume an AI coding agent can read every file your user account can read and run every command you can run, because unless you sandbox it, it can. Then make that assumption cheap: keep real values out of the project directory, inject secrets only into the program that needs them at the moment it starts, add deny rules for env files in each agent, sandbox the agent's shell, and make sure any key it could still see is a narrow development key you can reissue in a minute. No single setting does all of this. This guide goes through the layers, what each one actually blocks according to the tools' own documentation, and how to check that they work.
What an agent can reach
A chat assistant sees what you paste. An agent is different: it acts. Within a session it typically can:
- read files in the project, and often outside it, to build context;
- run shell commands to install packages, run tests and inspect output;
- call tools through MCP servers and integrations, each with its own credentials;
- send what it read to a model provider as part of the prompt.
The VS Code documentation on secure AI-assisted development states the consequence plainly: the agent can execute terminal commands and shell scripts with your user privileges, and context sent to models can expose API keys and credentials. That is not a flaw of one product. It is what makes agents useful.
There is a second risk on top of the first. Agents follow instructions, and instructions can arrive from content the agent reads: a README in a dependency, a comment in an issue, a web page it fetched. OWASP describes this as indirect prompt injection, where an LLM accepts input from external sources such as websites or files, in its prompt injection entry. An agent that can read .env and also make network requests is one injected instruction away from sending your keys somewhere.
Four ways a secret reaches an agent
Knowing the path tells you which layer stops it.
- It reads a file. The
.envin the project root,config/secrets.yml, a~/.aws/credentialsfile, a key in a test fixture. This path is the easiest to close. - It runs a command that prints values.
cat .env,printenv, a test that logs its configuration, a debug flag that dumps settings, a stack trace that includes a connection string. File rules don't see these. - You give it the value. Pasting a key into the chat to "just make it work" sends the value to the model provider and into the session's history.
- A tool holds it. An MCP server configured with an API key in a project file, or an integration that runs with a token the agent can use without seeing it.
The layers below are ordered by how much each one removes. The first is the only one that works no matter which agent you use.
Layer 1: no real values in the project directory
The best deny rule is a file that isn't there. If the project contains only a .env.example with names and placeholders, a file-reading agent finds nothing worth reading, and a mistake in some tool's ignore list costs nothing.
That means real values have to reach the program some other way. The usual answer is to inject them when the program starts. With Crunu, your development environment lives in an encrypted environment, and you start the program through crunu run:
crunu run --env web-app/development -- npm run devcrunu run decrypts the environment in memory and starts the command with its variables. No file is written into the project. By default the program gets a short allowlist of variables from your shell, such as PATH and HOME, plus the environment's variables, and never the credentials of crunu itself. The crunu run reference has the details. The same pattern works with other run-time injectors; the point is the absence of a file, not the brand.
Be clear about what this doesn't do:
- The program sees its values. They are in its process environment, and so are visible to the program and every process it starts. If the agent runs the tests and the tests print the configuration, the agent reads the output. The OWASP Secrets Management Cheat Sheet notes that environment variables are generally accessible to all processes and may end up in logs or system dumps.
- An agent can start the injector too. If your terminal session is signed in and your keys are held in memory, an agent running commands as you can run
crunu runitself. While an agent works unattended, runcrunu lockso the keys are forgotten, or give the agent a shell under a different user or in a container that can't reach your session. Aftercrunu loginthe keys stay in memory for one hour, andcrunu unlock --ttlsets a limit of up to 12 hours. - Other files in your home directory are still files.
~/.aws/credentials,~/.npmrcand similar files are outside the project but often inside the agent's reach. Prefer short-lived credentials there as well.
Layer 1 turns "the agent read the production database password" into "the agent read the variable names". That is most of the value of this whole guide.
Layer 2: tell each agent what not to read
Each of the agents below has a way to exclude files. They differ in what they cover, and their own documentation is honest about the gaps.
| Tool | Setting | What it covers | What it doesn't |
|---|---|---|---|
| Claude Code | permissions.deny rules such as Read(./.env) | its file tools, file commands it recognizes in the shell such as cat and head, and shell redirections | commands that read without naming the file, such as grep -r, and scripts that open files themselves |
| Cursor | .cursorignore, plus .gitignore and a default list that includes .env* | code available to Agent, Tab and @ mentions | the terminal and MCP tools used by Agent; protection is best-effort |
| GitHub Copilot | content exclusion at repository, organization or enterprise level | Copilot Chat responses and references | Copilot CLI and Agent mode in IDEs |
Claude Code
Add deny rules to your settings. The example in the Claude Code settings documentation blocks .env files; a secrets directory is a common addition:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}Put team-wide rules in the project's .claude/settings.json and commit it, so everyone gets them. Deny rules from a project file apply at once, without the trust step that allow rules wait for. A .claudeignore file has no effect; move its entries into Read deny rules.
The permissions documentation says exactly where the rules stop: they apply to the built-in file tools, to file commands Claude Code recognizes in Bash, and to redirection targets, but not to a command like grep -r pattern . or to a Python or Node script that opens files itself. For enforcement that covers every process, it points to the sandbox.
Cursor
Cursor ignores files in .gitignore, a default list that already includes .env*, and anything you add to .cursorignore. Its ignore files documentation adds two caveats: complete protection isn't guaranteed because of LLM unpredictability, and the terminal and MCP server tools used by Agent can't block access to ignored files. A global ignore list in user settings starts empty; adding **/.env and **/.env.* there covers projects that forget.
GitHub Copilot
Copilot's content exclusion is configured by repository, organization or enterprise administrators. Per GitHub's documentation, Copilot CLI and Agent mode in Copilot Chat in IDEs do not support content exclusion, and changes can take up to 30 minutes to reach IDEs that already loaded the settings. In VS Code, the setting chat.tools.edits.autoApprove accepts patterns such as "**/.env": false to require approval for edits to env files. That controls edits, not reads.
Agents not listed here
For any other tool, four questions from its documentation tell you how much Layer 2 can do:
- Can it exclude files, and from which tools? A rule that covers file reads but not shell commands is a convenience, not a boundary.
- Does it ask before running commands, and can you approve narrowly? Approval per exact command is useful. Approval per program, such as every
pythoncall, is close to none. - Can it run commands in a sandbox? Look for OS-level restrictions on files and network, not only a list of allowed commands.
- Where are transcripts stored, and for how long? Anything the agent read may sit in local history, in the provider's logs and in shared session links.
If the documentation doesn't answer a question, assume the answer is no until you have tested it.
Test the layer you just added
Don't trust a rule you haven't tried. Put a canary file in the project, such as .env.canary with CANARY_KEY=not-a-real-key-7f3a, add it to your deny patterns, and ask the agent to find and print CANARY_KEY. Then ask it to find the value with a shell command. If either attempt prints the value, you know which layer is missing. Delete the canary afterwards.
Layer 3: sandbox the agent's shell
Deny rules match on names and commands. A sandbox restricts what processes can do, whatever the command looks like.
- Claude Code has a sandbox for Bash commands and their child processes, which enforces filesystem and network restrictions at the OS level. Its permissions page calls permissions and sandboxing complementary, and notes that sandbox restrictions still apply even if a prompt injection bypasses Claude's decision-making.
- VS Code can turn on agent sandboxing to restrict file system access for agent-run shell commands and their child processes. Its documentation notes that outbound network access remains enabled by default, and suggests a dev container as an additional boundary.
- A container or separate user works for any agent. Mount only the project, don't mount your home directory, and the agent's commands can't read
~/.awsor your session's sockets.
Network limits matter as much as file limits. An agent that reads a value but can't reach an unknown host has a harder time sending it anywhere. If your sandbox supports a network allowlist, allow the package registries and the services your tests need, and nothing else.
Agents that run on someone else's machine
Cloud sessions change the picture again. Claude Code's settings documentation explains that a cloud session runs in a cloud environment on a fresh clone of your repository, not on your machine, and that your user settings and project local settings are not read there. Two consequences follow:
- Rules must be in the repository to apply, and not every cloud session reads them. A deny rule that lives only in
~/.claude/settings.jsonprotects your laptop and does nothing in a cloud session. A committed.claude/settings.jsonis read in a session with one repository, but its permission rules don't apply in a cloud thread inside a project or in a session with several repositories, per the same documentation. Check which keys each environment reads. - Variables you configure for the environment are the agent's. If a cloud environment needs credentials to run tests, everything said in Layer 5 applies with more force: the machine isn't yours, so the key should be a test key you are willing to reissue.
The upside is real too. A fresh clone on a remote machine contains no ~/.aws directory and no signed-in vault session, so the only secrets it can see are the ones you deliberately give it.
Layer 4: keep keys out of MCP server configs
MCP servers give agents tools, and tools need credentials: a token for your issue tracker, a key for a database, a key for a search API. The quickest setup puts the key in the server's config file, and project config files are often committed. Claude Code, for example, stores project-scoped servers in .mcp.json at the project root and recommends checking that file into version control so the team shares the same tools.
Two practices keep the key out of that file:
- Reference variables instead of values. Claude Code expands
${VAR}and${VAR:-default}in a server'scommand,args,env,urlandheaders, per its MCP documentation. The committed file then saysBearer ${API_KEY}, and each developer supplies the value. - Keep credentialed servers in a local scope. The same documentation recommends the local scope for servers with credentials you don't want in version control.
Claude Code adds one safeguard of its own: in a remote server's url and headers, it reads its own credential variables, such as ANTHROPIC_API_KEY, as empty rather than expanding them, so a config can't quietly forward the agent's own key.
Then think about where the variable comes from. If you start the agent itself with the key in its environment, the key is out of the file but inside the agent's process, where a shell command can print it. That may be acceptable for a read-only key with tight limits. It isn't for a key that can change production. The real fix is the next layer.
Layer 5: make the keys an agent might see worth little
Every layer above can fail. The last one decides what a failure costs.
- Development keys only. The machine where an agent works should hold development and test credentials, never production ones. With per-environment keys this is a property, not a promise: in Crunu, a developer granted only development and staging never receives the production key, so no command run in their session can decrypt production.
- Narrow permissions. Use restricted keys where the provider offers them. Stripe recommends restricted API keys for most uses and lets you tag a restricted key as belonging to an autonomous agent, which subjects it to approval rules for sensitive actions such as payouts and refunds.
- Low limits. Spending caps, rate limits and short expiry on anything an agent can reach.
- Easy to reissue. Keep a list of which keys the agent's environment holds, and practice replacing one. If reissuing a key takes a meeting, you won't do it after a near miss.
Example. Kiteline is a fictional company. Jonas Lindqvist uses an agent in web-app. His access profile gives him Editor on the development and staging environments only. His shell runs the dev server through crunu run --env web-app/development, the project contains only .env.example, and the Stripe key in development is a test key. If the agent read everything it could reach, it would get test keys and variable names.
Prompt injection: limit what an instruction can make the agent do
You can't fully prevent an agent from reading a hostile instruction. You can limit what it can do afterwards. The OWASP entry recommends restricting the model's access privileges to the minimum necessary, and human approval for privileged operations. Applied to coding agents:
- Don't auto-approve commands broadly. Approving every
curlor every script once means approving whatever an injected instruction asks for later. Approve specific commands. - Review changes to sensitive files. A diff that touches
.env.example, CI configuration, deploy scripts or dependency manifests deserves a human read, because those are where a quiet change has the most reach. - Separate reading from acting. An agent that browses the web or reads untrusted issues is the one that should have the fewest credentials and the tightest sandbox.
Agents that run in CI
Coding agents can also run in pipelines: they triage issues, open pull requests and fix failing tests. In that setting they are CI jobs, and everything in the CI/CD OIDC guide applies.
- Give the agent's job its own identity, separate from build and deploy jobs.
- Grant it test or staging values only. A job that reacts to issue text is exposed to injected instructions by design.
- Set credentials on the step that needs them, not on the whole job, because every process in a job sees job-wide variables.
- Keep production secrets out of any job an agent drives.
With Crunu, the agent's job would use a service token granted only the staging environment and bound with OIDC to the repository and branch it runs on. If it asks for production, the agent is refused because the token simply doesn't have that environment's key. Logs remain the weak spot: a value printed by a test lands in the job log and in whatever the agent reports back.
Rolling it out across a team
Settings on one laptop protect one laptop. For a team, make the safe setup the default one, so nobody has to remember it.
- Commit the agent settings. Deny rules in
.claude/settings.json, a.cursorignore, and the editor settings your team shares live in the repository next to.env.example. A new clone gets them on day one. - Make the run command the documented one. The README says
crunu run --env web-app/development -- npm run dev, or the equivalent for your injector, not "copy.envfrom a colleague". People follow the first command that works. - Give developers development keys by default. Production access is granted separately, to fewer people, and never to a machine where an agent runs unattended.
- Keep agent identities separate in CI. The job an agent drives has its own token and its own environments, so its access can be reviewed and revoked on its own.
- Review the setup when tools change. Re-run the canary test when someone adopts a new tool or a major version, not only once.
None of this needs a policy document to start. It needs a repository that already contains the safe defaults, and one person who checks them each quarter.
If an agent saw a secret
Treat the value as leaked, whatever the tool's retention policy says. The value may be in the session transcript on your machine, in the model provider's request logs, and in any summary the agent wrote.
- Reissue the value at its provider, using an overlap window if the provider has one, so you can roll out the new value without downtime.
- Save the new value in your vault, not in the project and not in the chat with the agent.
- Check the provider's usage logs for activity between the moment the agent saw the value and the reissue.
- Find out which layer failed: a file in the project, a command that printed values, a pasted value or a tool config, and close it.
- Add a canary for that path to your next test of the agent setup.
The rotation guides by service list how to reissue keys for common providers, and the offboarding guide covers prioritizing several values at once.
What Crunu does and doesn't do here
To be precise:
- Crunu has no features built for AI agents. It doesn't detect agents, filter prompts or scan transcripts.
- What it does is keep values out of files.
crunu runinjects variables into the program it starts, so the project directory needs no.env, andcrunu pullwrites files with mode 0600, asks before writing to a path it hasn't written before, and refuses places such as~/.ssh/. - Per-environment keys limit what a session can decrypt. A developer, or an agent running as that developer, can only open environments the developer was granted.
- The process still sees its variables. On Linux,
crunu runcloses its own process to other processes of the same user, but not to root, and the program it starts holds its variables like any other process. - A signed-in, keys-in-memory session can be used by anything running as you. Lock it while an agent works unattended.
A short checklist
Related reading: do AI coding agents read your .env, API keys in MCP server configs, prompt injection and environment variables, and the glossary entries for runtime secret injection and MCP server.
Frequently asked questions
Something else? Write to support@crunu.com
Do AI coding agents read .env files?
They can. An agent reads files in your project to understand it, and many also run shell commands with your permissions. Some tools skip .env files by default, but in the tools we checked those rules cover only their own file tools, not every shell command or script the agent runs. Treat every file in the project as readable.
Is a deny rule for .env enough to protect secrets from Claude Code?
It is a good layer, not a complete one. Claude Code applies Read deny rules to its file tools and to file commands it recognizes in the shell, such as cat and head, but not to commands that read files without naming them, such as grep -r, or to scripts that open files themselves. Its documentation points to the sandbox for OS-level enforcement.
Does Crunu have features for AI agents?
No. Crunu has nothing built for agents. What helps is how it already works: crunu run passes values to the program it starts without writing a file into your project, so there is no env file for an agent to read. The program, and anything it starts, can still read its own environment variables.
Should I give my AI agent its own API keys?
If it needs keys at all, yes: separate keys with the narrowest permissions and lowest limits the provider offers, used only in development, and easy to reissue. Some providers support this directly, such as restricted API keys at Stripe.
What should I do if an agent printed a secret?
Treat the value as leaked. The transcript may be stored by the tool, sent to a model provider and kept in logs you don't control. Reissue the value at its provider, then check the provider's usage logs for anything unexpected between the leak and the reissue.
Sources
Every source this guide relies on, with the date we last checked it.
- Claude Code: settings files and precedence · checked October 30, 2026
- Claude Code: configure permissions · checked October 30, 2026
- Claude Code: MCP · checked October 30, 2026
- Cursor: ignore files · checked October 30, 2026
- Excluding content from GitHub Copilot, GitHub Docs · checked October 30, 2026
- Secure AI-assisted development in VS Code · checked October 30, 2026
- OWASP LLM01:2025 Prompt Injection · checked October 30, 2026
- OWASP Secrets Management Cheat Sheet · checked October 30, 2026
- API keys, Stripe Docs · checked October 30, 2026