Skip to content

feat(guardrails): default-off the two behavioral injectors and narrow the -m gate to real newlines - #2058

Merged
kyle-sexton merged 11 commits into
mainfrom
fix/guardrails-hook-remediation
Aug 9, 2026
Merged

feat(guardrails): default-off the two behavioral injectors and narrow the -m gate to real newlines#2058
kyle-sexton merged 11 commits into
mainfrom
fix/guardrails-hook-remediation

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

No linked issue

Summary

Implements the guardrails slice of #2021's remediation plan (proposed-work lines 1 and 2), plus the
two cosmetic dangling items the same classification flagged: the two clean behavioral-class context
injectors are config-disabled (default off, scripts kept, opt back in via userConfig),
block-noncanonical-commit is narrowed to the actual-newline -m hazard, the two identical
Bash|PowerShell PreToolUse matcher groups are merged into one, and three stale "Triggered on Bash"
headers now say Bash and PowerShell. guardrails 0.19.3 → 0.20.0.

Fix

1. Config-off the two behavioral injectors (flag-commit-pr-skill-bypass,
workflow-resilience-check).
Per docs/PLUGIN-PHILOSOPHY.md "Instruction economy", ablation is
config-disable first where a kill switch exists — no deletion. Mechanism: the plugin.json
userConfig defaults flip to "default": false, and each script replaces hook::check_enabled
(whose unset-env fallback is true) with an explicit opt-in test
([[ "${CLAUDE_PLUGIN_OPTION_<NAME>_ENABLED:-false}" == "true" ]] || exit 0) — the same shape
session-flow's default-off observer-arm uses — so the OFF default holds even where the harness
does not materialize userConfig defaults into the hook environment. Both hooks stay wired; both
test suites now export the switch ON for behavior cases and pin the unset-switch no-op.

2. Narrow block-noncanonical-commit to actual-newline -m. The guard denied every
git commit that was not the -F - stdin form, single-line -m "fix: typo" included. It now
blocks only when a -m/--message value really contains a newline (separated, attached,
--message=, and separated-cluster -am spellings), which is the cross-shell mangling hazard;
single-line -m, bare git commit, and repeated single-line -m paragraphs pass. Exemptions and
the fail-closed structural refusals are unchanged. On PowerShell, a here-string -m value still
blocks (blanked to a placeholder — uninspectable, multi-line by construction of the form) while a
single-line literal -m passes. The test suite is respelled in both directions: every
alias/wrapper/traversal fixture that asserted a block now carries a real-newline payload, and new
cases pin the allowed forms. Documented fail-open residual: a message attached to a short-option
cluster (-am"multi<NL>line").

3. hooks.json wiring cleanup. The two structurally separate PreToolUse groups with the
byte-identical Bash|PowerShell matcher are merged into one six-hook group — behavior identical
per the current hooks reference (all matching hooks run in parallel; same-matcher groups fire
independently).

4. Stale headers. block-hook-bypass.sh, block-noncanonical-commit.sh, and
flag-commit-pr-skill-bypass.sh said "Triggered on Bash tool calls" while wired Bash|PowerShell.

Fresh-docs mandate (contract surfaces touched: hooks/hooks.json, plugin.json): verified this
session against https://code.claude.com/docs/en/hooks (hook entry schema, timeout in seconds,
statusMessage, parallel execution within a group, independent firing of same-matcher groups,
PreToolUse exit-code semantics) and https://code.claude.com/docs/en/plugins-reference (userConfig
option fields — type/title/description/default; default is the "value used when the user
provides nothing"; options are exported to hook processes as CLAUDE_PLUGIN_OPTION_<KEY>), both
fetched 2026-08-08 via the docs/OFFICIAL-DOCS.md index.

Verification

  • plugins/guardrails/hooks/block-noncanonical-commit.test.sh — 181 passed, 0 failed
  • plugins/guardrails/hooks/flag-commit-pr-skill-bypass.test.sh — 29 passed, 0 failed
  • plugins/guardrails/hooks/workflow-resilience-check.test.sh — 16 passed, 0 failed
  • plugins/guardrails/hooks/block-hook-bypass.test.sh (touched header only) — 240 passed, 0 failed
  • shellcheck -x clean on all seven touched shell files; bash -n clean
  • Repo gates run locally: check-silent-skips (no silent prerequisite skips),
    check-hook-userconfig-argv, check-changelog-parity --check/--check-bump origin/main/--check-order,
    check-shell-portability --paths <touched files>, check-manifest-duplicate-keys,
    check-contract-clause-coverage — all pass; markdownlint-cli2 clean on the three touched
    markdown files
  • node scripts/generate-catalog.mjs (docs/CATALOG.md updated, committed) and
    node scripts/generate-cheatsheet.mjs (no diff)

Related

🤖 Generated with Claude Code

… the -m gate to real newlines

Guardrails slice of #2021's remediation plan (proposed-work lines 1-2)
plus the two cosmetic dangling items. Version 0.19.3 -> 0.20.0.

- flag-commit-pr-skill-bypass and workflow-resilience-check default OFF:
  behavioral-class prose injectors, config-disabled per the
  instruction-economy evidence gate (scripts kept; opt back in via the
  existing userConfig switch). plugin.json defaults flip to false and
  each script uses an explicit ${VAR:-false} opt-in test, since
  hook::check_enabled's unset fallback is "true".
- block-noncanonical-commit narrowed: only a -m/--message value that
  actually contains a newline blocks (the cross-shell mangling hazard).
  Single-line -m, bare git commit, and repeated single-line -m pass;
  exemptions and fail-closed refusals unchanged; PowerShell here-string
  -m still fails closed (uninspectable, multi-line by form). Test suite
  respelled in both directions.
- hooks.json: merged the two identical Bash|PowerShell PreToolUse
  groups into one (behavior identical per the current hooks reference).
- Fixed three stale "Triggered on Bash" headers (wired Bash|PowerShell).

Tests: block-noncanonical-commit 181/0, block-hook-bypass 240/0,
flag-commit-pr-skill-bypass 29/0, workflow-resilience-check 16/0;
shellcheck clean; repo gates (silent-skips, changelog parity, shell
portability, manifest keys, markdownlint) pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

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


Security review complete

  • Gather context (PR diff, changed files)
  • Review plugin.json userConfig changes
  • Review block-noncanonical-commit.sh newline-gate narrowing
  • Review flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gating
  • Review hooks.json matcher merge
  • Post final security review

