Skip to content

fix(hooks): the write guards follow a backslash-named link, and the out-of-project allowance is this project's memory and this session's scratchpad only (6.31.1) - #293

Merged
PrzemekGalarowicz merged 10 commits into
mainfrom
write-guard-narrowing
Sep 28, 2026
Merged

PrzemekGalarowicz merged 10 commits into
mainfrom
write-guard-narrowing

Conversation

@PrzemekGalarowicz

Copy link
Copy Markdown
Contributor

Summary

This is the write-guard-narrowing increment, released as 6.31.1 (a patch). It makes two changes to the write guards.

  • M4, the backslash-named link. On a / system, a backslash is part of a file name. protect-trusted-paths.cjs now carries a byte-equal copy of the physical-target resolution that enforce-writes-scope.cjs already uses; a ✧ test pins that the two copies stay equal. The hook judges that target after its own check, and only then. As a result, a symlink named like s\x that points at the root can no longer carry a write to a trusted doc, or to canon under a plan-origin scope. The canon escape never authorizes a target whose path contains a backslash. Every verdict this changes moves toward deny, and every old denial keeps its message.

  • M7, the install-posture out-of-project allowance. In an installed project, outside an open run, writes outside the project are now allowed in three places only:

    • this project's auto-memory folder, for either of two keys: the key of the folder holding the transcript, or the canonical main-checkout key;
    • this session's own scratchpad, only in the shape Claude Code writes it: <temp root>/claude-<uid>/<key>/<session_id>/scratchpad;
    • an ordinary temp path. Such a path never contains a claude-<uid> folder, never lies inside the Claude config directory, and is not inside HOME when HOME is itself inside a temp root.

    Before this change, every project's memory folder and both whole temp roots were writable. A payload field that is absent, malformed, or not in normal form grants nothing. A new Claude-state deny variant names the memory keys and the scratchpad rule, and it offers no Bash route.

The human-only bytes changed only through proposed/human-only.patch: .claude/hooks/enforce-writes-scope.cjs, .claude/hooks/protect-trusted-paths.cjs and LIMITS.md §7. The maintainer checked the patch's sha256 (6cceeebc…6d82b5aff) and applied it with proposed/apply.sh, which produced commit 2bf04a8. After the apply, shasum -a 256 -c proposed/human-only.sha256 reads OK for all three files. The agent-writable side of the change covers the hook tests, the CHANGELOG, CLAUDE.md, the README and pharn/floor/README.md, and SKILLS_VERSION.

Review rounds

  • GATE 1: approved by the orchestrator under the maintainer's delegation, with five rulings. This was a model decision, not a human approval (PLAN.md).
  • Grill: 7 advisory concerns, none blocking; each was amended in place.
  • GATE-2 review: GREEN, with F1 important and F2–F5 minor. GATE 2 → FIX: F1–F4 were fixed and F5 was accepted. F1 dropped an unmeasured clause. F2 made the scratch remedy reachable. F3 bounded the claims to keys. F4 corrected a count. The lesson candidate was skipped, and is kept in SHIP.md.
  • Case-sensitivity audit: the new tests were audited because CI runs on ext4. The audit is recorded in BUILD.md.
  • Independent patch review: I1 and m4 were fixed in apply.sh. It now checks the file list, applies with one --include per path, compares the working tree before and after, prints the patch's own sha256, and restores from HEAD on every failure, including a failed commit. m1 and m3 were fixed in the patch; both only add denials. m2 is named in LIMITS.md §7.
  • Merges: origin/main was merged twice, at c1bf663 and 17dda60, and the version renumbered to 6.31.1. The patch carries no version string.

Verification

/pharn-dev-verify over the applied tree: PASS. Every gate exits 0: format:check, lint, lint:md, validate, the trust-fence structural pair, and test (4250 of 4250). reconcile reads CLEAN under the epoch the apply anchored. check:changelog-entry is GREEN against 17dda60.

Before the apply, verify read FAIL with failing_gates: ["test"]. That was the designed stop, covering exactly the 32 expected-fail titles, all of which passed against the patch in a throwaway worktree.

Named follow-ups (not built, P7)

  • windows-claude-temp-layout
  • custom-auto-memory-dir
  • protect-fifo-git-hang, protect-firmlink-spelling and deep-path-segment-slowness. The patch review found these three and they predate this increment.

Closed by this increment: protect-backslash-separator.

Records: .dev/features/write-guard-narrowing/ (PLAN.md, GRILL.md, BUILD.md, REGRESSION.md, VERIFY.md, REVIEW.md, SHIP.md, proposed/).

