feat(integrity-guard): user-configurable path exclusion list#175
Open
Fearvox wants to merge 1 commit into
Open
feat(integrity-guard): user-configurable path exclusion list#175Fearvox wants to merge 1 commit into
Fearvox wants to merge 1 commit into
Conversation
Adds support for a user-supplied allowlist of regex path patterns that
exempt matching paths from ALL persistent-memory / protected-write
advisories in `hooks/integrity-guard.sh`.
## Problem
PUA Integrity Guard fires an advisory on any `Write` / `Edit` / `Bash`
mutating command that touches a `CLAUDE.md`, `memory/` path, or
`.claude/settings.json` — anywhere on the filesystem. This is correct
for canonical project governance files, but is **noisy for derived /
projected directories** (symlink views, build caches, mirrors,
generated docs) where the same-named files are governance pointers to
upstream, not canonical persistent memory themselves.
Concrete reproducer: a user maintains `~/projects/myrepo/views/admin/CLAUDE.md`
that is a guardrail pointer to `~/projects/myrepo/04-memory/`. Every
`touch` / `Write` / `Edit` on the view's `CLAUDE.md` fires advisory,
even though the write is pre-authorized and the file isn't itself
canonical memory.
There is currently no way to suppress this without disabling Integrity
Guard wholesale (`always_on: false`) or editing the cached script
directly (lost on next plugin upgrade).
## Fix
Read `~/.pua/integrity-guard-exclusions.json` (overridable via
`$PUA_INTEGRITY_EXCLUSIONS`). Format:
```json
{"patterns": ["(^|/)derived-view-[^/]+/CLAUDE\\.md$"]}
```
Bare JSON array also supported.
- Patterns compile with `re.I` and match via `re.search` against
forward-slash-normalized paths
- Missing / malformed config → empty list → behavior identical to
current default (fully backward-compatible)
Two integration points:
1. `find_reason_for_path()` short-circuits via `is_excluded_path(n)`
2. `command_hits()` buckets candidates so the fallback whole-command
regex is suppressed when any candidate was excluded (otherwise
`touch /excluded/CLAUDE.md` falls through and the fallback
substring-matches `/CLAUDE.md` against the literal command,
re-firing the advisory)
## Tests
Adds 9 new cases to `evals/test-integrity-guard.sh` covering:
- Excluded paths stay silent for Write / Edit / Bash mutating commands
- Non-excluded paths still trigger advisories (no regression)
- Second exclusion pattern matches independently
- Memory paths outside the exclusion list still trigger (boundary)
- Malformed config silently falls back to default behavior
- Bare JSON array config format works
Suite goes 19/19 → 28/28 passing. No existing test regression.
## Docs
Adds an FAQ section explaining the config file format, semantics, and
the typical derived-artifact use case.
Fearvox
added a commit
to Fearvox/derived-claude-md
that referenced
this pull request
May 23, 2026
…stream PR
Captures the actual deployment of this plugin from local dev → public GitHub →
skills.sh-compatible install. Includes:
- Install path A: project-scoped npx skills add (from EverOS cwd)
- Install path B: --global install (from ~), and how it auto-replaced
dev-era ~/.claude/skills/<name>/ directories with symlinks to the
central ~/.agents/skills/<name>/ source-of-truth
- SHA-identical verification: ~/.agents/skills/.../SKILL.md ===
~/repos/derived-claude-md/skills/.../SKILL.md
- Project-scope cleanup with `skills remove --project -y`
- skills.sh registration via GitHub topics (skills-sh discovery signal)
Also documents the cross-repo work the deployment triggered:
- tanweai/pua#175: paired upstream PR adding user-configurable path
exclusion list to PUA Integrity Guard (generalizes the local
derived-artifact workaround used here)
- Local PUA patch development trace including the bucketing-fix
iteration that the upstream PR's test suite caught
Adds a from-scratch deployment reproduction recipe at the bottom.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a user-configurable exclusion allowlist so specific paths can be exempted from Integrity Guard advisories (via env var or a config file), reducing noise for derived/mirrored views.
Changes:
- Added
PUA_INTEGRITY_EXCLUSIONS/~/.pua/integrity-guard-exclusions.jsonsupport to compile exclusion regexes and short-circuit checks. - Updated command/path advisory logic to ignore excluded candidates and avoid fallback whole-command matches in some cases.
- Added eval coverage and FAQ documentation for configuring exclusions.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| hooks/integrity-guard.sh | Implements loading/compiling exclusion patterns and applies them to path + mutating-command advisory logic. |
| evals/test-integrity-guard.sh | Adds regression tests for exclusions (file path tools + Bash) plus malformed/array config cases. |
| docs/FAQ.md | Documents how to configure exclusions and what behavior changes to expect. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+248
to
+263
| # Bucket candidates so we can ignore excluded paths at every layer. | ||
| excluded_hits = [c for c in candidates if is_excluded_path(c)] | ||
| non_excluded_candidates = [c for c in candidates if not is_excluded_path(c)] | ||
| for candidate in non_excluded_candidates: | ||
| for rx, reason in PROTECTED_WRITE_PATTERNS: | ||
| if rx.search(norm_path(candidate)): | ||
| return 'advisory', reason, candidate | ||
| for rx, reason in PROTECTED_WRITE_PATTERNS: | ||
| m = rx.search(normalized) | ||
| if m: | ||
| return 'advisory', reason, m.group(0) | ||
| # Fallback whole-command regex: skip if the command involved any | ||
| # excluded path — the regex would just rediscover it via substring | ||
| # match (e.g. `touch /repo/excluded-view/CLAUDE.md` re-matching | ||
| # `(^|/)CLAUDE\.md$` against the literal command string). | ||
| if not excluded_hits: | ||
| for rx, reason in PROTECTED_WRITE_PATTERNS: | ||
| m = rx.search(normalized) | ||
| if m: | ||
| return 'advisory', reason, m.group(0) |
Comment on lines
+249
to
+250
| excluded_hits = [c for c in candidates if is_excluded_path(c)] | ||
| non_excluded_candidates = [c for c in candidates if not is_excluded_path(c)] |
|
|
||
| OUT=$(run_guard_with_exclusions Bash '{"command":"touch /repo/CLAUDE.md"}') | ||
| assert_advisory "non-excluded path bash mutating command still advisory" "$OUT" "Persistent-memory risk" | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
hooks/integrity-guard.shfires an advisory on anyWrite/Edit/Bashmutating command that touches aCLAUDE.md,memory/path, or.claude/settings.json— anywhere on the filesystem. This is correct for canonical project governance files, but is noisy for derived / projected directories (symlink views, build caches, mirrors, generated docs) where the same-named files are governance pointers to upstream, not canonical persistent memory themselves.Concrete reproducer: a user maintains
~/projects/myrepo/views/admin/CLAUDE.mdthat is a guardrail pointer to~/projects/myrepo/04-memory/. Everytouch/Write/Editon the view'sCLAUDE.mdfires advisory, even though the write is pre-authorized and the file isn't itself canonical memory.Output:
{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"PUA Integrity Guard (advisory): Persistent-memory risk: ... Target: /CLAUDE.md"}}There is currently no way to suppress this without disabling Integrity Guard wholesale (
always_on: false) or editing the cached script directly (lost on next plugin upgrade).Fix
Read
~/.pua/integrity-guard-exclusions.json(overridable via\$PUA_INTEGRITY_EXCLUSIONS). Format:```json
{"patterns": ["(^|/)derived-view-[^/]+/CLAUDE\\.md$"]}
```
Bare JSON array also supported.
Two integration points:
Tests
Adds 9 new cases to `evals/test-integrity-guard.sh` covering:
Suite goes 19/19 → 28/28 passing. No existing test regression.
```
$ bash evals/test-integrity-guard.sh
...
Passed: 28
Failed: 0
Total: 28
```
Docs
Adds an FAQ section (`docs/FAQ.md`) explaining the config file format, semantics, and typical derived-artifact use case.
Why not just edit `PROTECTED_WRITE_PATTERNS` to be narrower?
The existing patterns are intentionally conservative — they're correct for the typical agent workspace. The drift cases (derived views, build caches) are user-specific and should be opt-in per-environment, not hardcoded. A config-driven allowlist preserves the safe default while giving advanced users an escape hatch.
Co-author note
Drafted with `Claude Opus 4.7 (1M context)` while building a paired skill pack for derived-artifact CLAUDE.md governance (Fearvox/derived-claude-md) — the use case that motivated this PR.