injection-lexicon.json 0.7.0 -> 0.8.0. hybrid-xss:script-tag and
hybrid-xss:iframe-src drop the `>`-only negated class for `[^><]*`.
Both were quadratic in scan length on input repeating the tag prefix without
ever supplying a `>`: every occurrence is a match start and `[^>]*` runs to end
of input from each one. Node v25.8.2, 16k/32k/64k/128k/256k chars - script-tag
32.65/113.36/479.02/1988.83/7772.25 ms, iframe-src
39.23/131.76/574.94/2469.55/9449.94 ms, x4 per doubling. Under `[^><]*` the same
inputs cost 0.08-0.66 ms and 0.10-1.00 ms. Flat, not merely faster.
This is NOT re-extraction and it is not commons correcting seed data. The
dependency direction inverted: at llm-security be14867 their four injection
tables are built from this file and hold zero local regex literals - measured on
their published surface at 47905da, their vendored lexicon confirmed
byte-identical to 0.7.0. There is no upstream literal left to re-read, so
re-extraction was unavailable as a mechanism. They re-measured rather than
accepting our report, rejected [^>]{0,256} because a bound is paddable and
[^>]{1,256} because it drops bare `<script>` along with two corpus cases, chose
[^><]*, and asked us to carry it.
Recorded in a new source_fidelity.owner_directed_changes block, deliberately not
in post_extraction_drift - that block's shape asserts the source moved and
commons followed, which would be false in the one way that matters now that the
source reads commons.
Not majority. The guard reached [^><] first and independently (cff0437), so all
three agree, but 3-of-3 is not what moved this value. The justification is the
one that kept us on [^>] through 0.7.0 and is quoted in the divergence doc: this
file tracks its declared source, and the declared source chose.
Cost, stated: `<script <x>` and `<script<div>` stop matching. Measured over all
90 conformance cases rather than the 4 citing these ids - 0 lost, 0 gained. The
dropped class is real and unwitnessed by the corpus.
count_against_llm_security_head is now undefined rather than 83: its comparison
target no longer exists. A diff against their vendored copy is a subtree
freshness check, not an independent agreement measurement, and the field says so.
Verified against the SHIPPED file, patterns read from the JSON and never from
literals in the harness: 83/83 compile in Node bare and `u`; whole-table ReDoS
budget at the guard's 512 KB cap, per-pattern unit derived from each pattern's
own literal prefix, slowest of 83 is 1.90 ms against a 250 ms budget; all 4
corpus cases citing the changed ids still match. Gate proven discriminating, not
assumed: the pre-0.8.0 forms measure 37 374 ms and 44 514 ms on that same check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLEZ4XCSnSrQUFA8SzkQB4
hybrid-xss:script-tag drops the closing-tag requirement, becoming byte-identical
to llm-security's RegExp.source at b1ba1fb (injection-patterns.mjs:170). Verified
by compiling both, not by reading them.
Why this does not breach behaviour-preservation: that invariant forbids commons
from CORRECTING seed data it believes is wrong. It does not forbid re-reading the
source after the source itself moved and its owner asked. This file's declared
provenance is llm-security's injection table, and being loadable verbatim by that
table's owner is the one thing it exists to do. The standing alternative was a
per-pattern override in their loader — a published core its own source repository
could not load as published.
Direction is what makes it safe: the new form matches a strict superset of the
old, so it can add matches and cannot introduce a false negative. The reverse
change would not have been adoptable on this reasoning.
NOT decided by the 2-of-3 majority across the three ports. A count of
implementations is not a mandate over detection data; the provenance argument
would hold with the guard on either side. Operator decision, 2026-08-11, on
llm-security's blocking coord request (their 10:46:28Z message, step-4 swap
parked behind it).
Measured collateral: none. Full corpus run under both patterns — 84/84
lexicon-scoped cases pass under 0.7.0, and exactly one case's finding set differs
between the forms. The widening added no finding to any other case's input.
source_fidelity keeps patterns_byte_identical_to_source at 83 and gains the field
it was missing: byte_identical_against_commit: b1ba1fb. Against the original
extraction commit b0de0ca the count is now 82/83, recorded explicitly.
post_extraction_drift is marked resolved and carries before/after pattern text,
so a consumer diffing against either commit has a coordinate for what it finds.
The divergence row is closed on the closing-tag axis after reversing twice in
three days. What stays open is the one-byte span difference: the guard excludes
`<` from its negated class and the other two do not, so `<script <x>` matches
commons and llm-security but not the guard. Measured by compiling all three.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuudQLGMyMenuyeZk8fKs5
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.
The guard asked twice which file:line carried it. Tracing it produced an
uncomfortable answer: no file of ours measured it. The claim arrived from
llm-security (coord 20260809T201048Z) and was written down here as a commons
finding in three places.
Measured against the guard's own tree: severity.mjs has never appeared in
src/llm_ingestion_guard/injection_lexicon.json at any point in that file's
history (git log -S: no commits), and at 0bf0729 - the commit our manifest
pins - the only tree-wide occurrence is docs/PLAN.md:114, correctly about the
report module. Their only lexicon source statement is the note at line 3, and
it names injection-patterns.mjs. The right file.
No detection data moves. hybrid severity is still high, still sourced to
injection-patterns.mjs:274-281 at b0de0ca; severity.mjs still has zero
occurrences of the four pattern arrays, re-measured today. Only the sentence
about the other repository falls.
Marked in place, not edited away. The claim survived review because it came
bundled with a correct measurement of the same question - a wrong citation to
a right value, which is exactly what the field it lived in was written to warn
against.
lexicon/injection-lexicon.json 0.5.0 -> 0.5.1 (provenance metadata only).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWKkmKQBsWtrkod4MusUBC
Two things the file was waiting on arrived, and both are now data rather
than open questions.
families[hybrid].severity was null by design - the seed dump supplied the
array but not the severity. It is "high". The value is not stored anywhere
as a field: the engine assigns it by pushing HYBRID_PATTERNS matches
straight into the high bucket at injection-patterns.mjs:274-281, under the
comment "Hybrid patterns are HIGH severity (v5.0 S6)".
severity_provenance names the file it is NOT in. Both this repository and
the Python guard first looked in severity.mjs, which contains no injection-
family severity at all - grep it for any of the four pattern constants and
you get nothing. The guard's port carries the right value while citing that
file, so a consumer following its citation finds no answer. A wrong citation
to a right value survives review in a way a wrong value does not, so it is
recorded rather than quietly fixed.
pattern_id_space.not_yet_confirmed is replaced by a ratification block.
Both seeding runtimes agreed on 2026-08-09: llm-security ratified the 0.2.0
proposal as-is and treats an id change as breaking on the same terms, and
the guard confirmed the space its own port supplied. The id is a
cross-runtime contract now, which is what conformance/ needed before a
fixture could name a finding at all.
alias_evidence.llm_security is sharpened, not upgraded. All 83 alias
strings were confirmed equal to the module's label field in array order, so
the alias is certainly the pattern's name in the table. It is still not
established that a FINDING carries it: output.mjs:finding() emits title and
has no label key. Verified at table level, one level short of where it
would matter.
No detection data moved: all 83 patterns still byte-identical to the module
at b0de0ca, and no pattern entry differs from HEAD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNMcqrfNyoLRQ7qXUFZnb9
The lexicon was seeded from a transcribed dump. With the source module now
readable read-only at b0de0ca on the public remote, it was imported in Node
and compared entry by entry - RegExp source, flags, label, family, array
order. That check contradicted the file twice, in mirror-image ways.
The file claimed it had rewritten raw code points into \uXXXX escapes in
homoglyph:cyrillic-latin-mix and unicode:zero-width-in-word, and carried
byte_identical_to_source: false for both. The module already writes them
escaped. Nothing was ever rewritten. The stored pattern text was correct
throughout - only the account of its origin was wrong, because the dump had
rendered the module's escapes as the characters they denote and this
repository escaped them back.
The same inversion ran the other way in multi-lang:french, which stored a raw
accented Latin e where the module writes the é escape. That was the one
pattern of 83 not byte-identical to source, and it is corrected here, making
it 83/83. Both spellings are the same regular expression: compiled in Node
bare and under u, and in Python re, over accented, unaccented, uppercase and
non-matching French input, with identical match offsets. No behaviour moved.
Exactly one pattern entry differs from HEAD, and no pattern in the file
contains a non-ASCII byte any more - which matches the module, whose regex
literals are ASCII throughout. A third claim fell with them: the translation
note asserting that the leetspeak and multi-language patterns embed accented
Latin directly was wrong in both halves.
normalisations is now [] with a normalisations_note, the convention already
in signatures/secret-egress.json. A new source_fidelity block carries the
counts, the method, the verified class membership, and both retractions in
full - retracted, not deleted. The equivalence evidence behind the withdrawn
claim (692 Node comparisons, 236 Python) is still a true statement about raw
versus escaped; it is just no longer load-bearing.
Provenance upgraded accordingly: source_commit pinned, and the caveat saying
these checks only proved agreement with a dump is gone, because they no
longer do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNMcqrfNyoLRQ7qXUFZnb9
Advisor review found six gaps the session's own checks did not cover. The one
that mattered was outward-facing: the divergence report told the guard repo
that two sub-agent bounds were undocumented. That was wrong. lexicon.py's
module docstring documents them explicitly under "Bounded token gaps", and
git log -S dates {0,12} to the original port commit f397cd9 and [^><] to the
ReDoS fix cff0437. Every divergence on the guard's side is documented and
traceable. The claim rested on two sed slices of one file; an absence claim
needs a search over the whole repository. Corrected here and by coord.
lexicon/injection-lexicon.json 0.2.0 -> 0.3.0:
- pattern_id_space.alias_evidence records the two aliases separately instead
of averaging them. llm_ingestion_guard is verified — coverage.py asserts on
that exact string, so it is demonstrably what a guard finding carries.
llm_security is not: it is the pattern table's name, the finding producer
was never supplied, and the known Node finding shape uses title, not label.
- normalisations[].affects now keys on id, with the prose names kept beside it
as affects_labels. An internal cross-reference on label was a second
identity space inside the file the id exists to unify.
Detection data unmoved again: labels, patterns, flags and the ids and aliases
added in 7b70f5b are all byte-identical in sequence; 166/166 Node compiles.
Also: the README lexicon row described thematic families the file does not
have (they are severity families; the theme is the id prefix), and the
conformance convertibility table gained its missing second condition — a case
is buildable only if the label it asserts maps to data this repository
publishes. Eleven cases fail that test (entropy, decoded, the sanitize rows,
the OKF scans), so the buildable set is ~94, not 105.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaYqid3mejFmd9ZHsiHgp3
A conformance fixture has to name a finding. The two runtimes that seed this
repository do not name the same pattern the same way -- llm-security says
"override: ignore previous instructions", the guard says
"override:ignore-previous" -- so until now no expected.json could be written
at all, including for the 64 patterns whose regex is byte-identical in both.
Each pattern now carries `id` plus an `aliases` object naming what each
seeding runtime calls it, and a top-level pattern_id_space block explains the
field, its provenance and its stability contract.
The id is adopted verbatim from the guard's port rather than invented here:
that port already carries both names, so the mapping is read from source data.
Matched by label <-> desc with em-dash normalised to hyphen, 83/83 one-to-one,
83 unique ids.
No detection data moved, and that is proven rather than asserted: labels,
patterns and flags are byte-identical in sequence, no flags key was invented
(78 before, 78 after), and stripping id + aliases + pattern_id_space
reproduces the previous committed file byte for byte -- 23566 bytes. All 83
patterns still compile in Node bare and under u (166/166) and in Python re
(83/83).
Neither consumer has ratified this id space; both were asked by coord today,
and the file records it as a proposal rather than implying agreement.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaYqid3mejFmd9ZHsiHgp3
The four prompt-injection pattern families — 21 critical, 32 high, 22 medium,
8 hybrid — with per-pattern label, ECMAScript source and declared flags.
Data only. The dump names checkCognitiveLoadTrap and scanForInjection's
variant building (normalise → fold → rot13 → unicode-tag escalation) as
ENGINE logic that never moves here; they are absent by intent, not omission.
Proven, not transcribed: all four arrays were rebuilt from the commons JSON
alone and diffed against the imported dump module — 83/83 identical on label,
source and flags, 81 of them byte-identical. All 83 compile in Node bare, Node
under `u`, and Python `re`.
Two deliberate deviations from byte-identity, declared in the file: the
zero-width and Cyrillic character classes carried raw code points, four of
them invisible. Escaped to \uXXXX (the convention forbids raw invisible code
points outside conformance inputs — an unreviewable class is where a silent
false negative hides) and proven equivalent by differential match-set
comparison over 208 adversarial inputs, 832 comparisons, 0 differences.
Class membership was counted from the dump bytes: the zero-width class holds
4 code points and does NOT include U+00AD.
HYBRID_PATTERNS' severity was not supplied and is left null rather than
inferred from its siblings. Verification log in docs/extraction-plan.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaYqid3mejFmd9ZHsiHgp3