forked from bchanot/claude
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.
64 lines
2.3 KiB
Markdown
64 lines
2.3 KiB
Markdown
# Claude Code — Settings Rule Syntax
|
|
|
|
## Rule syntax
|
|
|
|
### Bash
|
|
```json
|
|
"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
|
|
```json
|
|
"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
|
|
```json
|
|
"WebFetch(domain:docs.rs)" // specific domain only
|
|
"WebFetch" // all web fetches
|
|
"WebSearch" // no sub-patterns supported
|
|
```
|
|
|
|
### Agent / Skill / MCP
|
|
```json
|
|
"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` |
|