feat(carriers): three cases minted, and the id is named rather than adopted
Both runtimes answered the 2026-08-11 decision request, and they did not ask for the same name. The guard ruled `sanitize:` names its `Finding.detector` and offered all three labels for verbatim adoption. llm-security, asked directly, answered that the name would make its conformance result read as a claim about neutralisation it does not perform. Two things decided it. The guard's own unprompted correction: prefix == detector holds for those six labels and is no general law in its runtime (`egress:*` carries detector="output"; decode-and-rescan yields two-part `decoded:lexicon:*`). A prefix whose meaning is recoverable only by reading one implementation cannot carry a shared id space. And a measurement taken here at be9759b: on the surface the guard's own ruling pinned, `sanitize()` returns changed text on all three carriers, so the counterargument's decisive case -- that `scan_output` mutates nothing -- does not reach this surface. Not a mediation. Neither runtime claimed the shared id must equal its label, and `override:ignore-previous` already carries two different alias strings. - carriers.json 0.1.0 -> 0.2.0: carrier:zero-width / :bidi-override / :unicode-tag, aliased to the guard's labels. No aliases.llm_security -- that runtime's carrier findings carry no id yet, and publishing the alias is the irreversible act that forces the table into its declared set. - manifest 0.3.4 -> 0.4.0: entry_points_by_scope, carrying findings accessor and fixture presentation per scope per runtime. This was objection (c), and it blocked minting harder than the name did. - Corpus 90 -> 93. Measured through sanitize(text, source=Source.INPUT) at guard v0.7.0; verified by a separate checker that re-derives everything from disk -- a generator agreeing with itself proves nothing. - CLAUDE.md gains the two rules that are not derivable from the data: a shared id space cannot rest on a one-runtime prefix, and publishing an alias -- not minting the case -- is the irreversible act. Not minted on purpose: no artifact-side id (the other runtime would only fail them), and no ZWJ-exemption case (U+200D between two emoji is exempt on both guard surfaces since v0.6.1; the fixture avoids it rather than trips it). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U3o4zSQ2kwLsgJNU7apK3Z
This commit is contained in:
parent
7ce0ba706c
commit
8f08e9ac73
11 changed files with 350 additions and 30 deletions
39
README.md
39
README.md
|
|
@ -24,7 +24,7 @@ As a `git subtree` (recommended: history is preserved and upgrades are a single
|
|||
|
||||
```bash
|
||||
git subtree add --prefix vendor/commons \
|
||||
https://git.fromaitochitta.com/open/llm-security-commons.git v0.4.3 --squash
|
||||
https://git.fromaitochitta.com/open/llm-security-commons.git v0.5.0 --squash
|
||||
|
||||
# later, to move to a newer tag
|
||||
git subtree pull --prefix vendor/commons \
|
||||
|
|
@ -34,7 +34,7 @@ git subtree pull --prefix vendor/commons \
|
|||
Or pin a tag and copy — `fork-and-own` is an explicitly supported path:
|
||||
|
||||
```bash
|
||||
git clone --depth 1 --branch v0.4.3 \
|
||||
git clone --depth 1 --branch v0.5.0 \
|
||||
https://git.fromaitochitta.com/open/llm-security-commons.git
|
||||
```
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ and keeping it that small is the point.
|
|||
| Path | Contents |
|
||||
| --- | --- |
|
||||
| [`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. |
|
||||
| [`codepoints/carriers.json`](codepoints/carriers.json) | Invisible and deceptive carriers: zero-width characters, BIDI controls, Unicode Tag block ranges, and the homoglyph map. Carries three commons-owned ids (`carrier:zero-width`, `carrier:bidi-override`, `carrier:unicode-tag`) for the carriers observable on an input surface — the only id space here that was *named* rather than adopted verbatim from a runtime, and the file records why. |
|
||||
| [`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`](signatures/malware-signatures.json) | Known-bad **identity** for the malicious-code class (`SIG`): seven tight signatures over four families — PHP webshells, reverse shells, cryptominers, offensive tooling. Seven signatures are not malware coverage, and the file says so. |
|
||||
| [`signatures/active-content.json`](signatures/active-content.json) | Active content that renders or fetches on its own — Markdown images, links, reference definitions and autolinks, `data:` URIs, active HTML. The EchoLeak class. |
|
||||
|
|
@ -60,15 +60,15 @@ and keeping it that small is the point.
|
|||
| [`schema/finding.schema.json`](schema/finding.schema.json) | **Normative.** The finding contract — closed against its producer, ten properties — plus the SARIF output profile. The JSONL profile is recorded as `not applicable`, with the reason. |
|
||||
| [`schema/conformance-declaration.schema.json`](schema/conformance-declaration.schema.json) | **Normative.** The shape a runtime publishes alongside a conformance result: which commons tables it implements, the commons commit it measured, and the four verdict counts. Required by the corpus spec §1.1; not validated by anything here, because nothing here runs. |
|
||||
| [`spec/conformance-corpus.md`](spec/conformance-corpus.md) | **Normative.** How to read the corpus: what a case is, why `input.txt` is bytes rather than text, what `exact-within-scope` requires of a runtime, and how a runtime declares its table set so a case scoped outside it reads as `not-applicable` rather than as a failure. |
|
||||
| [`conformance/`](conformance/) | 90 cases. One directory per case: `input.txt` in, `expected.json` out. Ground truth. 84 cover the injection lexicon — 83 one per pattern, both seeding runtimes measured producing the same verdict on all 83, plus one variant case gating a pattern form against its predecessor. Six cover active content and are measured against the one runtime that implements that table — `not-applicable` for the other, not failing. See [`conformance/manifest.json`](conformance/manifest.json). |
|
||||
| `spec/decode-pipeline.md` | **Planned, still not shipped as of v0.4.3.** The decode order, in RFC 2119 language. Two runtimes that decode in different orders will disagree on identical input. Writing it needs the decode implementation, which is engine code and has not been supplied — and a normative spec guessed from a data dump would be worse than an absent one. |
|
||||
| [`conformance/`](conformance/) | 93 cases. One directory per case: `input.txt` in, `expected.json` out. Ground truth. 84 cover the injection lexicon — 83 one per pattern, both seeding runtimes measured producing the same verdict on all 83, plus one variant case gating a pattern form against its predecessor. Six cover active content and are measured against the one runtime that implements that table — `not-applicable` for the other, not failing. Three cover the input-side carriers, added in v0.5.0. See [`conformance/manifest.json`](conformance/manifest.json). |
|
||||
| `spec/decode-pipeline.md` | **Planned, still not shipped as of v0.5.0.** The decode order, in RFC 2119 language. Two runtimes that decode in different orders will disagree on identical input. Writing it needs the decode implementation, which is engine code and has not been supplied — and a normative spec guessed from a data dump would be worse than an absent one. |
|
||||
| [`docs/extraction-plan.md`](docs/extraction-plan.md) | Informative: where each file was seeded from, and what v0.1.0 promised. |
|
||||
| [`docs/lexicon-port-divergence.md`](docs/lexicon-port-divergence.md) | Informative: a measured disagreement between two ports of the injection lexicon — 13 patterns that behave differently, in both directions. Most of it is still open, and the two rows that closed in v0.4.0 closed because the runtime that owns the value decided, not because this document found them wrong. |
|
||||
|
||||
Every JSON file carries a top-level `version`. Every normative specification carries a
|
||||
`Status: normative` marker. Rows marked **Planned** are named here because the layout is
|
||||
part of the contract, but the file does not exist yet — they are not links, and nothing in
|
||||
v0.4.3 depends on them.
|
||||
v0.5.0 depends on them.
|
||||
|
||||
Each data file records its own provenance and, in `verified`, how strongly it is backed.
|
||||
`calibration/calibration.json` is currently the one file that says `false`: it was
|
||||
|
|
@ -89,8 +89,8 @@ in its own commit, with the reason written down.
|
|||
There is **no CI in this organisation** and nothing runs that comparison automatically. It
|
||||
runs in each consumer's own test suite, against a pinned tag.
|
||||
|
||||
The corpus covers two tables, and they do not carry equal weight — treating them as one
|
||||
number would misreport both:
|
||||
The corpus covers three tables, and they do not carry equal weight — treating them as one
|
||||
number would misreport all three:
|
||||
|
||||
- `lexicon/injection-lexicon.json` — 84 cases over 83 patterns. Both seeding runtimes
|
||||
implement it and both ratified its id space. One pattern carries a second, variant case;
|
||||
|
|
@ -100,16 +100,21 @@ number would misreport both:
|
|||
runtime declares which commons data files it implements, and a case scoped outside that
|
||||
set was never addressed to it. See [§1.1](spec/conformance-corpus.md) — and note that
|
||||
`not-applicable` says the corpus did not ask, never that the runtime is blind.
|
||||
- `codepoints/carriers.json` — 3 cases, added in v0.5.0. **Both** runtimes implement these
|
||||
tables; only one has published a label for the finding. So these three are `not-applicable`
|
||||
for the other *today*, and this is the one place in the corpus where that verdict records a
|
||||
missing **name** rather than a missing capability. It lapses the moment that runtime names
|
||||
its label and the alias is added.
|
||||
|
||||
Four cases remain unshipped, for the carrier and secret-egress tables, and neither is
|
||||
blocked on effort. Carriers has no *ratified* id space: six labels exist to adopt verbatim
|
||||
from one runtime, but adopting them would hand the other an id asserting a strip it does not
|
||||
perform, and publishing a single alias would force that runtime's declared table set to widen
|
||||
— turning three cases it cannot reach from `not-applicable` into failures. Put to both
|
||||
runtimes as a decision request on 2026-08-11; unanswered. Secret egress is not an id question at all — the two
|
||||
runtimes carry *different tables*, 19 entries against 25, cut at different granularities.
|
||||
`conformance/manifest.json` records both blockers under `scope_planned.blockers`, measured,
|
||||
so the gap is visible rather than inferred.
|
||||
One case remains unshipped, for the secret-egress table, and it is not blocked on effort.
|
||||
It is not an id question at all — the two runtimes carry *different tables*, 19 entries
|
||||
against 25, cut at different granularities, and a shared id space presupposes a reconciliation
|
||||
nobody has performed. `conformance/manifest.json` records that blocker under
|
||||
`scope_planned.blockers`, measured, so the gap is visible rather than inferred.
|
||||
|
||||
The carrier blocker closed in v0.5.0 and is kept, with its retired text, under
|
||||
`scope_planned.blockers_resolved` — including the correction one runtime volunteered against
|
||||
a general rule this repository had written down and should not have.
|
||||
|
||||
## Non-goals
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue