Hello, this is Ryuta Hamamoto from TIMEWELL. For teams embedding Claude Code in daily work, here is the secret-leak risk that is easy to miss, and how to stop it.
Claude Code (Anthropic's official coding agent) is now a standard partner across our projects at TIMEWELL, and inside many other companies.
In parallel, the same class of concern keeps landing:
Claude Code may load your
.envthe moment it opens your project
.env holds Stripe live tokens, AWS access keys, OpenAI keys, Anthropic sk-ant- keys: things that must not leave the machine. Claude Code can place them in context and potentially in Anthropic server logs. Once a secret is in server logs, treat it as leaked. That is not a Slack apology problem.
Developer @darkzodchi published a well-read English .env protection guide. I am restating what Japanese engineering teams actually need, in order.
Bottom line: a few deny lines in settings.json are the key. Alone they leave holes. There are three leak paths, each needing its own control. By the end you can reach "Claude Code cannot physically touch secrets" in about 30 minutes.
Guard three leak paths
Before the how-to: the map. Putting .env in .gitignore or writing "do not read .env" in CLAUDE.md only closes one path.
| Path | What happens | Main control |
|---|---|---|
| ① Direct read | Claude opens .env with the Read tool |
settings.json deny |
| ② Runtime output | Tests/app logs and errors print secrets | .env.test dummy values |
| ③ Grep/search collateral | Search hits include secret lines | deny rules + code review |
Plus two insurance layers:
- pre-commit automatic detection
- container isolation as a hard cut
That is the goal of this piece.
"It's only a side project" / "internal tool, no big deal if it leaks" — those are exactly the contexts where leaks happen.
Why CLAUDE.md cannot protect you
Claude Code supports a project instruction file, CLAUDE.md. Many teams write:
## Security rules
- Never load .env files
- Never output secret information
and relax.
CLAUDE.md is a request, not a system constraint. Soft guidance the model may or may not follow.
Situations where CLAUDE.md fails
Cases we hit or reproduced:
- Long context wash-out: dozens of turns bury early CLAUDE.md; attention drops and
.envgets opened. - Ambiguous tasks that seek "hints": "env vars seem broken," "auth fails." Claude opens
.envto debug. Good faith, bad outcome. - Error bait:
Cannot find env variable FOOmakes Claude "check .env." - Agent SDK and subagents: CLAUDE.md may not propagate to subagents or tool chains.
In April 2026 GitHub threads reported Claude echoing .env contents despite CLAUDE.md bans.
The only reliable defense is system-level deny
settings.json permissions.deny is enforced by the Claude Code runtime.
- CLAUDE.md: a note saying "please do not enter this room"
- settings.json deny: a locked room where the handle is out of reach
That difference is decisive. What follows is how to lock it.
Looking for AI training and consulting?
Learn about WARP training programs and consulting services in our materials.
Leak path ① — Claude reads .env directly
Clearest path, easiest fix.
What happens
Claude scans the tree for structure. Often it does not open .env yet. That changes when you say:
- "Confirm environment variables load correctly"
- "Review Stripe integration for misconfiguration"
- "Review project configuration"
Claude decides .env is the answer, Reads it, and STRIPE_SECRET_KEY=sk_live_... / OPENAI_API_KEY=sk-... enter conversation context.
In context means:
- used in Claude's reasoning
- shown on your screen
- potentially included in Anthropic server logs
- retained on later
/resume
Fix: settings.json deny rules
In ~/.claude/settings.json (global) or project .claude/settings.json:
{
"permissions": {
"deny": [
"Read(**/.env*)",
"Read(**/.dev.vars*)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(**/secrets/**)",
"Read(**/credentials/**)",
"Read(**/.aws/**)",
"Read(**/.ssh/**)",
"Read(**/config/database.yml)",
"Read(**/config/credentials.json)",
"Read(**/.npmrc)",
"Read(**/.pypirc)",
"Write(**/.env*)",
"Write(**/secrets/**)",
"Write(**/.ssh/**)"
]
}
}
Notes:
**/covers any subdirectory:apps/web/.env,packages/api/.env.production.env*sweeps.env.local,.env.production,.env.test,.env.staging- Deny
Writetoo so Claude does not "helpfully" rewrite.env .pemand.keyare SSL certs and SSH private keys.npmrcand.pypircmay hold registry tokens, critical with private registries
Try it
Restart Claude Code and ask:
Show me the contents of the .env file.
You should get a permissions deny message. That is "physically cannot touch." Highest-ROI control is done.
Leak path ② — command output leaks
This is the real topic. Deny alone is not enough. Most teams miss this path.
Example: tests print secrets
"Run the tests" executes npm test / pytest via Bash. Suppose:
// tests/stripe.test.ts
test("can reach Stripe API", async () => {
const res = await fetch("https://api.stripe.com/v1/charges", {
headers: { Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}` },
});
expect(res.status).toBe(200);
});
On network failure, frameworks dump request detail:
FAIL tests/stripe.test.ts
Expected 200, received undefined
Request: GET https://api.stripe.com/v1/charges
Headers: { Authorization: "Bearer sk_live_51H...abcDEF" }
Error: ETIMEDOUT
Bash captures the whole stream into conversation context. Claude never opened .env. Deny was never violated. The secret still leaked.
Other runtime leak patterns
- DB errors:
password "real_password_here" failedin connection strings - Third-party SDK debug logs when
DEBUG=*dumps auth headers - Stack traces that print
api_key=sk-...arguments
Fix: .env.test and dummy-value ops
Split test/local secrets from real ones.
# .env.test ── OK to commit, OK if leaked
STRIPE_SECRET_KEY=sk_test_not_a_real_key_dummy_value
DATABASE_URL=postgres://test:test@localhost:5432/testdb
OPENAI_API_KEY=sk-test-dummy-key-for-mocking
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AKIAIOSFODNN7EXAMPLE is AWS's documented example dummy. Commit or leak = zero damage.
Point test frameworks at .env.test:
// vitest.config.ts
import { defineConfig } from "vitest/config";
import { loadEnv } from "vite";
export default defineConfig(({ mode }) => ({
test: {
env: loadEnv("test", process.cwd(), ""),
},
}));
# conftest.py
import os
from dotenv import load_dotenv
def pytest_configure():
load_dotenv(".env.test", override=True)
Then Claude's "run tests" only ever sees dummies. Nothing dangerous exists to leak.
Extra: output filter
When you must use real local env vars, wrap commands:
#!/bin/bash
# scripts/safe-run.sh
"$@" 2>&1 | sed -E \
-e 's/sk-ant-[A-Za-z0-9_-]+/sk-ant-***REDACTED***/g' \
-e 's/sk_live_[A-Za-z0-9]+/sk_live-***REDACTED***/g' \
-e 's/AKIA[0-9A-Z]{16}/AKIA***REDACTED***/g'
Tell Claude to run through the wrapper.
Leak path ③ — grep and search
Quiet, frequent.
What happens
Claude greps the codebase:
Find where getUserById is defined
If a comment holds:
// TODO: remove old API key sk_live_oldkey_abc123 from getUserById
function getUserById(id) { ... }
grep context lines stream the secret. No .env open, no tests — just search.
Fix 1: thorough deny
Denying secrets/, credentials/, .aws/, .ssh/ also shrinks grep reach.
Fix 2: purge secrets from comments and logs
- Periodic grep audits for old secrets in comments/TODOs
- Slack/webhook URLs and internal bearer tokens too
- pre-commit blocks at commit time (below)
Fix 3: hide sensitive directories
For large repos, deny logs/, dumps/, backup/ as well:
"deny": [
"Read(**/logs/**)",
"Read(**/dumps/**)",
"Read(**/backup/**)"
]
Production settings.json — copy-paste base
TIMEWELL's shared base for ~/.claude/settings.json:
{
"permissions": {
"allow": [
"Read",
"Glob",
"Grep",
"LS",
"Edit",
"MultiEdit",
"Write(src/**)",
"Write(tests/**)",
"Write(docs/**)",
"Bash(npm run *)",
"Bash(npm test *)",
"Bash(npx tsc *)",
"Bash(npx vitest *)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git add *)",
"Bash(git commit *)"
],
"deny": [
"Read(**/.env*)",
"Read(**/.dev.vars*)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(**/*.p12)",
"Read(**/*.pfx)",
"Read(**/secrets/**)",
"Read(**/credentials/**)",
"Read(**/.aws/**)",
"Read(**/.ssh/**)",
"Read(**/.gcp/**)",
"Read(**/.azure/**)",
"Read(**/config/database.yml)",
"Read(**/config/credentials.json)",
"Read(**/config/master.key)",
"Read(**/.npmrc)",
"Read(**/.pypirc)",
"Read(**/logs/**)",
"Read(**/dumps/**)",
"Write(**/.env*)",
"Write(**/secrets/**)",
"Write(**/.ssh/**)",
"Write(.github/workflows/*)",
"Bash(rm -rf *)",
"Bash(sudo *)",
"Bash(git push *)",
"Bash(git push --force *)",
"Bash(npm publish *)",
"Bash(curl * | sh)",
"Bash(curl * | bash)",
"Bash(wget *)",
"Bash(chmod 777 *)"
],
"defaultMode": "acceptEdits"
}
}
Design notes
- Allow only what work needs. A whitelist reduces accidents.
- Scope writes to
src/**and similar so/etcand~/.sshstay out - Deny
git pushif humans must approve remotes; move to allow if you want auto-push - Deny
curl | shas a baseline against remote script execution defaultMode: acceptEdits: we allow edits, confirm creates and commands
Project overrides live in .claude/settings.json (for example, allow Bash(terraform *)).
pre-commit hook as a second line
Claude-side controls do not stop humans staging .env. Automate at the git edge.
Save as .git/hooks/pre-commit and chmod +x:
#!/bin/bash
# Block commits that look like secrets
PATTERNS=(
'sk-ant-' # Anthropic API key
'sk-live-' # Stripe live key
'sk_live_' # Stripe live key (alt)
'ghp_' # GitHub Personal Access Token
'gho_' # GitHub OAuth Token
'ghs_' # GitHub App Server Token
'AKIA[0-9A-Z]{16}' # AWS Access Key ID
'xox[bpors]-' # Slack Token
'SG\.[A-Za-z0-9_-]{22}' # SendGrid API Key
'eyJ[A-Za-z0-9_-]{20,}' # JWT
'BEGIN[[:space:]]\+\(RSA\|DSA\|EC\|OPENSSH\|PGP\)\?[[:space:]]*PRIVATE KEY'
)
BLOCKED_FILES=('.env' '.env.local' '.env.production' 'credentials.json' 'id_rsa' 'id_ed25519')
for pattern in "${PATTERNS[@]}"; do
if git diff --cached --diff-filter=ACM | grep -qE "$pattern"; then
echo "BLOCKED: staged content matches secret-like pattern '$pattern'."
echo " Inspect with git diff --cached, remove the line, recommit."
exit 1
fi
done
for file in "${BLOCKED_FILES[@]}"; do
if git diff --cached --name-only | grep -qF "$file"; then
echo "BLOCKED: sensitive file '$file' is staged."
echo " Run: git reset HEAD -- $file"
exit 1
fi
done
if git diff --cached --name-only | grep -qE '\.(pem|key|p12|pfx)$'; then
echo "BLOCKED: key files (.pem/.key/.p12/.pfx) are staged."
exit 1
fi
echo "pre-commit security check passed."
exit 0
Why pattern checks matter
".env is gitignored" is not enough:
- Tokens pasted into comments and left
- Real keys in "example" docs
- Jupyter cell outputs retaining API responses
- Debug
console.log(process.env)left behind
pre-commit is the last exit gate for those slips.
For production grade: gitleaks / trufflehog
When the simple hook is thin, use gitleaks or trufflehog — hundreds of patterns, CI on PRs.
TIMEWELL standard: local pre-commit as above, CI with gitleaks.
Container isolation — the nuclear option
The layers above suffice for most projects. Client production credentials, healthcare, and finance need one more.
Idea: run Claude Code in an environment where .env physically does not exist.
# Example Claude Code Docker launch
docker run -it \
-v "$(pwd)":/app \
-v /dev/null:/app/.env:ro \
-v /dev/null:/app/.env.local:ro \
-v /dev/null:/app/.env.production:ro \
--env-file /dev/null \
claude-code-dev
-v /dev/null:/app/.env:ro mounts an empty file over .env inside the container.
Operating model
- Dev: no repo
.env; inject with-e KEY=valueor Vault / 1Password CLI - Prod build: GitHub Actions Secrets, AWS Secrets Manager. Never secrets in source.
- Local secrets: keep outside the repo (for example,
~/.env-vault/)
Looks heavy once. Then accidents become structurally impossible. TIMEWELL uses this when work can touch production DBs.
Six-item checklist for today
Before the next Claude Code session:
- 1.
~/.claude/settings.jsondenies.env*,secrets/,.ssh/ - 2. Tests load
.env.testdummies — real secrets never log via tests - 3.
.git/hooks/pre-commitsecret patterns installed withchmod +x - 4. Production credentials in 1Password / AWS Secrets Manager / Vault — not plaintext files
- 5. All
.env*in.gitignore; history scanned (git log -p | grep -E 'sk_live_|sk-ant-') - 6.
.envoutside project tree or excluded via container mounts
All six checked, and Claude Code cannot reach secrets. Zero checks, and the next vague prompt can put your API key in Anthropic logs. Move now, not tomorrow.
Making AI security an organizational standard
So far: individual developer self-defense. Enterprise agent rollout outgrows that.
- Does every employee have equivalent settings.json?
- Are contractor repos forced to the same config?
- Who detects incidents and where do they stop?
- What audit and contract grain of accountability is enough?
Guidelines alone do not move this. You need workflow, contracts, training, and monitoring designed end to end.
TIMEWELL's WARP consulting partners on safe, high-efficiency AI embedding: Claude Code and agent rollout design, secrets policy, developer education, incident playbooks, on a monthly engagement.
If you want to push AI hard but fear security, if field teams "have it handled" without proof, or if you need a board-ready safety narrative, book a conversation. Thirty minutes online is enough to map the next move for your stack.
AI security works better as an organizational standard than as each developer's craft project. That is the problem WARP is built to solve with you.
Summary
Agent-driven development created a new risk class. When only humans touched code, "trusted people read local .env" was enough. Claude Code is like countless junior engineers who start reading files the moment the project opens.
- CLAUDE.md = a note on the cabinet: please don't open
- settings.json deny = never hand over the key
.env.test= dummy contents even if opened- Containers = secrets live in another building
Stack all four before you comfortably entrust development to agents.
Last line: security is not one-and-done. New secret formats need hook updates; Claude Code upgrades may change the settings.json schema. This is May 2026 practice. Schedule reviews.
Faster, safer development from here.
Ryuta Hamamoto, TIMEWELL
References
- Anthropic docs, "Claude Code settings" (https://docs.anthropic.com/)
- darkzodchi (@zodchiii), "The .env Setup That Keeps Claude Code From Leaking Your Secrets" (April 2026)
- gitleaks (https://github.com/gitleaks/gitleaks)
- OWASP Secrets Management Cheat Sheet
- AWS docs, "Example AWS Access Keys" (dummy value source)
![Keep Claude Code Off Your .env — settings.json and a Four-Layer Defense Against Secret Leaks [2026]](/images/columns/claude-code-env-security-guide/cover.png)
![Anthropic Claude Security Deep Dive [2026-May Update]: How the Opus 4.7-Powered Vulnerability Scanner Works and Integrates with the Enterprise Stack](/images/columns/anthropic-claude-security-public-beta-guide/cover.png)




