docs(security): the attack surface here is data, so the report route had to say where a wrong entry gets fixed

org-ops recorded SECURITY.md as missing against the org standard (coord,
2026-08-11) and this repository owed it for a sharper reason than "given what
the repo is about": nothing here runs, so a report is never a crash — it is a
detection entry that looks like it works and is not looking.

SECURITY.md therefore answers what an ordinary policy does not have to: how to
report that a detection-table entry is WRONG, and why a confirmed defect in
extracted data is decided in the runtime it was extracted from before it is
changed here. Correcting it here would make the copy disagree with the
implementation it was taken from — two runtimes, two answers on one input, the
exact failure this repository exists to prevent. Two classes skip that routing:
a real secret in the history, and data authored here rather than extracted.
Fix latency is stated plainly as bounded by the owning runtime's schedule and
the consumer's pull, not by ours.

secret-egress 0.1.0 -> 0.2.0 is a staleness DISCLOSURE, not a data change: all
18 patterns byte-identical, one evidence_limits entry added. llm-security
reports the source table at 19 entries now; recorded as their report and not
reproduced, because the commit carrying it is not on their public remote —
measured at b1ba1fb today. What was measured here: none of the 18 patterns
matches a legacy sk-...T3BlbkFJ... shape. A consumer vendoring this file
under-matches the seed hook by one entry, and now reads that in the file.

manifest 0.3.0 -> 0.3.1 corrects the secret-egress blocker. Through 0.3.0 it
named gcp-service-account-json and openai-api-key-legacy together as ids
"absent here". Measured against the guard at e671edb by running this file's own
18 patterns over a service-account document: a COMPLETE service-account key
file is matched here at order 11, since the PEM entry's prefix group is
optional and the bare PKCS#8 header matches; the same document with private_key
removed matches nothing here while the guard's marker still fires. That is a
cut-point difference, which is what the blocker is about, not a missing entry.
openai-api-key-legacy IS a real hole and is now recorded as one. Folded into
the existing blocker string rather than a sibling key, because blockers is a
map from table path to text.

Verified: all JSON well-formed; every non-fixture JSON has a top-level version;
charter clean (no executable code); patterns[] and count byte-identical to HEAD
for secret-egress; manifest key set unchanged and count still 90; 90 case
directories untouched; every spec still carries its normative marker.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T85QqeiWBEoBMjWaMiBnxD
This commit is contained in:
Kjell Tore Guttormsen 2026-08-11 14:03:24 +02:00
commit 0e765a02eb
5 changed files with 201 additions and 4 deletions

133
SECURITY.md Normal file
View file

@ -0,0 +1,133 @@
# Security policy
This repository ships **no runnable code** — no package, no build, no dependency tree,
nothing that executes on your machine. So the usual question, *can this be exploited*,
has an unusual answer here: the attack surface is the **data**.
Seven data files here carry the detection material — pattern tables, code-point carriers,
calibration thresholds, an OWASP mapping — and several independent runtimes read them at the
same time. A wrong code point, a mis-escaped regex, a fixture that expects a miss: none of that
crashes anything. It produces a detector that looks like it works and is not looking. That
is the vulnerability class this policy is about, and a report of one is welcome even though
no code changes as a result.
## Reporting
**Do not open a public issue.** A report here usually names an input that gets *past* a
detector, and that is a working bypass against every consumer until it is closed.
Report privately by email:
- **hello@fromaitochitta.com**, with `SECURITY` at the start of the subject.
Pull requests are not the channel either — they are switched off on the canonical
repository, and not as an oversight. This repository is vendored into independent runtimes
that pin a tag; a change to detection data changes what those runtimes *find*. Such a change
has to be coordinated with each consumer before it exists, which a merge button does not do.
Fork-and-own is the supported path.
Please include:
- the file and the entry — its `name`, `order` or `id`, whichever that file uses;
- the tag you read (`v0.3.0`, not "main");
- the input that should have matched and does not, or the input that matches and should not;
- what a consuming runtime actually does today, if you have measured it.
**Obfuscate live payloads.** Do not send a working credential or a live carrier. Spell
invisible characters as code points the way the tables do (`"U+200B"`), and use placeholder
key material — a report should not itself be a delivery mechanism.
## What counts as a vulnerability here
In scope — all of these are real reports:
1. **A detection entry that is a silent false negative.** A wrong code point, a regex whose
escaping is wrong for the declared dialect, missing or wrong flags, a pattern that fails
to compile in a documented engine and gets skipped rather than reported.
2. **A conformance fixture that sanctions a miss.** `expected.json` is ground truth: a
runtime that disagrees with it is deemed wrong. A fixture that expects too little makes
every conforming runtime wrong identically, and the corpus will not catch it.
3. **A normative clause that mandates unsafe behaviour.** The `spec/` files bind the
implementations that consume them, so a weak rule propagates to all of them.
4. **A real secret or personal data in the repository or its history.** The history is
public in full.
5. **Data that has gone stale against its declared source in a way that under-detects.**
Each data file names its source in a `provenance` block. If that source has since added
or corrected an entry, the copy here under-matches, and a consumer vendoring it is less
protected than the runtime it was taken from.
Out of scope — documented boundaries, not vulnerabilities. See **Known limitations** and
**Non-goals** in [README.md](README.md):
- a detection class absent from the tables entirely (coverage is the union of what the seed
implementations detected, not of what exists);
- a table implemented by only one runtime, and cases marked `not-applicable` for the others;
- disagreement about a `calibration.json` threshold — the thresholds are published, the
policy built on them belongs to the consumer;
- a divergence already recorded in [`docs/lexicon-port-divergence.md`](docs/lexicon-port-divergence.md);
- the five data files no fixture constrains, and the finite homoglyph map.
If you are unsure which side something falls on, report it privately anyway.
## Why a confirmed defect is usually not fixed here first
This is the part that differs from an ordinary repository, and it is worth reading before
you conclude that a fix is being stalled.
Most data here is an **extraction**: a copy of a table that lives in a runtime, kept
behaviour-identical to it on purpose. Correcting an entry here — even a genuinely wrong one
— would make the copy disagree with the implementation it was taken from. Two implementations
answering differently on the same input is precisely the failure this repository exists to
prevent, so producing one as a *fix* would be self-defeating.
A confirmed defect in extracted data therefore travels:
1. the report reaches the maintainer here, privately;
2. the owning runtime is identified — every data file names it in `provenance.source_repo`
— and the report is routed there;
3. the decision is taken **there**, where the pattern is under test against a real suite;
4. once the source has moved, this repository **re-extracts** from a pinned public commit
and tags a release;
5. consumers pull that tag on their own schedule.
Stated plainly, because it affects you: fix latency is bounded by the owning runtime's
schedule and by each consumer's pull, not by this repository's. If you need protection
sooner than that, the fix belongs in your own runtime; this repository is where it becomes
shared, not where it becomes real.
Two things do **not** take that route:
- **A real secret in the repository or its history** (class 4) is handled here, immediately.
- **Data authored in this repository** rather than extracted — it is flagged as such where
it occurs, for example `authored_payloads` in `conformance/manifest.json` — is this
repository's own to correct.
The precedent is on the record. In `v0.3.0` a detection pattern changed value here for the
first time, and it changed because the owning runtime had changed its own and this
repository re-read the source — not because a reviewer here judged the old value wrong.
`docs/lexicon-port-divergence.md` records a row where two runtimes still disagree and this
repository deliberately did *not* pick a winner. Provenance is the ground for moving a
value. Merit is not, and the day it becomes the ground, the guarantee is gone.
## Supported versions
Pre-1.0. Only the latest tag is fixed; there are no back-ported branches.
Consumers vendor this repository (`git subtree`, or a pinned copy) rather than installing
it, so a fix reaches a consumer only when that consumer pulls the new tag. There is no CI in
this organisation and nothing polls for updates. When a fix changes detection data, the
maintainer notifies the known consumers directly — but their upgrade is their own action, on
their own schedule.
Read the `CHANGELOG.md` entry before upgrading rather than the version number: in 0.x, a
change to what a conforming runtime *finds* is still a minor bump.
## Disclosure
There is no formal embargo SLA here. The maintainer will acknowledge the report, agree a fix
and disclosure timeline with the reporter, and credit the reporter in the `CHANGELOG.md`
entry unless they prefer to remain anonymous.
If the report is a false negative in a table that has already shipped, the changelog entry
will say what slipped through, in enough detail that a consumer still pinned to the older
tag can judge whether it is exposed. Naming it is the point of fixing it.