# Lexicon port divergence — commons vs the Python guard **Status: informative.** Nothing here is normative and nothing here changes a data file. It records a measured disagreement between two ports of one source table, so the decision can be taken where each table is tested. Under this repository's behaviour-preservation invariant, a divergence found here is **reported, not fixed**. Produced 2026-08-09. Every number below came from a command; the scripts live in the session scratchpad rather than in this repository, because executable code here would breach the charter. They are reproducible from the method column. **Revised the same day, after `llm-security`'s source became readable and both runtimes replied.** Four things changed, and three of them are corrections to this file rather than new results: 1. The claim that **neither runtime misses an attack** is **retracted**. It does. See *What this does not show* — the measurement behind that claim unioned pattern tables belonging to two different runtimes and read the result as a statement about each. 2. One of the 13 divergences does not reach report level, so **12** is the number that changes what a report says. The 13 still blocks `conformance/`. 3. The `hybrid` **severity is resolved** to `high` — the reported hint was correct, and the citation behind it was not. 4. The **pattern id space is ratified** by both runtimes. Corrections are marked in place rather than edited away, because a reader who saw the first version needs to know which sentence moved. ## What was compared | Side | Artefact | Version | | --- | --- | --- | | commons | [`lexicon/injection-lexicon.json`](../lexicon/injection-lexicon.json) | file `version` as committed | | guard | `llm-ingestion-pipeline-security` `src/llm_ingestion_guard/injection_lexicon.json` | lexicon `version` 1.0, repo v0.3.4, commit `0bf0729` | Both are **ports of the same file**: `llm-security/scanners/lib/injection-patterns.mjs`. The guard's JSON says so in its own `note` field — *"Injection lexicon ported from llm-security injection-patterns.mjs. Single source of truth."* Commons extracted the same table from an operator dump of that module. That is what makes the comparison worth running. These are not two different detectors that happen to overlap; they are two transcriptions of one table, and where they disagree, they disagree about what the same source says. ## Result | Measure | Method | Result | | --- | --- | --- | | Pattern count, both sides | count entries | 83 and 83 | | Label correspondence | match commons `label` to guard `desc`, em-dash normalised to hyphen | **83/83** | | Regex source byte-identical | string compare | **64/83** | | Differing regex text | string compare | 19 | | — of those, provably equivalent | unescape commons' JS-isms (`\/` → `/`, `\uXXXX` → literal) and compare for string identity | **6/6 identical** | | — of those, behaviourally divergent | differential match-set comparison (offsets + matched text), targeted corpus per pattern family | **13**, each with a concrete witness input | | — of those, divergent at REPORT level | re-check whether a sibling pattern raises an equivalent finding on the same witness | **12** — one of the 13 is a label-set difference only | | Total input comparisons | count | 401 | | Flags | compare declared flags | **0 differences** | | Severity / family | commons family vs guard `severity` | **8 differences** (all `hybrid`) — resolved, see *Severity* below | 64 identical + 6 escaping-only + 13 divergent = 83. **Read the 13 and the 12 as answering different questions.** Thirteen patterns produce different label sets. Twelve of those change what a report would say. The gap is the `hybrid-xss: iframe with executable src` row: the guard's version of that one pattern misses the witness, but its `hybrid-xss: javascript: URI scheme` pattern fires on the same input at the same severity and the same OWASP anchor, so a reader of the guard's report still sees a `high` / `LLM01` finding on that payload. Measured, not reasoned: the guard's table matched `hybrid-xss:javascript-uri` (high, LLM01) and nothing else; the Node engine matched both `hybrid-xss: javascript: URI scheme` and `hybrid-xss: iframe with executable src`. A `conformance/expected.json` would have to encode the difference, because a fixture names labels — which is why the 13 stood as the number that blocked the corpus. *(Superseded 2026-08-10: it blocks a fixture written over a **witness** input, and the corpus contains none. See* Consequence for `conformance/` *below.)* The 6 escaping-only pairs are equivalent by **string identity after unescaping**, not merely by absence of a counter-example: `<\/system>` vs ``, `text\/html` vs `text/html`, `(?:https?:\/\/…)` vs `(?:https?://…)`, `(?:~\/)?` vs `(?:~/)?`, and two `\uXXXX`-vs-literal character classes. Commons writes code points as escapes by convention; the guard's `note` records the same choice for non-ASCII. ## The 13, and which side misses what Divergence is **not one-directional**. Each side has inputs the other's pattern does not match. | Pattern | Direction | Witness input | | --- | --- | --- | | `html-obfuscation: display:none element with content` | commons misses; span differs | attribute run padded past 256 chars | | `html-obfuscation: visibility:hidden element` | span only | `
` | | `html-obfuscation: off-screen positioned element` | span only | same shape, `position:absolute` | | `html-obfuscation: zero font-size element` | span only | same shape, `font-size:0` | | `html-obfuscation: zero opacity element` | span only | same shape, `opacity:0` | | `html-obfuscation: zero-size overflow-hidden element` | span only | same shape, `height:0;overflow:hidden` | | `html-obfuscation: injection in aria-label attribute` | **commons misses** | `x` | | `markdown: suspicious instruction in link-reference comment` | **guard misses** | `[//]: # (a (b) ignore)` | | `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: ` returns `high` (hybrid-xss), and the short aria-label variant returns `critical`. So the `{0,256}` window is a real evasion window and the `