fix(claude-config): let installed hook coverage narrow a baseline deny finding, and stop calling the addition mechanical - #2206
Conversation
…y finding, and stop calling the addition mechanical Category B iterates the baseline permission patterns and states flatly that each "must appear" in permissions.deny. The only two ways out were prose the consuming repo writes — a documented exemption in its own rules files, or its own documented hook conventions. Neither is keyed on a hook that is actually installed and enabled, and grep -rn "hook" across the whole skill returns no hooks.json read, no plugin-hook enumeration, and no coverage concept. A repo that blocks git push --force with a PreToolUse hook exiting 2 — which the permissions reference says stops the call before permission rules are evaluated, ahead even of an allow rule — was told its security floor was missing. "Narrowing the baseline" now carries a third narrowing: a family already blocked by an installed, enabled PreToolUse hook is info, not error, whether the hook came from the repo or a plugin — with the residual named (plugin disable/uninstall, per-guard opt-out, allowManagedHooksOnly / strictPluginOnlyCustomization). Where no hook inventory was taken the finding is stated conditionally rather than asserted, because the skill has no enumeration path over a plugin's hooks/hooks.json. The off-ramps were also unreachable from where the check runs — Category B delegates by pattern and the checklist asserts presence per sub-category, neither naming the off-ramp sections. Both now point at them, and the B.1-B.3 severity table says its ratings are the unnarrowed ones. Phase 5 graded "Add missing baseline deny rules" auto-fixable / judgment No, which is the column that routes this false positive into --fix as mechanical and which contradicted this skill's own baseline reference (a deny added over a family a project hook escalates to an ask suppresses that prompt). It is now judgment-required with the judgment written out; moving a deny rule from local to project stays mechanical, and SKILL.md's prose restatement splits the two the same way instead of asserting the opposite. Eval 8 grades the chain. Closes #2201 Closes #2202 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
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 finished @kyle-sexton's task in 3m 15s —— View job Review complete — PR #2206Todo list
SummaryRepo-root This is skill-content-as-code (Markdown + Two things worth the author's judgment, posted inline:
No correctness bugs or missing test coverage found beyond those two documentation/consistency points — both are |
|
Claude finished @kyle-sexton's task in 39s —— View job Security review complete
Scope: All 8 changed files reviewed via Finding: none. This PR is entirely prose/documentation content plus a JSON evals fixture and a manifest version bump — no executable code, no scripts, no GitHub Actions workflow files, no dependency or action pins, and no settings/permissions files that are themselves consumed as live config. The changes teach the Specifically checked and ruled out as inapplicable to this diff:
No security concerns to flag on this PR. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a9b6b6d20
ℹ️ 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".
|
Claude has reviewed this PR 5 times. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
…e, and the specific family Review found three ways the new off-ramp was too generous, and one changelog shape that misrepresented the bump. Liveness: installed and enabled is not sufficient. disableAllHooks turns every hook off, and managed allowManagedHooksOnly / strictPluginOnlyCustomization suppress non-exempt hooks outright — a hook a setting has already disabled blocks nothing, so under any of those the finding stands unnarrowed. Those settings were named only as a future residual; they are now a precondition too. Tool surface: sensitive-file-deny is a Read-pattern family and a Read deny covers the built-in file tools as well as recognized Bash file commands, so a hook matching only Bash leaves Read/Grep/Glob open and retires nothing there. Family precision: coverage of git push --force says nothing about git clean -fd, nor a long flag about its short spelling. Eval 8 now grades exactly that discrimination — force-push and hard-reset drop to info under a live Bash hook while git clean and the Read patterns hold — and the fail-open half moves to its own eval 9 with a distinguishable prompt. The changelog was entirely ### Fixed while the bump is minor. Two graded outputs really do move, so the behavior-changing entries now sit under ### Changed with the reason stated up front, and the new evals under ### Added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 41s —— View job Review complete — PR #2206
SummaryRepo-root I diffed the two commits separately rather than treating the final diff as one unit, since the second commit ( One new finding, posted inline at all three locations (Suggestion-level, not blocking): the second commit corrected No correctness bugs found in the core wiring change itself — the Category B → "Narrowing the baseline" → checklist severity table → Phase 5 fix matrix → eval #8/#9 chain is internally consistent and each file's severity/judgment split agrees with the others. |
|
Claude finished @kyle-sexton's task in 2m 14s —— View job Security review complete
Scope: All 8 changed files in this PR ( Two prior review passes on this PR (Codex, at commit One finding survives that fix, introduced by the fix itself: [IMPORTANT] The new liveness precondition cites a check that doesn't exist, reopening the same false-assurance gap one layer down The new "hook is live, not merely present" precondition — the one meant to stop the audit from downgrading a missing baseline deny rule to Category D does not read it. I checked both So the precondition that's supposed to gate narrowing 3 — confirm the hook can actually run before trusting it as coverage — delegates its own verification to a check that isn't wired anywhere in the skill's instruction path. Followed literally, an auditor reads "Category D already reads it," takes that as license to skip an independent settings.json check, confirms only "installed, enabled," and downgrades a missing This is the identical two-link defect pattern (Category B claims a narrowing that isn't backed by wiring elsewhere) that this PR's own summary calls out and fixes for the original off-ramps — just reintroduced one layer down, inside the fix commit that closed the first two instances. Suggested resolution: either add the three settings to Category D's checklist (and have narrowing 3 cite it correctly), or have narrowing 3 state the settings.json check directly inline rather than pointing at a category that doesn't perform it. Confidence: High — verified by reading the current Category D content in both files and a full-skill grep for the three setting names; not a matter of interpretation. Ruled out / out of scope for this lane: No GitHub Actions files changed (dangerous trigger patterns, permission-widening, unpinned actions N/A). No secrets/token handling touched. No injection surfaces — |
…to narrowing 3 The previous commit tightened "Narrowing the baseline" §3 but left the three sites that point at it — Category B, the B.1-B.3 severity note, and the Phase 5 judgment gloss — still saying "installed, enabled". That is the exact shape of drift this PR exists to remove: a reader who stops at the entry point, which for Category B is the common case, would apply the unfenced version. All three now say "live", name the tool-surface constraint, and send the reader to the three preconditions rather than restating a stale summary of them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 8s —— View job Review complete — PR #2206Todo list
SummaryRepo-root This sync added one commit, One correctness issue remains, not touched by this sync (posted inline at I re-verified the core wiring chain independently rather than trusting the PR's own description of it: Category B ( No other correctness bugs or missing test coverage found. The one inline finding is a documentation/wiring accuracy issue ( |
|
Claude finished @kyle-sexton's task in 1m 52s —— View job Security review complete
Scope: All 8 files changed in this PR at HEAD ( One finding, posted inline — still live at HEAD. [IMPORTANT] Liveness precondition in Narrowing 3's "hook is live, not merely present" precondition tells the auditor "Check the settings-declared layer for these before downgrading — Category D already reads it." It doesn't: I re-read Category D in both This is a repeat finding: a prior automated security-review pass on this PR flagged the identical line at commit Confidence: High — verified directly by reading current file content and a full-skill grep, not inferential. Ruled out / not re-reported (already covered by prior review rounds and confirmed fixed at this HEAD): the earlier |
…n levers the narrowing depends on The liveness precondition told the auditor to take the disableAllHooks / allowManagedHooksOnly / strictPluginOnlyCustomization reading from Category D. Category D does not take it: it checks script paths, readability, timeout units, matchers, exec-form resolution, duplicates, and events, and never asks whether the hooks it inventoried can run at all. Grep confirms all three names appeared nowhere in the skill before this PR. That is the same false-assurance shape this PR set out to remove — a precondition that cannot be evaluated is one the reader will quietly skip, and skipping it means downgrading a real finding on the strength of an inert hook. So the check is now real. Category D reads all three levers and reports each as set or unset with the hooks it switches off, rated info because a repo may set any of them deliberately and the reading is state rather than a defect. And the precondition no longer assumes the reading was taken: where it was not, the narrowing is unavailable rather than assumed clear, because an unread lever is not an unset one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 3s —— View job Security review complete
Scope: All 8 files changed in this PR at HEAD ( Prior finding — resolved at this HEAD. Two prior automated security-review passes on this PR (at Commit
That second piece is what actually closes the false-assurance gap rather than relocating it: even if a future run doesn't wire Category D's new check correctly, the precondition's default (unread → narrowing unavailable → finding stays at unnarrowed severity) fails closed, not open. This is the same "fail open on no inventory" pattern already used elsewhere in this PR for the plugin-hook enumeration gap. No new security issues found in the rest of the diff at this HEAD — the remainder is unchanged from what prior passes already reviewed (no executable paths, no injection surfaces, no secrets/token handling, no GitHub Actions files touched, Confidence: High — verified by reading the current content of all three files ( |
|
Claude finished @kyle-sexton's task in 3m 40s —— View job Review complete — PR #2206Todo list
SummaryRepo-root The latest commit, Two new findings, posted inline (both
Re-verified, no regressions found: the rest of the wiring chain (Category B → "Narrowing the baseline" → checklist B.1–B.3 table → Phase 5 fix matrix in |
…, and grade the suppressed-hook case Two gaps in the liveness fence, both real. Category B owns the narrowing and runs before Category D, which owns the lever reading it depends on. A-I is presentation order, not a dependency ban, so Category B now says so: pull the reading forward, or defer the downgrade until Category D has run and revise the severity then. What it may not do is take the narrowing on an unread lever — and on a scope-filtered run like /audit permissions, which never reaches Category D, the narrowing is simply unavailable unless the operator supplies the state. And every eval so far graded the clean case. Eval 10 grades the case the precondition exists for: a PreToolUse hook declared on Bash under disableAllHooks: true narrows nothing, the destructive-git patterns hold at error, and the report says why the narrowing did not apply instead of omitting it silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 31s —— View job Security review complete
Scope: All 8 files changed in this PR at HEAD ( Both open findings — resolved at this HEAD.
No new security issues found in this commit or the rest of the diff at this HEAD. The remainder is unchanged from what prior review passes already covered: no executable paths, no injection surfaces, no secrets/token handling, no GitHub Actions files touched, Confidence: High — verified by reading the current content of |
|
Claude finished @kyle-sexton's task in 2m 43s —— View job Review complete — PR #2206Todo list
SummaryRepo-root The latest commit,
Two new findings, posted inline (both
No other changes since the last review round — |
… it a changelog bullet The pull-forward-or-defer rule and the scope-filtered /audit permissions case lived only in Category B, while Category B tells the reader to go read the three preconditions in required-permissions.md. A reader who lands on precondition 1 directly got "take the reading from Category D" with nothing about Category D running later. Both files now carry it. The same commit's normative addition also went unrecorded in the changelog while every other prose change in this PR got a bullet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 48s —— View job Security review complete
Scope: All 8 files changed in this PR at HEAD ( Latest commit (
Both changes close code-review-lane suggestions from the prior round (cross-reference gap and missing changelog bullet) — not security findings. No new trust surface, no new setting, no new hook, no change to any enforcement path. No new security issues found, and no regressions in previously-resolved findings. Everything else in the diff is unchanged from what five prior review passes on this PR already covered and confirmed resolved:
Confidence: High — verified by diffing the exact commit range and reading the changed content directly, not inferential. |
|
Batch-4 filing note: #2275 files what this PR left standing in the same skill, and one of its rows is sharper because of this change.
Also in #2275: |
Summary
claude-config'sauditskill had a two-link defect chain in Category B. Batch-4 reconciliationAD-13 requires both links ship as one change, and they do.
Link 1 — no channel for hook coverage to retract a baseline finding (#2201). Category B iterates the
baseline permission patterns and states flatly that each "must appear" in
permissions.deny, ratederror. Its only two off-ramps are keyed on the consuming repo writing prose: a documented exemptionin its own rules files (
required-permissions.md:138-142), or its own documented hook conventions(
:157-160). Neither is keyed on a hook that is actually installed and enabled, andgrep -rn "hook"across the whole skill returns no
hooks.jsonread, no plugin-hook enumeration, and no coverage conceptat all. So a repo that blocks
git push --forcewith aPreToolUsehook exiting 2 — whichpermissions.md says "stops the tool call before
permission rules are evaluated, so the block applies even when an allow rule would otherwise let the call
proceed" (raw fetch, 2026-08-11) — was told its security floor was missing.
"Narrowing the baseline" now carries a third narrowing: a family already blocked by an installed,
enabled
PreToolUsehook isinfo, noterror, whether the hook came from the repo or from a plugin.It obliges the report to name the residual — plugin disable/uninstall, per-guard opt-out, and
allowManagedHooksOnly/strictPluginOnlyCustomization— because hook coverage is contingent in ways adeny rule is not, and an
infothat hides that is worse than theerrorit replaced. And because theskill genuinely cannot enumerate a plugin's
hooks/hooks.json, a run that took no inventory now statesthe finding conditionally ("if a
PreToolUsehook onBashalready blocks this family, this findingis void") rather than asserting it. Fail open, not fail silent.
Verification sharpened the wiring half. The two existing off-ramps were not in Category B's
instruction path at all — the category delegates by pattern ("iterate the patterns in
required-permissions.md") and
audit-checklist.md:21-24says "assert presence per sub-category". Neithernames the off-ramp sections. A third off-ramp added to
required-permissions.mdalone would inherit thatsame weak wiring, so Category B and the checklist's B.1–B.3 severity table now both point at "Narrowing
the baseline", and the table says its ratings are the unnarrowed ones. (
audit-checklist.mdis not inthe ledger's fix sketch; it is added deliberately under AD-13's own logic — leaving a flat
errorratingthere would have left a third file asserting the opposite.)
Link 2 — the false positive was routed into
--fixas mechanical (#2202).procedures.md:49graded"Add missing baseline deny rules"
Auto-fixable: Yes (from checklist)/Requires judgment: No, andSKILL.md:212-214restated that in prose. It is now judgment-required with the judgment written out —is the family already covered, and would the addition suppress a gate the project built deliberately.
Moving a deny rule from local to project stays mechanical (that is #8961 placement, not a policy
change), and
SKILL.mdsplits the two the same way.Corrected rationale, stated so it is not shipped wrong. The filed claim was "no human judgment gate",
which is accurate as written and must not be read as "no human gate" — a confirmation gate exists at
SKILL.md:187-193and is already pinned by eval #2. Adding a confirmation prompt is therefore not thefix. The residual harm is narrower: the matrix told the user this class of edit needed no thought, next to
an offered reply
'all'that applies the lot in one keystroke. Two more scope statements kept honest —the applied change was more deny rules, which is fail-closed rather than a security regression; and the
ask-suppression harm requires a hook that returns an
askdecision, since a hook blocking byexit 2short-circuits before permission rules and suppresses nothing.
What review added, and it materially improved the change. Seven threads, all substantive, all
addressed:
be live:
disableAllHooks,allowManagedHooksOnly, andstrictPluginOnlyCustomizationwere namedonly as a future residual, and a hook one of them has already switched off blocks nothing, so under
any of them the finding stands unnarrowed. The hook must be on the tool surface the pattern defends:
sensitive-file-denyis aRead-pattern family, so aBash-only hook leavesRead/Grep/Globopen and retires nothing there. And it must block that family — force-push coverage says nothing
about
git clean -fd, nor a long flag about its short spelling.reading from Category D; Category D checks paths, readability, timeouts, matchers, exec-form
resolution, duplicates, and events, and never asked whether the hooks it inventoried could run. All
three lever names appeared nowhere in the skill. That is the same false-assurance shape this PR set
out to remove, so Category D now reads and reports all three (
info— a repo may set themdeliberately, and the reading is state, not a defect), and where the reading was not taken the
narrowing is unavailable rather than assumed clear.
presentation order, not a dependency ban, so Category B says to pull the reading forward or defer the
downgrade and revise the severity after Category D runs. On a scope-filtered run like
/audit permissions, which never reaches Category D, the narrowing is unavailable unless the operatorsupplies the state.
Fixed, then swept.
### Fixedwhile the bump isminor. Two graded outputs genuinely move, so the behavior changes are now under
### Changedwith thereason up front, the wiring repairs under
### Fixed, and the new material under### Added.Three evals, not one, and they grade the discriminations rather than the happy path:
baseline-deny-narrowed-by-installed-hookinfounder a live Bash hook, whilegit cleanpatterns it does not match andsensitive-file-denyRead patterns it cannot reach hold aterrorno-hook-inventory-states-the-finding-conditionallysuppressed-hook-does-not-narrow-the-baselinedisableAllHooks: truenarrows nothingNo trust surface widened: no new hook, no new grant, no new external read or write. The permission
guidance moves in the direction of fewer manufactured deny-rule additions, and the new Category D row is
a read-and-report of settings the audit already has in hand.
Test plan
There is no executable test for this change — it is skill content plus
evals.json, and this skill'sscripts/cover plugin-drift and settings structure, not Category B prose. The honest gates are thestatic ones, all run against the working tree:
Both
check-skillwarnings pre-date this change (the SKILL.md soft target and the absent Gotchassurface); this diff adds 4 lines to
SKILL.md. Every gate above was re-run after each review round;the output shown is from the final tree.
Related
Closes #2201
Closes #2202
Inbox item:
20260810-225905-claude-config-audit-category-b-hook-blindness.md(batch-4 ledgerI2,rows
A1+A3; rowV1is folded intoA3's judgment wording per the ledger and is deliberatelynot filed separately).
Ledger rows in
I2left filed-not-fixed by design:A2(baseline recommends the pattern class upstreamcalls fragile — MED, adjacent section of the same file),
A4(nothing reads a plugin'shooks/hooks.json— MED, and the reason the new off-ramp has to fail open rather than enumerate),
B5's Category G half(belongs to item
…-category-g-unmeasurable),MN1(LOW). Issue filing for those rows is held pendingthe operator's call on batch-4 filing volume (RECONCILE OR-4).