Files
Bastien Chanot 07ca738b3f fix(settings): Write() deny rules inert — convert to Edit(), close write gaps
Startup emitted 15 warnings: "Write(**/.env) is not matched by file
permission checks — only Edit(path) rules are."

Write(path) rules never matched. The 5 secret-file write bans were dead
config — .env, secrets/**, *.pem, *.key were freely writable. Converting
to Edit() makes them enforced: permissions.md:242 "Edit rules apply to all
built-in tools that edit files", and :244 prescribes exactly this ("add an
Edit deny rule for paths no tool may change").

- settings.json: Write(...) -> Edit(...) on the 5 patterns.
- Mirror the 9 secret patterns Read denied but Edit did not: *.p12, *.pfx,
  id_rsa*, id_ed25519*, .ssh/**, credentials, credentials.json,
  .aws/credentials, .azure/**. Read/Edit parity now 14/14. Claude could
  previously overwrite an SSH private key or ~/.aws/credentials.
- New read-allowed/write-denied class: lockfiles (*.lock,
  package-lock.json, pnpm-lock.yaml, go.sum) + node_modules/**. Reading
  aids diagnosis; hand-editing is always wrong — the package manager
  regenerates them via Bash, which Edit deny does not block.
- templates/settings/SETTINGS.md taught the broken Write() pattern; fixed
  at the source so /onboard stops propagating it.

Rule syntax has no negation and deny beats allow, so deny globs cannot
carry exceptions — see the .env.example conflict noted in the follow-up.
2026-07-16 14:45:26 +02:00

2.3 KiB

Claude Code — Settings Rule Syntax

Rule syntax

Bash

"Bash(git status)"         // exact match
"Bash(npm run test:*)"     // wildcard suffix
"Bash(git push*)"          // prefix match
"Bash(curl * | bash)"      // pipe pattern — block code injection

Read / Edit — gitignore syntax

"Read(**/.env)"            // any .env in any subdirectory
"Read(**/secrets/**)"      // anything inside secrets/
"Read(src/**/*.ts)"        // all .ts under src/
"Edit(**/*.key)"           // deny writing any .key file — Edit covers
                           // Write/Edit/MultiEdit/NotebookEdit

Write(path) rules are inert: file permission checks only match Edit(path). Claude Code warns at startup for every Write(glob) rule. Always write the file-write ban as Edit(...).

WebFetch / WebSearch

"WebFetch(domain:docs.rs)" // specific domain only
"WebFetch"                  // all web fetches
"WebSearch"                 // no sub-patterns supported

Agent / Skill / MCP

"Agent(explorer)"
"Skill(deploy *)"
"mcp__github__*"           // all tools from github MCP server

defaultMode values

Value Behavior When to use
default Prompts on first use of each tool Normal development
acceptEdits Auto-accepts file edits, prompts for Bash Trusting sessions
plan Read-only — Claude plans, cannot execute Code review, audit
auto Research preview — agentic default, permission model evolving. This config's default (BDR-004) Daily driving with guardrails
bypassPermissions Skips all prompts — dangerous CI/CD only, sandboxed env

Security notes

  • Read(**/.env) only blocks the Read tool. Bash(cat .env) bypasses it unless separately denied. → Use .claudeignore for hard file exclusion regardless of tool.
  • disableBypassPermissionsMode: "disable" prevents switching to bypass mode mid-session.
  • Prefer ask over allow for anything touching external systems.
  • deny in ~/.claude/settings.json cannot be overridden by project-level allow — deny always wins.

managed-settings.json (enterprise)

OS Path
Windows C:\ProgramData\ClaudeCode\managed-settings.json
macOS /Library/Application Support/ClaudeCode/managed-settings.json
Linux /etc/claude-code/managed-settings.json