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

@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [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
- **`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
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
- **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
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
Security and correctness patch. 47 verified fixes from the v7.8.1/v7.8.2