feat(active-content): the seventh case, and the classifier it needed came with it
Adopting `active:raw-html-link` was one id. Publishing it honestly was the whole of `active_tag_class` — one function, three branches, no way to state the split without the no-URL narrowing and the 0.6.0 external-target rule. On the old predicate a bare `</a>` is active by name, so a consumer implementing from the hybrid would emit the new label where the seed runtime emits nothing. The file is now two pins, stated as two: v0.3.4/0bf0729 everywhere except the raw-HTML classifier, v0.7.0/be9759b there. The drift between them was measured field by field against the imported module rather than assumed, after stripping inline-flag rendering and applying the file's own declared quote normalisation so a spelling difference could not masquerade as drift. Exactly one published field had moved, and not the one this release was about: `html.active_tags` carried the MUTATOR's 23-name set where the gate means the SCANNER's 22. Correct at the 0.3.4 pin, wrong from 0.6.0 on. Kept as `html.mutator_tags`. The sweep covered 93 cases, not the 6 obvious ones. The narrowing can silence an `active:` finding inside the `observed_out_of_scope` evidence of a LEXICON case, and that field is guarded by no test anywhere — stale entries there survive forever. One case moved: html-obfuscation__aria-label, whose `<a aria-label=…>` carries no URL attribute. Its fixture is deliberately not rewritten; the residue is true at the commit `measurement` pins, and rewriting one of 83 would leave two commits under a header naming one. Recorded, dated and pinned in the manifest. The strongest check is not the digest: the checker rebuilds the published classifier from the JSON alone, importing nothing from the runtime, and differential-tests it against `active_tag_class` over 42 probe tags. 0 disagreements. That is what licenses shipping a classifier as data. No `aliases.llm_security` published, on this file or on carriers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LTTaT4quwNPwBYVqmgAt8t
This commit is contained in:
parent
8f08e9ac73
commit
e6ca5ae5ee
7 changed files with 393 additions and 22 deletions
18
README.md
18
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.5.0 --squash
|
||||
https://git.fromaitochitta.com/open/llm-security-commons.git v0.6.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.5.0 \
|
||||
git clone --depth 1 --branch v0.6.0 \
|
||||
https://git.fromaitochitta.com/open/llm-security-commons.git
|
||||
```
|
||||
|
||||
|
|
@ -54,21 +54,21 @@ and keeping it that small is the point.
|
|||
| [`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. |
|
||||
| [`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. Raw HTML carries **two** classes: `active:raw-html` for what a renderer acts on unattended, `active:raw-html-link` for anchors, which need a human. One pattern, one scan, two buckets — the file spells that out, because giving the second class its own pass would double-count. |
|
||||
| [`calibration/calibration.json`](calibration/calibration.json) | The numbers a detector must not invent: risk-score tier constants, verdict thresholds, risk-band cutoffs, posture grade thresholds. Transcribed from a prose summary, not differentially verified — the file says so itself. |
|
||||
| [`mapping/owasp-map.json`](mapping/owasp-map.json) | Finding-id prefix → OWASP taxonomy entry (LLM / ASI / AST / MCP). |
|
||||
| [`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/) | 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. |
|
||||
| [`conformance/`](conformance/) | 94 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. Seven 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.6.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.5.0 depends on them.
|
||||
v0.6.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
|
||||
|
|
@ -95,7 +95,9 @@ 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;
|
||||
see `case_id_derivation.variant_suffix` in the manifest.
|
||||
- `signatures/active-content.json` — 6 cases. One runtime implements it. For a runtime that
|
||||
- `signatures/active-content.json` — 7 cases, one per published id, the seventh added in
|
||||
v0.6.0 when the seed runtime split raw HTML into two carrier classes. One runtime
|
||||
implements it. For a runtime that
|
||||
does not, these cases are **`not-applicable`**, a third verdict beside pass and fail: a
|
||||
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
|
||||
|
|
@ -143,7 +145,7 @@ a general rule this repository had written down and should not have.
|
|||
table and a runtime implementing four can print the same number. `not-applicable` cases
|
||||
must be reported, not dropped from the denominator — `76/83` and `76 passed, 6
|
||||
not-applicable` describe different runtimes.
|
||||
- **The six active-content cases prove less than the 83.** Their payloads come from the only
|
||||
- **The seven active-content cases prove less than the 83.** Their payloads come from the only
|
||||
runtime that implements the table, so no second implementation's agreement could be
|
||||
measured. They pin one runtime's behaviour as a contract a future implementer can be held
|
||||
to; they are not cross-runtime agreement.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue