Skip to content

fix(policy): normalize paths + scan Bash commands in policy guards - #28

Merged
iaj6 merged 1 commit into
mainfrom
fix/policy-depth
Jul 12, 2026
Merged

iaj6 merged 1 commit into
mainfrom
fix/policy-depth

Conversation

@iaj6

@iaj6 iaj6 commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Deepens the policy guard layer, which the July audit found bypassable in practice:

  • PathRestriction — comparisons now run through a purely lexical normalizePathForPolicy() (resolves ..//./, collapses //, case-insensitive, no fs access so not-yet-existing files still match). Closes the /repo/src/../secrets/key.pem traversal and macOS case bypasses. NotebookEdit (notebook_path) is now covered by PathRestriction, FileLimitCount, and BranchProtection guards and the hook's action mapping — it was previously a blanket bypass. Prefix-from-start semantics preserved and pinned by tests (.env matches .env.local, not src/.env).
  • SecretDetection — the pre-tool guard now scans Bash.command and NotebookEdit.new_source in addition to Write/Edit content; the post-hoc evaluator scans cmd.command as well as stdout. The hook now records the written content as the FileEdit diff (10k-char cap), so post-hoc secret detection can actually match on hook-produced runs (it previously always tested an empty string). Block messages never echo the matched secret (asserted).
  • minCoverage removed — no coverage number exists anywhere in the domain model (TestResult carries name/passed/duration/message only) and hooks always finalize with empty testResults, so the dashboard's "min N% coverage" summary advertised enforcement that could never happen. Removed from the config type and the policy summary; legacy DB rows carrying the key stay inert (pinned by tests in core and web).
  • Documented limitation — Bash-mediated writes (tee, > redirects, cp, heredocs) are not intercepted by PathRestriction; a code comment states this and points at RiskyOpFlag patterns as the mitigation.

Tests

~42 new/reworked, including the first direct evaluatePreToolPolicies suite in core (traversal, ./, case, NotebookEdit across four policy types, Bash secret scanning, disabled-policy skip). Verified end-to-end through the real hook binary with an isolated HOME: traversal Write, case-variant NotebookEdit, and secret-bearing Bash all block with exit 2; benign writes allowed. Full workspace suite green, web lint clean.

🤖 Generated with Claude Code

July 2026 audit found the policy guard layer shallower than the product
story. Three findings addressed:

1. PathRestriction bypass — the pre-tool guard did a raw startsWith on
   the unnormalized tool-input path, and only for Edit/Write. Both the
   guard and the post-hoc evaluator now compare lexically-normalized
   paths (./ and ../ segments resolved with posix-normalize semantics,
   no fs access so not-yet-existing files still work) case-insensitively
   (macOS filesystems are case-insensitive by default), and NotebookEdit
   is inspected via notebook_path wherever file_path was handled
   (PathRestriction, FileLimitCount, BranchProtection, SecretDetection,
   and the hook's tool-to-action mapping). Bash-mediated writes (tee,
   > redirects) remain out of scope and are now documented as such at
   the guard.

2. SecretDetection never saw Bash commands — the pre-tool guard now
   scans the Bash command string (and NotebookEdit new_source); the
   post-hoc evaluator scans cmd.command in addition to cmd.stdout; and
   the hook now populates FileEdit.diff with the written content
   (new_string / content / new_source, capped at 10000 chars) so
   post-hoc re.test(edit.diff) can actually match on hook-produced
   runs instead of always testing the empty string.

3. minCoverage removed from TestEnforcementConfig — it was declared,
   persisted, and rendered in the dashboard ("min 80% coverage") but
   never enforced, and no coverage number exists anywhere in the
   domain model to enforce it against. Removed from the config type
   and the dashboard summary so the UI stops claiming enforcement
   that cannot happen; legacy rows carrying the key stay inert.

Verified end-to-end through the real CLI hook: traversal Write,
case-variant NotebookEdit, and secret-bearing Bash command all block
with exit 2; benign writes pass; post-tool-use persists the diff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@iaj6
iaj6 merged commit 70df282 into main Jul 12, 2026
3 checks passed
@iaj6
iaj6 deleted the fix/policy-depth branch July 12, 2026 15:41
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