Defect
permission-rule-check.sh falls through to $PWD when it cannot resolve a repository root. Outside a
git repo that is whatever directory the session happens to stand in — on a developer machine, typically
the user profile — and the script then walks it unbounded with find, swallows every error, and exits
0. A timeout or a swallowed permission error is indistinguishable from a clean bill.
Evidence at HEAD
plugins/claude-config/skills/audit-permission-grants/scripts/permission-rule-check.sh:71-76:
if [[ -n "${PERMISSION_HYGIENE_FIXTURE_DIR:-}" ]]; then
ROOT="$PERMISSION_HYGIENE_FIXTURE_DIR"
else
ROOT="$(git rev-parse --show-toplevel 2>/dev/null | tr -d '\r')"
[[ -n "$ROOT" ]] || ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
fi
Aggravators, all confirmed by grep at HEAD:
- two unbounded
find "$ROOT" walks with stderr discarded — :222 and :259;
- no
-maxdepth, no -prune list;
- unconditional
exit 0 at :264 (the --count path) and :272 (the report path);
disable-model-invocation: false at SKILL.md:5, so the skill is model-invocable.
There is no refusal branch for an unresolvable root.
Not reproduced, deliberately
The originating report measured a 5m00s timeout that did not complete. That measurement is carried
forward unverified. Running the unbounded scan would sweep this machine's user profile, which the
operating instructions for this work forbid. The code path is provable from the repository without it;
only the magnitude rests on the unreproduced run.
What the fix must carry
-
Both output modes need the refusal. --count exits 0 at :264; a 0 printed from a scan that
resolved no root is exactly the clean-bill confusion this is about.
-
Reuse exit 2 rather than minting a code. SKILL.md:56 already documents exit 2 as the
environment-gap channel — "a missing jq exits 2 (report the environment gap rather than a clean
bill)" — and an unresolvable scan root is precisely an environment gap reported instead of a clean
bill. This keeps the advisory exit-0-for-findings contract intact.
-
The "always exits 0" statements must move together. grep at HEAD finds five, not the three
the ledger recorded: reference/criteria.md:20, and scripts/permission-rule-check.sh:24, :43,
:44, :50.
Ledger correction: the ledger cites SKILL.md:56 as a third "always exits 0" site. It is not —
grep -n "exits 0\|exit 0" over SKILL.md returns no match, and :56 is the jq-exits-2 line
quoted above. SKILL.md carries no always-exits-0 claim to update.
Grading
- Severity: HIGH — a model-invocable skill whose default failure mode is an unbounded filesystem
walk that exits 0.
- Provenance: AUDITOR_VERIFIED on the code path; the timing measurement is SELF_REPORTED and
unreproduced.
- Verdict: PRESENT_AT_HEAD.
- Dedup: none found.
- Originating inbox item: batch-4 ledger
I10, row A4.
Defect
permission-rule-check.shfalls through to$PWDwhen it cannot resolve a repository root. Outside agit repo that is whatever directory the session happens to stand in — on a developer machine, typically
the user profile — and the script then walks it unbounded with
find, swallows every error, and exits0. A timeout or a swallowed permission error is indistinguishable from a clean bill.
Evidence at HEAD
plugins/claude-config/skills/audit-permission-grants/scripts/permission-rule-check.sh:71-76:Aggravators, all confirmed by grep at HEAD:
find "$ROOT"walks with stderr discarded —:222and:259;-maxdepth, no-prunelist;exit 0at:264(the--countpath) and:272(the report path);disable-model-invocation: falseatSKILL.md:5, so the skill is model-invocable.There is no refusal branch for an unresolvable root.
Not reproduced, deliberately
The originating report measured a 5m00s timeout that did not complete. That measurement is carried
forward unverified. Running the unbounded scan would sweep this machine's user profile, which the
operating instructions for this work forbid. The code path is provable from the repository without it;
only the magnitude rests on the unreproduced run.
What the fix must carry
Both output modes need the refusal.
--countexits 0 at:264; a0printed from a scan thatresolved no root is exactly the clean-bill confusion this is about.
Reuse exit 2 rather than minting a code.
SKILL.md:56already documents exit 2 as theenvironment-gap channel — "a missing
jqexits 2 (report the environment gap rather than a cleanbill)" — and an unresolvable scan root is precisely an environment gap reported instead of a clean
bill. This keeps the advisory exit-0-for-findings contract intact.
The "always exits 0" statements must move together.
grepat HEAD finds five, not the threethe ledger recorded:
reference/criteria.md:20, andscripts/permission-rule-check.sh:24,:43,:44,:50.Ledger correction: the ledger cites
SKILL.md:56as a third "always exits 0" site. It is not —grep -n "exits 0\|exit 0"overSKILL.mdreturns no match, and:56is the jq-exits-2 linequoted above.
SKILL.mdcarries no always-exits-0 claim to update.Grading
walk that exits 0.
unreproduced.
I10, rowA4.