fix(lexicon,docs): the script-tag row reversed — commons is now the sole diverger

llm-security commit 90f576f (2026-08-10) dropped the closing-</script>-tag
requirement from hybrid-xss:script-tag, matching what the guard has always had.
Both now agree in shape (no closing tag required), still not byte-identical
([^><] vs [^>]). Commons' own extracted pattern is unchanged under
behaviour-preservation, so it is the only one of the three still requiring a
closing tag — the opposite of what the divergence doc said when it was written.

injection-lexicon.json gains source_fidelity.post_extraction_drift: the
existing 83/83-at-b0de0ca claim is untouched (extraction-commit fidelity never
decays), and the new 82/83-at-b1ba1fb count carries its own commit and date so
it doesn't read as a bare, decaying "vs HEAD" claim.

Verified directly against both upstream trees this session, not relayed:
llm-security's public remote re-fetched to b1ba1fb, guard read at HEAD 0dce50f.
This commit is contained in:
Kjell Tore Guttormsen 2026-08-11 12:45:44 +02:00
commit 6f79a6ea57
3 changed files with 81 additions and 3 deletions

View file

@ -11,6 +11,22 @@ case ids, disposition semantics). Each JSON file additionally carries its own
## [Unreleased]
### Added
- `lexicon/injection-lexicon.json` (`0.5.1``0.6.0`) — `source_fidelity.post_extraction_drift`,
recording that `llm-security` commit `90f576f` (2026-08-10) dropped the closing-tag
requirement from `hybrid-xss:script-tag`'s source pattern, which this file — a frozen
extraction under behaviour-preservation — does not follow. `patterns_byte_identical_to_source`
(83, at extraction commit `b0de0ca`) is unchanged and still true; the new field adds the count
against `llm-security`'s later HEAD (82/83, measured against `b1ba1fb`, 2026-08-11) so a
consumer diffing against current upstream has a coordinate to resolve the mismatch against
instead of finding it unexplained. No pattern text, id, or existing key changed — additive
only, hence minor. Companion informative update in `docs/lexicon-port-divergence.md`, whose
`hybrid-xss:script-tag` row is reversed: commons is now the sole diverger of the three runtimes
on this one construct, not the guard.
Repository tag unaffected — no file set, key name, case id, or disposition semantics moved.
## [0.2.0] — 2026-08-11
The contract gained a normative MUST, which is why this is a release rather than a

View file

@ -94,7 +94,7 @@ Divergence is **not one-directional**. Each side has inputs the other's pattern
| `markdown: injection payload in link anchor text` | **guard misses** | `[click ignore this](http://e.com/a(b))` |
| `sub-agent: spawn instruction with dangerous capability keywords` | **guard misses** | `create an agent that` + 20 filler words + `execute` |
| `sub-agent: delegation with safety bypass instruction` | **guard misses** | `delegate to a new agent` + 200 chars + `bypass` |
| `hybrid-xss: <script> tag in content (agent context XSS)` | **commons misses**; span differs | `<script>alert(1)` (unclosed), `<script src=x.js>` |
| `hybrid-xss: <script> tag in content (agent context XSS)` | **reversed 2026-08-11, see below** | `<script>alert(1)` (unclosed), `<script src=x.js>` |
| `hybrid-xss: iframe with executable src (agent context XSS)` | **guard misses the label, not the attack** | `<iframe data-x="<" src="javascript:alert(1)">` |
The last row is the one of the 13 that does not reach report level. The guard's
@ -109,6 +109,48 @@ consumer reports offsets or evidence text; it does not change whether a finding
The commons-side misses were confirmed in a real JS engine (Node v25.8.2, `RegExp` built from
the committed JSON), not only in the Python harness used for the differential.
### `hybrid-xss:script-tag`: the row reversed, 2026-08-11
At the time this file was written, `<script>alert(1)` (unclosed) and `<script src=x.js>` were
commons misses only in the sense that commons was a byte-identical extraction of
`llm-security`, so "commons misses" meant "commons and `llm-security` both miss, the guard
does not." That symmetry broke on `llm-security`'s side.
`llm-security` commit `90f576f` (`fix(llm-security): v8.x-A - close <script> recall hole, add
whole-table ReDoS gate`) replaced the source pattern
```
<script\b[^>]*>[\s\S]*?<\/script>
```
with
```
<script\b[^>]*>
```
dropping the closing-tag requirement that produced the recall hole this file first measured.
Both witnesses now match on `llm-security`'s side. Verified directly against the module at
commit `c67bad3` (2026-08-10, first commit carrying the change) and re-confirmed unmoved at
`b1ba1fb` (2026-08-11, current public-remote HEAD as fetched this session) — `git diff
c67bad3..b1ba1fb -- scanners/lib/injection-patterns.mjs` is empty, so no later commit touched
the pattern a second time.
The guard's pattern was never the same construct: `<script\b[^><]*>`, negated-class excluding
both `>` and `<` (`src/llm_ingestion_guard/injection_lexicon.json:463`, unchanged since
`de09711`, re-confirmed unmoved through current guard HEAD `0dce50f`, 2026-08-11). The two
patterns now agree on **shape** — both drop the closing-tag requirement, both are a single
negated-class run immediately after `<script\b` — but they are still not byte-identical: the
guard's excluded-character class also excludes `<`, `llm-security`'s does not. That one-byte
difference is unresolved on both sides (see *Open questions* in the coordination thread); it
does not reopen the closing-tag question, which is settled.
Commons still carries the pre-`90f576f` form, unchanged, per this repository's
behaviour-preservation invariant — commons does not follow either upstream regex change
without an operator decision to re-extract. **Commons is now the sole diverger of the three**
on the closing-tag requirement: both `llm-security` and the guard match `<script src=x.js>`
and unclosed `<script>alert(1)`; commons' extracted pattern still requires `</script>`.
## Why they diverge: two different ReDoS mitigations of one table
This is not drift, and framing it as a bug in either repository would be wrong.

