chore(llm-security): v7.8.1 — security patch release

Version bump + release notes for the auto-cleaner command-injection fix
(f083cda). Security patch only; no feature changes. 1865 tests, 0 fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcQyMTQfyrsAapaCMPxTtQ
This commit is contained in:
Kjell Tore Guttormsen 2026-07-18 09:00:18 +02:00
commit 55d01d1656
6 changed files with 65 additions and 5 deletions

View file

@ -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"
}

View file

@ -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";<command>;".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

View file

@ -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";<command>;".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.

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)*
![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";<command>;".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. |

View file

@ -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

View file

@ -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": {