From 07ca738b3f5240883da574961aa6758f2a035b01 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Thu, 16 Jul 2026 14:45:26 +0200 Subject: [PATCH] =?UTF-8?q?fix(settings):=20Write()=20deny=20rules=20inert?= =?UTF-8?q?=20=E2=80=94=20convert=20to=20Edit(),=20close=20write=20gaps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- settings.json | 26 ++++++++++++++++++++------ templates/settings/SETTINGS.md | 8 ++++++-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/settings.json b/settings.json index 2cbd3f9..5495f95 100644 --- a/settings.json +++ b/settings.json @@ -134,11 +134,25 @@ "Read(**/credentials.json)", "Read(**/.aws/credentials)", "Read(**/.azure/**)", - "Write(**/.env)", - "Write(**/.env.*)", - "Write(**/secrets/**)", - "Write(**/*.pem)", - "Write(**/*.key)", + "Edit(**/.env)", + "Edit(**/.env.*)", + "Edit(**/secrets/**)", + "Edit(**/*.pem)", + "Edit(**/*.key)", + "Edit(**/*.p12)", + "Edit(**/*.pfx)", + "Edit(**/id_rsa*)", + "Edit(**/id_ed25519*)", + "Edit(**/.ssh/**)", + "Edit(**/credentials)", + "Edit(**/credentials.json)", + "Edit(**/.aws/credentials)", + "Edit(**/.azure/**)", + "Edit(**/*.lock)", + "Edit(**/package-lock.json)", + "Edit(**/pnpm-lock.yaml)", + "Edit(**/go.sum)", + "Edit(**/node_modules/**)", "Bash(eval *)", "Bash(exec *)", "Bash(find * -delete*)", @@ -236,7 +250,7 @@ "disableBypassPermissionsMode": "disable", "additionalDirectories": [] }, - "model": "claude-fable-5[1m]", + "model": "opus[1m]", "hooks": { "SessionStart": [ { diff --git a/templates/settings/SETTINGS.md b/templates/settings/SETTINGS.md index 20b9e73..e462765 100644 --- a/templates/settings/SETTINGS.md +++ b/templates/settings/SETTINGS.md @@ -10,13 +10,17 @@ "Bash(curl * | bash)" // pipe pattern — block code injection ``` -### Read / Write / Edit — gitignore syntax +### Read / Edit — gitignore syntax ```json "Read(**/.env)" // any .env in any subdirectory "Read(**/secrets/**)" // anything inside secrets/ "Read(src/**/*.ts)" // all .ts under src/ -"Write(**/*.key)" // deny writing any .key file +"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