Can Claude Code read your .env? What the 2026 incidents show
Short answer: yes. A coding agent that can run shell commands as you can read any file you can read, and deny rules for the file-reading tool do not cover every shell command. The only dependable fix is that the secret is not in the workspace, and the checks below tell you if it already leaked through your own package or an extension.
This is a dated roundup of what is actually documented about Claude Code and secrets, with every claim linked to its source and the checks you can run in a minute. It is not a claim that Claude Code is uniquely unsafe. The same applies to any agent with a shell. Updated as new incidents land (last verified 2026-11-01).
What the documentation says
Claude Code lets you write permission rules in settings. Anthropic’s permissions page is explicit about what they cover: “Permission rules are enforced by Claude Code, not by the model,” and Read and Edit deny rules “apply to Claude’s built-in file tools, to file commands Claude Code recognizes in Bash, such as cat, head, tail, sed, and tee, and to the targets of Bash redirections.” The same page says they do not apply to a command that reads files without naming them, such as a recursive grep run from the directory that holds the file, or to arbitrary subprocesses that read files indirectly. For enforcement that does not depend on command text it points you to sandboxing: “For filesystem and network enforcement that doesn’t depend on the command text, use sandboxing.”
So a deny rule on .env is a guard rail, not a boundary. One developer found this the hard way: in an August 2026 write-up, their agent ran grep -rn "CAPTCHA_SECRET\|JWT_SIGNING_KEY" over an infra directory and printed two secret values, even though Read deny rules for that directory were configured. The author’s conclusion: “Do not put plaintext secrets in your repository.” That matches Anthropic’s own description of the rules; it is documented behaviour, not a bug report.
Incident 1: CVE-2026-21852, your API key sent before the trust prompt
NVD describes a flaw in Claude Code’s project-load flow, before version 2.0.65: “An attacker-controlled repository could include a settings file that sets ANTHROPIC_BASE_URL to an attacker-controlled endpoint and when the repository was opened, Claude Code would read the configuration and immediately issue API requests before showing the trust prompt, potentially leaking the user’s API keys.”
- Published 2026-01-21; fixed in 2.0.65 (auto-updating installs already have it).
- CVSS 4.0 base score 5.3 (medium), as scored by GitHub’s advisory team; see GHSA-jh7p-qr78-84p7.
- Exposure: your Anthropic API key, not your project secrets. It still needs one click on a hostile repository.
What to do: be on a current version, and rotate the key if you ran an affected version on a repository you did not trust.
Incident 2: settings files shipped inside npm packages
When you choose “allow always” for a command, Claude Code stores the command string in .claude/settings.local.json. If you typed a token inline in that command, the token is now in the file. Lakera’s research (22 April 2026), as reported by IT Brief and NHI Mgmt Group, watched the npm registry feed: of about 46,500 monitored packages, 428 contained a .claude/settings.local.json, and 33 of those files across 30 packages held credentials, including npm tokens, GitHub personal access tokens, Telegram bot tokens and Hugging Face tokens.
We could not load Lakera’s own article page at the time of writing, so the numbers above rest on those two secondary reports. Treat them accordingly. The mechanism is easy to check yourself, which is better than trusting anyone’s count. We ran this with npm 11.12.1 on a throwaway package:
mkdir -p .claude && echo '{}' > .claude/settings.local.json
npm pack --dry-run 2>&1 | grep claude
# npm notice 3B .claude/settings.local.json <- it ships
With a .claude/ line in .gitignore, or a files allowlist in package.json, the same command prints nothing. Check your own packages before the next publish, and treat any credential that already shipped as compromised: published tarballs are effectively permanent, and cached copies can remain available after a version is deprecated.
Incident 3: a tampered “Claude Code” extension in Cursor’s marketplace
On 2026-10-29, Cursor reported that versions 2.1.312 and 2.1.313 of Anthropic.claude-code in the Cursor VS Code Extension Marketplace for Windows and macOS “were not developed by Anthropic.” It says they were available between about 2:27am and 1:35pm PT, and that the code collected host details including “environment variable names,” added an attacker SSH key labelled svc-telemetry to authorized_keys, and tried to open a persistent reverse SSH tunnel. Cursor says the attacker’s server has since been taken offline and that, on most hosts, the step that enables SSH fails without admin rights.
Phorion’s analysis (30 October, updated 31 October) adds what was collected: “Up to 40 environment variable names (not values) matching /KEY|TOKEN|SECRET|PASS|AWS|GCP/i.” Names only, but a list of which secrets your machine has is reconnaissance. Cursor’s own advice is that the limited collection “does not rule out later credential access over the backdoor,” and that uninstalling the extension may not be enough, because the payload drops standalone persistence scripts. The Hacker News thread has about 970 points.
Check on macOS or Linux (Cursor’s notice also has a Windows version):
grep -qs svc-telemetry ~/.ssh/authorized_keys && echo "svc-telemetry key found" || echo "No key found"
If the key is there, follow Cursor’s remediation steps and rotate everything that user could reach, from a trusted machine. This is an extension supply-chain incident, not a Claude Code flaw, and it would have worked on any secret the machine held.
The data point behind all of this
GitGuardian’s State of Secrets Sprawl 2026 reports that Claude Code-assisted commits showed a 3.2% secret-leak rate versus a 1.5% baseline across public GitHub commits, and says plainly that this “should not be read as a simple tool failure,” since developers decide what is accepted and pushed. It also found 24,008 unique secrets in MCP-related config files on public GitHub, 2,117 of them valid credentials. Plaintext secrets sitting next to an agent tend to end up somewhere public.
What actually reduces the risk
-
Take plaintext out of the workspace. What is not on disk cannot be grepped. With Dynv, import the file once, check the diff, run the app, then move the file out of the repository:
dynv admin import web .env --dry-run # masked key-level diff, writes nothing dynv admin import web .env --yes # writes (without --yes it asks first) dynv run -- node app.js # check the app starts with its variables # only then: move .env out of the repository (keep a copy until you are sure)The app then gets its variables when it starts:
dynv run -- node app.js(quickstart). No.envin the repo means nothing for a grep, a settings file or a packaging mistake to leak. -
Give agents names, not values. Dynv’s MCP server is read-only by default, masks values unless you explicitly enable reveal, and previews every write with
dry_run. An agent can see thatSTRIPE_KEYexists, which services use it and what a change would affect, without holding the key. -
Use the OS, not the prompt, for boundaries. Anthropic’s docs point to sandboxing for filesystem and network limits. Do that for anything you cannot afford to leak.
-
Make rotation cheap. If a secret was on a machine that ran the bad extension, you want one preview-then-apply operation across every service that shares it (rotation docs), not a week of hunting copies.
What none of this fixes
- Malware running as you. An extension or script with your user’s rights can read your files, your shell history and the environment of processes you start. Moving secrets out of
.envremoves the easy target; it does not make your user account a safe place to hold credentials. - An agent you let run the app. If an agent can run
dynv run -- env, it can print the environment. Masking applies to what Dynv’s MCP server returns, not to a shell command you allow. - Local token files. The MCP token is a file on your machine; per our MCP docs, an agent that can read files as your OS user may read it. That is why the default token is read-only and why it must not sit inline in a client config.
- Runtime containment is not shipped yet. A process firewall that restricts what a service can execute and connect to is coming soon; today Dynv does not block a compromised dependency or extension from running.
Checklist
-
npm pack --dry-run | grep claudeshows nothing for every package you publish -
.claude/settings.local.jsonis in.gitignore, and no command in it contains a token - Claude Code is on a current version
- You ran the
svc-telemetrycheck if you used the Cursor marketplace extension on 29 October - No production secret sits in a file an agent can read; the agent gets names and masked values
- You know how to rotate a leaked secret everywhere in one pass
Facts last verified . Spotted an error? Tell us.