Split out of #2282 so two reproducing rows are not lost when #2382 auto-closes that issue. #2382 fixes rows A2, A3 and A19; these two are untouched and both reproduce at origin/main.
Rows
Evidence at HEAD
Both reproduced against the shipped pattern, assembled verbatim from the script:
$ _sl='/'; _bs='\'; _seg="[^${_sl}${_bs}*<>\${}~ ]"
$ P2="${_sl}Users${_sl}${_seg}|${_sl}home${_sl}${_seg}|[A-Za-z]:[${_sl}${_bs}]Users[${_sl}${_bs}]${_seg}"
$ printf '%s\n' 'Bash(~kyle/scripts/x.sh:*)' | grep -oE "$P2"
# (no output) — A12: not flagged
A12's mechanism, stated precisely. _seg excludes ~, so the class was written to exempt the portable ~/ home anchor — and ~user rides that exemption even though it is not portable at all. It names a specific account, leaks the username into version control, and per the permissions page's wrapper handling is not expanded in a Bash rule, so the grant is also inert. This is the one genuine P2 miss.
Do not fold the Windows env-var forms in under P2. Also unflagged, and deliberately not this row:
$ printf '%s\n' 'Bash(%USERPROFILE%/scripts/x.sh:*)' 'Bash($env:USERPROFILE\scripts\x.sh:*)' | grep -oE "$P2"
# (no output)
Checked against docs/conventions/permission-rule-hygiene/README.md:106-118, whose anti-pattern 2 is scoped to "hardcoded absolute machine/user paths" that "leak a username into version control". %USERPROFILE% and $env:USERPROFILE leak no username and are not absolute. They are a different defect — an unexpandable token making the rule inert, the same class as A7b — and no convention rule currently names them, so filing them under P2 would be inventing a rule. Fold them into A7b's new inert-grant check instead.
A7b's design constraint, from the packet's own correction. The new check must branch, not emit a flat remedy:
The detector already knows which file it is scanning, so the branch is computable.
${CLAUDE_PLUGIN_ROOT} is settled and the fix should not re-litigate it. It does not appear on the skills page's substitution list, and the plugins reference scopes its broad row to content, not frontmatter. This repo's own convention already ruled at permission-rule-hygiene/README.md:125-126: "${CLAUDE_PLUGIN_ROOT} is not among them … so a rule written with it stays a literal string, never matches, and the grant is inert."
Why P2 cannot simply be widened to catch A7b
_seg at scripts/permission-rule-check.sh explicitly excludes $ and {, so every ${…} form — correct or inert — is exempt from P2 by construction. A7b needs its own check, not a P2 tweak.
Related
Provenance
Severity: MED · Provenance: AUDITOR_VERIFIED (A7b, A12), with the A12/env-var split and the reproductions above re-run at origin/main by the validation pass.
Origin: handoff-inbox item 20260811-024628-claude-config-audit-permission-grants-defects-and-fleet-grant-hygiene
Ledger: .work/handoff-inbox-batch-4/ledgers/I10-permission-grants-fleet.md § A7b, A12
Split out of #2282 so two reproducing rows are not lost when #2382 auto-closes that issue. #2382 fixes rows A2, A3 and A19; these two are untouched and both reproduce at
origin/main.Rows
plugins/claude-config/skills/audit-permission-grants/scripts/permission-rule-check.sh(no such check exists) — nothing detects the inert${CLAUDE_PLUGIN_ROOT}-in-allowed-toolsshape, which is the check that would have caught every one of the seven grants fix(plugins): paired body+rule rewrite for seven interpreter-led allowed-tools grants #2221/fix(plugins): pair seven allowed-tools grants with the invocations they must match #2225 had to fix by hand.scripts/permission-rule-check.shP2 —Bash(~kyle/scripts/x.sh:*)leaks a username and is not flagged.Evidence at HEAD
Both reproduced against the shipped pattern, assembled verbatim from the script:
A12's mechanism, stated precisely.
_segexcludes~, so the class was written to exempt the portable~/home anchor — and~userrides that exemption even though it is not portable at all. It names a specific account, leaks the username into version control, and per the permissions page's wrapper handling is not expanded in a Bash rule, so the grant is also inert. This is the one genuine P2 miss.Do not fold the Windows env-var forms in under P2. Also unflagged, and deliberately not this row:
Checked against
docs/conventions/permission-rule-hygiene/README.md:106-118, whose anti-pattern 2 is scoped to "hardcoded absolute machine/user paths" that "leak a username into version control".%USERPROFILE%and$env:USERPROFILEleak no username and are not absolute. They are a different defect — an unexpandable token making the rule inert, the same class as A7b — and no convention rule currently names them, so filing them under P2 would be inventing a rule. Fold them into A7b's new inert-grant check instead.A7b's design constraint, from the packet's own correction. The new check must branch, not emit a flat remedy:
${CLAUDE_SKILL_DIR}form. https://code.claude.com/docs/en/permissions and the skills page: "Claude Code substitutes${CLAUDE_SKILL_DIR}and${CLAUDE_PROJECT_DIR}in two places: the skill's markdown content, and Bash rules in theallowed-toolsfrontmatter."bin/— see the Known gap atdocs/conventions/permission-rule-hygiene/README.md:177-179and claude-config(audit-permission-grants): remediation prescribes a bare-name-on-PATH pattern that is unavailable on the measured platform #1398.The detector already knows which file it is scanning, so the branch is computable.
${CLAUDE_PLUGIN_ROOT}is settled and the fix should not re-litigate it. It does not appear on the skills page's substitution list, and the plugins reference scopes its broad row to content, not frontmatter. This repo's own convention already ruled atpermission-rule-hygiene/README.md:125-126: "${CLAUDE_PLUGIN_ROOT}is not among them … so a rule written with it stays a literal string, never matches, and the grant is inert."Why P2 cannot simply be widened to catch A7b
_segatscripts/permission-rule-check.shexplicitly excludes$and{, so every${…}form — correct or inert — is exempt from P2 by construction. A7b needs its own check, not a P2 tweak.Related
${CLAUDE_SKILL_DIR}substitution scope.Recommendthat A7b's "otherwise" branch must avoid re-prescribing.Provenance
Severity: MED · Provenance: AUDITOR_VERIFIED (A7b, A12), with the A12/env-var split and the reproductions above re-run at
origin/mainby the validation pass.Origin: handoff-inbox item
20260811-024628-claude-config-audit-permission-grants-defects-and-fleet-grant-hygieneLedger:
.work/handoff-inbox-batch-4/ledgers/I10-permission-grants-fleet.md§ A7b, A12