Skip to content

feat(claude-config): stable audit-instructions finding identity (#4116) - #4851

Merged
kyle-sexton merged 9 commits into
mainfrom
cursor/4116-finding-identity-37e9
Sep 29, 2026
Merged

kyle-sexton merged 9 commits into
mainfrom
cursor/4116-finding-identity-37e9

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Closes #4116

Summary

Give every audit-instructions finding a stable audit-pass identity, and persist I30–I33 from the lane.

Deviation from #4116: the issue says a lane-sourced finding's confidence is fixed to high because "a deterministic detector still fired". No scanner produces I30–I33, so that premise does not hold; lane rows leave Confidence blank instead of claiming a detector confidence.

Fix

  • Identity is (check, claim, sites): check is claude-config/audit-instructions/<id>, claims live in reference/finding-identity.md, anchors are anchor/v1 excerpts with the heading-path discriminator, and an I15 conflict is one finding with two sites.
  • scripts/finding-ids.sh derives finding_id/v1 and group/v1 through audit-pass's finding-identity.sh.
  • emit-findings.sh --from-lane emits I30–I33 (detector-findings 3.2.0 crosswalk). I32 in frontmatter and I31/I33 outside a spoke are declined with a named reason. Phase D table gains a Finding ID column.
  • Rows that share a finding_id are emitted once, with an Identity collisions: finding_id=<id> count=<n> line under ## Surfaces, per the audit-pass identity contract.
  • The I32 fallback target matches only /plugin:skill or a backticked plugin:skill; anything else reports shape=.
  • detector-findings README adopter row states the I30–I33 tier spread.
  • Version 0.51.27 with CHANGELOG.

Verification

  • finding-ids.test.sh: 28 passed (a new case proves a fenced ## not a heading is not in the heading path).
  • emit-findings.test.sh: 170 passed (new Case 16: identical lines collide to one row with count=2; 10:30 yields shape=).
  • scripts/check-detector-findings-crosswalk.sh, scripts/validate-plugins.sh, scripts/check-changelog-parity.sh --check --check-order, scripts/check-changed-skills.sh origin/main: pass.

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hx63mvV8t39y644DQ3uLnn

@cursor
cursor Bot force-pushed the cursor/4116-finding-identity-37e9 branch 3 times, most recently from 333f142 to c46859c Compare September 28, 2026 12:24
Give every finding an audit-pass (check, claim, sites) identity via
finding-ids.sh, and emit I30–I33 from lane findings through
emit-findings.sh --from-lane with detector-findings 3.2.0 crosswalk rows.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/4116-finding-identity-37e9 branch from c46859c to 84051dc Compare September 28, 2026 12:38
kyle-sexton and others added 5 commits September 28, 2026 11:46
# Conflicts:
#	plugins/claude-config/.claude-plugin/plugin.json
#	plugins/claude-config/CHANGELOG.md
- SKILL.md back under the 500-line cap: Finding ID prose points at
  reference/finding-identity.md instead of restating it.
- finding-ids.sh follows symlinks with plain readlink in a bounded loop
  instead of GNU-only readlink -f.
- finding-ids.test.sh renames AB/BA to FWD/REV for typos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PT4esxbdC7eQiwQxy35Mie
Identical sentences under one heading path share a finding_id; emit the
finding once and name the collision with its occurrence count in
## Surfaces, per audit-pass finding-identity. The I32 fallback target now
reads only /plugin:skill or a backticked plugin:skill, so a clock time
falls back to shape=. Pins the post-fence heading path in finding-ids
tests and limits the adopter row's downgrade sentence to the I28 rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hx63mvV8t39y644DQ3uLnn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hx63mvV8t39y644DQ3uLnn
@kyle-sexton
kyle-sexton marked this pull request as ready for review September 28, 2026 18:00
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-28T18:07:06.139037Z e00f825 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

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


Claude finished the security review

  • Gather context (PR diff, changed files)
  • Invoke /review:security-review per custom instructions
  • Post findings

Reviewed the two new scripts (finding-ids.sh, emit-findings.sh) and the doc/context changes in this PR. Most of the code is careful about the usual shell pitfalls (quoted expansions, cd --/basename --/dirname -- to block option injection, ..-traversal fencing with fail-closed behavior, temp-file-mediated cygpath invocation instead of string interpolation). One finding below.

IMPORTANT — finding-ids.sh reads and excerpts any file under $HOME, not just the Claude-config subtree, before persisting an identity

File: plugins/claude-config/skills/audit-instructions/scripts/finding-ids.sh
Lines: 160–189 (surface_of) and 233–270 (site_of)

surface_of() accepts any resolved path that is a prefix-match under "$ROOT" or under the entirety of $HOME ("$abs" == "$HOME_P"/*", line 184), not scoped to ~/.claude (or $CLAUDE_CONFIG_DIR). Once a row's path clears that check, site_of() (called from process_row, lines 302/310) reads the actual line text out of that file via line_context and hands it to finding-identity.sh anchor --excerpt "$text" ... to build the finding's anchor/identity — i.e. this script will happily open and excerpt any file the process can read anywhere under the user's home directory (~/.ssh/id_rsa, ~/.aws/credentials, ~/.netrc, etc.), as long as a row names it.

Exploit path: rows here are <path>:<line>:<check-id>, and for the new I30–I33 families they come from the Phase B/C model lane described in context/execution-and-report.md — a subagent reading (potentially adversarial) instruction content and proposing findings, which this PR wires straight into --from-lane → finding-ids.sh (emit-findings.sh lines 272–274). A prompt-injection payload in an audited file that gets the lane to fabricate a row such as /home/<user>/.ssh/id_rsa:1:I30 — or a repo-committed symlink under context//reference/ resolving outside the repo into $HOME — clears surface_of's home-directory check and causes finding-ids.sh to open and excerpt that file's content into the emitted identity/anchor. This is exactly the class of attack the audit-instructions skill is designed to catch elsewhere (self-referential/injected instructions), so the row-provenance trust here is worth hardening rather than assuming benign input.

Note: emit-findings.sh's own findings table additionally requires relativize_in_repo() to succeed before a row is printed (repo-relative Location only), so a user:-scoped row is declined there and its content does not reach the written ## Findings table via that path. But finding-ids.sh is also documented as a standalone, directly-invoked tool (reference/finding-identity.md lines 40–50, and Phase D per SKILL.md), and in that direct-invocation / --records path there is no equivalent fence — the home-directory-wide surface and its excerpted anchor are exactly what get emitted. Whether the anchor format embeds the literal excerpt text or only a hash of it is defined in the sibling audit-pass plugin (../../audit-pass/scripts/finding-identity.sh), which isn't part of this diff/repo checkout, so I can't confirm the exact severity of content disclosure from here — but the read-and-excerpt of arbitrary $HOME content on a lane-supplied path, unscoped to the Claude-config surface the skill is meant to audit, is a new, concrete data-flow this PR introduces.

Suggested fix: scope the "user surface" branch of surface_of() to ${CLAUDE_CONFIG_DIR:-$HOME/.claude} (the actual claimed audit surface per persist-findings.md's "Phase A inventory spans user-level surfaces under CLAUDE_CONFIG_DIR") rather than all of $HOME.

Fix this →

No other security-relevant issues found in this diff — the shell-injection surfaces (path handling, temp-file usage, YAML/markdown escaping) are handled defensively, and GitHub Actions hardening is out of scope for this lane.

@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: e00f8256bb

ℹ️ 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/claude-config/skills/audit-instructions/scripts/emit-findings.sh Outdated
Comment thread plugins/claude-config/skills/audit-instructions/scripts/emit-findings.sh Outdated
… as typos

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

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


Security review — /review:security-review

  • Gather context (PR diff, changed files, prior review comments)
  • Invoke /review:security-review per custom instructions
  • Post findings

Skip gate triggered. The diff since the last full security review on this PR (commit e00f825, reviewed above) is a single commit, 04c66dc, that only renames local test variables in emit-findings.test.sh (DOUT→DUPOUT, DROWS→DUPROWS) for spell-check compliance. No source script, no crosswalk logic, and no input-handling code changed — there is no new security-relevant surface for this lane to review.

I re-checked the one finding from that prior review against the current head rather than re-running a full pass:

Still open — IMPORTANT: finding-ids.sh's surface_of() accepts any path under $HOME, not just the audited ~/.claude surface
Lines 165–189 (surface_of), called from site_of, are byte-for-byte unchanged since e00f825. The "$abs" == "$HOME_P"/*" check at line 184 scopes the "user surface" branch to the entire home directory rather than ${CLAUDE_CONFIG_DIR:-$HOME/.claude}, so a lane-supplied row naming a path anywhere under $HOME (e.g. ~/.ssh/id_rsa:1:I30) still clears the surface check and gets opened.

One correction to the earlier writeup, from reading ../../audit-pass/scripts/finding-identity.sh (present in this checkout, at plugins/claude-config/skills/audit-pass/scripts/finding-identity.sh, which wasn't inspected in the prior pass): an excerpt anchor is e:<sha256(normalized excerpt) truncated to 12 hex>:<n> — a truncated hash, never the literal line text — and finding-ids.sh --records emits only {surface, anchor}, not raw content. So the concrete impact is narrower than "reads and excerpts arbitrary $HOME file content into the output": it's an arbitrary-file-read-and-hash primitive under an attacker-supplied path (existence/readability confirmation, plus a low-value hash oracle against a guessed line), not literal secret disclosure. That's still worth fixing — it's a real, unscoped file read driven by adversarial (lane/prompt-injectable) input, on a tool documented to audit only Claude-config surfaces — but the severity is IMPORTANT rather than CRITICAL.

Since the code path is unchanged from the prior review and this correction is informational, I haven't filed a new inline comment for it; the fix suggested previously still applies: scope HOME_P's prefix check to ${CLAUDE_CONFIG_DIR:-$HOME/.claude} instead of all of $HOME, and update finding-ids.test.sh's user-scope test accordingly.

No new security issues found in the 04c66dc diff itself.
· branch cursor/4116-finding-identity-37e9

…nces/ I33

Heading-shaped lines inside a block HTML comment outside a fence no longer
enter the anchor heading path. I33 accepts references/ spokes as I31
already did. An I32 line with more than one skill-ref candidate falls back
to shape= since the writer cannot tell which one is absent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

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


Security review — /review:security-review

  • Gather context (PR diff, changed files, prior review comments)
  • Invoke /review:security-review per custom instructions
  • Post findings

Skip gate triggered. The diff since the last full security review on this PR (commit e00f825, reviewed here, re-checked against 04c66dc here) is one new commit, 7dba2d2, which resolves the three Codex P2 review comments and adds matching test coverage:

  • finding-ids.sh's line_context awk (now lines 190–219) gained an incomment state that skips heading-shaped lines inside a block `` comment outside a fence, so such a line no longer enters the anchor heading path. Traced the rule ordering by hand — incomment is checked before the fence-toggle and heading-capture rules, and is only set when `!infence`, so a `<!--` inside a fenced code block still isn't treated as a comment. Covered by a new case in `finding-ids.test.sh`.
  • emit-findings.sh's in_rule_surfaces for I33 now accepts references/ alongside reference/ and context/, matching what I31 already allowed and what the I33 catalog documents (diff).
  • fired_marker's I32 branch (lines 586–599) now counts all slash-form and backtick-form skill-ref candidates in the line before picking one; with more than one candidate it falls back to shape="route-to-absent-skill" instead of silently naming the first (possibly valid) match as the unresolved target.

All three fixes are narrow, match the reported failure mode, and are unit-tested (finding-ids.test.sh Case for the comment-heading fix; emit-findings.test.sh Case 17 for the references/ spoke and the two-candidate I32 line). I wasn't able to execute the test scripts myself in this sandbox (the auto-mode permission classifier denied running them directly), so this is verified by reading the awk/bash logic and the added assertions rather than by re-running the suite — the PR body's own numbers (170/28 passed) are consistent with what the diff adds.

Still open — not touched by this commit, no new severity assessment needed: finding-ids.sh's surface_of() (lines 165–189, HOME_P check unchanged from prior review) still scopes the "user surface" branch to all of $HOME rather than ${CLAUDE_CONFIG_DIR:-$HOME/.claude}. A lane-supplied row naming a path anywhere under $HOME (e.g. ~/.ssh/id_rsa:1:I30) still clears the surface check and gets opened by site_of(), yielding an arbitrary-file-read-and-hash primitive on adversarial (lane/prompt-injectable) input, on a tool documented to audit only Claude-config surfaces. Confirmed byte-identical to the version reviewed at 04c66dc. Suggested fix stands: narrow HOME_P's prefix check to ${CLAUDE_CONFIG_DIR:-$HOME/.claude} and update finding-ids.test.sh's user-scope test accordingly.

No new security issues introduced by 7dba2d2.
· branch cursor/4116-finding-identity-37e9

@claude

claude Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

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


Code review (/review:code-review lane)

  • Gather context (PR diff, changed files)
  • Invoke /review:code-review per custom instructions
  • Post findings

Scope: correctness and maintainability in the two new/changed scripts (finding-ids.sh, emit-findings.sh) and the doc/context changes. Security-shaped findings are scoped to the separate security lane, which already reviewed this PR.

No new blocking findings. I read finding-ids.sh in full (identity derivation, symlink-following surface_of, the heading-path/fence/comment-tracking line_context awk, process_row's pairwise/single-site handling) and the changed regions of emit-findings.sh (the --from-lane intake, IDMAP construction, fired_marker, in_rule_surfaces, relativize_in_repo/win_long/has_dotdot_segment, and the identity-collision dedup in the main awk loop). I also traced the three items the automated Codex review left as P2 suggestions against the current head (7dba2d2) and confirmed each is already fixed there, not just proposed:

  • Heading-shaped lines inside a block HTML comment are now excluded from the heading path (line_context's incomment tracking, finding-ids.sh#L209-L212), and it's covered by a dedicated test case (finding-ids.test.sh "a heading-shaped line inside a block HTML comment is not in the heading path").
  • I33 now admits the plural references/ spoke directory alongside context//reference/ (emit-findings.sh#L346-L349).
  • fired_marker's I32 branch now counts all slash/backtick skill-reference candidates in the sentence first and falls back to a generic shape= when there's more than one, instead of always reporting the first syntactic match (emit-findings.sh#L591-L600).

Beyond re-verifying those, I looked for a cross-file inconsistency between emit-findings.sh's relativize_in_repo() (which anchors on a caller-pwd-derived root specifically to survive a documented Git-Bash/MSYS toplevel-spelling mismatch) and finding-ids.sh's own, simpler root resolution (git rev-parse --show-toplevel physically resolved, no --root passed by the caller). On reflection this isn't a solid finding: finding-ids.sh compares paths after a real cd/pwd -P filesystem resolution on both sides, which should converge on the same canonical spelling regardless of how the input path was originally written, unlike the pure string-prefix matching relativize_in_repo() has to do inside awk — so I'm not reporting it without a reproducible failing input.

Not independently verified: I don't have Bash execution allowed in this session, so I could not run finding-ids.test.sh / emit-findings.test.sh myself; the PR's stated 28/170 pass counts are author-claimed, not confirmed by me here.
· branch cursor/4116-finding-identity-37e9

@claude

claude Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

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


Security review — /review:security-review

  • Gather context (PR diff, changed files, prior review comments)
  • Invoke /review:security-review per custom instructions
  • Post findings

Skip gate triggered. Diffing the last fully-reviewed commit (7dba2d2, re-checked at 04c66dc and 7dba2d2 in prior comments on this PR) against current HEAD (5d0f897) shows finding-ids.sh and emit-findings.sh are byte-identical — git diff 7dba2d2..HEAD -- .../finding-ids.sh .../emit-findings.sh is empty. The only changes since the last review are a Merge origin/main bringing in unrelated main-branch commits (version bumps for unhobble/audit-permission-grants, a routing-text reword in SKILL.md changing "your session" → "this session", and the resulting plugin.json/CHANGELOG.md version renumbering to 0.51.27). Confirmed against git diff origin/main...HEAD --stat, which matches the PR's own 12 changed files with no new ones. No new security-relevant surface for this lane to review.

Still open — unchanged, re-verified at current HEAD: finding-ids.sh's surface_of() (lines 165–189) still scopes its "user surface" branch to the entire $HOME ("$abs" == "$HOME_P"/*", line 184) rather than ${CLAUDE_CONFIG_DIR:-$HOME/.claude}, the surface the skill is documented to audit. A lane-supplied row naming a path anywhere under $HOME (e.g. ~/.ssh/id_rsa:1:I30) still clears the check and is opened by site_of(), which reads and hashes the file's content into the emitted anchor. As corrected in an earlier pass on this PR: the anchor is a truncated SHA-256 of the excerpt (../../audit-pass/scripts/finding-identity.sh), not the literal line text, and --records emits only {surface, anchor} — so this is an arbitrary-file-read-and-hash primitive on adversarial (lane/prompt-injectable) input, not literal secret disclosure. Severity: IMPORTANT.

Suggested fix (unchanged from prior reviews): narrow HOME_P's prefix check to ${CLAUDE_CONFIG_DIR:-$HOME/.claude} and update finding-ids.test.sh's user-scope test accordingly.

Fix this →

No new security issues introduced since the last review.
· branch cursor/4116-finding-identity-37e9

@claude

claude Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

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


Code review (/review:code-review lane)

  • Gather context (PR diff, changed files, prior review comments)
  • Invoke /review:code-review per custom instructions
  • Post findings

Skip gate triggered (rule 4: this head already has a successful review from this lane that still applies).

The last full code-review pass on this PR ran at commit 7dba2d2 (here) and found no blocking issues. The head has since moved to 5d0f897 via two commits, 4081930 and 5d0f897, both merges of origin/main bringing in an unrelated change (#3563 / #5082, the unhobble deletion-tier grammar) plus routine version bumps.

I diffed 7dba2d2..HEAD and confirmed none of the files this PR actually owns changed:

  • plugins/claude-config/skills/audit-instructions/scripts/{finding-ids,emit-findings}.sh and their *.test.sh — byte-identical since 7dba2d2.
  • plugins/claude-config/skills/audit-instructions/{context/persist-findings.md,reference/finding-identity.md,evals/evals.json,SKILL.md} — byte-identical since 7dba2d2.
  • docs/conventions/detector-findings/{README.md,CHANGELOG.md} — byte-identical since 7dba2d2.

The only diffs in that range touch plugins/claude-config/skills/audit-instructions/reference/criteria.md and reference/bundled-claude-api.md — but those two files are unchanged by this PR relative to origin/main (they aren't in this PR's own file list); the diff is origin/main's content arriving through the merge, not something authored on this branch. There is no new review surface for this lane.

No new findings. The prior pass's read of the code (identity derivation, symlink-following surface_of, the heading-path/fence/comment-tracking line_context awk, --from-lane intake, IDMAP construction, fired_marker, in_rule_surfaces, dedup/collision handling) still applies, and all three Codex P2 suggestions remain fixed at 7dba2d2 as previously confirmed.
· branch cursor/4116-finding-identity-37e9

@kyle-sexton
kyle-sexton merged commit 395ec40 into main Sep 29, 2026
27 checks passed
@kyle-sexton
kyle-sexton deleted the cursor/4116-finding-identity-37e9 branch September 29, 2026 01:15
kyle-sexton added a commit that referenced this pull request Sep 29, 2026
No related issue: partial delivery of #4115; its AC2 (five
previously-retained findings still seed) cannot be verified because
those five findings are not recorded anywhere, so #4115 stays open for
that check.

## Summary

Refs #4115. Fence the I6 pre-scan to the docs-hygiene
`rule-negation-without-positive` gate set (minus its hard-guardrail
carve-out), and report I33 as one finding per spoke with an
opener-sentence excerpt anchor.

## Fix

- I6 rows are sentences that open with a prohibition and carry neither a
paired positive nor a rationale marker. Soft-wrapped lines join first;
frontmatter, fences, tables, headings, and HTML comment openers are
skipped. A fence closes only on the opener's character, at least the
opener's length, with only whitespace after it; blockquoted fences are
recognized. `--i6-counts` prints raw and surviving counts.
- audit-noise's hard-guardrail carve-out is not adopted: a guardrail
"never" still owes I6's fallback rationale (I7), so it stays a
candidate. Stated in the scanner header and CHANGELOG.
- I6 emits one row per physical line, skips Setext headings, and reads a
cue wrapped in underscore emphasis.
- I33 is one finding per spoke, anchored on the opener sentence with the
heading path as discriminator. Phase D rolls I33 up per plugin.
- `criteria.md` catalog 1.24.0. Four fixtures plus evals 27 and 28.
- Version 0.51.28 with CHANGELOG.

## Verification

- `instruction-scan.test.sh` 139/139 (Case 3f: fence closing; Case 3g:
one row per line, Setext headings skipped, underscore-emphasized cues
read), `emit-findings.test.sh` 172/172, `finding-ids.test.sh` 29/29,
`lane-runs.test.sh` 52/52.
- Count reproduction at `2dfaaa40` (990 files) prints `I6 raw=6608
surviving=913`:
`find . -name '*.md' -not -path './.git/*' \( -path
'./plugins/*/skills/*' -o -path './plugins/*/agents/*' -o -path
'./.claude/*' -o -name CLAUDE.md -o -name AGENTS.md \) -print0 | sort -z
| xargs -0 bash
plugins/claude-config/skills/audit-instructions/scripts/instruction-scan.sh
--i6-counts`
- `scripts/validate-plugins.sh`, `scripts/check-changelog-parity.sh
--check --check-order`, `scripts/check-changed-skills.sh origin/main`:
pass.
- Known limits: lines inside a multi-line HTML comment are still read;
an enumeration after the cue (`Never commit files, logs, or caches.`)
reads as a named alternative, as in audit-noise.

## Related

- Refs #4115 (AC2 open).
- Finding identity for I30–I33 is #4116 (PR #4851).

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

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Sep 29, 2026
…e and permission lint (#5311)

Refs: #3563
Refs: #3568
Refs: #4027
Refs: #4094
Refs: #4113
Refs: #4114
Refs: #4115
Refs: #4116
Refs: #4583
Refs: #4600
Refs: #4656

## Summary

Fixes the `plugins/claude-config` findings from the audit of the
unattended Cursor PR run. Every change is inside
`plugins/claude-config/`; `hooks/exec-bash.mjs` is untouched.

- `audit-instructions`: the findings relay now follows `criteria.md`
(I31 and I33 surfaces, I32 tier by arm), `finding-ids.sh` refuses
non-instruction files under `$HOME`, `SKILL.md` records the
subagent-window claim and the I33 dispatch rule, and
`execution-and-report.md` (a second home for facts other files own) is
deleted.
- `unhobble`: delivers the #4094 must-fix items (product-surface class,
session branch, convention oracle test, `readd` refusal) and
nice-to-have items (`status` fields, ledger grouping, `decide`), and
removes silence as a deletion warrant (#3563).
- `audit-permission-state`: independent review of the #5154
`defaultMode` lint, which reached main under a "Do not merge" body with
no review.
- `audit`: the live-hook ask row carries the unattended-lane note
(#4600). `conflict-criteria.md` loses its dead repo-root link (#3568).
- `README.md` and `CHANGELOG.md` are corrected; version 0.52.0.

## Fix

- I15 link (#3568): `conflict-criteria.md` used six `../` segments that
resolved outside the repository, and an installed plugin does not carry
`docs/`. It now names the path in code font.
- Relay (#4116, #4656): `emit-findings.sh` admits I31 in any
skill-directory file and I33 in any skill-loaded file except `SKILL.md`,
names the nearest ancestor `SKILL.md` as the I33 hub, and tiers I32
CRITICAL under `plugins/` and IMPORTANT elsewhere. The persist-admission
text in `criteria.md` and `persist-findings.md` names the scanner (I28,
I29) and lane (I30 to I33) intakes.
- `finding-ids.sh` `surface_of()` (#4116, the security-lane finding left
open at merge of #4851): the user surface stays home-wide, and now
admits only instruction-file shapes. Basis: Claude Code reads
`CLAUDE.md`, `CLAUDE.local.md` and `AGENTS.md` from the working
directory and every directory above it, and follows imports to absolute
paths, so narrowing to `${CLAUDE_CONFIG_DIR:-$HOME/.claude}` would drop
real user surfaces. Any markdown file, `settings.json`,
`settings.local.json` and `hooks.json` inside a `.claude` tree or the
resolved `CLAUDE_CONFIG_DIR`, and files beneath a `skills/` directory in
those trees are admitted; `~/.ssh/config`, credentials, transcripts and
dotfiles are refused as `surface-not-an-instruction-file`. The scope
carries its four-part verification record in the code. This is the
citation for dismissing the open PR-comment finding on #4851.
`relativize_in_repo` is unchanged.
- `audit-instructions` docs (#4113, #4114, #4115, #4656): lane sizing
loses its ticket back-references, the subagent-window claim gets a
four-part record, the read-only contract covers lane reports and
run-state writes, I33 rows count as one dispatch outside per-lane
verifier batches, and `SKILL.md` regains line headroom under the
500-line cap.
- `audit-engine.sh` (#4600): the `HOOKS_LIVE=1` row ends with
`$lane_note`, like the other ask-rule rows.
- `unhobble` (#4094, #3563): the one-row watch disqualifier versus the
two-row re-add grammar is this skill's own rule (`SKILL.md` Phase 4 step
1), not an upstream one. `SKILL.md` and `evals/evals.json` (evals 27 to
30 added; 18 and 22 tightened). Items 1 to 4 and 7 of #4094 were already
on main (#5081). Items 5, 6, 8, 9 and 10 to 12 land here.
- #5154 review (#4027): `permission-state.sh` classified a settings file
`invalid-json` when the last key under `permissions` was a string,
because `jq -e` reads only the last output of a per-key check, so
`C2-defaultMode` and `C5-disableType` could not fire on real files.
Fixed with tests that fail against the old reader. The masking claim now
carries its condition, and the unsourced auto version boundary is
removed. Claims were checked against pages fetched 2026-09-29 (recorded
in the commit body).
- #4656 equivalence proof: regenerated at the #4839 pair; every output
matched byte for byte and the grep count is constant. Evidence only, no
change committed.
- `CHANGELOG.md` (#4027): the repeated 0.51.17 and 0.51.18 bodies became
pointers, 0.51.7 records that it shipped through #5154, and the header
notes the unreleased reserved versions.
- Cross-group requests applied (#4027): `criteria.md` I21 and the
`audit` effort-pin row state the effort defaults from the model-config
resolution order fetched 2026-09-28 (Opus 5.5 and Sonnet 5.5 default to
`medium`, Opus 4.7 to `xhigh`; the first-run-hold clause is gone), with
Verified, Source and the recheck trigger restamped. `unhobble`
`SKILL.md` and eval 15 stop restating the `block-hook-bypass` exit code
and heredoc coverage and defer to the hook.

In-place changelog corrections:

- 0.51.18: body replaced by a pointer; it repeated the 0.51.16 Fixed
entry (released by #5159) and a reworded near-duplicate of the 0.51.10
Changed entry (released by #5161).
- 0.51.17: body replaced by a pointer; it repeated the 0.51.15 entry
(released by #5156).
- 0.51.7: states it shipped through #5154 (commit 9c2db71), not #5059
(closed unmerged).
- File header: notes that 0.51.8 to 0.51.9 and 0.51.11 to 0.51.14 were
reserved by parallel branches and never released.

No issue is closed by this PR. #4094 stays on `Refs` because its 14-item
checklist is not verified complete here, and the others are
decision-held or have leftovers in other groups.

## Verification

Run in `/home/kyle/worktrees/ccp-fix-claude-config` after merging
`origin/main` (aebb9bb):

- All 34 `plugins/claude-config/**/*.test.sh` suites: exit 0 (includes
`emit-findings.test.sh`, `finding-ids.test.sh`,
`finding-identity.test.sh`, `permission-plane-lint.test.sh`,
`permission-state.test.sh`, `audit-engine.test.sh`, `lane-runs.test.sh`,
`instruction-files.test.sh`).
- `bash scripts/check-detector-findings-crosswalk.sh --check`: OK, 38
rule rows.
- `bash scripts/check-changed-skills.sh origin/main`: 4 skills checked,
0 failed.
- `bash scripts/check-changelog-parity.sh --check --check-order`,
`--check-bump origin/main`, `--check-preserved origin/main` (190
headings compared): pass.
- `bash scripts/validate-plugins.sh`: all manifests and the catalog
validated.
- `shellcheck` on every changed `.sh` file (the SC2016 hit on the
`emit-findings.test.sh` rule fixture line is suppressed with a reason)
and `check-evals-quality.sh` on the changed evals pass; `check-skill.sh`
warnings that remain were present on main.

#5154 review outcome (#4027):

- Claims checked: the `defaultMode` masking claim, the `permissions` key
shapes the lint reads, and the auto-mode version boundary, each against
Claude Code docs pages fetched 2026-09-29.
- Evidence: the masking claim holds only under a stated condition, now
carried in the text; the auto version boundary had no source and is
removed; `permission-state.sh` reproduced the `invalid-json`
misclassification when the last `permissions` key was a string.
- Defect fixed: the per-key `jq -e` check counted only the last output,
so `C2-defaultMode` and `C5-disableType` could not fire on real files.
New tests in `permission-state.test.sh` fail against the old reader and
pass now.

## Related

Audit report: `.work/audit/REPORT.md` findings by issue number: #3563,
#3568 (row 3c), #4027 (3b), #4094 (3b), #4113 (3b), #4114, #4115, #4116,
#4583, #4600, #4656 (3c).

Cross-group requests (not done here):

- core-docs: `docs/plugin-philosophy.md` #4583 structure and citation
defects, the `docs/upstream/claude-code.md` ledger re-point from #5059
to #5154, #5159 and #5161, the row 257-089 status, the FORCE decline
reconciliation, and review of the #5154 philosophy changes.
- playbooks: review of the #5154 boris changes.
- scripts: `check-changelog-parity.sh` should reject a CHANGELOG entry
whose body is byte-identical to an earlier entry.
- source-control: a body containing "Do not merge" must block the
unattended merge lane.
- review: closing comment on #3566.
- ci: reopen #4094 and comment that this PR delivers items 5, 6, 8, 9,
10 to 12.
- conventions: `docs/conventions/detector-findings/README.md` crosswalk
rows for I31, I32 and I33 restate the surface set and the I32 tier as
`criteria.md` defines them.

Cross-group requests received and not applied here:

- playbooks (docs/upstream row 257-083 rewrite): forwarded to core-docs,
which owns `docs/upstream/claude-code.md` and already edits that row.
- ci (#4094): items 5, 6, 8, 9 and 10 to 12 already land in this PR.
- review (#3566): the review group posts that comment.
- tracker (#4656): label removed and results noted on the issue.
- context-guard (row 260-002 home), context-budget (Items column),
skill-quality and session-flow follow-ups: added to the #4027 packet as
open questions. decisions-docs (#3568): both questions added to the
#3568 packet. Nothing implemented.

Issue operations done: #4027 and #4115 reopened with decision packets,
packets on #3568 and #4027 extended, comments on #4113, #4116, #4656,
#4583 and #3563.

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

https://claude.ai/code/session_01EugXnFddtpHcY5gTuyEirB

---------

Co-authored-by: Claude Opus 5.5 <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.

audit-instructions: adopt (check, claim, sites) finding identity and admit I30 to I33 to --persist-findings

2 participants