docs(lexicon): split the alias evidence, correct a false absence claim
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
This commit is contained in:
parent
7b70f5b52f
commit
f68f1e19d5
5 changed files with 78 additions and 11 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -53,6 +53,19 @@ checks that were not run instead of attaching a caveat to a pass.
|
|||
|
||||
### Changed
|
||||
|
||||
- `lexicon/injection-lexicon.json` **0.2.0 → 0.3.0** — the two aliases are no longer presented
|
||||
as equally backed. `pattern_id_space.alias_evidence` now records each one separately:
|
||||
`llm_ingestion_guard` is **verified** (the guard's coverage matrix asserts on that exact
|
||||
string, so it is demonstrably what a guard finding carries), while `llm_security` is
|
||||
**not** — it is the pattern table's own name, and the finding producer was never supplied,
|
||||
with the known Node finding shape using `title` rather than `label`. Averaging the two into
|
||||
one file-level claim would have repeated the defect this repository corrects per-table
|
||||
elsewhere.
|
||||
|
||||
Also: `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 was added to unify.
|
||||
|
||||
- `lexicon/injection-lexicon.json` **0.1.0 → 0.2.0** — every pattern gains a commons-owned
|
||||
`id` and an `aliases` object naming what each seeding runtime calls it, plus a top-level
|
||||
`pattern_id_space` block explaining the field. This exists because a `conformance/`
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ and keeping it that small is the point.
|
|||
|
||||
| Path | Contents |
|
||||
| --- | --- |
|
||||
| [`lexicon/injection-lexicon.json`](lexicon/injection-lexicon.json) | Prompt-injection pattern lexicon: instruction-override, exfiltration and role-confusion families with per-pattern ids. |
|
||||
| [`lexicon/injection-lexicon.json`](lexicon/injection-lexicon.json) | Prompt-injection pattern lexicon: 83 patterns in four **severity** families (`critical`, `high`, `medium`, `hybrid`), each with a stable `id` and per-runtime aliases. The thematic class (`override:`, `evasion:`, `hitl-trap:`, …) is the id prefix, not the family. |
|
||||
| [`codepoints/carriers.json`](codepoints/carriers.json) | Invisible and deceptive carriers: zero-width characters, BIDI controls, Unicode Tag block ranges, and the homoglyph map. |
|
||||
| [`signatures/secret-egress.json`](signatures/secret-egress.json) | Credential and token shapes that must never leave a machine, in a portable regex dialect. |
|
||||
| `signatures/malware-signatures.json` | **Planned, not in v0.1.0.** Signature set for the malicious-code class (`SIG`). The seed data has not been delivered yet. |
|
||||
|
|
|
|||
|
|
@ -279,8 +279,25 @@ engine composes two findings a particular way. This repository publishes data an
|
|||
explicitly does not own an API, so those rows belong to the guard's own suite, the same way
|
||||
the `Planned` rows in the README are named rather than faked.
|
||||
|
||||
Of the 103 `_scan_case` rows, 83 are one-per-lexicon-pattern. Those are also the rows most
|
||||
affected by the id question below.
|
||||
**Convertibility has a second condition, and it is the one easy to miss.** A case is a
|
||||
candidate only if the probe is text-in / findings-out **and** the label it asserts maps to data
|
||||
this repository actually publishes. A fixture asserting a label commons has no table for would
|
||||
quietly extend the contract to cover engine behaviour the charter excludes — the corpus would
|
||||
then be testing something this repository does not define.
|
||||
|
||||
| Group | Cases | Asserted label maps to commons data? |
|
||||
| --- | --- | --- |
|
||||
| `lexicon` | 83 | Yes — `lexicon/injection-lexicon.json`, now via the `id` field |
|
||||
| `active` | 7 | Yes — `signatures/active-content.json` |
|
||||
| `carrier` | 8 | Partly. `output:*-present` and `lexicon:unicode-tags-present` rest on the code-point tables in `codepoints/carriers.json`. The five `sanitize:*` rows assert **stripper** behaviour, which is engine. |
|
||||
| `egress` | 1 | Yes — `signatures/secret-egress.json` |
|
||||
| `entropy` | 2 | **No.** Shannon-entropy detection is the guard's `entropy.py`; commons publishes no entropy table. (`active-content.json` carries an opacity floor — a different thing, for a different pass.) |
|
||||
| `decoded` | 2 | **No.** These assert a finding *after* a decode pass. The decode order is exactly what `spec/decode-pipeline.md` would fix, and it is Planned because it is engine. |
|
||||
| `okf` (`_scan_case_okf`) | 2 | No — the OKF document model is not published here. |
|
||||
|
||||
So the buildable set is smaller than 105: roughly 83 + 7 + 3 + 1 ≈ 94, minus the 13 lexicon
|
||||
patterns whose expected behaviour is still disputed. The rest are named here so a later session
|
||||
does not rediscover them as a gap.
|
||||
|
||||
**Blocking issue found while counting.** The guard and the Node table are two ports of one
|
||||
source file and they do not agree: 64 of 83 patterns byte-identical, 6 differing only by
|
||||
|
|
|
|||
|
|
@ -85,12 +85,35 @@ Both ports have been hardened against catastrophic backtracking, by **different
|
|||
- **The guard excludes the anchor character.** `[^><]`, `[^\]\[]`, `[^)(]`. Cost: content that
|
||||
legitimately contains that character stops matching.
|
||||
|
||||
The guard's campaign is documented in its own CHANGELOG (v0.3.2 and v0.3.4: *"exclude the
|
||||
character that opens the pattern's own anchor"*, a sweep over *"150 patterns across 11
|
||||
tables"*), including its measured recall cost — it names exactly the two exceptions this
|
||||
**Every divergence on the guard's side is documented at source, and traceable to the commit
|
||||
that introduced it.** An earlier draft of this file claimed the two sub-agent bounds were not;
|
||||
that was wrong, and it was wrong because the search behind it never looked outside the
|
||||
CHANGELOG. Both mechanisms are named in `lexicon.py`'s own module docstring:
|
||||
|
||||
> **Bounded token gaps** — the two sub-agent patterns whose seed form nested `.*?` are ported
|
||||
> with `(?:\S+\s+){0,N}?`.
|
||||
>
|
||||
> **Anchor exclusion** — […] Measured across all 83 patterns arm by arm, two markdown patterns
|
||||
> had this defect; both now exclude the anchor character from the run.
|
||||
|
||||
`git log -S` separates the two: the eight `[^><]` patterns (six html-obfuscation, two
|
||||
hybrid-xss) arrived with `cff0437`, *"fix(output): 19 quadratic regex runs on the output path"*
|
||||
— the v0.3.2 campaign, whose CHANGELOG describes exactly this remedy (*"exclude the character
|
||||
that opens the pattern's own anchor (`[` for markdown, `<` for tags)"*) across a sweep of
|
||||
*"150 patterns across 11 tables"*. The `{0,12}` / `{0,120}` sub-agent bounds are older still:
|
||||
they arrived with the original port commit `f397cd9`, so they were never a divergence
|
||||
introduced later — they are how that table was transcribed in the first place.
|
||||
|
||||
The v0.3.4 entry also states the measured recall cost, naming precisely the two exceptions this
|
||||
comparison rediscovered: *"URLs containing a literal `(` inside a markdown link target and
|
||||
comment bodies containing a literal `(` before the keyword."* The bounded `{0,12}` / `{0,120}`
|
||||
sub-agent quantifiers produce misses that the guard's exception list does not mention.
|
||||
comment bodies containing a literal `(` before the keyword."*
|
||||
|
||||
Worth recording, because it anticipates the criticism the Node side invites: the guard
|
||||
considered bounding those runs and **rejected it**, on the grounds that *"the content is
|
||||
attacker-controlled, so padding past a bound would be a one-line bypass."* That is the same
|
||||
objection the `{0,256}` witness above demonstrates against the Node table. The two projects
|
||||
reached opposite conclusions from the same reasoning, which is the substance of the
|
||||
disagreement — not an oversight on either side.
|
||||
|
||||
Neither strategy is free, and neither is obviously right. That is the decision the two owning
|
||||
repositories have to take, and it is not commons' to take for them.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.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",
|
||||
|
|
@ -10,8 +10,18 @@
|
|||
"adopted_from": "llm-ingestion-pipeline-security src/llm_ingestion_guard/injection_lexicon.json (lexicon version 1.0, repo v0.3.4, commit 0bf0729) — the `id` field of its port of the same source table. Adopted verbatim rather than invented: the guard's port already carries both names (`id` and `desc`), so the mapping is read from verified source data rather than constructed here.",
|
||||
"matching_method": "Each commons pattern was matched to a guard pattern by comparing this file's `label` to the guard's `desc`, with em-dash normalised to hyphen (the two differ only by that character, in the eight hitl-trap entries). 83/83 matched, one-to-one.",
|
||||
"stability": "An id is a stable identifier. Changing one is a BREAKING change for any consumer matching on it, and requires a major bump of this file.",
|
||||
"aliases": "Each pattern carries an `aliases` object naming what each seeding runtime calls it today. A runtime renames its own label by changing its alias here; the `id` does not move.",
|
||||
"not_yet_confirmed": "Neither llm-security nor the guard has yet agreed to this id space — both were asked via coord on 2026-08-09. Until they answer, `id` is this repository's proposal, not a ratified cross-runtime contract."
|
||||
"aliases": "Each pattern carries an `aliases` object naming what each seeding runtime calls it today. A runtime renames its own label by changing its alias here; the `id` does not move. The two aliases do NOT have the same evidentiary status — see alias_evidence.",
|
||||
"not_yet_confirmed": "Neither llm-security nor the guard has yet agreed to this id space — both were asked via coord on 2026-08-09. Until they answer, `id` is this repository's proposal, not a ratified cross-runtime contract.",
|
||||
"alias_evidence": {
|
||||
"llm_ingestion_guard": {
|
||||
"verified": true,
|
||||
"method": "The guard's coverage matrix asserts on this exact string: coverage.py builds one case per lexicon pattern passing `pattern.id` as the expected value, and its probe compares that against the set of `label` fields on the findings the guard actually returns. The id is therefore demonstrably what a guard finding carries."
|
||||
},
|
||||
"llm_security": {
|
||||
"verified": false,
|
||||
"method": "Not established. This value is the `label` field of the pattern table, which is what the source module names the pattern — not necessarily what a llm-security FINDING carries. The finding producer was never supplied (see finding.schema.json), and the known Node finding shape uses `title`, not `label`, with the SARIF profile deriving rule ids by slugging that title. Treat this alias as the pattern's name in the source table, and confirm the finding-level string before matching on it."
|
||||
}
|
||||
}
|
||||
},
|
||||
"$comment": "Extracted without behaviour change from llm-security/scanners/lib/injection-patterns.mjs, delivered as operator dump 2/2 through the local coord mailbox on 2026-08-09. Only the four pattern arrays are data. The dump states explicitly that checkCognitiveLoadTrap (the buried-after-2000-chars logic) and scanForInjection's variant building are ENGINE logic that never moves here; they are absent by intent, not by omission. Array order is preserved exactly as delivered.",
|
||||
"provenance": {
|
||||
|
|
@ -59,6 +69,10 @@
|
|||
{
|
||||
"id": "unicode-escape-invisible-and-confusable",
|
||||
"affects": [
|
||||
"homoglyph:cyrillic-latin-mix",
|
||||
"unicode:zero-width-in-word"
|
||||
],
|
||||
"affects_labels": [
|
||||
"homoglyph: Cyrillic-Latin mixing in adjacent characters",
|
||||
"unicode: zero-width character inside word (keyword splitting)"
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue