WARP

Keep Claude Code Off Your .env — settings.json and a Four-Layer Defense Against Secret Leaks [2026]

Published2026-05-01Ryuta Hamamoto

An implementation guide to keep .env files and API keys away from Claude Code. settings.json deny rules, .env.test dummy values, pre-commit hooks, and container isolation — the four-layer defense TIMEWELL runs in production.

Keep Claude Code Off Your .env — settings.json and a Four-Layer Defense Against Secret Leaks [2026]
シェア

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 .env the 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:

  1. Long context wash-out: dozens of turns bury early CLAUDE.md; attention drops and .env gets opened.
  2. Ambiguous tasks that seek "hints": "env vars seem broken," "auth fails." Claude opens .env to debug. Good faith, bad outcome.
  3. Error bait: Cannot find env variable FOO makes Claude "check .env."
  4. 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 Write too so Claude does not "helpfully" rewrite .env
  • .pem and .key are SSL certs and SSH private keys
  • .npmrc and .pypirc may 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" failed in 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.

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 /etc and ~/.ssh stay out
  • Deny git push if humans must approve remotes; move to allow if you want auto-push
  • Deny curl | sh as 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=value or 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.json denies .env*, secrets/, .ssh/
  • 2. Tests load .env.test dummies — real secrets never log via tests
  • 3. .git/hooks/pre-commit secret patterns installed with chmod +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. .env outside 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)

Considering AI adoption for your organization?

Our DX and data strategy experts will design the optimal AI adoption plan for your business. First consultation is free.

Share this article if you found it useful

シェア

Newsletter

Get the latest AI and DX insights delivered weekly

Your email will only be used for newsletter delivery.

無料ダウンロード資料

おすすめの資料

無料診断ツール

あなたのAIリテラシー、診断してみませんか?

5分で分かるAIリテラシー診断。活用レベルからセキュリティ意識まで、7つの観点で評価します。

Learn More About WARP

Discover the features and case studies for WARP.

Related Articles