release: v5.4.0 — plugin-hygiene & settings-validation hardening

Three additive findings extend existing PLH and SET scanners (scanner count
stays 13; --json/--raw byte-stable):
- CA-PLH-015 plugin-folder shadowing — a plugin.json component-path key in the
  replaces-set (commands/agents/outputStyles) pointing at a custom path while
  the default folder still exists; mirrors CC /doctor & claude plugin list.
- CA-PLH-016 skills:-array validation — each entry must resolve to a directory
  inside the plugin root; flags non-string/escapes-root/not-found/not-a-directory;
  mirrors claude plugin validate.
- CA-SET autoMode — structure (only environment/allow/soft_deny/hard_deny string
  arrays; "$defaults" valid) = medium; dead-config (autoMode in shared
  .claude/settings.json is not read by CC) = low.

Release mechanics: version 5.3.0 -> 5.4.0 (plugin.json); CHANGELOG [5.4.0];
README badge/TOC/What's-New/version-history; knowledge v5.4.0 scanner-backing
facts. Gates: suite 949/949, self-audit A/A + readmeCheck.passed (count 13),
SC-5 byte-equal, gitleaks clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
This commit is contained in:
Kjell Tore Guttormsen 2026-06-19 22:32:38 +02:00
commit a86b92e2b5
4 changed files with 75 additions and 28 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "config-audit", "name": "config-audit",
"description": "Multi-agent workflow for analyzing, reporting, and optimizing Claude Code configuration across your entire machine", "description": "Multi-agent workflow for analyzing, reporting, and optimizing Claude Code configuration across your entire machine",
"version": "5.3.0", "version": "5.4.0",
"author": { "author": {
"name": "Kjell Tore Guttormsen" "name": "Kjell Tore Guttormsen"
}, },

View file

@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [5.4.0] - 2026-06-19
### Summary
Plugin-hygiene & settings-validation hardening. Three additive findings extend the existing PLH and
SET scanners — no new scanner file, so the scanner count stays **13**. PLH gains plugin-folder
shadow detection and `skills:`-array validation; the settings validator now checks `autoMode`
structure and flags it when placed in shared project settings, where Claude Code does not read it.
Scanner internals only — no command, agent, or output-format changes; `--json` and `--raw` remain
byte-stable.
### Added
- **PLH plugin-folder shadowing** (`CA-PLH-015`) — flags a `plugin.json` component-path key in the
*replaces* set (`commands`/`agents`/`outputStyles`) that points at a custom path while the
default folder of that name still exists on disk, so the folder is silently ignored (dead
config). Mirrors Claude Code's own warning in `/doctor`, `claude plugin list`, and the `/plugin`
detail view (v2.1.140+). Severity **medium**, `category: 'plugin-hygiene'`,
`details: { field, ignoredDir, customPaths }`. Deliberately excludes `skills` (adds to the
default scan, never shadows) and `hooks`/`mcpServers`/`lspServers` (own merge rules); honors the
explicit-address exception (a custom path resolving *into* the default folder is not flagged).
- **PLH `skills:`-array validation** (`CA-PLH-016`) — validates each `plugin.json` `skills` entry
(string or array) resolves to an existing directory inside the plugin root; one finding per bad
entry with `problem``non-string` / `escapes-root` / `not-found` / `not-a-directory`. Mirrors
`claude plugin validate` (~2.1.145). Severity **medium**, `category: 'plugin-hygiene'`,
`details: { field: 'skills', entry, problem }`. Path-escape detection is containment-based
(resolve + plugin-root prefix), backed by the docs' path-traversal rule.
- **SET autoMode structure + dead-config** (`CA-SET`) — `autoMode`, if present, must be an object
whose only keys are `environment`/`allow`/`soft_deny`/`hard_deny`, each a **string array** (the
literal `"$defaults"` is a valid entry); unknown sub-keys and wrong value types are flagged
(`problem``not-an-object` / `unknown-subkey` / `not-string-array`), severity **medium**.
Separately, `autoMode` placed in **shared** project settings (`.claude/settings.json`) is flagged
as dead config (`problem: 'shared-project-scope'`), severity **low** — Claude Code's classifier
does not read `autoMode` from shared project settings. The two sub-checks are independent. Both
verified against `code.claude.com/docs/en/auto-mode-config`.
### Internal
- **Test count** — 936 → **949** (+13: PLH-015, PLH-016, and SET autoMode structure/dead-config).
- **Scanner count stays 13** — all three findings extend existing PLH and SET scanners; no new
scanner file, no badge/table/`countScannerShape` change.
- **Byte-stability**`json-backcompat` + `raw-backcompat` + the SC-5 default-output snapshot stay
green; the three findings are additive and appear only in configs that trigger them.
## [5.3.0] - 2026-06-19 ## [5.3.0] - 2026-06-19
### Summary ### Summary