No security issues found. This PR touches only plugins/guardrails (no .github/workflows files are in the diff, so no Actions trust-boundary surface applies). Summary of what was reviewed for logic/trust-boundary risk:

  • block-noncanonical-commit.sh narrowing (the one change to a blocking guard): walked the new -m/--message/-m*/--message=*/-[!-]*m case arms against git's own short-option-cluster attachment rules (a value-taking option anywhere in a cluster consumes the cluster's remainder as its attached value, so -ma is handled correctly by the -m* arm, not just the -am-shaped -[!-]*m arm). Found no case where a real multi-line -m payload slips through undetected in a way the PR doesn't already document — the one fail-open residual (-am"multi<NL>line" attached-to-cluster) is explicitly disclosed in the script header and changelog, and the guard's own docs already scope it as "a friction guard against the accidental anti-pattern, not a sandbox." The -- early-break (line 759 of the updated file) does not create a bypass: git itself stops option parsing at --, so a message-shaped token after it is a pathspec to git too.
  • Opt-in gating for flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh: both are advisory-only (exit 0 always — they were never a PreToolUse block gate), so flipping their default to off is a reduction in nudging, not a reduction in enforcement. The new [[ "${CLAUDE_PLUGIN_OPTION_..._ENABLED:-false}" == "true" ]] || exit 0 line is placed before any input parsing in both scripts, correctly fails closed on unset/false, and doesn't skip any check that used to run first.
  • hooks.json matcher merge: both pre-merge groups already matched Bash|PowerShell; per the hooks docs, same-matcher groups fire independently and all hooks in a group run in parallel, so the six-hook merged group is execution-identical to the prior four-plus-two split — no scope widening or narrowing.
  • plugin.json: the one blocking guard's kill switch (block_noncanonical_commit_enabled) stays default true; only the two non-blocking advisories flip to false. Descriptions accurately reflect the new behavior.

No injection surfaces, credential/token handling, or authorization logic were touched by this diff.

@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: 157ae63704

ℹ️ 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/hooks/block-noncanonical-commit.sh Outdated
…ne tracker-ref rule

"issue #2021" in a code comment matches the org comment-hygiene
issue-reference pattern; the bare "#2021" spelling (already used
elsewhere in these hooks) does not. Comment-only change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kyle-sexton kyle-sexton closed this Aug 8, 2026
@kyle-sexton kyle-sexton reopened this Aug 8, 2026
kyle-sexton and others added 2 commits August 8, 2026 19:52
… events

The synchronize event for 3996770 and the reopen event both fired only
the pull_request_target workflows; ci/pr-title never started. No file
changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings in 0.19.4 (block-dangerous-git lease-message fix, #2047); the
0.20.0 entry now stacks above 0.19.4 and the version bump reads
0.19.4 -> 0.20.0. Content changes from this branch are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 56s —— View job


Security review complete

  • Gather context (PR diff, changed files)
  • Review plugin.json userConfig changes
  • Review block-noncanonical-commit.sh newline-gate narrowing
  • Review flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gating
  • Review hooks.json matcher merge
  • Post final security review

Reviewed only the files this PR touches (gh pr diff / git diff origin/main...HEAD), scoped to logic/trust-boundary reasoning — no re-litigating what zizmor already covers (unpinned actions, dangerous triggers, permissions, template injection). This diff touches no .github/workflows files, so that lane doesn't apply here anyway.

One finding, already surfaced by Codex — independently confirmed, and it's the only thing worth flagging:

[SUGGESTION, confidence: high] block-noncanonical-commit.sh--message long-option abbreviations bypass the newline guard
plugins/guardrails/hooks/block-noncanonical-commit.sh#L784-L791

The message-detection arms only match the fully-spelled -m, --message, --message=*, and the attached/clustered spellings — there's no prefix-matching helper anywhere in this file (confirmed: no is_prefix_of/longopt helper exists), so Git's own accepted unambiguous abbreviations of --message (e.g. --mess=, --messag=) fall through to the catch-all *) arm at line 805 and never set msg_newline. Codex's review already reproduced this against Git 2.43 (git commit --mess="subject<newline>body" commits the multi-line body while the hook exits 0), which matches what a static read of the case arms shows — same root cause as the already-documented -am"multi<NL>line" attached-cluster residual, just via a different arm.

I'd keep this at SUGGESTION rather than higher severity: the script's own header is explicit that this is "a friction guard against the accidental anti-pattern, not a sandbox" — there's no trust boundary or attacker-controlled input here, just a self-inflicted shell-mangling foot-gun the guard is trying to nudge a legitimate caller away from. A caller who deliberately reaches for an abbreviated long option to route around a friction guard has bypassed the spirit of the check, not a security control. Worth closing the gap for completeness (and to keep the guard's own "no known bypass beyond the documented residual" posture accurate), but it doesn't need to block merge.

Fix this →

Everything else reviewed clean:

  • plugin.json — the one blocking guard's kill switch (block_noncanonical_commit_enabled) stays default true; only the two always-exit 0 advisory hooks flip to default false. Descriptions accurately describe the new behavior.
  • flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gatinghook::check_enabled (unset-fallback true) is correctly replaced by an explicit [[ "${CLAUDE_PLUGIN_OPTION_..._ENABLED:-false}" == "true" ]] || exit 0, placed before any input parsing in both scripts, so it fails closed on unset/false and doesn't skip a check that used to run first. Both hooks were already advisory-only (never exit non-zero to block), so this is a reduction in nudging, not enforcement.
  • hooks.json matcher merge — both pre-merge groups already matched Bash|PowerShell; per the hooks docs, same-matcher groups fire independently and hooks within a group run in parallel, so the merged six-hook group is execution-identical to the prior split — no scope widening or narrowing, no ordering dependency introduced between the merged hooks.
  • No injection surfaces, credential/token handling, or authorization logic touched elsewhere in the diff. The -- early-break in the guard's segment scan doesn't create a bypass either: Git itself stops option parsing at --, so anything after it is a pathspec to Git too, not a message source.

…eading

The merge resolution glued the 0.20.0 section's last bullet to the
## [0.19.4] heading (MD032/MD022).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 47s —— View job


Security review complete

  • Gather context (gh pr diff origin/main...HEAD, changed files)
  • Review block-noncanonical-commit.sh newline-gate narrowing
  • Review flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gating
  • Review plugin.json userConfig changes
  • Review hooks.json matcher merge
  • Post final security review

Reviewed the full diff between origin/main and this branch at HEAD 1f69d1b. The two commits since the prior review on this PR (3df98fc merge-from-main, 1f69d1b changelog whitespace fix) touch only CHANGELOG.md and unrelated autonomy-plugin content — none of the hook logic changed, so the finding below (already surfaced by Codex and a prior automated pass on this PR) is still live in the current HEAD.