🤖 Generated with Claude Code

PrzemekGalarowicz and others added 10 commits September 27, 2026 22:30
…e out-of-project allowance is this project's and this session's (6.28.3, GATE-2 snapshot)

The agent-writable half of write-guard-narrowing as it stood at GATE 2: the hook
tests asserting the patched guards, the docs, the CHANGELOG entry and the
feature's stage artifacts, with the human-only hook and LIMITS.md change carried
as a reviewed patch under .dev/features/write-guard-narrowing/proposed/. The two
hooks and LIMITS.md are untouched; the patch is not applied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ber to 6.29.1

Conflicts: CHANGELOG.md (main's sections kept byte for byte; this branch's entry
moved to a new ## [6.29.1] section above main's [6.29.0]), README.md (main's
bytes with this branch's badge, guarantee-row and posture-paragraph edits
re-applied), SKILLS_VERSION (6.29.1). Every 6.28.3 this branch had added is now
6.29.1. The human-only patch carries no version string and is unchanged by the
renumber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cratch remedy, the key bound, one unverified clause dropped (6.29.1)

- F1: the LIMITS §7 text in the proposed patch no longer states how Claude Code
  keys a non-git session started in a subdirectory (unverified).
- F2: the Claude-state deny message offers this session's scratchpad as a route
  only when the call's payload identifies it, and otherwise says it cannot be
  reached (L27); two M7 tests assert both variants.
- F3: "another project's memory stays denied" is bounded to keys (the encoding
  collision) in LIMITS §7, the enforce header, CLAUDE.md, README.md,
  pharn/floor/README.md, the CHANGELOG entry and APPLY.md.
- F4: BUILD.md's capability count corrected to 36.
- The claude-<uid> fold test now probes the temp volume's case sensitivity and
  shows the fold-based verdict on both branches.
- proposed/human-only.patch and .sha256 regenerated once and re-verified; the two
  hooks and LIMITS.md themselves are still untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd the non-test gates

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ber to 6.31.1

Conflicts: CHANGELOG.md (main's sections kept byte for byte; this branch's entry
moved to a new ## [6.31.1] section above main's [6.31.0], its bump sentence
6.31.0 → 6.31.1), README.md (the badge only), SKILLS_VERSION (6.31.1). Every
6.29.1 this branch had added in CLAUDE.md, README.md and pharn/floor/README.md is
now 6.31.1. Neither hook, LIMITS.md nor either hook test file changed on main
since 70cb51c, so the human-only patch and its checksums are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r to 6.31.1

PLAN §5 carries the new number; BUILD.md "After merge #2" records the
conflicts, the unchanged patch (git apply --check and shasum -c against a
throwaway apply), the expected-fail list re-derived on the merged tree (the same
30) and the re-anchored reconcile epoch; SHIP.md points to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h bounds what it applies; config dir and session paths deny-only (6.31.1)

- I1: apply.sh prints the patch's own sha256 first, refuses a patch whose
  `git apply --numstat` is not exactly the three files, applies with one
  --include per path, and requires the working tree (tracked, untracked and
  ignored) to change in exactly those three regular files.
- m4: every failure from the apply on, the commit included, restores the three
  files from HEAD and exits 1; APPLY.md says what is enforced.
- m1 (deny-only): nothing inside the Claude config directory is a temp path,
  whichever of it and the temp root contains the other.
- m3 (deny-only): transcript_path and scratchpad_dir must be in normal form; the
  scratchpad is accepted only as <temp root>/claude-<uid>/<key>/<session_id>/scratchpad.
- m2: LIMITS §7 names cc-socks, claude-mcp-browser-bridge-* and ShipIt folders.
- Two new hook tests (m1, m3); the expected-fail list is now 32.
- proposed/human-only.patch and .sha256 regenerated once; the hooks and
  LIMITS.md themselves are still untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed, failed commit restored, honest run clean

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t-of-project allowance is this project's and this session's (human-applied)
The maintainer applied proposed/human-only.patch (2bf04a8: the two hooks and
LIMITS.md, checksums OK). /pharn-dev-verify over the applied tree reads PASS:
every gate exits 0 (npm test 4250/4250) and reconcile is CLEAN under the epoch
the apply anchored. origin/main is still 17dda60, so no merge was needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 28, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 8ceef1b0-86e8-48e0-ab0c-8f0db2a7e86c


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@PrzemekGalarowicz
PrzemekGalarowicz merged commit 9490b1b into main Sep 28, 2026
8 checks passed
@PrzemekGalarowicz
PrzemekGalarowicz deleted the write-guard-narrowing branch September 28, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant