diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 0ad369a..aeab751 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,5 +1,5 @@ { "name": "llm-security", "description": "Security scanning, auditing, and threat modeling for Claude Code projects. Detects secrets, validates MCP servers, assesses security posture, and generates threat models aligned with OWASP LLM Top 10.", - "version": "7.8.0" + "version": "7.8.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e0011..b866834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +## [7.8.1] - 2026-07-18 + +Security patch. Fixes a CRITICAL command-injection defect in the auto-cleaner +that shipped in v7.8.0. No feature changes. 1865 tests, 0 fail. + +### Fixed + +- **CRITICAL — command injection via scanned filename** + (`scanners/auto-cleaner.mjs`). `validateContent()` syntax-checked + `.mjs`/`.js`/`.cjs` candidates with + ``execSync(`node --check "${tmpPath}"`)``, where `tmpPath` derives from the + **untrusted scanned-repo filename**. The F-2 guard added in v7.8.0 checks + path containment but neither strips nor quotes shell metacharacters, and a + filename containing `"` closes the interpolated quote. A repository shipping + a file named ``x";;".mjs`` therefore turned `/security clean` — + whose live mode is the documented default — into arbitrary command execution + on the operator's machine. Verified with a live proof-of-concept before the + fix. Both subprocess call sites (the syntax check, and the CLI's + scan-orchestrator fallback) now use `spawnSync` with an argv array, so no + shell parses the path. +- **Defense-in-depth:** `applyFixes()` now refuses findings whose `file` field + carries shell or control metacharacters, reporting them as `skipped` rather + than passing them to any sink. This guards against a future call site + re-introducing string interpolation. + +### Changed + +- `validateContent` is exported from `scanners/auto-cleaner.mjs` so the + regression suite can exercise the subprocess sink directly, independently of + the `applyFixes` guard that would otherwise mask it. + ## [7.8.0] - 2026-06-20 Three new deterministic deep-scan scanners (TRG/SIG/AST) targeting the diff --git a/CLAUDE.md b/CLAUDE.md index fbfd513..e72640f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,8 +1,10 @@ -# LLM Security Plugin (v7.8.0) +# LLM Security Plugin (v7.8.1) Security scanning, auditing, and threat modeling for Claude Code projects. 5 frameworks: OWASP LLM Top 10, Agentic AI Top 10 (ASI), Skills Top 10 (AST), MCP Top 10, AI Agent Traps (DeepMind). 1820+ unit, integration, and end-to-end tests (`tests/e2e/` covers the multi-hook attack chain, multi-session state simulation, and the full scan-orchestrator pipeline); mutation-testing coverage not published. -Release notes for v7.0.0 → v7.8.0: see `docs/version-history.md` — read on demand. +Release notes for v7.0.0 → v7.8.1: see `docs/version-history.md` — read on demand. + +**v7.8.1 highlights** — Security patch, no feature changes. Fixes a CRITICAL command injection in `scanners/auto-cleaner.mjs`: `validateContent()` syntax-checked candidate `.mjs`/`.js`/`.cjs` content via ``execSync(`node --check "${tmpPath}"`)``, where `tmpPath` derives from the **untrusted scanned-repo filename**. The v7.8.0 F-2 guard checks path containment but neither strips nor quotes shell metacharacters, so a file named ``x";;".mjs`` closes the interpolated quote and injects a command; since `/security clean` runs live by default, scanning a hostile repository sufficed for arbitrary local command execution (live-PoC verified). Both subprocess sites — the syntax check and the CLI's inline scan-orchestrator fallback — now use `spawnSync` with an argv array, so no shell parses a path. Defense-in-depth: `applyFixes()` refuses findings whose `file` carries shell/control metacharacters, surfaced as `skipped`. `validateContent` is now exported so regression tests can drive the sink directly — the guard would otherwise mask a re-introduced shell. **v7.8.0 highlights** — Three new deterministic deep-scan scanners (TRG/SIG/AST) targeting the skills/agents attack surface, each with its own finding prefix, OWASP/AST mapping, policy block, and graceful-skip behaviour. **TRG** (`scanners/trigger-scanner.mjs`) inspects command/agent/skill `name` + `description` frontmatter for activation-surface abuse — `TRG-shadow` (name collides with a built-in and intercepts it), `TRG-baiting` (maximally-activating phrases that bait indiscriminate invocation), `TRG-broad` (generic name + universal-applicability claim); descriptions pass the decode pipeline first so obfuscated baiting still trips (LLM06/AST04). **SIG** (`scanners/signature-scanner.mjs`) is a pure-Node known-malware *identity* engine (webshells, reverse shells, cryptominers, hacktools) that tests each signature against both raw bytes and the decode pipeline, so obfuscated known-malware a byte-matcher misses is still caught; rules in `knowledge/signatures.json` (LLM03/LLM02). **AST** (`scanners/ast-taint-scanner.mjs`) shells out to a PARSE-ONLY `python3` helper (`scanners/lib/py-ast-taint.py`) for scope-aware Python taint analysis, falling back to the regex `taint-tracer.mjs` when `python3` is absent; the helper only `ast.parse`s the target, never executes it (LLM01/LLM02/AST02). Built behind a security-fix gate (F-1/F-2/F-3 landed first). No existing scanner, hook, or command behaviour changes. diff --git a/README.md b/README.md index 8942d05..d6d61e6 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-7.8.0-blue) +![Version](https://img.shields.io/badge/version-7.8.1-blue) ![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple) ![Commands](https://img.shields.io/badge/commands-20-orange) ![Agents](https://img.shields.io/badge/agents-6-orange) @@ -628,6 +628,7 @@ demonstrations — each with `README.md`, fixture, run script, and | Version | Date | Highlights | |---------|------|------------| +| **7.8.1** | 2026-07-18 | **Auto-cleaner command-injection fix (CRITICAL).** `scanners/auto-cleaner.mjs` syntax-checked candidate `.mjs`/`.js`/`.cjs` content via ``execSync(`node --check "${tmpPath}"`)``, where `tmpPath` derives from the untrusted scanned-repo **filename**. The v7.8.0 F-2 guard checks path containment but does not strip or quote shell metacharacters, so a file named ``x";;".mjs`` closes the interpolated quote and injects a command — and `/security clean` runs live by default, making a hostile repository sufficient for arbitrary local command execution. Reproduced with a live PoC before the fix. Both subprocess sites (syntax check + the CLI scan-orchestrator fallback) now use `spawnSync` with an argv array, so no shell parses a path. Defense-in-depth: `applyFixes()` refuses findings whose `file` carries shell/control metacharacters, reported as `skipped`. Regression coverage is split across both layers so the guard cannot mask a re-introduced shell in the sink. No feature changes. 1865 tests, 0 fail. | | **7.8.0** | 2026-06-20 | **TRG/SIG/AST deep-scan scanners.** Three new deterministic deep-scan scanners targeting the skills/agents attack surface, each with its own finding prefix, OWASP/AST mapping, policy block, and graceful-skip behaviour. **TRG** (`scanners/trigger-scanner.mjs`) inspects command/agent/skill `name` + `description` frontmatter for activation-surface abuse: `TRG-shadow` (name collides with a built-in and intercepts it), `TRG-baiting` (maximally-activating phrases that bait indiscriminate invocation), `TRG-broad` (generic name + universal-applicability claim); descriptions pass the decode pipeline first so obfuscated baiting still trips (LLM06/AST04). **SIG** (`scanners/signature-scanner.mjs`) is a pure-Node known-malware *identity* engine (webshells, reverse shells, cryptominers, hacktools) that tests each signature against both raw bytes and the decode pipeline (base64/hex/url/entity/unicode, homoglyph fold, rot13), so obfuscated known-malware a byte-matcher misses is still caught; rules ship in `knowledge/signatures.json` (LLM03/LLM02). **AST** (`scanners/ast-taint-scanner.mjs`) shells out to a PARSE-ONLY `python3` helper (`scanners/lib/py-ast-taint.py`) for variable-level, scope-aware Python taint analysis — higher recall than the ~70% regex `taint-tracer.mjs` — and falls back to the regex tracer when `python3` is unavailable, so the scan never hard-fails; the helper only `ast.parse`s the target and never executes it (LLM01/LLM02/AST02). Built behind a security-fix gate (F-1/F-2/F-3 shell-injection + path-traversal fixes landed first). 1863 tests, 0 fail. | | **7.7.2** | 2026-05-19 | **Language consistency pass.** Norwegian had crept into surface text across v7.5-v7.7. Per the `~/.claude/CLAUDE.md` convention (English for code and documentation, Norwegian for dialog only), this release translates: the HTML Report-step appended by all 18 skill commands, the canonical CLI renderer `scripts/lib/report-renderers.mjs` (display strings + JS comments), the playground UI strings, the `skill-scanner-agent` and `mcp-scanner-agent` system prompts, the Recent versions table and playground architecture prose in this README, the v7.7.x highlights in `CLAUDE.md`, and the llm-security entries in the marketplace root `README.md` + `CLAUDE.md`, plus six table cells in `docs/scanner-reference.md`. Demo-state fixture content for the `dft-komplett-demo` project (intentional Norwegian persona) and regex alternations that match Norwegian-language report markdown (`/^high\|^høy/`, `/resolution\|løsning/`) were preserved. No scanner, hook, or behavior changes — purely surface text. | | **7.7.1** | 2026-05-18 | **Playground UX strip.** Operator feedback immediately after v7.7.0: the home surface led with three project tracks (Re-onboard / New project / Command catalog) even though the catalog was the important entry point. Minimum strip delivered as three atomic commits (`b732eee` + `2a6f73f` + `81b7beb`): (1) the router always forces `activeSurface = 'catalog'` (the onboarding/home/project render functions are preserved in source but no longer routable); (2) the topbar `Home` and `Re-onboard` buttons removed, `Catalog` retained; (3) the breadcrumb org-name (`shared.organization.name` from demo state) replaced with a static `llm-security` neutral scope anchor. Fix: the hardcoded `'Plugin v7.6.1'` on line 6933 of `renderHome` (template literal not caught by the v7.7.0 grep) was synced. The onboarding concept is documented as a v7.8.0 candidate (per-command context injection) in `ROADMAP.md`. No scanner or hook behavior changes. | diff --git a/docs/version-history.md b/docs/version-history.md index 7272e97..01f0b00 100644 --- a/docs/version-history.md +++ b/docs/version-history.md @@ -2,6 +2,32 @@ Per-release notes for v7.0.0 onward. Imported from `CLAUDE.md` via `@docs/version-history.md`. +## v7.8.1 — Auto-cleaner command-injection fix (CRITICAL) + +Security patch for a CRITICAL defect introduced with the v7.8.0 auto-cleaner +hardening pass. No feature changes; full suite green at 1865/0. + +`scanners/auto-cleaner.mjs` validated candidate `.mjs`/`.js`/`.cjs` content by +shelling out to ``node --check "${tmpPath}"`` via `execSync`. `tmpPath` is +derived from the finding's `file` field — an untrusted scanned-repo filename. +The F-2 containment guard that landed in v7.8.0 verifies the resolved path +stays inside the scanned tree, but performs no shell-metacharacter handling, so +a filename whose `"` terminates the interpolated quote injects a second +command. Because `/security clean` runs in live mode by default, scanning a +hostile repository was sufficient to execute attacker-chosen commands locally. +The defect was reproduced with a live proof-of-concept before being fixed. + +Both subprocess sites now use `spawnSync` with an argv array — the syntax check +and the CLI's inline scan-orchestrator fallback — so no shell interprets a path. +As defense-in-depth, `applyFixes()` additionally refuses any finding whose +`file` carries shell or control metacharacters, surfacing it as `skipped`. + +Regression coverage is deliberately split across both layers +(`tests/scanners/auto-cleaner-rce.test.mjs`): one test drives `validateContent` +directly to prove the shell is gone, because the metacharacter guard would +otherwise stop the hostile input before it reached the sink and the test would +pass without testing the fix. + ## v7.8.0 — Trigger, signature, and AST-taint scanners Three new deterministic deep-scan scanners (TRG/SIG/AST), each with its own diff --git a/package.json b/package.json index 44ef2dc..f371aa0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "llm-security", - "version": "7.8.0", + "version": "7.8.1", "description": "Security scanning, auditing, and threat modeling for Claude Code projects", "type": "module", "bin": {