[SUGGESTION, confidence: high] block-noncanonical-commit.sh — unambiguous --message abbreviations bypass the newline guard
block-noncanonical-commit.sh#L784-L807

The message-detection arms match only -m | --message, --message=*, -m* (attached short form), and -[!-]*m (cluster ending in m) — there is no prefix-matching for long options anywhere in the file. Git itself resolves any unambiguous abbreviation of a long option (e.g. --mess=, --messag=, --messa "..."), so git commit --mess="subject$'\n'body" creates a real multi-line commit while this hook's *) catch-all silently leaves msg_newline unset and the guard exits 0. This is the same root cause Codex's review reproduced against Git 2.43 on the identical commit. It is a distinct gap from the one residual the script header and CHANGELOG do disclose (-am"multi<NL>line" attached-to-cluster) — the abbreviation path isn't mentioned in either the header comment (block-noncanonical-commit.sh#L67-L71) or the 0.20.0 changelog entry, so the guard's "no bypass beyond the documented residual" framing is currently inaccurate.

Kept at SUGGESTION, not higher: the header is explicit this is "a friction guard against the accidental anti-pattern, not a sandbox" (block-noncanonical-commit.sh#L72) — there's no trust boundary or attacker-controlled input, just a self-inflicted shell-mangling foot-gun. Worth closing before merge for completeness (and so the "documented residual" claim stays accurate), but not a blocker.

Fix this →

Everything else reviewed clean:

  • plugin.json — the one blocking guard's kill switch (block_noncanonical_commit_enabled) stays default true; only the two always-exit 0 advisory hooks (flag_commit_pr_skill_bypass_enabled, workflow_resilience_check_enabled) flip to default false. Version and descriptions match the code.
  • flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gatinghook::check_enabled (unset-fallback true) is correctly replaced by [[ "${CLAUDE_PLUGIN_OPTION_..._ENABLED:-false}" == "true" ]] || exit 0, placed before any input parsing in both scripts, so it fails closed on unset/false and doesn't skip a check that used to run earlier. Both hooks are advisory-only (never exit non-zero), so this is a reduction in nudging, not enforcement — no security regression.
  • hooks.json matcher merge — both pre-merge groups already matched Bash|PowerShell; per the hooks reference, same-matcher groups fire independently and hooks within a group run in parallel, so the merged six-hook group is execution-identical to the prior split. No scope widening/narrowing, no new ordering dependency.
  • No injection surfaces, credential/token handling, or authorization logic touched elsewhere in the diff. This PR contains no .github/workflows changes, so the Actions trust-boundary lane doesn't apply.

@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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


Code review complete

  • Gather context (gh pr diff, changed files)
  • Review plugin.json userConfig changes for correctness/maintainability
  • Review block-noncanonical-commit.sh newline-gate narrowing (correctness, test coverage)
  • Review flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gating
  • Review hooks.json matcher merge
  • Review test suites and docs (CHANGELOG, README, CATALOG)
  • Post inline findings and final summary

Scoped to the 12 files this PR touches (per git diff origin/main...HEAD --stat), focused on correctness/architecture/test-coverage/maintainability per this lane's split — leaving the block-noncanonical-commit.sh bypass surface (the --message abbreviation gap already raised by Codex and independently confirmed in the security-review comments above) to the security lane, since that's the class of finding this repo's review split routes there.

One finding posted inline (suggestion-level, non-blocking):

  • block-noncanonical-commit.test.sh#L48 — the suite pins the attached --message=<msg> spelling but not the separated --message <msg> form. Same code arm as the already-tested -m <msg> cases, so risk is low, but the PR explicitly respelled every other variant "in both directions."

What I verified by reading the code (not re-stating the author's own test-run claims, which I did not independently execute — my Bash tool required interactive approval I don't have in this environment, so I could not run the test suites myself):

  • check_segment()'s case-arm ordering (block-noncanonical-commit.sh:758-810): confirmed the new -m | --message, --message=*, -m*, and -[!-]*m arms are ordered correctly relative to the pre-existing -F/--amend/-C/-c exemption arms — e.g. -c*/-C* (exempt) is matched before -[!-]*m could misfire on a hypothetical -cm-shaped cluster, and the ((stdin_form || exempt)) && return 0 short-circuit at line 813 runs before the msg_newline check, so --amend -m "<newline>" correctly returns allowed via the exemption path (pinned by the new "amend -m multi-line (allowed)" test).
  • hooks.json matcher merge: both pre-merge groups carried the byte-identical Bash|PowerShell matcher, and block-hook-bypass.sh (whose header this PR also fixes) was already wired into that same group pre-merge — the "Triggered on Bash and PowerShell" header correction is accurate to the existing (not newly introduced) wiring.
  • Opt-in gating shape: flag-commit-pr-skill-bypass.sh and workflow-resilience-check.sh both place [[ "${CLAUDE_PLUGIN_OPTION_..._ENABLED:-false}" == "true" ]] || exit 0 before any stdin/jq work, matching the pattern already used by session-flow's observer-arm (CLAUDE_PLUGIN_OPTION_OBSERVER_ENABLED:-false) — no bespoke helper needed, consistent with existing convention rather than a maintainability regression.
  • Test coverage for the opt-in flip: both .test.sh files export the switch =true for their existing behavior cases and add a dedicated "unset switch → no-op" case — correctly distinguishes "explicitly off" (already tested pre-PR) from "unset" (the new default posture), which is the case that would have silently regressed under the old hook::check_enabled unset-means-true fallback.
  • Docs/changelog consistency: README.md's guard table, per-hook kill-switch table, plugin.json descriptions, docs/CATALOG.md, and CHANGELOG.md all agree on the new default-off posture and the narrowed block condition — no stale cross-reference found.

Nothing else stood out as a correctness, architecture, or maintainability concern in the reviewed diff.

Comment thread plugins/guardrails/hooks/block-noncanonical-commit.test.sh
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 5 times. The lane skips further automatic reviews after 5; deleting this comment resets the count.

…arated form

Review follow-ups on the -m narrowing:

- git's parse-options accepts any unique long-option prefix, and
  --message is git commit's only m-initial long option, so --m through
  --messag all parse as --message (verified on git 2.55: `git commit
  --dry-run --mess=x` and each shorter prefix parse; --mainline errors).
  The scan now recognizes every abbreviation in both the =-attached and
  the separated form, so `git commit --mess="multi<NL>line"` no longer
  bypasses the newline gate.
- New test cases pin the separated `--message <msg>` form and the
  abbreviated attached/separated forms, multi-line (deny) and
  single-line (allow) each, plus the shortest `--m=` spelling.

Suite: 188 passed, 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 50s —— View job


Security review complete

  • Gather context (git diff/git show on changed files, prior review threads)
  • Review latest commit 1cca9df (gates abbreviated --message spellings) against Codex's/prior findings
  • Re-review block-noncanonical-commit.sh newline-gate narrowing end-to-end
  • Review flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gating (unchanged since prior pass, confirmed still sound)
  • Review hooks.json matcher merge and plugin.json (unchanged since prior pass, confirmed still sound)
  • Post final security review

No security issues found on the current HEAD (1cca9df). The one previously-flagged finding — the --message long-option abbreviation bypass (raised by Codex and confirmed in three prior automated passes on this PR) — is now fixed by commit 1cca9df, and I verified the fix directly rather than taking the commit message at its word:

  • Fix verified correct and complete. block-noncanonical-commit.sh#L784-L791 now matches every accepted unique prefix of --message (--m, --me, --mes, --mess, --messa, --messag, --message) in both separated and =-attached form (L792-L795). I independently ran git commit -h in this environment against the installed git and confirmed -m, --[no-]message <message> is git commit's only m-initial long option — there is no other long option this prefix set could ambiguously collide with, so the newly-added arms are exhaustive and correct, not just plausible. The --message=* arm's replacement of a literal-prefix strip (${word#--message=}) with a generic ${word#*=} strip is also correct: it works uniformly for every abbreviated spelling without needing a matching strip per case-arm.
  • Test coverage closes both gaps identified in review. block-noncanonical-commit.test.sh#L49-L67 adds the separated --message <msg> case (closing the test-coverage gap I flagged in the prior code-review pass on this PR) plus multi-line/single-line pairs for the abbreviated attached and separated forms, and the shortest --m= spelling.
  • Changelog claim now accurate. CHANGELOG.md#L38-L44 documents the abbreviation coverage, so the guard's "no bypass beyond the documented residual" framing (the one remaining disclosed gap is -am"multi<NL>line" attached-to-a-short-option-cluster) is now truthful — no undocumented gap remains.
  • Everything reviewed in prior passes on this PR re-confirmed unchanged and still sound at current HEAD: flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh both gate [[ "${CLAUDE_PLUGIN_OPTION_..._ENABLED:-false}" == "true" ]] || exit 0 before any stdin/input handling, correctly failing closed on unset/false, and both hooks remain advisory-only (exit 0 always) so the default flip is a reduction in nudging, not enforcement. plugin.json keeps the one blocking guard (block_noncanonical_commit_enabled) default true, with only the two advisories defaulting false. hooks.json's merged Bash|PowerShell group is execution-identical to the prior split per the hooks reference (same-matcher groups fire independently, hooks within a group run in parallel).

No injection surfaces, credential/token handling, or authorization logic elsewhere in the diff. This PR contains no .github/workflows changes, so the Actions trust-boundary lane doesn't apply.
· Branch: fix/guardrails-hook-remediation

…ge patterns

The literal one-letter-short option prefix in the two case arms trips
the typos gate ("should be message"). The case patterns keep the
literal behind the config-blessed spellchecker:disable-line pragma; the
three prose mentions are reworded to describe the prefix range instead
of spelling it. No behavior change (spot-checked both directions;
typos --config _typos.toml now clean on the plugin).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 59s —— View job


Security review complete

  • Gather context (diff since last review, changed files)
  • Review latest commit 43ace1f (spell-checker silencing on abbreviated --message patterns)
  • Re-confirm prior findings (abbreviation bypass fix, opt-in gating, matcher merge) still hold
  • Post final security review

No security issues found on the current HEAD (43ace1f).

This sync added exactly one commit since the last reviewed HEAD (1cca9df), touching three files:

plugins/guardrails/CHANGELOG.md                            |  8 ++++----
plugins/guardrails/hooks/block-noncanonical-commit.sh      | 13 +++++++------
plugins/guardrails/hooks/block-noncanonical-commit.test.sh |  7 ++++---

I diffed 1cca9df..43ace1f directly (not just trusting the commit message) and confirmed it is comment/prose-only:

  • block-noncanonical-commit.sh#L784 and #L793 — the two abbreviated---message case-arm patterns (-m | --m | --me | ... | --message and --m=* | --me=* | ... | --message=*) are byte-identical to the prior commit; the only change is an appended # spellchecker:disable-line comment on each arm and a reflow of the explanatory comment block above them. No case pattern, guard condition, or control flow changed.
  • block-noncanonical-commit.test.sh — no new/changed assertions, just a reworded comment above the existing abbreviated-spelling test cases.
  • CHANGELOG.md — prose reflow only ("any prefix from --m up to one letter short of the full spelling" vs. the prior "--m through --messag" phrasing), same claim, no code-relevant change.

This confirms the fix for the previously-flagged --message abbreviation bypass (verified in the prior review pass on this PR) is unaffected — the case arms covering every unique --message prefix in both separated and =-attached form are still intact and correct.

Everything else reviewed and confirmed sound in prior passes on this PR remains unchanged at this HEAD:

  • flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gating ([[ "${CLAUDE_PLUGIN_OPTION_..._ENABLED:-false}" == "true" ]] || exit 0, fails closed, advisory-only hooks so the default flip is a reduction in nudging, not enforcement).
  • plugin.json — only the two advisory hooks flip to default false; the blocking guard's kill switch stays default true.
  • hooks.json matcher merge — execution-identical per the hooks reference (same-matcher groups fire independently, hooks within a group run in parallel).

No injection surfaces, credential/token handling, or authorization logic touched by this diff. No .github/workflows files are in the diff, so the Actions trust-boundary lane doesn't apply.
· Branch: fix/guardrails-hook-remediation

@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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


Code review complete

  • Gather context (gh pr diff, changed files)
  • Review plugin.json userConfig changes
  • Review block-noncanonical-commit.sh narrowing logic (correctness, maintainability)
  • Review block-noncanonical-commit.test.sh coverage
  • Review flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gating + tests
  • Review hooks.json matcher merge
  • Review docs/changelog consistency
  • Post inline findings and final summary

Reviewed the current HEAD (43ace1f) against origin/main, scoped to the 12 changed files, on the correctness/architecture/test-coverage/maintainability lane — leaving the --message abbreviation bypass class of finding to the security lane (already raised there by Codex and this repo's security-review passes, and fixed in 1cca9df/43ace1f; I did not re-litigate it).

One finding posted inline (test-coverage, non-blocking):

  • block-noncanonical-commit.test.sh#L66-L67 — the shortest --message abbreviation (--m) is only pinned in the =-attached, multi-line-blocked direction. The separated form (git commit --m 'msg') and the single-line-allowed control for --m= aren't tested, unlike every other spelling in this suite (which the PR explicitly respells "in both directions"). Low risk, but --m is the boundary of the accepted-prefix range so it's the spelling most likely to silently regress if the case-arm list is ever edited.

What I verified by reading the code (I could not execute the test suites or bash -n/shellcheck myself — this sandbox's Bash tool auto-denies commands here, so I'm not independently confirming the author's or prior reviewers' claimed test-run output; treat those as author-claimed and unverified by me):

  • check_segment()'s new case arms (block-noncanonical-commit.sh:784-807): traced the bash glob-pattern precedence by hand. -m | --m | … | --message (exact match), --m=* | … | --message=* (equals-attached), -m* (single-dash attached), and -[!-]*m (cluster ending in m) don't overlap — e.g. -ma (m first in cluster, taking the remainder as its value per git's own clustering rule) matches -m* since it's tried before -[!-]*m, and -am (m last, taking the next word) only matches -[!-]*m since it doesn't start with -m. The one residual (a value attached to a cluster where m isn't first or last, e.g. -ams) falls through to the catch-all untouched — but that's the exact case the script header and CHANGELOG already disclose.
  • Exemption/order interaction: -C*/-c* (exempt) and the message-detection arms don't conflict; ((stdin_form || exempt)) short-circuits before the new ((msg_newline)) check, so --amend -m "<newline>" still correctly allows via the exemption path (pinned by the new "amend -m multi-line (allowed)" case).
  • hooks.json: JSON structurally sound after the merge — one Bash|PowerShell PreToolUse group now carries all six hooks in the order previously split across two groups; per the hooks reference (cited in the PR/CHANGELOG), same-matcher groups already fired independently and in parallel, so this is a no-op restructuring.
  • Opt-in gating (flag-commit-pr-skill-bypass.sh, workflow-resilience-check.sh): both replace hook::check_enabled (unset→true fallback) with an explicit [[ "${…:-false}" == "true" ]] || exit 0 before any stdin/input handling — matches the existing session-flow observer-arm.sh precedent (grep confirms it's the same three-instance idiom across two plugins, not a one-off). Not flagging duplication-across-scripts as a maintainability issue since it already follows established repo convention and hooks can't share code across plugin boundaries.
  • Test isolation for the new "unset switch" cases: both .test.sh files correctly use env -u <VAR> … bash "$HOOK" in a subshell rather than unset in the test process itself, so the suite's own export …=true (set for the behavior cases above) doesn't leak into the default-off assertion.
  • Docs/changelog consistency: README.md's guard table and kill-switch table, plugin.json descriptions/defaults, docs/CATALOG.md, and CHANGELOG.md all agree on the new default-off posture, the narrowed block condition, and the disclosed residual — no stale cross-reference found. The 0.20.0 changelog entry's blank-line formatting above the 0.19.4 heading is correct (verified by direct read, not by re-running the author's check-changelog-parity gate).

Nothing else stood out as a correctness, architecture, or maintainability concern in the reviewed diff.
· Branch: fix/guardrails-hook-remediation

Comment thread plugins/guardrails/hooks/block-noncanonical-commit.test.sh
…breviation

--m was the only abbreviation covered in one direction only; the separated
form now has multi-line (deny) and single-line (allow) cases like every
other spelling. Suite: 190 passed, 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Fixed — the separated --m form is now pinned in both directions (multi-line deny, single-line allow), matching every other abbreviation's coverage. Full suite 190/0.

@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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


Security review complete

  • Gather context (diff since last reviewed HEAD 43ace1f, changed files)
  • Review latest commit 3dd7edc (pin separated form of the shortest --message abbreviation) against prior findings
  • Re-confirm prior findings (abbreviation bypass fix, opt-in gating, matcher merge) still hold at current HEAD
  • Post final security review

No security issues found on the current HEAD (3dd7edc).

Only one commit landed since the last reviewed HEAD (43ace1f), and it touches a single file:

plugins/guardrails/hooks/block-noncanonical-commit.test.sh | 4 ++++

I diffed 43ace1f..3dd7edc directly and confirmed it is test-only — two new assertions pinning the separated --m <msg> spelling (multi-line blocked / single-line allowed), the last gap a prior review pass flagged. No hook script, hooks.json, or plugin.json content changed, so there is no new security-relevant surface to review.

I re-read the current state of block-noncanonical-commit.sh's message-detection case arms (L784-L807) to confirm the previously-fixed --message abbreviation bypass (originally raised by Codex, fixed in 1cca9df) is still intact: the exact-match arm (-m | --m | --me | --mes | --mess | --messa | --messag | --message), the =-attached arm, the -m* attached-short-option arm, and the -[!-]*m cluster arm are unchanged from the last reviewed commit and remain correctly ordered relative to the -F/--amend/-C/-c exemption arms above them.

Everything else reviewed and confirmed sound in prior passes on this PR remains unchanged at this HEAD:

  • flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh — both gate [[ "${CLAUDE_PLUGIN_OPTION_..._ENABLED:-false}" == "true" ]] || exit 0 before any stdin/input handling, correctly failing closed on unset/false; both hooks are advisory-only (exit 0 always), so the default-off flip is a reduction in nudging, not enforcement.
  • plugin.json — the one blocking guard's kill switch (block_noncanonical_commit_enabled) stays default true; only the two advisory hooks default to false.
  • hooks.json matcher merge — execution-identical per the hooks reference (same-matcher groups fire independently, hooks within a group run in parallel); no scope widening or narrowing.

No injection surfaces, credential/token handling, or authorization logic touched by this diff. No .github/workflows files are in the diff, so the Actions trust-boundary lane doesn't apply.
· Branch: fix/guardrails-hook-remediation

@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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


Code review complete

  • Gather context (diff since last code-review pass at 43ace1f, current HEAD 3dd7edc)
  • Confirm prior review findings on this PR (test-coverage gaps for --m/--message forms) are addressed at current HEAD
  • Review plugin.json userConfig changes
  • Review block-noncanonical-commit.sh narrowing logic
  • Review block-noncanonical-commit.test.sh coverage
  • Review flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gating + tests
  • Review hooks.json matcher merge
  • Review docs/changelog consistency
  • Post inline findings and final summary

This sync added one commit since the last code-review pass on this PR (43ace1f): 3dd7edc, touching only block-noncanonical-commit.test.sh (+4 lines). I diffed 43ace1f..3dd7edc directly and confirmed plugin.json, flag-commit-pr-skill-bypass.sh, workflow-resilience-check.sh, hooks.json, CHANGELOG.md, README.md, and docs/CATALOG.md are byte-unchanged since that already-reviewed pass — not re-litigating those here.

One finding posted inline (test-coverage, non-blocking — a partial fix of my own prior finding on this PR):

  • block-noncanonical-commit.test.sh#L66-673dd7edc added the separated --m 'msg' form in both directions (closing that half of the gap), but the attached --m='msg' spelling still lacks a single-line-allowed control, unlike every sibling --mess=/--message= pair in the suite.

What I verified by reading the code directly (this sandbox denies interactive Bash approval for anything beyond read-only git show/git diff, so I could not execute the test suite myself — treating the author's "188 passed, 0 failed" claims as author-claimed and unverified by me):

  • Case-arm exhaustiveness (block-noncanonical-commit.sh#L784-L807): re-read the full abbreviated---message arm set at current HEAD. The exempt arms (-C*/-c*, --amend/--fixup/etc.) are ordered before the message-detection arms with no pattern overlap, -m* vs. -[!-]*m correctly disambiguate cluster position (-ma-m*, -am-[!-]*m), and the --m=* arm's generic ${word#*=} strip works uniformly across every abbreviated spelling. This matches what the security-lane passes on this PR already confirmed against git's actual prefix-resolution behavior.
  • Test coverage for the abbreviation fix: confirmed --message/--mess/--m are now each pinned in both the =-attached and separated forms, both directions (blocked/allowed), except for the one residual attached-form gap flagged above.
  • No other file changed in this syncplugin.json's default-off flip on the two advisory hooks, the hooks.json Bash|PowerShell matcher merge, and the opt-in gating shape in flag-commit-pr-skill-bypass.sh/workflow-resilience-check.sh are unchanged from the prior code-review pass on this PR, which already reviewed them clean.

Nothing else stood out as a correctness, architecture, or maintainability concern.
· Branch: fix/guardrails-hook-remediation

Comment thread plugins/guardrails/hooks/block-noncanonical-commit.test.sh
The attached shortest abbreviation was pinned only in the blocked
direction; it now has the allow control like every sibling pair.
Suite: 191 passed, 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 37s —— View job


Security review complete

  • Gather context (diff since last reviewed HEAD 3dd7edc, changed files)
  • Review latest commit f34aff3 (adds the --m= single-line control case) against prior findings
  • Re-confirm prior findings (abbreviation bypass fix, opt-in gating, matcher merge) still hold at current HEAD
  • Post final security review

No security issues found on the current HEAD (f34aff3).

Only one commit landed since the last reviewed HEAD (3dd7edc), and it touches a single file:

plugins/guardrails/hooks/block-noncanonical-commit.test.sh | 2 ++

I diffed 3dd7edc..f34aff3 directly and confirmed it is test-only — one new assertion pinning the attached --m='feat: x' single-line-allowed control, the last gap flagged by the prior code-review pass on this PR (block-noncanonical-commit.test.sh#L67-68). No hook script, hooks.json, or plugin.json content changed, so there is no new security-relevant surface to review.

I re-read block-noncanonical-commit.sh's message-detection case arms (L784-L807) to confirm they are byte-identical to the previously-reviewed commit: the previously-fixed --message abbreviation bypass (originally raised by Codex, fixed in 1cca9df) remains closed — the exact-match arm (-m | --m | --me | --mes | --mess | --messa | --messag | --message), the =-attached arm, the -m* attached-short-option arm, and the -[!-]*m cluster arm are unchanged and remain correctly ordered relative to the -F/--amend/-C/-c exemption arms above them.

Everything else reviewed and confirmed sound in prior passes on this PR remains unchanged at this HEAD:

  • flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh — both gate [[ "${CLAUDE_PLUGIN_OPTION_..._ENABLED:-false}" == "true" ]] || exit 0 before any stdin/input handling, correctly failing closed on unset/false; both hooks are advisory-only (exit 0 always), so the default-off flip is a reduction in nudging, not enforcement.
  • plugin.json — the one blocking guard's kill switch (block_noncanonical_commit_enabled) stays default true; only the two advisory hooks default to false.
  • hooks.json matcher merge — execution-identical per the hooks reference (same-matcher groups fire independently, hooks within a group run in parallel); no scope widening or narrowing.

No injection surfaces, credential/token handling, or authorization logic touched by this diff. No .github/workflows files are in the diff, so the Actions trust-boundary lane doesn't apply.
· Branch: fix/guardrails-hook-remediation

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Fixed in f34aff3 — the attached --m= spelling now has its single-line allow control, completing both directions for every abbreviation pair. Suite 191/0.

@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 51s —— View job


Code review complete

  • Gather context (diff since last code-review pass at 3dd7edc, current HEAD f34aff3)
  • Confirm the previously-flagged test-coverage gap (missing --m= single-line-allowed control) is closed
  • Re-check surrounding test cases for consistency/duplication
  • Post final summary

This sync added exactly one commit since the last code-review pass on this PR (3dd7edc): f34aff3, touching only block-noncanonical-commit.test.sh (+2 lines). I diffed 3dd7edc..f34aff3 directly:

+run "git commit --m=<shortest abbreviation> single-line (allowed)" \
+  "git commit --m='feat: x'" 0

This closes the gap flagged in my prior review pass on this PR (block-noncanonical-commit.test.sh#L67 at 3dd7edc): the attached --m= spelling now has both directions pinned (multi-line blocked at line 66-67, single-line allowed at the new line 68-69), matching every sibling --mess=/--message= pair in the suite.

I re-read the full abbreviation block at current HEAD (lines 53-73) to confirm the set is now symmetric and non-duplicated: --message, --mess, and --m each get an attached (=) and separated form, each pinned in both directions (blocked/allowed) — no leftover asymmetry, no duplicate case labels.

No other file changed in this sync — block-noncanonical-commit.sh's case-arm logic, plugin.json's default-off flip on the two advisory hooks, the hooks.json matcher merge, and the opt-in gating in flag-commit-pr-skill-bypass.sh/workflow-resilience-check.sh are byte-unchanged from the prior code-review pass on this PR (at 43ace1f), which already reviewed them clean — not re-litigating those here.

No new findings. This test-only commit fully addresses the last open item from this lane's review history on this PR; I have no further inline comments to add.

Note: I could not execute the test suite myself in this environment (interactive Bash approval unavailable beyond read-only git diff/git log), so the author's "190/0" pass-count claim in their comment above remains author-claimed and unverified by me — my confirmation here is limited to reading the added assertions and tracing the code path by hand.
· Branch: fix/guardrails-hook-remediation

@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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


Security review complete

  • Gather context (git diff origin/main...HEAD, changed files at current HEAD ea2b63a)
  • Diff each changed file against origin/main directly (not just against the last-reviewed commit) since this sync is a merge commit
  • Re-verify the previously-fixed --message abbreviation bypass is still intact post-merge
  • Cross-check plugin.json against the .sh scripts' own opt-in logic and the CHANGELOG's claims about it
  • Post final security review

This sync merged origin/main into the branch (commits 3df98fc, ea2b63a), pulling in an unrelated main-branch commit (afe0d0e, PR #2008, block-hook-bypass stdout-redirect fix — untouched by this PR's own diff). Because a merge can silently drop hunks during conflict resolution, I diffed every changed file directly against the current origin/main tip rather than trusting the stat summary, and found one real issue caused by exactly that.

[IMPORTANT, confidence: high] The merge silently reverted this PR's own plugin.json changes — goal #1 ("default-off the two behavioral injectors") is not actually in effect at this HEAD

plugins/guardrails/.claude-plugin/plugin.json

git diff origin/main...HEAD -- plugins/guardrails/.claude-plugin/plugin.json at the current HEAD shows only the version bump (0.19.50.20.0) — every other line, including the two userConfig entries this PR's Fix #1 is supposed to flip, is byte-identical to origin/main:

  • L83-L87 workflow_resilience_check_enabled → still "default": true (PR claims false)
  • L89-L93 flag_commit_pr_skill_bypass_enabled → still "default": true (PR claims false)
  • L53-L57 block_noncanonical_commit_enabled description still reads "does not pipe its message via -F -" — the pre-narrowing description, not the new actual-newline one
  • L113-L117 block_noncanonical_commit_allow description still reads "permits a bare -m" — not the new "even with a newline" wording

Meanwhile the .sh scripts and CHANGELOG.md did survive the merge correctly and now contradict plugin.json:

  • flag-commit-pr-skill-bypass.sh#L52-L53 and the equivalent line in workflow-resilience-check.sh both carry the comment "an unset switch must read as the plugin.json default, which is false" — a claim the manifest at this HEAD does not make.
  • CHANGELOG.md#L18-L20 states outright: "the plugin.json defaults flip to false so the configuration dialog and ${user_config.*} agree" — false at the current HEAD; they disagree.
  • README.md's guard table and kill-switch table (verified, both correctly updated) also describe default-off, agreeing with the code and CHANGELOG, not with plugin.json.

Why this matters for a guardrails plugin specifically: the PR's own fresh-docs research (cited in the CHANGELOG) establishes that default is "the value used when the user provides nothing" and that the harness exports it as CLAUDE_PLUGIN_OPTION_<KEY>. If the harness materializes plugin.json's declared default into the hook environment for a consumer who hasn't configured anything, CLAUDE_PLUGIN_OPTION_..._ENABLED would be materialized as "true", and the scripts' own [[ "${VAR:-false}" == "true" ]] || exit 0 gate would then let both advisory hooks run — the opposite of the intended default-off posture, silently, for every fresh install. (If the harness does not materialize defaults, the scripts' own :-false fallback saves the intended behavior regardless of what plugin.json says — but that's exactly the failure mode the PR says it's defending against for the other direction, and here it would be the manifest, not the harness, that's wrong.) Impact is capped at "advisory nudge fires when the PR says it shouldn't" — both hooks stay exit 0-only, never block — so this is not an exploitable vulnerability, but it is a real logic/config-drift defect in the merged state that undermines the primary claim of this PR (Fix #1) and should be fixed before merge: flip plugin.json L87 and L93 to false and update the two descriptions at L53-57/L113-117 to match what README.md/CHANGELOG.md/the scripts already say.

Everything else re-verified clean against the current origin/main tip:

  • block-noncanonical-commit.sh's narrowing logic (message-newline detection arms, abbreviation coverage, exemption ordering) is present and correct — confirmed by direct diff, not by trusting prior review comments. The previously-flagged --message abbreviation bypass (Codex, fixed in 1cca9df) remains closed: the exact-match, =-attached, -m*, and -[!-]*m arms are all intact.
  • hooks.json's Bash|PowerShell matcher merge is a pure structural move (no content/logic change) and is execution-identical per the hooks reference, as established in prior passes.
  • block-hook-bypass.sh's change in this diff is header-comment-only ("Triggered on Bash and PowerShell").
  • No .github/workflows files are touched by this PR, so the Actions trust-boundary lane doesn't apply. No injection surfaces, credential/token handling, or authorization logic elsewhere in the diff.
    · Branch: fix/guardrails-hook-remediation

@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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


Code review complete

  • Gather context (git diff origin/main...HEAD against the true merge-base, not just the last-reviewed commit)
  • Review new commit afe0d0e — confirmed this is already on origin/main (merged separately as fix(guardrails): catch the explicit stdout redirect, which bypassed both lanes #2008, pulled in by the ea2b63a merge), so it is not new work in this PR and was not re-reviewed
  • Diff this PR's actual payload (origin/main...HEAD) against the last code-review pass (f34aff3) to find what really changed
  • Review plugin.json for consistency with README/CHANGELOG's stated behavior
  • Re-confirm hook script logic and hooks.json are unaffected
  • Post inline findings and final summary

One finding posted inline, and it's significant: the ea2b63a merge from origin/main silently reverted this PR's entire plugin.json diff, keeping only the version bump.

plugin.json#L4 — diffing against the actual merge-base (origin/main = afe0d0e, which already carries an unrelated, separately-merged fix also touching this file) shows plugin.json now differs from origin/main by exactly the version string. Concretely, at current HEAD:

  • workflow_resilience_check_enabled and flag_commit_pr_skill_bypass_enabled are back to "default": true — the PR's headline claim ("default-off the two behavioral injectors") does not hold in the shipped manifest.
  • Their descriptions no longer mention the default-off posture or #2021, contradicting README.md#L17/#L20, which still correctly say "Opt-in — default off since 0.20.0."
  • block_noncanonical_commit_enabled's description reverted to the pre-PR text ("Block git commit that does not pipe its message via -F -"), no longer matching the narrowed newline-only behavior block-noncanonical-commit.sh actually implements after this PR.
  • CHANGELOG.md's own 0.20.0 entry states outright that "the plugin.json defaults flip to false so the configuration dialog and ${user_config.*} agree" — currently false for the shipped manifest.

The hook scripts themselves are unaffected and correct — both flag-commit-pr-skill-bypass.sh and workflow-resilience-check.sh gate on [[ "${CLAUDE_PLUGIN_OPTION_..._ENABLED:-false}" == "true" ]], so a harness that doesn't materialize userConfig defaults into the environment still behaves default-off regardless of this bug. But per this PR's own stated mechanism, flipping plugin.json's declared default exists specifically to cover harnesses that do materialize it — for those, the current manifest turns both advisories back on, the opposite of this PR's intent. None of the touched test suites catch this, since they set/unset the env var directly and never assert against plugin.json's declared default.

Full detail and a fix link are in the inline comment on plugin.json:4.

Everything else re-confirmed sound (unchanged from the prior code-review pass at f34aff3, verified by diffing this PR's actual scope against the true merge-base rather than assuming the merge was a no-op):

  • block-noncanonical-commit.sh's newline-detection case arms, hooks.json's matcher merge, and the opt-in-gating shape in both advisory hooks are all still correct and match what CHANGELOG/README describe.
  • afe0d0e (the "stdout redirect" fix pulled in by the merge) is pre-existing content on origin/main, not new work from this PR — it was not re-reviewed here.

Note: I could not execute the test suites, shellcheck, or any of the repo's check scripts myself — this sandbox denies interactive Bash approval for git-network operations and anything beyond simple read-only git diff/git show. The finding above was confirmed by direct file inspection (grep/diff against the actual merge-base commit objects, which were present locally despite the shallow clone), not by running tooling. The author's/prior comments' test-pass counts remain author-claimed and unverified by me.

Branch: fix/guardrails-hook-remediation

Comment thread plugins/guardrails/.claude-plugin/plugin.json
The origin/main merge resolved plugins/guardrails/.claude-plugin/plugin.json
by taking main's copy and re-applying only the version bump — silently
reverting this PR's payload: the two injector defaults (back to true) and
the three description updates. Restored from the pre-merge commit; caught
by fresh-context verification of the PR tree against the changelog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 18s —— View job


Security review complete

  • Gather context (git diff against the PR base commit, changed files at current HEAD c2176a8)
  • Confirm whether the previously-flagged plugin.json merge-revert (found at ea2b63a) is actually fixed by c2176a8
  • Re-verify block-noncanonical-commit.sh newline-gate logic at current HEAD
  • Re-verify flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh opt-in gating at current HEAD
  • Re-verify hooks.json matcher merge at current HEAD
  • Post final security review

No security issues found on the current HEAD (c2176a8).

The one prior finding on this PR — the ea2b63a merge from origin/main silently reverting this PR's plugin.json payload (both advisory hooks' "default": false and the four updated descriptions) — is fixed by c2176a8. I diffed ea2b63a c2176a8 -- plugins/guardrails/.claude-plugin/plugin.json directly and confirmed the restore is complete and correct, then re-read the live file to be sure it isn't just the diff hunk that looks right:

  • workflow_resilience_check_enabled and flag_commit_pr_skill_bypass_enabled are both back to "default": false, matching README/CHANGELOG's "default off since 0.20.0" claim.
  • block_noncanonical_commit_enabled's description now correctly describes the narrowed actual-newline behavior (not the old blanket -F - requirement), and stays "default": true — the one blocking guard is unaffected by the ablation, as intended.
  • The two advisory hooks' descriptions now correctly state the default-off/opt-in posture, and the top-level plugin description matches.
  • CHANGELOG.md's "the plugin.json defaults flip to false" claim is now truthful against the shipped manifest.

Everything else re-verified at current HEAD and unaffected by the merge (diffed each hook file against the PR base directly, not assuming a no-op):

  • block-noncanonical-commit.sh — the actual-newline -m/--message detection (check_segment(), lines ~784–826) is intact: the exact-match, =-attached, -m*, and -[!-]*m case arms; the exemption ordering (-F -/--amend/-C/-c/--fixup/--squash/sequencer-in-progress) short-circuiting before the newline check; and the previously-fixed --message abbreviation coverage (--m through --messag, both =-attached and separated) are all present and correctly ordered. No new bypass surface introduced since the last reviewed pass on this PR.
  • flag-commit-pr-skill-bypass.sh / workflow-resilience-check.sh — both gate [[ "${CLAUDE_PLUGIN_OPTION_..._ENABLED:-false}" == "true" ]] || exit 0 before any stdin/input handling, correctly failing closed on unset/false; both remain advisory-only (exit 0 always, never block), so the default-off flip is a reduction in nudging, not enforcement.
  • hooks.json — the Bash|PowerShell matcher merge is unchanged from the prior reviewed state: execution-identical per the hooks reference (same-matcher groups fire independently, hooks within a group run in parallel), no scope widening or narrowing.
  • block-hook-bypass.sh — header-comment-only change ("Triggered on Bash and PowerShell"), no logic touched.

No injection surfaces, credential/token handling, or authorization logic touched by this diff. No .github/workflows files are in the diff, so the Actions trust-boundary lane doesn't apply.
· Branch: fix/guardrails-hook-remediation

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Confirmed and fixed in c2176a8 — the merge resolution had taken main's plugin.json and re-applied only the version bump, reverting the two injector defaults and three description updates. plugin.json is restored from the pre-merge commit (both defaults false again; catalog regenerated, which the revert had also stalled). Independently caught by a fresh-context verification pass diffing the PR tree against its changelog claims before merge.

@kyle-sexton
kyle-sexton merged commit 0ec2334 into main Aug 9, 2026
33 checks passed
@kyle-sexton
kyle-sexton deleted the fix/guardrails-hook-remediation branch August 9, 2026 14:48
kyle-sexton added a commit that referenced this pull request Aug 9, 2026
One conflict, in the guardrails suite's 20-hop dual-spelling traversal case, where
both sides edited the same call. They are orthogonal and both are kept:

- main (#2058) narrowed the `-m` gate to real newlines, so the case's command had to
  become a genuinely multi-line `-m` to still expect exit 2.
- this branch replaced the wall-clock ceiling with `run_guarded`, which asserts the
  exit code and keeps `timeout` only as a hang guard.

The resolution takes main's command and label with this branch's mechanism.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Aug 9, 2026
…line number

Merging main brought #2058's hook changes in ahead of the cited text, moving "The
ceiling counts ANALYSES rather than seconds" from line 489 to 507 — so the citation
that carries this file's whole boundedness argument pointed a reader at an unrelated
MEMO paragraph instead.

Re-pinning the number would re-arm the same trap on the next edit to the hook. The
surrounding sentence already names HOOK_ALIAS_WORK_MAX, which is greppable and
survives line shifts, so the citation now points at where the cap is set and the
coordinate is dropped.

Co-Authored-By: Claude Opus 5 (1M 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.

1 participant