Runtime-neutral core for LLM and agent security detection: detector data, normative contracts and a conformance corpus that several runtimes can share.
Find a file
Kjell Tore Guttormsen 1482c0bc48 feat(schema,spec): give the §1.1 MUST a shape, since v0.2.0 shipped it without one
schema/conformance-declaration.schema.json 0.1.0 — what a runtime publishes
alongside a conformance result: runtime, the commons commit measured, the declared
table set, and the four verdict counts.

Requested by llm-security (coord 2026-08-11T10:46:28Z) in those terms, with the
reason that matters: two runtimes publishing free-form declarations makes
"83/83 + 6 not-applicable" unparseable by anyone but its author, which is most of
the value of the MUST gone.

The schema does more than name fields, and each addition beyond their ask exists to
turn a §1.1 MUST into a failure rather than a sentence:

- The two arithmetic invariants §1.1 implies but cannot state unambiguously in
  prose — counts MUST sum to total, total MUST equal the corpus case count at the
  commit measured — are stated normatively in `invariants`. JSON Schema cannot
  express either.
- The enumeration arrays are REQUIRED whenever their counts are non-zero, which is
  §1.1's "MUST still be enumerated" made checkable. A bare count would satisfy the
  arithmetic and defeat the purpose.
- `error` and `not_applicable` stay structurally distinct. §1.1 turns on that
  distinction: the first means this runtime tried and could not, the second means
  the question was never addressed to it.
- All five counts are required including zeros. An absent count is
  indistinguishable from one the runtime never tracked.
- `declaration_source` records whether the declared set is DERIVED from the
  constant the runner uses to accept or reject a scope, or hand-maintained beside
  it. Only the derived form makes the anti-narrowing fence structural: narrowing it
  is then a code change with tests behind it. Formulation owed to llm-security, who
  stated the constraint for their own side before commons had a field for it.

Deliberately not a gate. Nothing in this repository runs (charter), and no
validation was asked for. Publishing the shape is the whole contribution.

Mutation-tested rather than eyeballed: the example validates as written, and five
distinct defect classes are rejected — missing enumeration when n/a > 0, missing
enumeration when failed > 0, a dropped zero-count, a non-conforming table path, an
unknown property. The example's `$comment` sits outside the example object because
`declaration` closes additionalProperties, so an example carrying its own $comment
would not validate against the schema it illustrates.

Its six example case ids were checked against the directories on disk — the first
guess had one wrong name. The caseIdList pattern was checked against all 89
existing ids, not just the new shape.

spec §1.1 gains the normative pointer plus a SHOULD that a runtime derive its
declared set from the runner's own constant and record which it did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuudQLGMyMenuyeZk8fKs5
2026-08-11 13:39:37 +02:00
calibration feat(calibration): add calibration.json, marked transcribed-only 2026-08-09 21:14:13 +02:00
codepoints feat(codepoints): add carriers.json from verified llm-security dump 2026-08-09 21:08:01 +02:00
conformance feat(conformance): the witness case, and the derivation rule that had no room for it 2026-08-11 13:39:19 +02:00
docs fix(lexicon): converge script-tag on its source — re-extraction, not revision 2026-08-11 13:39:01 +02:00
lexicon fix(lexicon): converge script-tag on its source — re-extraction, not revision 2026-08-11 13:39:01 +02:00
mapping fix(mapping): state that three of four OWASP maps have no production consumer 2026-08-10 20:47:37 +02:00
schema feat(schema,spec): give the §1.1 MUST a shape, since v0.2.0 shipped it without one 2026-08-11 13:39:37 +02:00
signatures fix(active-content,conformance): cite line numbers per commit — they do not resolve at the pin 2026-08-10 21:16:55 +02:00
spec feat(schema,spec): give the §1.1 MUST a shape, since v0.2.0 shipped it without one 2026-08-11 13:39:37 +02:00
.gitignore feat: initialize llm-security-commons (charter, license, extraction plan) 2026-08-09 14:29:16 +02:00
CHANGELOG.md fix(lexicon,docs): the script-tag row reversed — commons is now the sole diverger 2026-08-11 12:45:44 +02:00
CLAUDE.md feat: initialize llm-security-commons (charter, license, extraction plan) 2026-08-09 14:29:16 +02:00
LICENSE feat: initialize llm-security-commons (charter, license, extraction plan) 2026-08-09 14:29:16 +02:00
README.md release(0.2.0): the contract gained a MUST, so the tag has to move 2026-08-11 06:27:23 +02:00

llm-security-commons

Runtime-neutral core for LLM and agent security detection: detector data, normative contracts and a conformance corpus that several runtimes can share.

License: MIT

Detection logic gets reimplemented every time it crosses a language boundary, and the copies drift: the Node scanner flags a zero-width carrier the Python guard misses, and nobody notices until an incident. This repository holds the part that should never have been copied — the pattern tables, the code-point carriers, the calibration thresholds, the finding contract, and a fixture corpus with expected verdicts — so that two independent implementations can be held to the same answer on the same input.

