Skip to content

fix(claude-config): permission-rule-check has no inert-grant check, and Bash(~user/...) leaks a username unflagged #2397

Description

@kyle-sexton

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions