chore(llm-security): release v8.0.0 - version sync and changelog

Phase 6 of the approved v8.0.0 plan. HEAD was 45 commits past v7.8.3 with
every version-bearing file still reading 7.8.3 and no v8 tag anywhere.

Version sync: plugin.json, package.json, README badge, CLAUDE.md header.

CHANGELOG: [Unreleased] becomes [8.0.0] - 2026-09-18. It covered 3 of the
user-visible commits since v7.8.3; the other 18 fix/feat/refactor commits
are now recorded from their commit bodies - the commons extraction (five
tables, scanners/commons/ subtree, knowledge/signatures.json removed),
conformance spec 1.1, the golden-gate coverage relabel, and eight fixes
(script-tag recall, whole-table ReDoS gate and its own 8-of-45 blind spot,
Berry and nested lockfile-v1 misses, per-occurrence strip attribution,
legacy OpenAI keys, compileRules coercion, SIG self-flag, SARIF tags).
Counts verified against the loaded modules: 83 injection patterns, 19
secret shapes, 7 SIG rules.

SECURITY.md supported-versions table said 7.3.x Active; now 8.0.x Active,
7.0.x - 7.8.x best-effort. README gets the 8.0.0 row; CLAUDE.md gets the
v8.0.0 highlights and the test floor moves 2045+ to 2250+.

Tests: 2253 / 2247 pass / 0 fail / 6 skipped, exit 0, measured on HEAD
before the edits. The post-edit full run had 3 red, all timing ceilings,
under load average 60 from unrelated jobs on the machine; each passes
alone (460 ms of 1000, 370 ms of 2000, 2127 ms) and doc-consistency is
45/45 alone. The diff is docs and version strings only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-18 00:16:19 +02:00
commit c14501e99c
6 changed files with 114 additions and 7 deletions

View file

@ -1,5 +1,5 @@
{ {
"name": "llm-security", "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.", "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.3" "version": "8.0.0"
} }

View file

@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased] ## [Unreleased]
## [8.0.0] - 2026-09-18
Major release. The breaking part is small and was announced in v7.3.0: four
deprecated `LLM_SECURITY_*` env-vars and the long-dead `riskScoreV1()` are
removed. The larger part is structural and behaviour-preserving: the detection
tables (injection lexicon, codepoint carriers, OWASP map, secret shapes, malware
signatures) are now built from a vendored, versioned data repository under
`scanners/commons/` instead of source literals, each swap proven content-identical
against a golden baseline. Several recall and correctness fixes ride along.
2253 tests, 2247 pass, 6 skipped, 0 fail.
### Added ### Added
- **`llms.txt` at the repository root.** A minimal entry point for an AI agent - **`llms.txt` at the repository root.** A minimal entry point for an AI agent
@ -25,6 +36,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
one stated exception for security-critical removal, and an explicit note that one stated exception for security-critical removal, and an explicit note that
this is a notice period and not an SLA. this is a notice period and not an SLA.
- **Commons conformance, spec 1.1.** The suite runs the vendored commons
conformance corpus through the real entry points and publishes a
schema-shaped declaration of the result. The set of commons tables this
runtime implements is declared from the same constant the runner uses to
accept a scope, so the declaration cannot drift from what actually ran. Cases
scoped to a table this runtime does not implement are reported as
*not-applicable* — enumerated by name, counted as skipped rather than passed,
and kept in the denominator. An empty declaration fails the suite instead of
turning every case not-applicable and passing with zero cases run. The
declaration artifact is generated per run and gitignored.
### Removed — BREAKING ### Removed — BREAKING
- **The four `LLM_SECURITY_*` configuration env-vars deprecated in v7.3.0.** - **The four `LLM_SECURITY_*` configuration env-vars deprecated in v7.3.0.**
@ -80,6 +102,88 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
migrated project would have dropped from PASS to PARTIAL. The PARTIAL migrated project would have dropped from PASS to PARTIAL. The PARTIAL
finding now recommends the policy key instead of the removed env-var. finding now recommends the policy key instead of the removed env-var.
- **Detection tables are built from vendored commons data, not source
literals.** `scanners/commons/` is a pull-only subtree of the
`llm-security-commons` data repository, inside the published `scanners/`
directory so it ships with the plugin. Five tables moved, each measured
position-by-position against the literal it replaced before the swap, with the
golden dump diffed record-for-record afterwards: the codepoint carriers
(zero-width, bidi, Unicode Tag range, homoglyph map), `OWASP_MAP`, the 83
injection patterns, the 19 fixed credential shapes in `pre-edit-secrets.mjs`,
and the 7 SIG malware signatures. Published module surfaces are unchanged —
the tables are re-exported from where they always lived.
`knowledge/signatures.json` is **removed** rather than kept as a second,
ungated copy of the SIG table; operator rules via `sig.custom_rules_path` are
unaffected. The commons location is resolved from the plugin's own directory
only — never from a scanned target's `policy.json`, which would let a hostile
repository substitute an empty detection corpus. Loaded artifacts are
deep-frozen. An unresolvable injection lexicon or SIG ruleset writes one line
to stderr naming the disabled capability instead of scanning with zero
patterns and reporting clean; it never throws, because a module-load throw in
a hook would break the tool call rather than degrade the scan.
- **Golden gate `coverage` block relabelled to what it measures.** It probes
payload strings against patterns in-process, which is static reachability, not
what the reference run exercised. `coverage.kind = 'static-reachability'`;
`patternsExercised``patternsReachable`, `uncoveredPatterns`
`unreachablePatterns`, `tablesExercised``corpusContains`. The gate pins the
label so it cannot be dropped quietly.
### Fixed
- **`<script>` injection recall.** The `hybrid-xss` script-tag pattern required a
closing `</script>`, so `<script>alert(1)` and `<script src=x.js>` passed
`scanForInjection()` with `found: false` while the closed form returned high.
The opening tag alone is now the signal, matched linearly. Still open and
documented: the bounded HTML patterns evade on more than 256 characters of
attribute padding.
- **Whole-table ReDoS gate, and the gate's own blind spot.** Every exported
injection pattern is now timed, not just the six the v7.8.3 defect was found
on. The first version timed each pattern against hand-written inputs and so
actually reached 8 of 45 prefix-bearing patterns — the rest failed on the
first character and reported green having measured nothing, including both
quadratic `hybrid-xss` rows. Attack inputs are now derived from each pattern's
own literal prefix. The two quadratic rows (`script-tag` 1429 ms, `iframe-src`
1161 ms against a 150 ms budget) are fixed in the vendored lexicon
(`[^>]*``[^><]*`); all 45 probes now run in under 20 ms.
- **Supply-chain re-check missed pinned compromised packages in two lockfile
shapes.** `supply-chain-recheck.mjs` did not recognise Yarn Berry's unquoted
`version: x` form, so a Berry lockfile yielded zero dependencies; and the
`lockfileVersion: 1` fallback did not recurse nested `dependencies`, so a
transitive non-hoisted compromised copy was invisible. Both now mirror the
install hook's parsers.
- **`content-extractor.mjs` could pass a second encoded injection unflagged.**
Strip attribution was tracked per label, so once any occurrence of a label had
been line-redacted, a second cross-line-only occurrence of the same label
reached `sanitized_content` without `unstripped: true`. Attribution is now per
label and line.
- **Bare legacy OpenAI keys passed `pre-edit-secrets.mjs`.** An unquoted legacy
key with no label assignment and no `Bearer` prefix was not blocked. A pattern
anchored on the `T3BlbkFJ` watermark closes it without matching the
collision-prone bare `sk-` + 48 alphanumerics form.
- **A malformed SIG rule became a near-universal matcher instead of being
dropped.** `new RegExp(pattern)` does not throw on a truthy non-string — it
coerces it — so a rule whose `pattern` was an object compiled to a character
class over the letters of `[object Object]`. `compileRules()` now requires a
string. Applies to the built-in ruleset and `sig.custom_rules_path` alike.
- **SIG flagged the plugin's own vendored detection data.** `scanners/commons`
joins the existing `knowledge/`, `tests/`, `docs/` exclusions. Stated cost:
the exclusion matches that two-segment path anywhere in a target, so a payload
planted under `vendor/scanners/commons/` in a hostile repository is invisible
to SIG identity-matching (TRG, AST, entropy and supply-chain still read it). A
test asserts the blind spot so it cannot be rediscovered by accident.
- **SARIF output dropped the second OWASP mapping.** A multi-mapping `owasp`
string such as `MCP03, MCP06` (emitted by `mcp-live-inspect.mjs` and
`ide-extension-scanner.mjs`) was wrapped as a single tag. It is now split into
one tag per mapping, for both rule and result properties.
## [7.8.3] - 2026-07-18 ## [7.8.3] - 2026-07-18
Security and correctness patch. 47 verified fixes from the v7.8.1/v7.8.2 Security and correctness patch. 47 verified fixes from the v7.8.1/v7.8.2

View file

@ -1,9 +1,11 @@
# LLM Security Plugin (v7.8.3) # LLM Security Plugin (v8.0.0)
Security scanning, auditing, and threat modeling for Claude Code projects. 5 frameworks: OWASP LLM Top 10, Agentic AI Top 10 (ASI, 2026 edition), Skills Top 10 (AST), MCP Top 10, AI Agent Traps (DeepMind). 2045+ 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. Security scanning, auditing, and threat modeling for Claude Code projects. 5 frameworks: OWASP LLM Top 10, Agentic AI Top 10 (ASI, 2026 edition), Skills Top 10 (AST), MCP Top 10, AI Agent Traps (DeepMind). 2250+ 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.2: see `docs/version-history.md` — read on demand. Release notes for v7.0.0 → v7.8.2: see `docs/version-history.md` — read on demand.
**v8.0.0 highlights** — Breaking cleanup + commons foundation. BREAKING: the four `LLM_SECURITY_*` env-vars deprecated in v7.3.0 (`INJECTION_MODE`, `TRIFECTA_MODE`, `ESCALATION_WINDOW`, `AUDIT_LOG`, plus `DEPRECATION_QUIET`) are removed — `.llm-security/policy.json` is the only source and a removed variable is **inert** (no warning, silently back to default); `riskScoreV1()` + `SEVERITY_WEIGHTS_V1` deleted (`riskScore()` v2 untouched). Structural, behaviour-preserving: five detection tables are now built from the pull-only vendored subtree `scanners/commons/` (`llm-security-commons`) instead of source literals — codepoint carriers (`scanners/lib/codepoints.mjs`), `OWASP_MAP` (`owasp-map.mjs`), the 83 injection patterns (`injection-lexicon.mjs`), the 19 fixed secret shapes (`secret-egress.mjs`), the 7 SIG rules (`malware-signatures.mjs`; `knowledge/signatures.json` removed). Each swap was measured position-by-position first and diffed record-for-record against the golden baseline (`scripts/golden-baseline.mjs`, `tests/golden/`). The commons root is resolved from the plugin directory only, **never** from a scanned target's policy (a substitutive override would let a hostile repo empty the detection corpus). **Loud failure is contract:** an unresolvable injection lexicon or SIG ruleset writes one stderr line and never throws — an empty table would be the v7.8.2 defect class (clean report, nothing run). Commons conformance spec 1.1 implemented (`tests/lib/conformance-corpus.test.mjs`; not-applicable verdict, derived declaration, anti-narrowing floor). Fixes: `<script>` recall hole + two quadratic `hybrid-xss` rows (whole-table ReDoS gate with inputs derived from each pattern's own prefix), Yarn Berry + nested lockfile-v1 misses in `supply-chain-recheck.mjs`, per-occurrence strip attribution in `content-extractor.mjs`, bare legacy OpenAI keys in `pre-edit-secrets.mjs`, `compileRules()` non-string coercion, SIG self-flag on vendored commons, SARIF multi-OWASP tags. Known open: bounded HTML patterns evade on >256 chars of attribute padding. 2253 tests.
**v7.8.3 highlights** — Security/correctness patch, no feature changes. 47 verified fixes from the v7.8.1/v7.8.2 completion-review MEDIUM tier (52 findings triaged; 3 missing-detector feature-requests and 1 non-defect scoped out; the #11 persistence detector and #27 AST-taint f-string recall deferred to v8). No CRITICAL/HIGH — every review-claimed HIGH downgraded to MEDIUM on re-verification. Supply-chain gate bypasses closed (npm bare-install blocklist skip, nested-key name derivation, yarn.lock false-BLOCK + Yarn Berry miss, `pip audit``pip-audit`); pathguard now covers `Edit`; HTML-pattern ReDoS (28s→4ms) and an MCP-stdout memory-exhaustion DoS fixed; toxic-flow/TRG false positives and a bare-`if:` Dependabot-spoof false negative fixed; YAML block-scalar key-leak and embedded-base64→SIG decode closed; docs/counts synced (14 orchestrated scanners, 16 posture categories, 72 red-team scenarios, 2013 tests). **v7.8.3 highlights** — Security/correctness patch, no feature changes. 47 verified fixes from the v7.8.1/v7.8.2 completion-review MEDIUM tier (52 findings triaged; 3 missing-detector feature-requests and 1 non-defect scoped out; the #11 persistence detector and #27 AST-taint f-string recall deferred to v8). No CRITICAL/HIGH — every review-claimed HIGH downgraded to MEDIUM on re-verification. Supply-chain gate bypasses closed (npm bare-install blocklist skip, nested-key name derivation, yarn.lock false-BLOCK + Yarn Berry miss, `pip audit``pip-audit`); pathguard now covers `Edit`; HTML-pattern ReDoS (28s→4ms) and an MCP-stdout memory-exhaustion DoS fixed; toxic-flow/TRG false positives and a bare-`if:` Dependabot-spoof false negative fixed; YAML block-scalar key-leak and embedded-base64→SIG decode closed; docs/counts synced (14 orchestrated scanners, 16 posture categories, 72 red-team scenarios, 2013 tests).
**v7.8.2 highlights** — Security patch, no feature changes. Five defects from the v7.8.1 completion review, four sharing one failure mode: **the check reported success without running**. (1) `hooks/scripts/pre-bash-destructive.mjs` did not block `rm -rf /` or `rm -rf ~` — the target alternation `(?:\/|~|\$HOME)\b` ended in a word boundary that cannot hold after `/` or `~` at end-of-command, so the bare forms the rule is named for fell through to WARN (exit 0, command executed) while `/etc` and `$HOME` blocked normally, making the rule look functional from either end. (2) `scanners/entropy-scanner.mjs` matched its test/fixture suppression against the **absolute** path, so any ancestor directory named `test`/`spec`/`fixture`/`mock` silenced every entropy finding in the target while still returning status `ok`; it now keys off the relative path. (3) `scanners/ide-extension-scanner.mjs` guarded only `parseVSCodeExtension`'s bare-`null` failure signal, not `parseIntelliJPlugin`'s truthy `{ manifest: null, warnings }`, so any malformed JetBrains plugin dereferenced `manifest.hasSignature`; the TypeError escaped `mapConcurrent`'s unguarded `Promise.all` and aborted the scan of every other installed extension. Guard widened + per-extension fault isolation. (4) `scanners/content-extractor.mjs` — the remote-scan injection boundary — detected obfuscated injections but did not strip them: a decoded-only `match[0]` never occurs in the raw text, so the literal replace was a silent no-op and the payload reached the agent verbatim via `sanitized_content` alongside a finding announcing it. Removal is now line-level; unattributable multi-line payloads carry `unstripped: true`. This boundary had no direct test coverage before v7.8.2. (5) `scanners/lib/ide-extension-parser.mjs` emptied any plugin.xml field holding a character reference above `0x10FFFF` (`Number.isFinite` bounds nothing) — filed as HIGH, actually lower, since such a document is not well-formed XML. **v7.8.2 highlights** — Security patch, no feature changes. Five defects from the v7.8.1 completion review, four sharing one failure mode: **the check reported success without running**. (1) `hooks/scripts/pre-bash-destructive.mjs` did not block `rm -rf /` or `rm -rf ~` — the target alternation `(?:\/|~|\$HOME)\b` ended in a word boundary that cannot hold after `/` or `~` at end-of-command, so the bare forms the rule is named for fell through to WARN (exit 0, command executed) while `/etc` and `$HOME` blocked normally, making the rule look functional from either end. (2) `scanners/entropy-scanner.mjs` matched its test/fixture suppression against the **absolute** path, so any ancestor directory named `test`/`spec`/`fixture`/`mock` silenced every entropy finding in the target while still returning status `ok`; it now keys off the relative path. (3) `scanners/ide-extension-scanner.mjs` guarded only `parseVSCodeExtension`'s bare-`null` failure signal, not `parseIntelliJPlugin`'s truthy `{ manifest: null, warnings }`, so any malformed JetBrains plugin dereferenced `manifest.hasSignature`; the TypeError escaped `mapConcurrent`'s unguarded `Promise.all` and aborted the scan of every other installed extension. Guard widened + per-extension fault isolation. (4) `scanners/content-extractor.mjs` — the remote-scan injection boundary — detected obfuscated injections but did not strip them: a decoded-only `match[0]` never occurs in the raw text, so the literal replace was a silent no-op and the payload reached the agent verbatim via `sanitized_content` alongside a finding announcing it. Removal is now line-level; unattributable multi-line payloads carry `unstripped: true`. This boundary had no direct test coverage before v7.8.2. (5) `scanners/lib/ide-extension-parser.mjs` emptied any plugin.xml field holding a character reference above `0x10FFFF` (`Number.isFinite` bounds nothing) — filed as HIGH, actually lower, since such a document is not well-formed XML.

View file

@ -6,7 +6,7 @@ Security scanning, auditing, and threat modeling for Claude Code projects. OWASP
*AI-generated: all code produced by Claude Code through dialog-driven development. Every change is human-directed, reviewed, and validated before commit. Per Anthropic Consumer Terms §4, ownership of outputs is assigned to the user; this plugin is licensed MIT.* *AI-generated: all code produced by Claude Code through dialog-driven development. Every change is human-directed, reviewed, and validated before commit. Per Anthropic Consumer Terms §4, ownership of outputs is assigned to the user; this plugin is licensed MIT.*
![Version](https://img.shields.io/badge/version-7.8.3-blue) ![Version](https://img.shields.io/badge/version-8.0.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-22-cyan) ![Scanners](https://img.shields.io/badge/scanners-22-cyan)
![License](https://img.shields.io/badge/license-MIT-lightgrey) ![License](https://img.shields.io/badge/license-MIT-lightgrey)
@ -730,6 +730,7 @@ demonstrations — each with `README.md`, fixture, run script, and
| Version | Date | Highlights | | Version | Date | Highlights |
|---------|------|------------| |---------|------|------------|
| **8.0.0** | 2026-09-18 | **Breaking cleanup + detection tables on vendored commons data.** BREAKING: the four `LLM_SECURITY_*` mode env-vars deprecated in v7.3.0 are removed (`.llm-security/policy.json` is the only source; a removed variable is inert) and `riskScoreV1()` is deleted — see [Migrating to v8.0.0](#migrating-to-v800). The injection lexicon (83 patterns), codepoint carriers, `OWASP_MAP`, the 19 fixed secret shapes and the 7 SIG signatures are now built from the pull-only `scanners/commons/` subtree, each swap proven content-identical against a golden baseline; an unresolvable lexicon fails loudly on stderr instead of scanning with zero patterns. Fixes: `<script>` recall hole, two quadratic `hybrid-xss` patterns (whole-table ReDoS gate added), Yarn Berry + nested lockfile-v1 misses in `supply-check`, a second-occurrence strip miss in `content-extractor.mjs`, bare legacy OpenAI keys, a malformed-SIG-rule false-positive matcher, SARIF multi-OWASP tags. Commons conformance spec 1.1 implemented. 2253 tests. |
| **7.8.3** | 2026-07-18 | **Completion-review MEDIUM sweep — 47 verified fixes, no CRITICAL/HIGH.** 52 findings triaged (48 confirmed; 3 feature-requests + 1 non-defect scoped out; the #11 persistence detector and #27 AST-taint f-string recall deferred to v8). Supply-chain gate bypasses (npm bare-install blocklist skip, nested-key name derivation, yarn.lock false-BLOCK + Yarn Berry miss, `pip audit` no-op). Hook coverage (pathguard now `Edit|Write`; trifecta window no longer diluted by markers; pipe-to-shell interposition; bare provider-key patterns). Scanner robustness (HTML-pattern ReDoS 28s to 4ms; MCP-stdout memory exhaustion; VSIX redirect loop; scalar-policy TypeError; atomic cache writes). False positives/negatives (toxic-flow substring trifectas, TRG scoped-phrase FPs, leading-BOM HIGH, bare-`if:` Dependabot-spoof FN, reflog `reset` FP, diff duplicate-fingerprint mislabel, hex double-report). Parser divergence (YAML block-scalar key leak + indicators, ANSI-C octal/unicode, embedded-base64 to SIG, `.env.local` discovery). Docs consistency (scanner count 14, posture 16, red-team 72, SARIF version, dangling `ROADMAP.md`). Plus a live-protocol fix: `post-mcp-verify` now reads the PostToolUse `tool_response` field, so MCP-output injection scanning fires in live sessions. 2013 tests, 0 fail. | | **7.8.3** | 2026-07-18 | **Completion-review MEDIUM sweep — 47 verified fixes, no CRITICAL/HIGH.** 52 findings triaged (48 confirmed; 3 feature-requests + 1 non-defect scoped out; the #11 persistence detector and #27 AST-taint f-string recall deferred to v8). Supply-chain gate bypasses (npm bare-install blocklist skip, nested-key name derivation, yarn.lock false-BLOCK + Yarn Berry miss, `pip audit` no-op). Hook coverage (pathguard now `Edit|Write`; trifecta window no longer diluted by markers; pipe-to-shell interposition; bare provider-key patterns). Scanner robustness (HTML-pattern ReDoS 28s to 4ms; MCP-stdout memory exhaustion; VSIX redirect loop; scalar-policy TypeError; atomic cache writes). False positives/negatives (toxic-flow substring trifectas, TRG scoped-phrase FPs, leading-BOM HIGH, bare-`if:` Dependabot-spoof FN, reflog `reset` FP, diff duplicate-fingerprint mislabel, hex double-report). Parser divergence (YAML block-scalar key leak + indicators, ANSI-C octal/unicode, embedded-base64 to SIG, `.env.local` discovery). Docs consistency (scanner count 14, posture 16, red-team 72, SARIF version, dangling `ROADMAP.md`). Plus a live-protocol fix: `post-mcp-verify` now reads the PostToolUse `tool_response` field, so MCP-output injection scanning fires in live sessions. 2013 tests, 0 fail. |
| **7.8.2** | 2026-07-18 | **Silent-failure fixes from the completion review (HIGH).** Five defects, four sharing one failure mode: the check reported success without running. `hooks/scripts/pre-bash-destructive.mjs` did not block `rm -rf /` or `rm -rf ~` — the target alternation ended in a `\b` that cannot hold after a non-word character, so the bare forms the rule is named for fell through to WARN (exit 0, command executed) while `/etc` and `$HOME` blocked normally. `scanners/entropy-scanner.mjs` matched its test/fixture suppression against the **absolute** path, so any ancestor directory named `test`/`spec`/`fixture`/`mock` silenced every finding in the target and still returned status `ok`. `scanners/ide-extension-scanner.mjs` guarded only `parseVSCodeExtension`'s bare-`null` failure signal, not `parseIntelliJPlugin`'s truthy `{ manifest: null }`, so any malformed JetBrains plugin threw a TypeError that escaped `mapConcurrent`'s unguarded `Promise.all` and aborted the scan of every other extension. `scanners/content-extractor.mjs` detected obfuscated injections but did not remove them: a decoded-only `match[0]` never occurs in the raw text, so the literal replace was a no-op and the payload reached the LLM agent verbatim through `sanitized_content` — removal is now line-level, with unattributable multi-line payloads flagged `unstripped`. `scanners/lib/ide-extension-parser.mjs` emptied any plugin.xml field containing a character reference above `0x10FFFF`. No feature changes. 1901 tests, 0 fail. | | **7.8.2** | 2026-07-18 | **Silent-failure fixes from the completion review (HIGH).** Five defects, four sharing one failure mode: the check reported success without running. `hooks/scripts/pre-bash-destructive.mjs` did not block `rm -rf /` or `rm -rf ~` — the target alternation ended in a `\b` that cannot hold after a non-word character, so the bare forms the rule is named for fell through to WARN (exit 0, command executed) while `/etc` and `$HOME` blocked normally. `scanners/entropy-scanner.mjs` matched its test/fixture suppression against the **absolute** path, so any ancestor directory named `test`/`spec`/`fixture`/`mock` silenced every finding in the target and still returned status `ok`. `scanners/ide-extension-scanner.mjs` guarded only `parseVSCodeExtension`'s bare-`null` failure signal, not `parseIntelliJPlugin`'s truthy `{ manifest: null }`, so any malformed JetBrains plugin threw a TypeError that escaped `mapConcurrent`'s unguarded `Promise.all` and aborted the scan of every other extension. `scanners/content-extractor.mjs` detected obfuscated injections but did not remove them: a decoded-only `match[0]` never occurs in the raw text, so the literal replace was a no-op and the payload reached the LLM agent verbatim through `sanitized_content` — removal is now line-level, with unattributable multi-line payloads flagged `unstripped`. `scanners/lib/ide-extension-parser.mjs` emptied any plugin.xml field containing a character reference above `0x10FFFF`. No feature changes. 1901 tests, 0 fail. |
| **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.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. |

View file

@ -9,8 +9,8 @@ need stronger guarantees (see [`CONTRIBUTING.md`](CONTRIBUTING.md)).
| Version | Status | | Version | Status |
|---------------|------------------------------------------------| |---------------|------------------------------------------------|
| 7.3.x | **Active.** Bug + security fixes. Stabilization line. | | 8.0.x | **Active.** Bug + security fixes. Stabilization line. |
| 7.0.x 7.2.x | Best-effort security fixes only. Upgrade to 7.3.x recommended. | | 7.0.x 7.8.x | Best-effort security fixes only. Upgrade to 8.0.x recommended. |
| < 7.0 | End of life. No fixes. | | < 7.0 | End of life. No fixes. |
The project is in **stabilization mode** as of 2026-05-01. New features are The project is in **stabilization mode** as of 2026-05-01. New features are

View file

@ -1,6 +1,6 @@
{ {
"name": "llm-security", "name": "llm-security",
"version": "7.8.3", "version": "8.0.0",
"description": "Security scanning, auditing, and threat modeling for Claude Code projects", "description": "Security scanning, auditing, and threat modeling for Claude Code projects",
"type": "module", "type": "module",
"bin": { "bin": {