View file

@ -1,5 +1,5 @@
{
"version": "0.5.1",
"version": "0.6.0",
"id": "injection-lexicon",
"description": "Prompt-injection pattern lexicon: the four pattern families a detector matches against untrusted text, and the severity family each belongs to. Data only. The variant building that feeds these patterns (normalisation, homoglyph folding, rot13, unicode-tag escalation) and the buried-payload heuristic are engine behaviour and are deliberately NOT described here.",
"owasp": "LLM01",
@ -112,7 +112,27 @@
"why_retracted": "The same transcription inversion in the opposite direction. The module writes pr[e\\u00e9]c[e\\u00e9]dentes? with escapes; this was the only one of the 83 patterns not byte-identical to source. Corrected in 0.4.0 to the module's spelling, which makes the count 83/83.",
"behaviour_impact": "None. The two spellings are the same regular expression. Verified by compiling both forms and comparing match presence and offsets in Node (bare and `u` mode) and in Python `re` over accented, unaccented, uppercase and non-matching French inputs - identical results throughout."
}
]
],
"post_extraction_drift": {
"$comment": "patterns_byte_identical_to_source above states fidelity AT the commit this file was extracted from (b0de0ca) and does not update when llm-security's source moves further - that is what behaviour-preservation means. Recorded here because llm-security has since moved on one pattern, and a consumer diffing this file against llm-security's current source would otherwise find an unexplained mismatch with no coordinate to resolve it against. See docs/lexicon-port-divergence.md for the full account.",
"extracted_from_commit": "b0de0ca",
"extracted_from_commit_date": "2026-08-09",
"count_at_extraction": 83,
"drift": [
{
"pattern_id": "hybrid-xss:script-tag",
"source_commit": "90f576f",
"source_commit_date": "2026-08-10",
"change": "llm-security dropped the closing-tag requirement: <script\\b[^>]*>[\\s\\S]*?<\\/script> became <script\\b[^>]*>, closing a recall hole where `<script>alert(1)` (unclosed) and `<script src=x.js>` produced no finding.",
"commons_pattern_unchanged": true,
"reconfirmed_no_further_drift_at": "b1ba1fb",
"reconfirmed_date": "2026-08-11",
"reconfirmed_method": "git diff 90f576f..b1ba1fb -- scanners/lib/injection-patterns.mjs on a fresh fetch of the public remote is empty for this pattern; no later commit touched it a second time."
}
],
"count_against_llm_security_head_as_of_2026_08_11": 82,
"count_against_llm_security_head_note": "b1ba1fb was llm-security's public-remote HEAD when this was measured, 2026-08-11. Not re-checked automatically; re-verify before citing against a later HEAD."
}
},
"families": [
{