It is for anyone building or maintaining a detector for prompt injection, secret egress, unicode-carrier smuggling or active content in untrusted text, on any runtime.

It holds no runnable code. Data, specifications and fixtures only.

Install

Nothing to install — this repository is vendored into consumers, not installed.

As a git subtree (recommended: history is preserved and upgrades are a single command):

git subtree add --prefix vendor/commons \
  https://git.fromaitochitta.com/open/llm-security-commons.git v0.2.0 --squash

# later, to move to a newer tag
git subtree pull --prefix vendor/commons \
  https://git.fromaitochitta.com/open/llm-security-commons.git <newer-tag> --squash

Or pin a tag and copy — fork-and-own is an explicitly supported path:

git clone --depth 1 --branch v0.2.0 \
  https://git.fromaitochitta.com/open/llm-security-commons.git

Always vendor a tag, never main. The tag is what a conformance result can be attributed to.

Requirements

A JSON parser and the ability to read a text file. That is the entire dependency surface, and keeping it that small is the point.

What it does

Path Contents
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 Invisible and deceptive carriers: zero-width characters, BIDI controls, Unicode Tag block ranges, and the homoglyph map.
signatures/secret-egress.json Credential and token shapes that must never leave a machine, in a portable regex dialect.
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 Active content that renders or fetches on its own — Markdown images, links, reference definitions and autolinks, data: URIs, active HTML. The EchoLeak class.
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 Finding-id prefix → OWASP taxonomy entry (LLM / ASI / AST / MCP).
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.
spec/conformance-corpus.md Normative. How to read the corpus: what a case is, why input.txt is bytes rather than text, and what exact-within-scope requires of a runtime.
conformance/ 89 cases. One directory per case: input.txt in, expected.json out. Ground truth. 83 cover the injection lexicon, one per pattern, and both seeding runtimes were measured producing the same verdict on all 83. 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.
spec/decode-pipeline.md Planned, still not shipped as of v0.2.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 Informative: where each file was seeded from, and what v0.1.0 promised.
docs/lexicon-port-divergence.md Informative: a measured disagreement between two ports of the injection lexicon — 13 patterns that behave differently, in both directions, and why no data file was changed because of it.

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.2.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 transcribed from a prose summary rather than diffed against a running implementation.

How a consumer proves it conforms

Run every conformance/<case>/input.txt through your detector and compare the finding ids to expected.json — exactly, but only within the data files the case names in scope. spec/conformance-corpus.md is the normative reading; the short version is that a runtime must raise every listed finding and no other finding from the same table, and that what it does with tables outside the case's scope is not compared.

Disagreement means your runtime is wrong, or the fixture is — and the fixture only changes 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:

  • lexicon/injection-lexicon.json — 83 cases. Both seeding runtimes implement it and both ratified its id space.
  • signatures/active-content.json — 6 cases. 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 — and note that not-applicable says the corpus did not ask, never that the runtime is blind.

Four cases remain unshipped, for the carrier and secret-egress tables, and neither is blocked on effort. Carriers has no adoptable id space: one runtime labels the same carrier differently depending on pipeline stage, which would make a case's verdict depend on the entry point it was measured through. Secret egress is not an id question at all — the two runtimes carry different tables, 18 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.

Non-goals

  • Not a scanner. There is no engine here, and there will not be one. If you are looking for something to run, you want a consumer — llm-security for Claude Code.
  • Not a framework or a library. No package manifest, no dependencies, no build.
  • Not a general-purpose Unicode or regex toolkit. The tables cover what the detection classes need, not the standard.
  • Not a vulnerability feed. No CVEs, no advisories, nothing time-sensitive. Everything here is offline and deterministic.
  • Not a policy engine. calibration.json publishes the thresholds; deciding what to do when one is crossed belongs to the consumer.
  • Not the place to fix a consumer's behaviour. Data extracted from an implementation is kept behaviour-identical on purpose. A disagreement is reported to that implementation and decided there, where it is tested.

Known limitations

  • Coverage is the union of what the seed implementations detected, not of what exists. A class absent from the tables above has not been shown to work anywhere.
  • The corpus is narrower than the data. conformance/ constrains two of the seven data files. The other five are published, provenance-checked and unfixtured: a runtime can pass every case and still read calibration.json wrongly. Passing the corpus is evidence about the injection lexicon and about active content, and about nothing else.
  • A pass count is unreadable without the declared table set. A runtime implementing one 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 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.
  • Regex portability is a real risk. Pattern data is written for a common subset, but engines differ (lookbehind, named groups, Unicode property escapes). A consumer whose engine rejects a pattern must report it rather than silently skip it — a skipped pattern is an invisible false negative.
  • Fixtures prove agreement, not correctness. Two runtimes passing the same corpus agree with each other and with the fixture author. A wrong expected.json makes both wrong identically.
  • The homoglyph map is finite. Confusable coverage is a long tail; absence from the map is not evidence a character is safe.

Changelog

See CHANGELOG.md.

License

MIT — see LICENSE. Fork-and-own is an intended use, not a tolerated one.