Squashed 'scanners/commons/' changes from 532d70d..4641a7b

4641a7b release(0.3.0): a detection pattern changed value — that is new here
1482c0b feat(schema,spec): give the §1.1 MUST a shape, since v0.2.0 shipped it without one
25a2cf9 feat(conformance): the witness case, and the derivation rule that had no room for it
c1b2385 fix(lexicon): converge script-tag on its source — re-extraction, not revision
6f79a6e fix(lexicon,docs): the script-tag row reversed — commons is now the sole diverger

git-subtree-dir: scanners/commons
git-subtree-split: 4641a7b5184047460e3f10038b615a61e7a4ac21
This commit is contained in:
Kjell Tore Guttormsen 2026-08-11 14:03:23 +02:00
commit bbd03f9b52
9 changed files with 585 additions and 25 deletions

View file

@ -1,14 +1,21 @@
{
"version": "0.2.0",
"version": "0.3.0",
"id": "llm-security-commons/conformance",
"description": "Enumeration and measurement header for the conformance corpus. Every case directory holds input.txt (the exact bytes to scan) and expected.json (the findings a conforming runtime must produce). The normative reading of those files is spec/conformance-corpus.md; this file records where the cases came from and what was measured.",
"$comment": "Fixture files carry no individual version field. The corpus is versioned as a whole, here — a case is added, removed or corrected by bumping this version, and a case-id change is a MAJOR bump because consumers name cases.",
"case_id_derivation": {
"rule": "case_id = pattern_id with ':' replaced by '__'",
"reverse": "pattern_id = case_id with '__' replaced by ':'",
"why": "':' is not a legal filename character on Windows, and this repository is fork-and-own. '__' does not occur in the ratified id space, so the transform is one-to-one — verified collision-free across all 89.",
"rule": "case_id = pattern_id with ':' replaced by '__', optionally followed by '--' and a variant slug",
"reverse": "pattern_id = case_id truncated at the first '--' if present, then '__' replaced by ':'",
"why": "':' is not a legal filename character on Windows, and this repository is fork-and-own. '__' does not occur in the ratified id space, so the transform is one-to-one — verified collision-free across all 90.",
"stability": "A case id is a stable identifier. Changing one is a BREAKING change.",
"one_case_per_pattern_id": "The derivation takes a pattern id and nothing else, so a single-finding scope holds at most one case per pattern id — there is nowhere in the name to put a second. See spec/conformance-corpus.md section 6, and `omitted_payloads` below for the one payload this actually cost."
"variant_suffix": {
"added_in": "0.3.0",
"syntax": "'--' + a slug of [a-z0-9-]",
"purpose": "To hold a SECOND case for one pattern id when the two inputs answer different questions about the same rule. The first such case is hybrid-xss__script-tag--src-no-close: the original case's input matches the pattern under both its pre-0.7.0 and post-0.7.0 forms, so it cannot gate the difference between them, and the variant input can.",
"separator_is_unambiguous": "Measured, not assumed: '--' occurs in none of the 83 ratified pattern ids and in none of the 89 case ids that predate this version. Ids use single hyphens throughout. So the reverse transform stays purely LEXICAL — split at the first '--', no lookup against the id list required — which is the property the original one-to-one rule was protecting.",
"constraint": "A variant case MUST be scoped and matched exactly like the base case, and MUST expect the same pattern id. The suffix distinguishes INPUTS, never findings. It is not a licence to record a second, different verdict for one rule.",
"supersedes": "This replaces the `one_case_per_pattern_id` note carried through 0.2.0, which read: 'The derivation takes a pattern id and nothing else, so a single-finding scope holds at most one case per pattern id — there is nowhere in the name to put a second.' That was an accurate description of the rule and it cost a real case: see `omitted_payloads`, where the guard's seventh active-content payload was dropped for exactly this reason. The rule is now extended rather than worked around. NOTE that the omitted payload has NOT been added back — extending the derivation makes it expressible, but it was omitted on a second ground as well (its in-scope finding set is identical to the case built from coverage.py:476), and that ground still stands unexamined against the new rule. Adding it is a separate decision, not a consequence of this one."
}
},
"match_semantics": "exact-within-scope",
"scope_covered": [
@ -30,12 +37,38 @@
"source": "llm-ingestion-pipeline-security src/llm_ingestion_guard/coverage.py:484, at commit de09711 (line numbers are commit-relative; the structure is the fifth `_scan_case` of `_build_cases()`'s `active` group)",
"described_as": "opaque (base64) path segment",
"expected_label": "active:markdown-image",
"reason": "Its in-scope finding set is `[active:markdown-image]` — identical, measured, to the case built from coverage.py:476. The only thing that distinguishes it is `entropy:base64-blob`, and this repository publishes no entropy table, so the difference falls outside every declared scope. A second case could not have failed in any way the first does not, and the case-id derivation has no room for it (see `case_id_derivation.one_case_per_pattern_id`).",
"$comment": "Recorded so that 6 built from 7 offered reads as a decision rather than as a miscount."
"reason": "Its in-scope finding set is `[active:markdown-image]` — identical, measured, to the case built from coverage.py:476. The only thing that distinguishes it is `entropy:base64-blob`, and this repository publishes no entropy table, so the difference falls outside every declared scope. A second case could not have failed in any way the first does not, and the case-id derivation had no room for it.",
"$comment": "Recorded so that 6 built from 7 offered reads as a decision rather than as a miscount.",
"derivation_ground_withdrawn_in_0_3_0": "The second half of `reason` — that the derivation has no room for a second case per pattern id — stopped being true in 0.3.0, when `case_id_derivation.variant_suffix` was added. It is left in the text above with this note rather than silently deleted, because the omission decision was taken on TWO grounds and only this one lapsed. The first ground stands: the payload's in-scope finding set is identical to the case already built, so it still could not fail in any way that case does not. The payload therefore remains omitted, on one ground instead of two. Re-examining it is a separate decision."
}
],
"authored_payloads": [
{
"case_id": "hybrid-xss__script-tag--src-no-close",
"added_in": "0.3.0",
"added_date": "2026-08-11",
"input": "<script src=x.js>",
"authored_by": "llm-security-commons",
"$comment": "The FIRST case input authored in this repository rather than reproduced from a runtime's payload set, and it is flagged rather than folded into payload_provenance because that block's value is the claim that its inputs are verbatim upstream. That claim stays exactly as strong as it was — 83 of 83 — and this case sits outside it.",
"why_authored": "No runtime had a payload for it. The input has to distinguish `<script\\b[^>]*>` from `<script\\b[^>]*>[\\s\\S]*?<\\/script>`, and the guard's _LEX_PAYLOADS carries one payload per pattern id chosen to MATCH the pattern, not to discriminate between two candidate forms of it. Both witnesses for this axis were named by llm-security in their coord message of 2026-08-10 (`<script src=x.js>` and unclosed `<script>alert(1)`); this is the first of the two, and they offered to supply them.",
"why_not_authored_earlier": "It was declined on 2026-08-11 in a coord reply, on the ground that a fixture encoding a DISAGREEMENT is worse than an absent one: at that point the input matched both implementations and failed against commons' own published lexicon, so its expectation would have contradicted the data file it is scoped to. Lexicon 0.7.0 removed that contradiction. The stated order was 'settle the row, then the case is trivial to add', and this is that step.",
"measurement": {
"date": "2026-08-11",
"guard_commit": "0dce50f",
"guard_version": "0.5.0",
"method": "Measured through the guard's public API — scan_lexicon() and scan_active_content() imported from llm_ingestion_guard — not read off the regex. The same harness was run against the existing hybrid-xss__script-tag case in the same execution, and it reproduced that case's committed bytes (24) and sha256 exactly, which is what licenses trusting its output for the new one. Digest independently recomputed with shasum -a 256 over the file on disk: agrees.",
"guard_in_scope_result": [
"hybrid-xss:script-tag"
],
"guard_out_of_scope_result": [
"active:raw-html"
],
"llm_security_not_measured": "The llm-security side was verified at the PATTERN level only: its regex source at b1ba1fb is byte-identical to the lexicon's 0.7.0 pattern, and that pattern was compiled and tested against this input. Their runtime was NOT run over this fixture from here — this repository does not execute consumer code, and their own harness will report the verdict. Stated so that `observed_out_of_scope` naming only the guard reads as a measurement boundary rather than an oversight."
}
}
],
"payload_provenance": {
"$scope": "The 83 lexicon cases. The 6 active-content cases have their own provenance in `active_content_provenance` below — they come from a different structure in the same file, at a different commit, and folding them in here would let one pin stand for two measurements.",
"$scope": "The 83 lexicon cases derived from _LEX_PAYLOADS. NOT all 84 lexicon-scoped cases as of 0.3.0 — the variant case hybrid-xss__script-tag--src-no-close was authored in this repository and is covered by `authored_payloads`, not by this block. The 6 active-content cases have their own provenance in `active_content_provenance` below — they come from a different structure in the same file, at a different commit, and folding them in here would let one pin stand for two measurements.",
"source_repo": "llm-ingestion-pipeline-security",
"source_file": "src/llm_ingestion_guard/coverage.py",
"source_export": "_LEX_PAYLOADS",
@ -46,7 +79,7 @@
"stability_note": "llm-ingestion-pipeline-security states (coord message 2026-08-10T12:42:31Z) that _LEX_PAYLOADS and the 83 pattern ids are an internal surface on their side, with no README/CHANGELOG/docs statement promising id or payload stability - their own test suite enforces id coverage as their gate, not as a promise to this repository. Their stated position: if a payload changes upstream, this manifest's pin diverges and should be re-pinned; divergence is a re-pin signal, not a breach of a contract they never granted."
},
"measurement": {
"$scope": "The 83 lexicon cases. The 6 active-content cases are measured in `active_content_measurement`.",
"$scope": "The 83 lexicon cases derived from _LEX_PAYLOADS, measured 2026-08-10. The variant case added in 0.3.0 is measured separately in `authored_payloads` — it did not exist on this date, and stretching this header to cover it would misdate its measurement. The 6 active-content cases are measured in `active_content_measurement`.",
"date": "2026-08-10",
"method": "Each payload was run through both runtimes' PUBLIC entry point — not through a rebuilt regex table — and the resulting finding labels were mapped to commons pattern ids through the lexicon's own aliases block. Comparing at the entry point is deliberate: a table-level comparison produces a number that describes neither runtime.",
"runtimes": [
@ -127,11 +160,12 @@
},
"guard_drift_note": "The guard's HEAD moved twice during this measurement (3c56d50 -> de09711 -> 398eb74). Measurement ran at de09711. Checked rather than hoped: 398eb74 touches only docs/PLAN-v1.md and tests/test_disposition.py, so no measured file changed under the measurement. active_content.py DID change between 0bf0729 — the commit signatures/active-content.json pins — and de09711; the change adds a scan-cap self-safety finding and touches no construct. See that file's `pattern_id_space.verified.at_commit_note`."
},
"count": 89,
"count": 90,
"count_by_scope": {
"lexicon/injection-lexicon.json": 83,
"lexicon/injection-lexicon.json": 84,
"signatures/active-content.json": 6
},
"count_by_scope_$comment": "84 lexicon-scoped cases, of which 83 come from the guard's _LEX_PAYLOADS one-per-pattern-id (see payload_provenance) and one was authored here (see authored_payloads). The lexicon count therefore no longer equals the pattern count, and that is deliberate as of 0.3.0 — a pattern id may carry a variant case. See case_id_derivation.variant_suffix.",
"cases": [
"override__ignore-previous",
"override__forget-instructions",
@ -213,6 +247,7 @@
"hybrid-recursive__inject-output",
"hybrid-recursive__conditional-response",
"hybrid-xss__script-tag",
"hybrid-xss__script-tag--src-no-close",
"hybrid-xss__javascript-uri",
"hybrid-xss__event-handler",
"hybrid-xss__iframe-src",