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:
parent
342acda5bc
commit
c14501e99c
6 changed files with 114 additions and 7 deletions
104
CHANGELOG.md
104
CHANGELOG.md
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue