Skip to content

fix(guardrails): configurable hook-manager bypass + broader machine-path roots - #932

Merged
kyle-sexton merged 1 commit into
mainfrom
fix/918-configurable-hook-manager-and-machine-roots
Jul 22, 2026
Merged

fix(guardrails): configurable hook-manager bypass + broader machine-path roots#932
kyle-sexton merged 1 commit into
mainfrom
fix/918-configurable-hook-manager-and-machine-roots

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Two-lane posture fixes (audit #912): externalize hardcoded assumptions in two guardrails detectors.

W3 - hook-manager bypass detection

block-no-verify matched only lefthook* env-var disables, silently missing HUSKY=0 and others. Now resolves a configurable prefix set (block_no_verify_hook_manager_prefixes userConfig; default lefthook, husky, pre_commit, simple_git_hooks). Consumer values are reduced to identifier characters before splicing into the regex alternation, so no metacharacter injection.

W4 - machine-path checkout roots

hardcoded-path-check's drive-letter-anchored checkout-parent pattern matched only X:\repos\..., missing C:\Projects\... (this very repo) and C:\Dev\.... Broadened to also match Projects and Dev (both capitalizations). A consumer's own checkout root remains caught by the driver's project-root literal scan.

Verification

  • block-no-verify.test.sh 83/0 (adds husky/pre_commit/simple_git_hooks + configurability + sanitization cases)
  • hardcoded-path-check.test.sh 38/0
  • shellcheck clean; guardrails 0.9.5 -> 0.9.6 + CHANGELOG

Related

Closes #918

@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 43s —— View job


PR Review

  • Gather context and understand the PR
  • Read changed files
  • Review block-no-verify.sh changes (W3)
  • Review machine-path-patterns.sh changes (W4)
  • Review test coverage
  • Review plugin.json and CHANGELOG
  • Post final review

Overall this is solid, well-scoped work. The regex injection protection is correct, the fallback logic is safe, and the test coverage is thorough. Two minor findings below.


Finding 1 — Minor: Stale description on block_no_verify_enabled in plugin.json

plugin.json line 38:

"description": "Block git hook-bypass attempts (--no-verify, core.hooksPath=, lefthook disables)",

This description still says only "lefthook disables". Since W3 broadens the default set to lefthook, husky, pre_commit, simple_git_hooks and makes it configurable, the toggle's description now understates what the guard covers. A consumer reading this to decide whether to enable/disable the guard would have an incomplete picture.

Suggested update:

"description": "Block git hook-bypass attempts (--no-verify, core.hooksPath=, hook-manager env-var disables [lefthook/husky/pre_commit/simple_git_hooks by default, configurable])",

Fix this →


Finding 2 — Minor: PR description says "extends"; code (and test) actually says "replaces"

block-no-verify.sh line 82–88 and block-no-verify.test.sh line 181:

The PR body says "a consumer extends it via the block_no_verify_hook_manager_prefixes userConfig" but the code replaces the default set entirely when a value is provided, and the test confirms this:

run "custom set replaces the default (lefthook now allowed)" "LEFTHOOK=0 git commit -m test" 0 \
  CLAUDE_PLUGIN_OPTION_BLOCK_NO_VERIFY_HOOK_MANAGER_PREFIXES="myhooks"

The in-code comment at line 82 says "the default set covers the common managers; a consumer extends it via..." which also implies additive behavior, but the implementation is replacement. Not a code bug — replacement is the correct behavior for a configurable guard — but the inline comment and PR description are misleading. A consumer who reads "extends" and supplies myhooks expecting to ADD it will silently drop lefthook/husky/etc.

Suggest updating the inline comment at block-no-verify.sh line 82 to say "overrides" or "replaces" rather than "extends".

Fix this →


W3 correctness — looks good

  • Regex injection protection: ${_hm//[^a-zA-Z0-9_]/} strips all metacharacters before splicing into the alternation. Subsequent lowercase normalization (${_hm,,}) is consistent with lc="${w[k],,}" used at match time. Safe.
  • :- fallback: ${CLAUDE_PLUGIN_OPTION_...:-lefthook,husky,...} correctly fires for both the unset AND empty-string cases (bash :- treats null/empty as absent), so the plugin.json claim that "empty uses the built-in default set" is accurate.
  • Patternless guard fallback ([[ -n "$HM_ALT" ]] || HM_ALT="lefthook"): This is only reached when every entry in the user-supplied list reduces to an empty string after sanitization (e.g., block_no_verify_hook_manager_prefixes="*.*"). Falling back to lefthook rather than leaving the pattern empty is the right defense-in-depth choice.
  • Match semantics: The suffix class [_a-z0-9]* after the alternation correctly catches HUSKY_EXPERIMENTAL=0husky_experimental=0 matching ^husky[_a-z0-9]*=(0|false)$. ✓

W4 correctness — looks good

  • Both HPP_WIN_REPO_BODY and HPP_ESCAPED_WIN_REPO_BODY are updated in parallel, so the forward-slash form and the JSON-escaped backslash form both cover the new roots. ✓
  • The drive-letter anchor ([A-Za-z]:) keeps false-positive risk low for the broader root set (dev, Dev, projects, Projects). These only match absolute Windows paths, not prose. ✓
  • Explicit dev|Dev and repos|Repos spelling (rather than [Rr]epos) is intentional per the comment — avoids a partial-token the typos-gate would flag. ✓

Summary: Two minor doc/comment issues (stale description in plugin.json, misleading "extends" language). Core logic is correct. Tests cover all new cases including the replace-not-extend behavior and metacharacter sanitization. Good to merge once the stale description is addressed.

@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 25s —— View job


Security Review

  • Gather PR diff and changed files
  • Review block-no-verify.sh for injection surfaces and logic flaws
  • Review machine-path-patterns.sh for regex/path safety
  • Review plugin.json and CHANGELOG.md for supply-chain or config risks
  • Review test file changes
  • Synthesize and post findings

No CRITICAL or IMPORTANT security issues found. Two SUGGESTION-level findings below.


SUGGESTION — Replace-not-extend semantics for custom prefix set (confidence: HIGH)

Severity: SUGGESTION | Confidence: HIGH

block-no-verify.sh lines 82–88

When a consumer sets block_no_verify_hook_manager_prefixes to any non-empty value, that value replaces the entire built-in default set — it does not extend it. A consumer who wants to add, say, myhooks alongside the standard managers must explicitly re-list all four defaults. If they only write myhooks, HUSKY=0, PRE_COMMIT=0, and SIMPLE_GIT_HOOKS=false git commit pass undetected:

# Consumer sets: block_no_verify_hook_manager_prefixes = "myhooks"
# Result: HUSKY=0 git commit -m test → ALLOWED (silently missed)

The test at line 181 confirms this: "custom set replaces the default (lefthook now allowed)". This is by design, but the plugin.json description says only "empty uses the built-in default set" — it does not warn that a non-empty value drops all unmentioned managers. A consumer who reads the description and thinks they are adding a manager will inadvertently weaken the guard.

Recommendation: Either (a) change the semantics to extend rather than replace (append the sanitized custom list to the built-in defaults), or (b) strengthen the plugin.json description to include an explicit warning, e.g.: "Specifying any value replaces the built-in default set entirely; to add a manager while keeping the defaults, list all four (lefthook, husky, pre_commit, simple_git_hooks) plus your own."

Fix this →


SUGGESTION — All-non-alphanumeric prefix value falls back to lefthook only, not the full default set (confidence: MEDIUM)

Severity: SUGGESTION | Confidence: MEDIUM

block-no-verify.sh lines 83–88

for _hm in "${_hm_list[@]}"; do
  _hm="${_hm//[^a-zA-Z0-9_]/}"   # strip metacharacters
  _hm="${_hm,,}"
  [[ -n "$_hm" ]] && HM_ALT="${HM_ALT:+$HM_ALT|}$_hm"
done
[[ -n "$HM_ALT" ]] || HM_ALT="lefthook"   # ← fallback covers lefthook ONLY

If a consumer provides a value composed entirely of non-alphanumeric characters (e.g. "!!!"), all entries strip to empty, HM_ALT remains empty, and the last-resort fallback sets it to lefthook only — not the full four-manager default set. This path produces a weaker guard than either the unset/empty case (which uses the :- default of all four managers via line 82) or the intentional-empty case. The inconsistency is unlikely to be triggered in practice (a plugin system would validate against the string type), but the behavior is surprising and undocumented.

Recommendation: Replace the line-88 fallback with the same four-manager default, or reuse the :- expansion value as a constant:

_default="lefthook,husky,pre_commit,simple_git_hooks"
IFS=',' read -ra _hm_list <<<"${CLAUDE_PLUGIN_OPTION_BLOCK_NO_VERIFY_HOOK_MANAGER_PREFIXES:-$_default}"
# ...build HM_ALT...
[[ -n "$HM_ALT" ]] || {
  IFS=',' read -ra _hm_list <<<"$_default"
  # rebuild from default
}

What was reviewed

File Finding
hooks/block-no-verify.sh Sanitization of user-supplied prefixes correct; regex injection prevented. Two suggestion-level logic edge cases above.
lib/path-detection/machine-path-patterns.sh Hardcoded literal additions only; no injection surface, no bypass opened.
.claude-plugin/plugin.json New userConfig key is properly typed string with a safe empty default; description gap noted above.
hooks/block-no-verify.test.sh Metachar sanitization test at line 183 correctly verifies the intended injection-prevention behavior.
CHANGELOG.md Documentation only; no security surface.

No command injection, no credential exposure, no privileged operations, no workflow-level risks, no supply-chain changes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8b2ca960da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/guardrails/lib/path-detection/machine-path-patterns.sh
@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 23s —— View job


  • Gather context and understand the PR
  • Read full PR diff (origin/main...HEAD, all 3 commits)
  • Review block-no-verify.sh changes (W3)
  • Review machine-path-patterns.sh + hardcoded-path-patterns.sh changes (W4 + pre-filter fix)
  • Review test coverage for new commit
  • Review plugin.json and CHANGELOG.md
  • Post final review

PR Review — commit 9a33e66 (pre-filter widening fix)

The new commit correctly closes the fail-open gap flagged by Codex: the cheap pre-filter gate in hpp::scan_text now lists every root token that HPP_WIN_REPO_BODY and HPP_ESCAPED_WIN_REPO_BODY accept, keeping the gate a strict superset of the detailed patterns as documented. The regression test directly exercises the bypass path (D:\Projects\acme\src content that carried no "Users" or "repos" literal) and verifies exit 2 with the fix. Nothing new to flag on this commit.

Three minor findings from the prior reviews remain unaddressed — carried forward below for visibility.


Finding 1 — Minor: block_no_verify_enabled description understates what the guard covers

plugin.json line 38:

"description": "Block git hook-bypass attempts (--no-verify, core.hooksPath=, lefthook disables)",

This toggle description still says only "lefthook disables". The default manager set now covers four managers, and the set is configurable. A consumer reading this to decide whether to enable/disable the guard sees an incomplete picture. (The new block_no_verify_hook_manager_prefixes config entry at line 104 has an accurate description — this is just the toggle's description that lags.)

Suggested:

"description": "Block git hook-bypass attempts (--no-verify, core.hooksPath=, hook-manager env-var disables [lefthook/husky/pre_commit/simple_git_hooks by default, configurable])",

Fix this →


Finding 2 — Minor: Inline comment says "extends"; behavior is "replaces"

block-no-verify.sh line 75–76:

# The default set covers the common managers; a consumer extends it via the
# block_no_verify_hook_manager_prefixes userConfig (comma-separated, ...

The implementation and test at line 180 ("custom set replaces the default (lefthook now allowed)") confirm that a non-empty consumer value replaces the default set entirely, it does not extend it. A consumer who reads "extends" and sets only myhooks will silently lose protection for HUSKY=0, PRE_COMMIT=0, etc. Suggest changing "extends" to "overrides" and noting that keeping the defaults requires listing all four explicitly.

Fix this →


Finding 3 — Minor: All-non-alphanumeric fallback covers lefthook only, not the full default set

block-no-verify.sh line 88:

[[ -n "$HM_ALT" ]] || HM_ALT="lefthook"  # never leave the guard patternless

If every entry in a consumer-supplied value reduces to empty after metacharacter stripping (e.g. "!!!"), HM_ALT is never populated and this fallback sets it to lefthook alone — weaker than either the unset case (all four defaults via the :- expansion) or any intentional consumer value. The inconsistency is unlikely to hit in practice, but the two code paths (empty/unset → 4 managers; all-stripped → 1 manager) are surprising. Suggest the fallback restore the same four-manager default string rather than hardcoding lefthook.

Fix this →


New commit (9a33e66) — clean

Check Result
Pre-filter gate covers repos|Repos|projects|Projects|dev|Dev ✓ matches every token the detailed bodies accept
Gate stays a strict superset (looser is safe; tighter is a bypass) dev/Dev match as substring, which only adds wasted full-scans, no missed detections
Regression test exercises the exact bypass path D:\Projects\acme\src with no "Users"/"repos" literal
No new injection surface or logic change ✓ gate is read-only filter, not a security boundary
Commit message accurately describes the fix

Summary: The pre-filter fix is correct and the regression test is well-targeted. Three minor doc/comment issues from the prior reviews remain open — Finding 1 (stale toggle description in plugin.json) is the most consumer-visible and worth addressing before merge; Findings 2–3 are lower-stakes but also straightforward fixes.

@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 7s —— View job


Security Review

  • Fetch PR diff (2 commits: 8b2ca96, 9a33e66)
  • Read all 7 changed files
  • Analyze injection surfaces in block-no-verify.sh (W3)
  • Analyze machine-path-patterns.sh + hardcoded-path-patterns.sh (W4)
  • Check plugin.json and CHANGELOG.md for supply-chain / config risks
  • Synthesize findings

No CRITICAL or IMPORTANT security issues found. Two pre-existing SUGGESTION-level observations below; one is slightly sharper here than in previous reviews.


SUGGESTION — Fail-open window between commits 1 and 2 is now closed; verify squash-merge order (confidence: HIGH)

Severity: SUGGESTION | Confidence: HIGH

hardcoded-path-patterns.sh line 76

The first commit (8b2ca96) broadened HPP_WIN_REPO_BODY and HPP_ESCAPED_WIN_REPO_BODY to accept projects|Projects|dev|Dev, but left the pre-filter gate as 'Users|/home/|repos'. In that intermediate state, content containing only a C:\Projects\… or C:\Dev\… path would early-exit the gate as clean — the exact fail-open condition the second commit (9a33e66) fixed. This is correctly resolved on the branch tip; no fail-open exists in the final tree. The note is: since this PR squash-merges, the intermediate broken state never lands on main. That's the right merge strategy here — no action needed, just confirming it is squash-merge and not a regular merge.


SUGGESTION — Regex injection sanitization is correct but the fallback is inconsistent (confidence: MEDIUM)

Severity: SUGGESTION | Confidence: MEDIUM

block-no-verify.sh lines 82–88

IFS=',' read -ra _hm_list <<<"${CLAUDE_PLUGIN_OPTION_BLOCK_NO_VERIFY_HOOK_MANAGER_PREFIXES:-lefthook,husky,pre_commit,simple_git_hooks}"
for _hm in "${_hm_list[@]}"; do
  _hm="${_hm//[^a-zA-Z0-9_]/}"
  _hm="${_hm,,}"
  [[ -n "$_hm" ]] && HM_ALT="${HM_ALT:+$HM_ALT|}$_hm"
done
[[ -n "$HM_ALT" ]] || HM_ALT="lefthook"  # ← fallback covers one manager only

The [^a-zA-Z0-9_] strip is the correct tool for this job — after sanitization the only chars in _hm are [a-z0-9_] (post-lowercase), which carry no ERE metacharacter meaning inside (…). There is no injection path.

The residual concern is the last-resort fallback: if a consumer supplies a value composed entirely of non-alphanumeric characters (e.g., "!!!"), every entry strips to empty, HM_ALT stays empty, and the guard silently falls back to lefthook only — weaker than the :- expansion path (all four managers). This requires the consumer to have deliberately provided a nonsense config value, so practical exploitability is near-zero. However, the asymmetry is worth noting: the right fix is to either (a) reuse the same default string for the fallback, or (b) error on a value that sanitizes to empty rather than silently weakening.


What was reviewed

File Verdict
hooks/block-no-verify.sh Regex injection prevention is correct. No eval, no command substitution, no unsafe expansion of user-controlled data. One suggestion-level fallback inconsistency.
lib/path-detection/machine-path-patterns.sh Hardcoded literal additions only. No injection surface. Drive-letter anchor keeps false-positive risk low for the broader root set.
lib/path-detection/hardcoded-path-patterns.sh Pre-filter gate correctly widened in commit 2 to cover every new token. The two commits together are sound; squash merge is the right delivery vehicle.
.claude-plugin/plugin.json New block_no_verify_hook_manager_prefixes field typed string, default "". Empty-string default correctly triggers the :- expansion to the built-in set. No supply-chain changes, no new dependencies, no external references.
CHANGELOG.md Documentation only.
hooks/block-no-verify.test.sh Metacharacter sanitization test (my.*hooks,myhooks → only myhooks blocks) correctly verifies injection prevention at line 182-183.
hooks/hardcoded-path-check.test.sh New C:\Projects\… regression test ensures the pre-filter gate fix is exercised.

No command injection, no credential exposure, no privileged operations, no pull_request_target/workflow_run workflow patterns, no permission widening, no supply-chain changes, no unpinned external dependencies.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a33e665a4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/guardrails/.claude-plugin/plugin.json
@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Automated babysit-lane classification (safe tier — reply only; not resolving threads/merging).

# Finding Classification Evidence
1 chatgpt-codex-connector P1 — "Include newly supported checkout roots in the prefilter" Fixed commit 9a33e665a402 — widens scan_text's prefilter to the newly supported checkout roots, closing the security-relevant path-detection gap
2 chatgpt-codex-connector P2 — README understates the new manager-detection set (Husky/pre-commit/simple-git-hooks vs. "Lefthook only") Valid, deferred Docs-only drift; the code already detects the broader set correctly (docs undersell protection, not overstate it — no fail-open risk). Tracking in the rolling doc-drift sweep rather than blocking this PR.

Remaining blocker per prior check: none new — mergeStateStatus is BLOCKED (recomputing/pending review), not a conflict.

@kyle-sexton
kyle-sexton force-pushed the fix/918-configurable-hook-manager-and-machine-roots branch from 9a33e66 to d74e5e1 Compare July 22, 2026 03:59
@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 44s —— View job


  • Gather context and read changed files
  • Review latest commit (d74e5e1) vs prior reviewed state
  • Check whether prior findings were addressed
  • Post final review

PR Review — commit d74e5e1


Finding 0 — BLOCKER: Squash dropped the pre-filter fix for projects/dev roots (regression)

hardcoded-path-patterns.sh line 74:

if ! grep -qE 'Users|/home/|repos' <<<"$content" 2>/dev/null; then

machine-path-patterns.sh was correctly updated to match projects|Projects|dev|Dev in the detailed scan body. But the cheap pre-filter gate in hardcoded-path-patterns.sh was not updated and still only covers Users|/home/|repos. Content whose only machine path uses a Projects or Dev root (e.g. D:\Projects\acme\src\) hits the gate, finds no match, early-returns 0, and the guard reports clean — never reaching the detailed patterns that could catch it.

This is a verified fail-open: the guard reports no violation for content that should be blocked.

Intermediate commit 9a33e665 (reviewed in an earlier round as "correctly closes the fail-open gap") contained the fix to widen the gate to repos|Repos|projects|Projects|dev|Dev and added a regression test in hardcoded-path-check.test.sh. Both were dropped when the branch was squashed into d74e5e1. hardcoded-path-patterns.sh and hardcoded-path-check.test.sh are not in the PR's changed files at all.

Required fix: restore the widened gate and its regression test before merge.

# hardcoded-path-patterns.sh line 74 — restore the widened alternation:
if ! grep -qE 'Users|/home/|repos|Repos|projects|Projects|dev|Dev' <<<"$content" 2>/dev/null; then

Fix this →


Finding 1 — Minor: block_no_verify_enabled description understates guard coverage

plugin.json line 38:

"description": "Block git hook-bypass attempts (--no-verify, core.hooksPath=, lefthook disables)",

Still says only "lefthook disables". The default set now covers four managers and is configurable. Carried forward from all three prior reviews — unchanged in this squash.

Suggested:

"description": "Block git hook-bypass attempts (--no-verify, core.hooksPath=, hook-manager env-var disables [lefthook/husky/pre_commit/simple_git_hooks by default, configurable])",

Fix this →


Finding 2 — Minor: Inline comment says "extends"; behavior is "replaces"

block-no-verify.sh lines 75–76:

# The default set covers the common managers; a consumer extends it via the
# block_no_verify_hook_manager_prefixes userConfig (comma-separated, read from

The implementation and the test at line 181 ("custom set replaces the default (lefthook now allowed)") confirm replacement semantics. A consumer who reads "extends" and sets only myhooks will silently lose HUSKY=0, PRE_COMMIT=0, etc. Carried forward from all three prior reviews — unchanged in this squash.

Suggest: change "extends" → "overrides" and note that keeping defaults requires listing all four explicitly.

Fix this →


Finding 3 — Minor: All-non-alphanumeric fallback covers lefthook only

block-no-verify.sh line 88:

[[ -n "$HM_ALT" ]] || HM_ALT="lefthook"  # never leave the guard patternless

If a consumer value consists entirely of non-alphanumeric characters (e.g. "!!!"), all entries strip to empty and this fallback activates — producing a guard weaker than the :- expansion path (which returns all four managers). The fallback should restore the same four-manager set. Carried forward from prior reviews — unchanged.

Fix this →


W3 correctness — still looks good

The W3 implementation (sanitization, :- fallback, regex alternation) is correct and unchanged from prior reviews. Findings 1–3 above are doc/comment/edge-case issues, not logic bugs.

W4 status — incomplete due to dropped squash content

machine-path-patterns.sh correctly extends the detailed patterns. The pre-filter fix in hardcoded-path-patterns.sh and its regression test in hardcoded-path-check.test.sh from the prior intermediate commits are not present in the squashed commit. Finding 0 above must be resolved before merge.


Summary: One blocker (pre-filter gate not widened to match the new projects/dev roots — the squash dropped the hardcoded-path-patterns.sh fix from commit 9a33e665) plus three carry-forward minor findings. The W3 logic is clean. Needs the pre-filter fix re-applied before this is safe to merge.

@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 4m 16s —— View job


Security Review

  • Gather context and understand the PR
  • Fetch PR diff and read changed files
  • Analyze block-no-verify.sh (W3 — hook-manager bypass detection)
  • Analyze machine-path-patterns.sh (W4 — machine-path checkout roots)
  • Analyze hardcoded-path-patterns.sh pre-filter gate alignment
  • Analyze plugin.json and CHANGELOG.md for supply-chain / config risks
  • Synthesize and post findings

One IMPORTANT finding, two SUGGESTION-level findings. No CRITICAL issues.


IMPORTANT — Pre-filter gate in hardcoded-path-patterns.sh is not a superset of the newly added body tokens (confidence: HIGH)

lib/path-detection/hardcoded-path-patterns.sh line 74:

if ! grep -qE 'Users|/home/|repos' <<<"$content" 2>/dev/null; then

hardcoded-path-patterns.sh is not in the PR's changed files. The pre-filter gate still only tests for Users, /home/, and lowercase repos.

This PR updates machine-path-patterns.sh line 51 to expand HPP_WIN_REPO_BODY and HPP_ESCAPED_WIN_REPO_BODY from repos alone to the alternation repos|Repos|projects|Projects|dev|Dev. The gate invariant documented in the comment ("the alternation is a strict SUPERSET of every detailed pattern's invariant literal") is now broken for five of the six added tokens:

Body token In gate? Gate behavior on exclusive match
repos ✓ (repos) full scan runs
Repos early-returns 0 (clean)
projects early-returns 0 (clean)
Projects early-returns 0 (clean)
dev early-returns 0 (clean)
Dev early-returns 0 (clean)

Bypass scenario:

Content written: "See C:\Projects\acme\src\config.json for details"
Gate check:      grep -qE 'Users|/home/|repos' → NO MATCH
Gate result:     project_root not set OR root segment absent → return 0 (CLEAN)
HPP_WIN_REPO_BODY scan: never reached
Guard verdict:   BYPASSED — path leaks into the codebase

Any content whose only machine-specific path uses one of the five ungated root tokens silently passes the guard. The gate's early-return is the issue — the detailed HPP_WIN_REPO_BODY grep that would catch the path is never executed.

This was flagged as a P1 by Codex in commit 8b2ca96 and recorded as fixed in commit 9a33e665. The current branch HEAD (d74e5e1) is a squash that does not include hardcoded-path-patterns.sh in its diff — the fix appears to have been dropped during the squash. The five-token bypass is present in the branch as it stands.

Fix: Widen the gate to cover every token now in the body:

if ! grep -qE 'Users|/home/|repos|Repos|projects|Projects|dev|Dev' <<<"$content" 2>/dev/null; then

Fix this →


SUGGESTION — All-non-alphanumeric prefix value falls back to lefthook only, not the full default set (confidence: MEDIUM)

hooks/block-no-verify.sh line 88:

[[ -n "$HM_ALT" ]] || HM_ALT="lefthook"  # never leave the guard patternless

When a consumer-supplied value consists entirely of non-alphanumeric characters (e.g. "!!!"), every entry strips to empty and HM_ALT stays empty. The last-resort fallback then sets it to lefthook alone — weaker than both the unset/empty case (which resolves all four managers via the :- expansion on line 82) and any valid consumer value. This creates an inconsistency:

Consumer config Managers detected
Unset / empty lefthook, husky, pre_commit, simple_git_hooks ✓
"myhooks" myhooks ✓ (replace semantics, expected)
"!!!" lefthook only ✗ (weakened silently)

Fix: Replace the fallback with the same four-manager default string used in the :- expansion:

[[ -n "$HM_ALT" ]] || HM_ALT="lefthook|husky|pre_commit|simple_git_hooks"

Fix this →


SUGGESTION — Inline comment says "extends"; implementation replaces (confidence: HIGH)

hooks/block-no-verify.sh line 75:

# The default set covers the common managers; a consumer extends it via the
# block_no_verify_hook_manager_prefixes userConfig (comma-separated, ...

The test at line 180–181 confirms the replacement semantics:

run "custom set replaces the default (lefthook now allowed)" "LEFTHOOK=0 git commit -m test" 0 \
  CLAUDE_PLUGIN_OPTION_BLOCK_NO_VERIFY_HOOK_MANAGER_PREFIXES="myhooks"

A consumer who reads "extends" and configures only myhooks will silently drop HUSKY=0, PRE_COMMIT=0, and SIMPLE_GIT_HOOKS=false from detection. The word "extends" should be "overrides", with a note that preserving the defaults requires spelling out all four.

Fix this →


W3 — no new injection surfaces (block-no-verify.sh)

  • ${_hm//[^a-zA-Z0-9_]/} strips all ERE metacharacters before splicing into the alternation. The post-sanitization character set [a-z0-9_] carries no special meaning inside (…) in a bash =~ pattern. Safe.
  • :- expansion on line 82 correctly fires for both unset and empty-string cases.
  • jq -r '.tool_input.command // empty' + --arg in emit_tel use no unsafe shell expansion of user-controlled data. Safe.
  • No eval, no exec, no command substitution of user-supplied values.

W4 — no new injection surfaces (machine-path-patterns.sh)

  • All additions are hardcoded string literals inside single-quoted shell variables — no user input, no variable interpolation, no runtime expansion at define time. Safe.
  • Drive-letter anchor [A-Za-z]: limits false positives for the broader root set.

plugin.json and CHANGELOG.md

  • The new block_no_verify_hook_manager_prefixes field is typed string with a safe empty default that correctly falls through to the :- expansion. No supply-chain changes, no new external dependencies, no permission widening, no workflow changes.

Summary: The IMPORTANT finding — the pre-filter gate in hardcoded-path-patterns.sh is not updated to cover the five new body tokens — is a guard bypass for any path whose only machine-specific root is Repos, projects, Projects, dev, or Dev. This was fixed in an earlier iteration of the PR but appears to have been dropped in the squash to d74e5e1. It needs to be restored before merge. The two SUGGESTION-level items (fallback inconsistency, "extends" comment) are straightforward fixes with no exploitable path but meaningful consumer-safety impact.

…ath roots

Two-lane posture fixes (audit #912).

W3 - block-no-verify hook-manager env-var bypass detection matched only
`lefthook*`, silently missing HUSKY=0 and other managers. Resolve a configurable
prefix set (block_no_verify_hook_manager_prefixes userConfig; default lefthook,
husky, pre_commit, simple_git_hooks). Consumer values are reduced to identifier
chars so no regex metacharacter can be injected into the alternation.

W4 - hardcoded-path-check's drive-letter-anchored checkout-parent pattern
matched only `X:\repos\...`, missing `C:\Projects\...` (this very repo) and
`C:\Dev\...`. Broaden to also match Projects and Dev (both capitalizations); a
consumer's own checkout root stays caught by the driver's project-root scan.

guardrails 0.9.5 -> 0.9.6 + CHANGELOG. Tests: block-no-verify 83/0,
hardcoded-path-check 38/0, shellcheck clean.

Refs #918

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kyle-sexton
kyle-sexton force-pushed the fix/918-configurable-hook-manager-and-machine-roots branch from d74e5e1 to a318b5a Compare July 22, 2026 04:01
@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 53s —— View job


PR Review

  • Gather context and understand the PR
  • Read changed files at HEAD (a318b5a)
  • Check whether prior findings (1–3) were addressed
  • Review README.md changes (Codex P2 finding)
  • Verify pre-filter gate state in hardcoded-path-patterns.sh
  • Post final review

Pre-filter fix DROPPED from squash — W4 is broken at HEAD

This is the most important finding. Intermediate commit 9a33e665 added projects|Projects|dev|Dev to the cheap pre-filter gate in hpp::scan_text (hardcoded-path-patterns.sh:74), closing the fail-open gap that Codex P1 flagged. That commit was confirmed correct in prior reviews and has a passing regression test. But hardcoded-path-patterns.sh is absent from the current PR's changed-files list, and the gate at HEAD still reads:

hardcoded-path-patterns.sh:74:

if ! grep -qE 'Users|/home/|repos' <<<"$content" 2>/dev/null; then

machine-path-patterns.sh:51 now includes projects|Projects|dev|Dev in HPP_WIN_REPO_BODY/HPP_ESCAPED_WIN_REPO_BODY, but the gate (the comment at line 62 explicitly calls itself a strict superset of every detailed pattern's invariant literal) no longer covers those roots. The invariant comment is now stale and false:

#   repos   ⊇ Windows-repo + escaped-Windows-repo   ← no longer true after W4

Effect: Content whose only hardcoded path is C:\Projects\acme\src\file.ts or C:\Dev\acme\ (no repos, no Users, no /home/) passes the pre-filter and returns 0 — the guard silently misses it, exactly as Codex P1 described. The squash dropped the fix.

Required fix:

if ! grep -qE 'Users|/home/|repos|Repos|projects|Projects|dev|Dev' <<<"$content" 2>/dev/null; then

and update the comment at lines 58–63 to list the new roots. The corresponding regression test in hardcoded-path-check.test.sh (exercising D:\Projects\acme\src) was also dropped from the squash and should be restored.

Fix this →


Finding 1 (Minor) — block_no_verify_enabled description still understates the guard

plugin.json:38:

"description": "Block git hook-bypass attempts (--no-verify, core.hooksPath=, lefthook disables)",

This description carried over from prior reviews unchanged. The default manager set is now four managers and the set is configurable, but the toggle's one-liner still says only "lefthook disables". Suggest:

"description": "Block git hook-bypass attempts (--no-verify, core.hooksPath=, hook-manager env-var disables [lefthook/husky/pre_commit/simple_git_hooks by default, configurable])",

Fix this →


Finding 2 (Minor) — Comment says "extends"; behavior is "replaces"

block-no-verify.sh:75–76:

# The default set covers the common managers; a consumer extends it via the
# block_no_verify_hook_manager_prefixes userConfig (comma-separated, read from

The test at line 180 ("custom set replaces the default (lefthook now allowed)") confirms the implementation replaces the default set entirely. A consumer reading "extends" who sets only myhooks will silently lose HUSKY=0, PRE_COMMIT=0, etc. protection. The comment carried forward from prior reviews unchanged. Suggest changing "extends" to "overrides" and noting that keeping the defaults requires listing all four explicitly.

Fix this →


Finding 3 (Minor) — All-non-alphanumeric fallback restores lefthook only

block-no-verify.sh:88:

[[ -n "$HM_ALT" ]] || HM_ALT="lefthook" # never leave the guard patternless

When every entry in a consumer-supplied value strips to empty (e.g. "!!!"), this fallback restores only lefthook — weaker than the unset/empty path (all four managers via the :- expansion). Practical risk is near-zero, but the inconsistency is surprising. Suggest restoring the full four-manager default:

[[ -n "$HM_ALT" ]] || HM_ALT="lefthook|husky|pre_commit|simple_git_hooks"

Fix this →


What's correct at HEAD

  • Codex P2 (README) — Fixed. README.md now correctly describes the four-manager default set in the guards table (line 13), the "Hook-manager coverage" scope note (lines 27–34), and the Consumer seams section (lines 129–134). Closes the documentation gap.
  • W3 core logicblock-no-verify.sh hook-manager detection now covers husky, pre_commit, simple_git_hooks in addition to lefthook. Regex injection sanitization ([^a-zA-Z0-9_] strip before splicing into ERE alternation) is correct. The :- fallback correctly fires for both unset and empty-string cases. Tests at lines 171–175 exercise the new managers and the configurable path. All correct.
  • W4 machine-path-patterns.shHPP_WIN_REPO_BODY and HPP_ESCAPED_WIN_REPO_BODY both updated in parallel to accept repos|Repos|projects|Projects|dev|Dev. The drive-letter anchor keeps false-positive risk low. Explicit casing spellings (avoiding [Rr]epos) are intentional. Correct.
  • block_no_verify_hook_manager_prefixes config entry (plugin.json:104) — Correctly typed string, empty default triggers the :- expansion, description accurately describes replace-not-extend semantics.

Summary: The pre-filter gate drop is the blocking issue — it re-opens the Codex P1 fail-open that was confirmed fixed in the prior review cycle. Findings 1–3 are minor but have been open across multiple review rounds; straightforward to address. The README update and W3 core logic are solid.

@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 2s —— View job


Security Review

  • Gather context and understand the PR
  • Fetch PR diff (HEAD a318b5a)
  • Read all changed files
  • Analyze block-no-verify.sh for injection surfaces and logic flaws
  • Analyze machine-path-patterns.sh and pre-filter gate alignment
  • Analyze plugin.json, README.md, CHANGELOG.md for supply-chain / config risks
  • Synthesize and post findings

One IMPORTANT finding, two SUGGESTION-level findings. No CRITICAL issues.


IMPORTANT — Pre-filter gate in hardcoded-path-patterns.sh is not a superset of the five newly accepted body tokens (confidence: HIGH)

lib/path-detection/hardcoded-path-patterns.sh line 74this file is not in the PR diff.

The pre-filter gate still reads:

if ! grep -qE 'Users|/home/|repos' <<<"$content" 2>/dev/null; then

This PR updates machine-path-patterns.sh line 51 to expand HPP_WIN_REPO_BODY and HPP_ESCAPED_WIN_REPO_BODY from repos alone to the alternation repos|Repos|projects|Projects|dev|Dev. The gate's own comment documents a correctness invariant ("the alternation is a strict SUPERSET of every detailed pattern's invariant literal") that is now broken for five of the six accepted tokens:

Body token In gate? Gate result on exclusive match
repos ✓ (repos) full scan runs
Repos early-returns 0 (clean)
projects early-returns 0 (clean)
Projects early-returns 0 (clean)
dev early-returns 0 (clean)
Dev early-returns 0 (clean)

Bypass scenario:

Content: "See C:\Projects\acme\src\config.json for details"
Gate check: grep -qE 'Users|/home/|repos' → NO MATCH (case-sensitive; "Projects" ≠ "repos")
Gate result: early-return 0 (CLEAN)
HPP_WIN_REPO_BODY scan: never reached
Guard verdict: BYPASSED — hardcoded Windows path with a Projects/Dev/Repos root leaks through

This was introduced by the W4 body expansion in this PR without a corresponding gate update. The gate was fixed in an earlier iteration of this branch (commit 9a33e665) and a regression test was added to hardcoded-path-check.test.sh — both were dropped when the branch was squashed to d74e5e1 and remain absent in the current HEAD a318b5a. hardcoded-path-patterns.sh and its test file are not in the PR's changed-files list at all.

Required fix — widen the gate to cover every token now accepted by the body patterns:

# hardcoded-path-patterns.sh line 74:
if ! grep -qE 'Users|/home/|repos|Repos|projects|Projects|dev|Dev' <<<"$content" 2>/dev/null; then

Also update the comment at line 62 (repos ⊇ Windows-repo + escaped-Windows-repo) to reflect the full set, and restore the C:\Projects\… regression test in hardcoded-path-check.test.sh.

Fix this →


SUGGESTION — All-non-alphanumeric prefix value falls back to lefthook only, not the full default set (confidence: MEDIUM)

hooks/block-no-verify.sh line 88:

[[ -n "$HM_ALT" ]] || HM_ALT="lefthook"  # never leave the guard patternless

If a consumer-supplied block_no_verify_hook_manager_prefixes value consists entirely of non-alphanumeric characters (e.g. "!!!"), every entry strips to empty and HM_ALT stays empty. The last-resort fallback then sets it to lefthook alone — weaker than the unset/empty path (which resolves all four managers via the :- expansion on line 82). The asymmetry:

Consumer config Managers active
Unset / "" lefthook, husky, pre_commit, simple_git_hooks ✓
"myhooks" myhooks ✓ (replace semantics, expected)
"!!!" (all stripped) lefthook only ✗

Practical exploitability is near-zero (requires a deliberately nonsense config value), but the fix is a one-liner: use the same four-manager string the :- expansion uses.

[[ -n "$HM_ALT" ]] || HM_ALT="lefthook|husky|pre_commit|simple_git_hooks"

Fix this →


SUGGESTION — Inline comment says "extends"; implementation replaces (confidence: HIGH)

hooks/block-no-verify.sh lines 75–76:

# The default set covers the common managers; a consumer extends it via the
# block_no_verify_hook_manager_prefixes userConfig (comma-separated, ...

A non-empty consumer value replaces the entire default set — it does not extend it. The test at line 181 ("custom set replaces the default (lefthook now allowed)") confirms this. A consumer who reads "extends" and sets only myhooks expecting to ADD it alongside the defaults will silently drop HUSKY=0, PRE_COMMIT=0, and SIMPLE_GIT_HOOKS=false from detection.

Suggest changing "extends" → "overrides" and adding a note that preserving the defaults requires spelling out all four explicitly.

Fix this →


What was reviewed

File Verdict
hooks/block-no-verify.sh ${_hm//[^a-zA-Z0-9_]/} strips all ERE metacharacters before splicing into =~; post-lowercase chars are only [a-z0-9_|] where | is inserted by the loop itself, not user input. No eval, no command substitution of user-controlled data, no injection path. One suggestion-level fallback inconsistency (Finding 2).
lib/path-detection/machine-path-patterns.sh Hardcoded string literal additions only. No user input, no variable interpolation at define time. Drive-letter anchor [A-Za-z]: keeps false-positive risk low for the broader root set. Safe in isolation — but the gate in the calling driver is now under-inclusive (Finding 1).
hardcoded-path-patterns.sh Not in the PR diff. Gate at line 74 is a strict superset of the old body tokens but not the new ones — this is the IMPORTANT bypass above.
.claude-plugin/plugin.json New block_no_verify_hook_manager_prefixes field typed string with empty default that correctly falls through to the :- expansion. No supply-chain changes, no new external references, no permission widening.
CHANGELOG.md / README.md Documentation only. No security surface.
hooks/block-no-verify.test.sh Metacharacter sanitization test at line 183 (my.*hooks,myhooks → only myhooks blocks) correctly verifies injection prevention. Replace-not-extend test at line 181 accurately documents and tests the behavior.

No command injection, no credential exposure, no privileged operations, no pull_request_target/workflow_run script injection patterns, no permission widening, no supply-chain changes, no loosened/unpinned dependencies.

@kyle-sexton
kyle-sexton merged commit c52e30a into main Jul 22, 2026
25 checks passed
@kyle-sexton
kyle-sexton deleted the fix/918-configurable-hook-manager-and-machine-roots branch July 22, 2026 04:09
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
main released guardrails 0.9.7 (#932 block-no-verify hook-manager
prefixes + machine-path broadening) while this branch also carried
0.9.7 for the #740 config-env work; branch entry restacked to 0.9.8,
both changelog sections preserved. No file overlap with the #740
hooks; block-no-verify suite green post-merge (83/0). All sync,
drift, parity, and check-bump gates green vs current origin/main.
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
…8) (#946)

## Summary

The `hardcoded-path-check` guard's cheap `scan_text` pre-filter had
narrowed on `main`: it gated only on `Users|/home/|repos`, while the
detailed drive-letter bodies (`HPP_WIN_REPO_BODY` /
`HPP_ESCAPED_WIN_REPO_BODY`) accept the broadened
`repos|Repos|projects|Projects|dev|Dev` roots shipped in 0.9.7. Content
whose only machine path used a widened root (e.g. `C:\Projects\…`,
`C:\Dev\…`) therefore early-returned `0` before the detailed scan ever
ran — a fail-open in a security gate. This re-widens the pre-filter gate
to a strict superset of every root token the detailed bodies accept,
adds a `Projects`-root regression test, folds in the stale
`block_no_verify_enabled` description sync (it still read "lefthook
disables" only, now the full configurable default set), and bumps the
plugin to `0.9.8`.

All 40 `hardcoded-path-check` tests pass; shellcheck clean.

Closes #944

## Related

- #932 — the merged PR whose landed fail-open fix (`9a33e665`) was
dropped by a concurrent force-push, landing this regression on `main`;
this PR restores it fix-forward.
- #918 — the original broadened-machine-roots issue #932 addressed.
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
…ue grammar (#950)

## Summary

Two independent posture fixes from umbrella #912, batched under one
source-control bump (`0.16.2 → 0.16.3`).

**W1 — dependency-manager hold-merge login set is configurable.** The
babysit
merge gate held only the built-in `dependabot`/`renovate` product bots
(`DEPENDENCY_MANAGER_LOGINS`), so a non-dependabot/renovate dependency
bot an
operator runs slipped the cross-tier hold. `is_dependency_author` now
also
matches any login in the new `babysit_extra_dependency_manager_logins`
userConfig, threaded as the `--extra-dependency-manager-logins`
merge-wrapper
flag (mirroring the existing `--approver-bot-logins` arg-threading
through
`evaluate()`). Logins normalize identically on both sides (casefold,
strip
`app/` and `[bot]`). Ships empty → unconfigured installs match the
built-in
set alone. Wired only to the merge gate (the single
`is_dependency_author`
call site), not the snapshot.

**W2 — branch-to-issue grammar is configurable.**
`parse-branch-issue.sh`
hardcoded the `<type>/<N>-<slug>` (and `routine-issue-<N>`) convention,
so a
repo on a different scheme (e.g. Jira keys `feature/PROJ-123-slug`)
silently
failed to derive a `Closes #N` line. The script now accepts an ERE
`pattern`
positional (last capture group = issue id), passed from the new
`branch_issue_pattern` userConfig at the `/pull-request create` call
site; the
built-in convention stays the default when unset (an unsubstituted
`${user_config…}` placeholder is treated as absent). Per the
plugins-reference,
`CLAUDE_PLUGIN_OPTION_*` reaches hook processes only — not skill-invoked
scripts — so the value is passed as an arg rather than read from the
environment.

## Testing

- `is_dependency_author` extra-login normalization cases (casefold,
`app/`,
  `[bot]`; empty extra never widens the built-in set).
- An `evaluate()`-level integration test that flips the dependency hold
via the
config on the same PR — a pure-function test would pass even with broken
wiring, so this exercises the CLI-arg-shaped frozenset → `evaluate()`
param →
  line-715 hold path.
- Full babysit Python suite: 348 passed.
- `parse-branch-issue.test.sh`: 10 passed (incl. Jira-key custom pattern
and
  the unsubstituted-placeholder fallback). `shellcheck` clean.

## Docs

`plugin.json` userConfig (both keys), babysit `SKILL.md` config table,
`reference/feedback.md`, source-control README config table, and the
`create.md` call site.

## Related

- Part of umbrella #912 (contract + design there — not diverged).
- Sibling merged this session: #928 (guardrails 0.9.6), #932 (guardrails
0.9.7).

Closes #917

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
kyle-sexton added a commit to melodic-software/standards that referenced this pull request Jul 22, 2026
…#250)

Backfills the broadened Windows checkout-root alternation
(`repos|Repos|projects|Projects|dev|Dev`) in `HPP_WIN_REPO_BODY` /
`HPP_ESCAPED_WIN_REPO_BODY` that
melodic-software/claude-code-plugins#932 shipped directly in its managed
materialization, plus contract tests pinning the new roots.

Without this, `standards-sync` keeps proposing a revert of the reviewed
downstream behavior: the current sync PR
(melodic-software/claude-code-plugins#951) stomps the broadened patterns
and fails the downstream guardrails contract tests
(`hardcoded-path-check.test.sh`: "windows Projects checkout root"
cases). After this merges, the push-triggered sync refreshes that PR to
the runner-policy lockfile change only, unblocking Dependabot alert
remediation there.

The component file is byte-identical to the downstream reviewed copy
(blob `785d1809`).

## Related

- melodic-software/claude-code-plugins#932 (origin of the broadened
patterns, reviewed and merged downstream)
- melodic-software/claude-code-plugins#951 (sync PR currently blocked by
the revert)

No linked issue.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 23, 2026
…iling separator (#1095)

## Summary

The five machine-path bodies in `machine-path-patterns.sh` required a
separator AFTER the child segment, which inverted detection both ways
(#1093, reproduced live):

- A real bare path value at end of line (`root = <drive>:/Dev/GitHub`)
has no trailing separator and was **missed** — a false negative on
exactly the config-value shape the guard exists to catch.
- Prose satisfied the requirement anyway: the space-permitting segment
class greedily consumed words until a later slash on the same line, so a
comment like `<drive>:/Projects/x - personal repos (reference/reading
only here)` was flagged as "Windows repo path detected" while the actual
violations passed clean.

Fix: all five bodies now exclude whitespace and the double quote from
the child-segment class and drop the mandatory trailing separator. Bare
values at a natural boundary (EOL, whitespace, quote) are detected;
prose spans cannot match (the class requires at least one non-space
child character after the root); a bare ROOT with no child segment
(`C:/Dev`, a lone `/home`) still never matches. The driver's
`/Users/Shared` exclusion now covers the bare-at-EOL form.

## Synced-component constraint

`machine-path-patterns.sh` is a managed standards component
(`melodic-software/standards` `components/path-detection/`, distributed
by the sync bot). The identical pattern change is being filed upstream
so the next sync does not revert this fix — upstream PR linked below
once open. Local and upstream copies must stay byte-identical.

## Verification

- `hardcoded-path-check.test.sh` **59/0** (15 new regression cases: bare
values in all five body shapes incl. JSON-escaped, greedy-prose
negative, root-plus-whitespace negative, bare `Shared`)
- shellcheck clean (the `SC1003` disable became unnecessary — no
trailing backslash remains in the escaped body)
- guardrails `0.12.1` → `0.12.2` + CHANGELOG

## Related

- Producer finding: handoff-inbox item
`20260723-014618-guardrails-hpp-win-repo-pattern-gaps` (Finding 1)
- Prior art: #918 / #932 (root broadening), standards#250 (upstream
mirror precedent)
- Sibling finding: #1094 (non-repo project dir exemption), separate PR

Closes #1093

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 (200k context) <noreply@anthropic.com>
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.

fix(guardrails): configurable hook-manager bypass detection + machine-path roots

1 participant