1
0
Fork 0

docs(limits): two limits are conceded for 1.x, and the last 'pending' is retired

D3: :492 (Severity carries disposition intent) and :460 (the input-cap
asymmetry) are the two limits that can only be closed by changing an
exported symbol. Both now say 'conceded for 1.x', with the 2.0.0
consequence spelled out, instead of 'deferred deliberately'.

D5: :88's 'a calibration fix is pending' is gone. The concession is
narrower than the other two -- no fix is promised, but it is calibration,
so one may land in any 1.x release without breaking the contract.

SECURITY.md carries the part an outsider acts on: the support window is
rewritten off 'pre-1.0', the freeze is stated as a promise about the
Python surface and explicitly NOT about detection behaviour, and the two
permanent concessions join the documented-boundaries list.
This commit is contained in:
Kjell Tore Guttormsen 2026-08-13 22:15:02 +02:00
commit e9d8fb2b9d
2 changed files with 46 additions and 8 deletions

View file

@ -6,9 +6,19 @@ downstream corpus. Reports are welcome.
## Supported versions
The project is pre-1.0 (`0.7.x`, alpha). Only the latest published version receives
fixes; there are no back-ported security branches yet. Pin a version and watch the
`CHANGELOG.md` `### Security` entries.
The project is `1.x`. Only the latest published version receives fixes; there are no
back-ported security branches. Pin a version and watch the `CHANGELOG.md`
`### Security` entries.
**What `1.0.0` freezes, and what it does not.** The freeze is a semver promise about
the *Python surface*: no name exported from `llm_ingestion_guard` is removed, renamed
or given a different meaning without a `2.0.0`. It is **not** a promise that detection
behaviour holds still. Severities, thresholds, lexicon entries and the dispositions
they produce are calibration, and calibration moves in minor and patch releases — a
payload that disposes `WARN` on `1.0.0` may dispose `FAIL_SECURE` on a later `1.x`,
and that is a fix rather than a break. Pin a version if you depend on a specific
grading, and assert on the disposition your policy requires rather than on a severity
you happened to observe.
## Reporting a vulnerability
@ -50,7 +60,18 @@ Out of scope (documented boundaries — see the **Known limitations** section of
- a HIGH finding in *trusted* prose disposing to `WARN` (§4.7 trust-scaling);
- hex-wrapped (non-base64) secret egress;
- multimodal / binary-layer carriers (OCR, font stego, VBA/macros, encrypted files);
- the multilingual homoglyph-mix false positive.
- the multilingual homoglyph-mix false positive;
- a low `Severity` on an ordinary outward fetch — on the detection side 1.x does not
separate *what was seen* from *how bad it is*, so read the finding `id` for the
capability;
- the input-cap asymmetry at `MAX_INPUT_CHARS`: surfaces that return content raise
`OversizeInputError`, surfaces that return findings truncate and emit
`active:oversize-input`. Past the cap, "no finding" means "not looked at".
The last two are conceded for the whole of `1.x`, deliberately and in writing
(`docs/LIMITATIONS.md`): closing either changes an exported symbol's meaning and is
therefore a `2.0.0` change. The homoglyph false positive is conceded differently — no
fix is promised, but it is calibration, so one may land in any `1.x` release.
If you are unsure whether something is in scope, report it privately anyway.

View file

@ -89,7 +89,13 @@ items; this is the full list, each with the mechanism.
`homoglyph:cyrillic-latin-mix` (MEDIUM) flags a Latin letter adjacent to a
Cyrillic look-alike, so genuine bilingual prose → MEDIUM → under untrusted →
QUARANTINE_REVIEW — a real false positive for an inbox that expects multilingual
content. A calibration fix is pending.
content. **Conceded for 1.x: no fix is promised.** The rule fires on codepoint
adjacency, which genuine bilingual prose produces as readily as a substitution
attack does. Narrowing it is a calibration question, not an API one, so a fix
may land in any 1.x release without breaking the contract — but none is
scheduled, and a caller that ingests multilingual prose should raise its
untrusted-tier threshold rather than wait for one. `SECURITY.md` lists this as a
documented boundary, not a vulnerability.
- **Insider in-place edits** by a trusted author are out of the untrusted-content
threat model.
- **Text-only.** The core is `text -> findings`: it parses no files (no
@ -475,7 +481,13 @@ items; this is the full list, each with the mechanism.
an `oversize-input` finding (`active:oversize-input`, OWASP LLM10), and
`link_graph` records `(from_id, body_length)` in `LinkGraphResult.truncated`,
which is what lets a caller tell "no links past here" apart from "no links
*read* past here".
*read* past here". **The split itself is conceded for 1.x, not deferred.** It is
a surface property, not calibration: making the two halves agree later means
either raising where a caller gets a value today, or returning a truncated value
where one raises — a change to an exported symbol's contract in either
direction, therefore `2.0.0`. 1.x keeps the rule as stated: a surface that
returns *content* rejects at the cap, a surface that returns *findings*
truncates and says so.
## The six documented gaps (tracked by the coverage matrix)
@ -505,8 +517,13 @@ fails the test, forcing this doc to be updated:
capability, because the detector already decided it did not matter. Closing
this means giving detectors a channel that says what was seen separately from
how bad it is, which changes the grading and therefore fires the
consumer-notification promise in `docs/PLAN-v1.md`. Deferred deliberately, not
overlooked.
consumer-notification promise in `docs/PLAN-v1.md`. **Conceded for the whole of
1.x, not deferred.** That channel changes `Finding` and `Severity`, which is a
`2.0.0` change under the version contract, so 1.x ships with the coupling intact
by decision rather than by omission. A caller that needs the capability
separately from the grade must read the finding `id``active:markdown-image`
names the outward fetch whatever severity it carries — and must not infer
"nothing was seen" from a low `Severity`.
- **A ZWJ hidden between two emoji is exempt, and ZWNJ's own false-positive
class is untouched.** U+200D composes emoji (👩‍💻 is WOMAN + ZWJ + PERSONAL