Skip to content

fix(claude-config): permission-rule-check's loadability filter needs a model, not more path exclusions #2406

Description

@kyle-sexton

Split out of #2283 row A8 while implementing that issue. The row reproduces at HEAD, but its
stated rationale is falsified
, so the remedy it names would install a new defect rather than remove
one. Filing it separately with the corrected mechanism instead of shipping the filed direction.

What A8 says, and what is actually true

A8 asks to generalize permission-rule-check.sh's single path exclusion. Its argument is that the
vendor/ exclusion's own justification carries over:

plugins/claude-config/skills/audit-permission-grants/reference/criteria.md at HEAD —

Frontmatter files under a vendor/ path segment are skipped: they are vendored upstream
references, not loadable skills/agents/commands, so their allowed-tools never take effect and a
finding on them would be a false positive.

A8 reads that as "anything not at a canonical skills path is not loadable" and extends it to
node_modules/, worktrees, marketplace mirrors, and versioned plugin-cache copies.

That step does not hold. https://code.claude.com/docs/en/skills, fetched 2026-08-12:

Skills also load from nested .claude/skills/ directories below your working directory. When
Claude reads or edits a file in a subdirectory, skills from that subdirectory's .claude/skills/
become available. This lets a monorepo package provide its own skills that apply when working on
that package, even if the session started at the repo root.

and, on the same page:

Skills in nested .claude/skills/ directories below your starting directory aren't loaded at
startup. They load the first time Claude reads or edits a file inside that subdirectory, and stay
available for the rest of the session.

So node_modules/<pkg>/.claude/skills/<name>/SKILL.md is a loadable skill the moment Claude
touches a file under that package. A blanket node_modules/ exclusion would make P2 — an
error-tier check whose whole subject is a leaked username
— silently blind to a live grant. That
is strictly worse than the noise A8 is about, and it is the same failure shape as the // path
exemption on #2282/#2382: a filed fix direction that was exactly backwards once the docs were read.

The second half needs an oracle the detector does not have: distinguishing an installed plugin
version from an orphaned cached one requires reading installed_plugins.json, which introduces a new
resolution path and a new set of failure modes in a script whose current contract is "walk one root".

What shipped instead (claude-config 0.36.0)

The half of A8 that is defensible without a loadability model: the exclusion discloses itself.
The vendor/ filter moved out of the find predicate into the loop so every run reports how many
candidate files it removed, inside the new coverage block:

  frontmatter: 12 allowed-tools block(s) parsed from 214 candidate file(s); 2 excluded under a vendor/ path segment as non-loadable

An exclusion whose count is printed cannot suppress anything silently, which is the property that
made A8 worth filing. The predicate and the result set are unchanged.

What is left, and what would close it

A real loadability filter, which needs a model that at minimum distinguishes:

  • a SKILL.md at a package's own root or in a package's skills/ directory (not loadable by any
    documented path) from
  • a <anything>/.claude/skills/<name>/SKILL.md (loadable under the nested rule, wherever it sits),
    and
  • ~/.claude/skills/, project .claude/skills/ up to the repo root, --add-dir roots, and plugin
    skills/ directories (the four documented locations — same page, "Where skills live").

Note this also means the existing vendor/ rule is approximate: vendor/x/.claude/skills/y/SKILL.md
would load under the nested rule and is excluded today. That is pre-existing and not a regression;
it is recorded here because any principled fix has to settle it too.

Scope this honestly

A8's originating measurement — 448 raw findings → 109 after filtering to live installs — was taken
against the plugin cache, not this repository, and #2283 records that neither the validation pass
nor this implementation re-measured it. Inside this repo the noise sources it measured largely do not
exist. The row is LOW at HEAD; it bites when the detector is pointed at a home directory, which is
the configuration #2249 already refuses.

Related

Severity: LOW at HEAD · Provenance: AUDITOR_VERIFIED, rationale corrected against
https://code.claude.com/docs/en/skills (fetched 2026-08-12).
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 § A8

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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