View file

@ -6,7 +6,7 @@
*AI-generated: all code produced by Claude Code through dialog-driven development. [Full disclosure →](../../README.md#ai-generated-code-disclosure)* *AI-generated: all code produced by Claude Code through dialog-driven development. [Full disclosure →](../../README.md#ai-generated-code-disclosure)*
![Version](https://img.shields.io/badge/version-5.3.0-blue) ![Version](https://img.shields.io/badge/version-5.4.0-blue)
![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple) ![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple)
![Scanners](https://img.shields.io/badge/scanners-13-cyan) ![Scanners](https://img.shields.io/badge/scanners-13-cyan)
![Commands](https://img.shields.io/badge/commands-18-green) ![Commands](https://img.shields.io/badge/commands-18-green)
@ -21,7 +21,7 @@ A Claude Code plugin that checks configuration health, suggests context-aware im
## Table of Contents ## Table of Contents
- [What's New in v5.3.0](#whats-new-in-v530) - [What's New in v5.4.0](#whats-new-in-v540)
- [What Is This?](#what-is-this) - [What Is This?](#what-is-this)
- [The Configuration Problem](#the-configuration-problem) - [The Configuration Problem](#the-configuration-problem)
- [Quick Start](#quick-start) - [Quick Start](#quick-start)
@ -45,31 +45,28 @@ A Claude Code plugin that checks configuration health, suggests context-aware im
--- ---
## What's New in v5.3.0 ## What's New in v5.4.0
**Permission-rule & plugin-hygiene hardening.** Five additive scanner findings extend the **Plugin-hygiene & settings-validation hardening.** Three additive findings extend the plugin and
permission, CLAUDE.md, and plugin surfaces — no new scanner, so the count stays **13**: settings surfaces — no new scanner, so the count stays **13**:
- **DIS forbidden-param rules** — flags `Tool(param:value)` whose key is the tool's own - **PLH plugin-folder shadowing** (`CA-PLH-015`) — flags a `plugin.json` component-path key in the
canonicalizing field (`command`/`file_path`/`path`/`notebook_path`/`url`), which Claude Code *replaces* set (`commands`/`agents`/`outputStyles`) that points at a custom path while the
silently ignores with a startup warning. Severity by intent: deny/ask = false security default folder of that name still exists, so the folder is silently ignored (dead config).
(medium), allow = dead config (low). Valid forms (`Bash(npm:*)`, `WebFetch(domain:host)`, Mirrors Claude Code's own warning in `/doctor`, `claude plugin list`, and the `/plugin` detail
`Agent(model:opus)`) are never flagged. view. `skills` is excluded (it *adds to* the default scan, never shadows), as are
- **DIS ineffective allow-wildcards** — unanchored tool-name globs in `permissions.allow` `hooks`/`mcpServers`/`lspServers` (own merge rules); a custom path resolving *into* the default
(`*`, `B*`, `mcp__*`) that CC silently skips, plus `Tool(*)` treated as deny-all folder is not flagged.
(`Bash(*)``Bash`) so a bare allow killed by it is reported as dead config. - **PLH `skills:`-array validation** (`CA-PLH-016`) — validates each `plugin.json` `skills` entry
- **CML context-window-scaled char budget** — mirrors CC's own *"Large CLAUDE.md will impact (string or array) resolves to an existing directory inside the plugin root; flags `non-string`,
performance (X chars > 40.0k)"* startup warning; anchors on the conservative 200k window and `escapes-root`, `not-found`, and `not-a-directory` entries. Mirrors `claude plugin validate`.
discloses the relaxed ~200,000-char figure at 1M context. Char-keyed, complementary to the - **SET `autoMode` structure + dead-config** — checks that `autoMode` is an object whose only keys
existing line checks. are `environment`/`allow`/`soft_deny`/`hard_deny`, each a string array (the literal `"$defaults"`
- **PLH plugin namespace collision** — flags two or more plugins declaring the same `name` in is valid); unknown sub-keys and wrong types are flagged (medium). Separately, `autoMode` placed
`plugin.json`; their namespaces collapse and Claude Code silently drops the loser's components. in **shared** project settings (`.claude/settings.json`) is flagged as dead config (low) —
- **feature-gap `disableBundledSkills` lever** — recommends disabling bundled skills when the Claude Code's classifier does not read it there.
active skill listing is over budget (remediation companion to `CA-SKL-002`).
The cross-plugin command-name finding is reframed from a HIGH conflict to a **LOW ambiguity** to All three extend existing PLH and SET scanners. `--json` and `--raw` output remain byte-stable.
match Claude Code's namespacing (`/name:command` keeps both commands reachable). `--json` and
`--raw` output remain byte-stable.
--- ---
@ -650,6 +647,7 @@ This plugin is cautious by design — configuration files are important, and a b
| Version | Date | Highlights | | Version | Date | Highlights |
|---------|------|-----------| |---------|------|-----------|
| **5.4.0** | 2026-06-19 | Plugin-hygiene & settings-validation hardening. Three additive findings extend existing PLH and SET scanners (count stays **13**): PLH plugin-folder shadowing (`CA-PLH-015` — a `plugin.json` component-path key in the *replaces* set `commands`/`agents`/`outputStyles` pointing at a custom path while the default folder still exists) mirroring CC's `/doctor` & `claude plugin list` warning; PLH `skills:`-array validation (`CA-PLH-016` — each entry must resolve to a directory in the plugin root; flags `non-string`/`escapes-root`/`not-found`/`not-a-directory`) mirroring `claude plugin validate`; SET `autoMode` structure (only `environment`/`allow`/`soft_deny`/`hard_deny` string arrays) + dead-config (`autoMode` in shared `.claude/settings.json` is not read by CC). `--json`/`--raw` byte-stable. 949 tests |
| **5.3.0** | 2026-06-19 | Permission-rule & plugin-hygiene hardening. Five additive scanner findings extend existing scanners (count stays **13**): DIS forbidden-param rules (`Tool(param:value)` on a canonicalizing field — deny/ask = false security, allow = dead config) and ineffective allow-wildcards + `Tool(*)` deny-all; CML context-window-scaled 40.0k-char CLAUDE.md budget mirroring CC's startup warning; PLH plugin namespace collision (two plugins declaring the same `name`); feature-gap `disableBundledSkills` lever under skill-listing pressure. PLH cross-plugin command-name overlap reframed HIGH → LOW (namespacing keeps both reachable). `--json`/`--raw` byte-stable. 936 tests | | **5.3.0** | 2026-06-19 | Permission-rule & plugin-hygiene hardening. Five additive scanner findings extend existing scanners (count stays **13**): DIS forbidden-param rules (`Tool(param:value)` on a canonicalizing field — deny/ask = false security, allow = dead config) and ineffective allow-wildcards + `Tool(*)` deny-all; CML context-window-scaled 40.0k-char CLAUDE.md budget mirroring CC's startup warning; PLH plugin namespace collision (two plugins declaring the same `name`); feature-gap `disableBundledSkills` lever under skill-listing pressure. PLH cross-plugin command-name overlap reframed HIGH → LOW (namespacing keeps both reachable). `--json`/`--raw` byte-stable. 936 tests |
| **5.2.0** | 2026-06-18 | CC 2.1.114→181 compatibility + skill-listing budget. New orchestrated scanner **SKL** (`CA-SKL-001` 1,536-char listing cap, `CA-SKL-002` listing-budget sum) → 13 orchestrated scanners. Five validators refreshed for CC 2.1.114181 settings/hook surface (`xhigh` effort, `MessageDisplay` + post-session events, 28 hook events). False positives eliminated in MCP (auto-injected/POSIX env vars, invented `trust` field) and permissions (param-aware DIS/CNF). Hermetic HOME isolation across all CLI-spawning tests. 875 tests | | **5.2.0** | 2026-06-18 | CC 2.1.114→181 compatibility + skill-listing budget. New orchestrated scanner **SKL** (`CA-SKL-001` 1,536-char listing cap, `CA-SKL-002` listing-budget sum) → 13 orchestrated scanners. Five validators refreshed for CC 2.1.114181 settings/hook surface (`xhigh` effort, `MessageDisplay` + post-session events, 28 hook events). False positives eliminated in MCP (auto-injected/POSIX env vars, invented `trust` field) and permissions (param-aware DIS/CNF). Hermetic HOME isolation across all CLI-spawning tests. 875 tests |
| **5.1.0** | 2026-05-01 | Plain-language UX humanizer. Default output of all 18 commands now leads with prose; findings grouped by user-impact category (Configuration mistake, Conflict, Wasted tokens, Missed opportunity, Dead config) and led by urgency phrase (Fix this now → FYI). New `--raw` flag preserves v5.0.0 verbatim output for tooling that scrapes stderr; `--json` is unchanged and byte-stable. New scanner-lib modules: `humanizer.mjs`, `humanizer-data.mjs` with TRANSLATIONS for 13 scanner prefixes. Self-audit terminal output also humanized. 792 tests (+157 humanizer-tester) | | **5.1.0** | 2026-05-01 | Plain-language UX humanizer. Default output of all 18 commands now leads with prose; findings grouped by user-impact category (Configuration mistake, Conflict, Wasted tokens, Missed opportunity, Dead config) and led by urgency phrase (Fix this now → FYI). New `--raw` flag preserves v5.0.0 verbatim output for tooling that scrapes stderr; `--json` is unchanged and byte-stable. New scanner-lib modules: `humanizer.mjs`, `humanizer-data.mjs` with TRANSLATIONS for 13 scanner prefixes. Self-audit terminal output also humanized. 792 tests (+157 humanizer-tester) |

View file

@ -38,6 +38,14 @@
| **"Large CLAUDE.md will impact performance (X chars > 40.0k)"** — Claude Code emits this startup warning when a CLAUDE.md exceeds ~40,000 chars. CC 2.1.169 scales the threshold with the model's context window: ~40.0k at a 200k-context model, relaxing to ~200,000 chars at 1M context. The figure is a **char** count, not a line count — complementary to the 200/500-line adherence guidance (a file can be long by lines yet under budget, or short by lines yet over it). | CC changelog 2.1.169 + live startup-warning text | CML `CA-CML` char-budget finding — anchors on the conservative 200k window (fires earliest, since the user's window is unobservable) and discloses the relaxed 1M figure. Window constants in `scanners/lib/context-window.mjs`. Severity medium (token cost, not an adherence cliff). | | **"Large CLAUDE.md will impact performance (X chars > 40.0k)"** — Claude Code emits this startup warning when a CLAUDE.md exceeds ~40,000 chars. CC 2.1.169 scales the threshold with the model's context window: ~40.0k at a 200k-context model, relaxing to ~200,000 chars at 1M context. The figure is a **char** count, not a line count — complementary to the 200/500-line adherence guidance (a file can be long by lines yet under budget, or short by lines yet over it). | CC changelog 2.1.169 + live startup-warning text | CML `CA-CML` char-budget finding — anchors on the conservative 200k window (fires earliest, since the user's window is unobservable) and discloses the relaxed 1M figure. Window constants in `scanners/lib/context-window.mjs`. Severity medium (token cost, not an adherence cliff). |
| **`Tool(param:value)` permission matching is deny/ask-only.** The `param:value` form is honored only in `permissions.deny` and `permissions.ask`; in `permissions.allow` it matches nothing (dead config). Separately, a rule whose key is the tool's own *canonicalizing field*`command` (Bash/PowerShell), `file_path` (Read/Edit/Write), `path` (Grep/Glob), `notebook_path` (NotebookEdit), `url` (WebFetch) — is **ignored** by CC, which emits a startup warning. Valid parameter forms (`Bash(npm:*)`, `WebFetch(domain:host)`, `Agent(model:opus)`) are honored. | code.claude.com/docs/en/permissions | DIS forbidden-param finding (`forbiddenParamRule` in `permission-rules.mjs`): deny/ask = false security (medium, the block never applies), allow = dead config (low). | | **`Tool(param:value)` permission matching is deny/ask-only.** The `param:value` form is honored only in `permissions.deny` and `permissions.ask`; in `permissions.allow` it matches nothing (dead config). Separately, a rule whose key is the tool's own *canonicalizing field*`command` (Bash/PowerShell), `file_path` (Read/Edit/Write), `path` (Grep/Glob), `notebook_path` (NotebookEdit), `url` (WebFetch) — is **ignored** by CC, which emits a startup warning. Valid parameter forms (`Bash(npm:*)`, `WebFetch(domain:host)`, `Agent(model:opus)`) are honored. | code.claude.com/docs/en/permissions | DIS forbidden-param finding (`forbiddenParamRule` in `permission-rules.mjs`): deny/ask = false security (medium, the block never applies), allow = dead config (low). |
## v5.4.0 scanner-backing facts (verified 2026-06-19)
| Fact | Source | Backs |
|------|--------|-------|
| **plugin.json component-path keys in the *replaces* set shadow their default folder.** `commands`, `agents`, and `outputStyles` each replace a default folder (`commands/`, `agents/`, `output-styles/`); setting one to a custom path while the same-named default folder still exists makes the folder silently ignored — Claude Code warns in `/doctor`, `claude plugin list`, and the `/plugin` detail view. **`skills` is different — it *adds to* the default `skills/` scan (both load, never a shadow).** `hooks`/`mcpServers`/`lspServers` have their own merge rules (not folder shadows). A custom path that resolves *into* the default folder (e.g. `"commands": ["./commands/x.md"]`) keeps the folder scanned (explicit-address exception). | code.claude.com/docs path-behavior-rules; CC `/doctor` + `claude plugin list` + `/plugin` detail (v2.1.140+) | PLH `CA-PLH-015` plugin-folder shadowing — `SHADOWING_PATH_FIELDS` = `commands`/`agents`/`outputStyles` only; `addressesDefaultDir` exception. Severity medium, `category: 'plugin-hygiene'`. |
| **`claude plugin validate` requires each `skills:` entry to be a directory inside the plugin.** A `plugin.json` `skills` field (string or array) must point at an existing directory within the plugin root; installed plugins **cannot reference files outside their directory** (no `../shared-utils` path traversal). | `claude plugin validate` (~2.1.145); code.claude.com/docs path-traversal rule | PLH `CA-PLH-016` `skills:`-array validation — normalizes string→`[string]`, flags `non-string` / `escapes-root` / `not-found` / `not-a-directory`; escape detection is plugin-root containment. Severity medium. |
| **`autoMode` is a four-sub-key object and is not read from shared project settings.** Its only valid sub-keys are `environment`, `allow`, `soft_deny`, `hard_deny`, each a **string array** (entries are prose rules; the literal `"$defaults"` is valid). The classifier **does not read `autoMode` from shared project settings** (`.claude/settings.json`), so a checked-in repo cannot inject its own allow rules; valid scopes are user (`~/.claude/settings.json`), local (`.claude/settings.local.json`), and managed. | code.claude.com/docs/en/auto-mode-config | SET `CA-SET` autoMode — structure (object + four string-array sub-keys; `not-an-object`/`unknown-subkey`/`not-string-array`) = medium; dead-config (`shared-project-scope`, keyed on `file.scope === 'project'`) = low. |
## Official Configuration Guidance (Anthropic) ## Official Configuration Guidance (Anthropic)
These principles are backed by official docs and verified community reports. Use them to ground recommendations. These principles are backed by official docs and verified community reports. Use them to ground recommendations.