Skip to content

Security Rules

Three layers of secret protection, applied together. Each layer catches what the others miss. After the April 2026 Vercel breach, the Sensitive toggle on Vercel env vars is non-negotiable for any key that grants real access.

Direct file reads are the obvious risk, but they’re not the only one. Three paths matter:

  1. Direct file read. Claude Code opens .env.local (or any other secrets file) and the contents enter the conversation context.
  2. Runtime output capture. Claude Code runs your tests, your dev server, or a curl command. The output logs an Authorization: Bearer ... header, a database connection string with the password embedded, or a stack trace with credentials in scope. Claude Code captures the output. The secrets are now in the conversation, even though Claude Code never opened the file.
  3. Grep and search hits. Claude Code greps the codebase for a function name. The match includes a line from a config file containing credentials. The grep output surfaces the secret directly.

The three layers below address all three paths. The denylist blocks path 1 directly. The CLAUDE.md rules and bootstrap pattern of test-only env files (.env.test with dummy sentinel values) address path 2. The denylist’s recursive globs (**/.env*, **/*.pem, **/*.key, etc.) address path 3 by ensuring grep can’t surface lines from files Claude Code shouldn’t be reading in the first place.

The security model is layered defence.

  1. Layer 1

    .claude/settings.json denylist

    Hard-block Claude Code from reading .env.local and any other sensitive file. Even if you accidentally ask it to, the rule prevents the read.

  2. Layer 2

    CLAUDE.md rules

    Explicit instructions about how Claude Code should handle secrets. Never log them, never include them in error messages, never use NEXT_PUBLIC_ prefix on a key that isn't supposed to be public.

  3. Layer 3

    Vercel Sensitive toggle

    When adding environment variables to Vercel, toggle Sensitive on every secret. After the April 2026 Vercel breach, Sensitive marking is non-negotiable for any key that grants real access.

The denylist stops Claude Code from doing dangerous things. The allow list stops Claude Code from asking permission for safe things. Without it, every routine command — npm test, git status, git diff — triggers a confirmation prompt; thirty prompts a session interrupts flow.

The bootstrap templates seed a sensible allow list alongside the deny list in .claude/settings.json. Read operations are open, Bash is allowed only for specific safe tools (test runners, read-only git, basic file inspection), and Write access is scoped to the project’s source folders. Anything destructive — git push, npm publish, rm -rf, sudo — stays denied regardless.

When both allow and deny match the same tool call, deny wins. So an allow rule like Bash(git *) is safely paired with a deny rule like Bash(git push *) — read-only git is permitted, push is blocked.

Adjust the allow list per project as the stack changes (swap npm for pnpm, add Python commands). The denylist stays mostly identical project-to-project. For settings.json file locations and scope precedence, see Foundational elements.

Common keys, their public/private status, and what should access them.

Variable Prefix Committed Sensitive (Vercel) Server only
NEXT_PUBLIC_SUPABASE_URL NEXT_PUBLIC_ no no no
NEXT_PUBLIC_SUPABASE_ANON_KEY NEXT_PUBLIC_ no no no
SUPABASE_SERVICE_ROLE_KEY none no yes yes
OPENAI_API_KEY none no yes yes
ANTHROPIC_API_KEY none no yes yes
PERPLEXITY_API_KEY none no yes yes

The mistakes that show up most often in real reviews.

  • Hardcoding a key in source 'just for testing'
  • Logging request payloads that include secret headers
  • Calling external APIs from a Client Component (the key ships to the browser)
  • Prefixing a secret with NEXT_PUBLIC_ to 'fix' the previous mistake
  • Forgetting to toggle Sensitive in Vercel for one variable