From a86b92e2b5245cff9c9ad4cc87de1c62137ec399 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Fri, 19 Jun 2026 22:32:38 +0200 Subject: [PATCH] =?UTF-8?q?release:=20v5.4.0=20=E2=80=94=20plugin-hygiene?= =?UTF-8?q?=20&=20settings-validation=20hardening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8 --- .claude-plugin/plugin.json | 2 +- CHANGELOG.md | 41 ++++++++++++++++++++++++ README.md | 46 +++++++++++++-------------- knowledge/claude-code-capabilities.md | 8 +++++ 4 files changed, 72 insertions(+), 25 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index d5ca000..5e493a7 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "config-audit", "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": { "name": "Kjell Tore Guttormsen" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c0b822..8138395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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/), 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 ### Summary diff --git a/README.md b/README.md index 3d97e72..6a32082 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ *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) ![Scanners](https://img.shields.io/badge/scanners-13-cyan) ![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 -- [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) - [The Configuration Problem](#the-configuration-problem) - [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 -permission, CLAUDE.md, and plugin surfaces — no new scanner, so the count stays **13**: +**Plugin-hygiene & settings-validation hardening.** Three additive findings extend the plugin and +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 - canonicalizing field (`command`/`file_path`/`path`/`notebook_path`/`url`), which Claude Code - silently ignores with a startup warning. Severity by intent: deny/ask = false security - (medium), allow = dead config (low). Valid forms (`Bash(npm:*)`, `WebFetch(domain:host)`, - `Agent(model:opus)`) are never flagged. -- **DIS ineffective allow-wildcards** — unanchored tool-name globs in `permissions.allow` - (`*`, `B*`, `mcp__*`) that CC silently skips, plus `Tool(*)` treated as deny-all - (`Bash(*)` ≡ `Bash`) so a bare allow killed by it is reported as dead config. -- **CML context-window-scaled char budget** — mirrors CC's own *"Large CLAUDE.md will impact - performance (X chars > 40.0k)"* startup warning; anchors on the conservative 200k window and - discloses the relaxed ~200,000-char figure at 1M context. Char-keyed, complementary to the - existing line checks. -- **PLH plugin namespace collision** — flags two or more plugins declaring the same `name` in - `plugin.json`; their namespaces collapse and Claude Code silently drops the loser's components. -- **feature-gap `disableBundledSkills` lever** — recommends disabling bundled skills when the - active skill listing is over budget (remediation companion to `CA-SKL-002`). +- **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, so the folder is silently ignored (dead config). + Mirrors Claude Code's own warning in `/doctor`, `claude plugin list`, and the `/plugin` detail + view. `skills` is excluded (it *adds to* the default scan, never shadows), as are + `hooks`/`mcpServers`/`lspServers` (own merge rules); 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; flags `non-string`, + `escapes-root`, `not-found`, and `not-a-directory` entries. Mirrors `claude plugin validate`. +- **SET `autoMode` structure + dead-config** — checks that `autoMode` is an object whose only keys + are `environment`/`allow`/`soft_deny`/`hard_deny`, each a string array (the literal `"$defaults"` + is valid); unknown sub-keys and wrong types are flagged (medium). Separately, `autoMode` placed + in **shared** project settings (`.claude/settings.json`) is flagged as dead config (low) — + Claude Code's classifier does not read it there. -The cross-plugin command-name finding is reframed from a HIGH conflict to a **LOW ambiguity** to -match Claude Code's namespacing (`/name:command` keeps both commands reachable). `--json` and -`--raw` output remain byte-stable. +All three extend existing PLH and SET scanners. `--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 | |---------|------|-----------| +| **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.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.114–181 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) | diff --git a/knowledge/claude-code-capabilities.md b/knowledge/claude-code-capabilities.md index 1fa2f78..d6a6149 100644 --- a/knowledge/claude-code-capabilities.md +++ b/knowledge/claude-code-capabilities.md @@ -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). | | **`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) These principles are backed by official docs and verified community reports. Use them to ground recommendations.