`tags: [a, b, c]` is the form SPEC.md 4.1's own frontmatter skeleton writes out,
and 9/53 upstream reference concepts use it. It raised on the `[` indicator.
It parses now, to the same list its block-sequence sibling already produced.
The predicate is character-level, inside `_parse_flow_sequence`: an element is a
plain scalar only if it is non-empty and carries none of `{ } [ ] : , " ' #`,
and it then passes the unchanged scalar-indicator rule. Everything that would
need YAML semantics to split or unquote still raises - a quoted element (quotes
are retained here, never stripped), a colon or comma inside an element, a
sequence inside a sequence, an empty element, an anchor, an alias. A sequence
may not mix scalars and mappings, the rule the block list already carries, and
the mixing verdict is reached before the element is parsed so the caller is told
about the mix rather than about a key the allowlist would have named instead.
The 1.3.0 `sources` flow-mapping carrier is unchanged and pinned against
regression. Depth 1 is not spent: the elements are leaves.
Measured with the denominator, against the pinned corpus (`_okf-upstream` @
3fcbb9f, 53 non-reserved documents) and the pinned SPEC (`_okf-canonical` @
ad30107). Baseline reproduced first, with a known-positive control, at 0/53.
After: 6/53, all six in acme_retail. It does not close the corpus - 44/53 still
stop on `generated` written as a top-level block mapping, which spends the
no-nesting-past-depth-1 rule and is a security decision, out of scope here.
P1 alone, per the operator decision of 08.09. The two neighbouring predicates
were measured and deliberately not built: a flush-left block sequence and a
folded plain scalar release 0/53 each on their own, and stacked on this one they
still measure 6/53. `_consume_block_list`, the `description` continuation and
the allowlist are untouched.
docs/LIMITATIONS.md's tags/description entry is rewritten against the
measurement: three of its claims were wrong. The parser does have a
sequence-value type (since 1.3.0 - what it lacks is the indentation the corpus
omits); the figure is 6/53, not the 1.2.0-era 4/53; and tags/description are
not the residual that blocks the corpus. README gains the sequence carrier in
the paragraph that already describes the mapping one.
Self-safety: the predicate compiles no regex, so docs/redos-sweep.py cannot see
it. Measured instead on the CPU clock - linear in element length (exponent
0.86-0.99) and in element count (0.97-1.05) over four doublings to 800_000 -
and pinned by two bounds in tests/test_okf.py.
Six rows that pinned the old refusal are re-aimed at the class that still
holds - the quoted element - the way the 1.3.0 rows were when the carrier
opened. One of them lives in src/llm_ingestion_guard/coverage.py, which is why
the src diff is three files rather than one.
Version 1.4.0 in the code only. The CHANGELOG entry stays under Unreleased and
no tag is cut: README's badge and install pin must keep naming a tag that
exists.
Gates after `git add`: 893 passed (was 868), coverage 130/130 + 6/6 gaps,
redos-sweep exit 0, LIMITATIONS still 45 entries.
88 lines
5.5 KiB
Markdown
88 lines
5.5 KiB
Markdown
# llm-ingestion-pipeline-security
|
|
|
|
## Kontekst
|
|
|
|
Gjenbrukbar, minimal defensiv layer for LLM **ingestion**-pipelines (write-time),
|
|
til forskjell fra query-time chatbot-guardrails. Pakker det arkitektoniske
|
|
kontraktet — sanitize → fence → tool-less karantenert transform → per-stadium
|
|
capability-isolasjon → scan output før commit → fail-secure — som komponerbar,
|
|
framework-agnostisk kode.
|
|
|
|
Referanse-implementasjon: `claude-code-llm-wiki` Stage B (`tools/wiki_ingest/`).
|
|
Lexikon-seed: `injection-patterns.mjs` fra `llm-security`-pluginen.
|
|
|
|
Repoet er på **v1.4.0 i koden, UUTGITT** (`pyproject.toml` + `__init__.py` er
|
|
bumpet; README-badge, install-pinnen, ADOPTION-BRIEF og BRIEF står med vilje
|
|
igjen på `1.3.0`, som er den siste taggen som FINNES — en install-pin må peke på
|
|
en ekte tag). Release-commiten (CHANGELOG-overskrift datert, de fire
|
|
dokumentflatene bumpet, tag) er ikke tatt. Den eksporterte Python-surfacen er frosset under semver
|
|
(deteksjonsatferd er det IKKE; kalibrering flytter seg i 1.x). Stdlib-kjernen er
|
|
bygget og testet (15 moduler +
|
|
topp-nivå wiring, showcase + korpus), inkl. OKF-adapter og aktivt-innhold-
|
|
detektor (EchoLeak-klassen) i output-gaten. OKF-frontmatterens mapping-klasse
|
|
har **fire** uttrykkbare bærere (G3 21.08, G30 02.09): flow-mapping som verdi
|
|
og som blokkliste-element, flow-sekvens av flow-mappinger, og blokk-sekvens av
|
|
blokk-mappinger (SPEC §5.1s egen form). HVER nøkkel i alle fire står på
|
|
allowlisten og hvert blad er en ren skalar. Formen er trygg fordi allowlisten
|
|
inspiserer hver nøkkel; det blanke avslaget var håndhevelsen, ikke poenget.
|
|
**`resource` er allowlistet KUN inne i en `sources`-oppføring** — foreldre-
|
|
nøkkelen avgjør, så `executor`/`attester` sin `resource` (§10, dør C) avvises
|
|
gjennom hver eneste bærer. 1.2.0s begrunnelse for å utelate den (parseren
|
|
manglet foreldre-kontekst) var målt feil: konteksten var der, den var bare
|
|
aldri tredd gjennom. Topp-nivå blokk-mapping, dotted- og inline-kolon-rutene
|
|
raiser fortsatt, en blokkliste kan ikke blande skalarer og mappinger, og en
|
|
avvist mapping raiser — den degraderer aldri til en streng (1.1.0-defekten).
|
|
**Flow-sekvens av rene skalarer (`tags: [a, b]`) PARSER fra 1.4.0** (P1,
|
|
operatørbeslutning 08.09) — SPEC §4.1s eget skjelett. Et element er en ren
|
|
skalar kun hvis det er ikke-tomt og uten `{ } [ ] : , " ' #`, og så gjelder den
|
|
uendrete indikator-regelen; sitert element, kolon/komma i elementet, sekvens i
|
|
sekvens, tomt element, anker og alias raiser fortsatt, og en flow-sekvens kan
|
|
ikke blande skalarer og mappinger. **Målt med nevner: 0/53 → 6/53** på pinnet
|
|
OKF-korpus (`3fcbb9f`). Den bindende skranken er IKKE tags/description, men
|
|
`generated` som topp-nivå blokk-mapping (44/53) — den bruker opp dybde-1 og er
|
|
en sikkerhetsbeslutning. P2 (blokksekvens uten innrykk) og P3 (foldet plain
|
|
scalar) er MÅLT til 0/53 hver og bevisst IKKE bygget. `sources[].resource`
|
|
URL-valideres ALDRI (T3 ser kun topp-nivå `resource`) — §5.1 tillater
|
|
bundle-relative stier og scope-beskrivelser, så en https-gate ville over-blokkert
|
|
konforme bundles; konsumenten må selv kalle `validate_resource_url`. Mode-b `import_bundle` skanner
|
|
reserverte strukturfiler (`index.md`/`log.md`) i mottatte bundles i stedet for å
|
|
path-avvise dem; upload-front-end beholder shadow-reject (`allow_reserved=False`).
|
|
Output-gatens decode-and-rescan mater dekodet base64-klartekst gjennom BÅDE lexicon
|
|
og secret-egress (LLM02), så en base64-innpakket credential fanges som
|
|
`decoded:egress:*` i stedet for å forsvinne; hex-innpakket er en dokumentert
|
|
restgap (entropy eksponerer kun base64-klartekst). `active:raw-html` krever et
|
|
EKSTERNT mål på URL-attributt-grenen, og `<base>` er ute av det aktive navnesettet;
|
|
scanner og mutator har hver sin predikat (`is_active_tag` / `is_defangable_tag`).
|
|
Rå HTML graderes nå også på BÆRER: `<a>`/`<area>` er klikk-krevende og rapporteres
|
|
som `active:raw-html-link` (MEDIUM), og en tagg hvis hele affordans ER en URL den
|
|
ikke bærer (`</a>`, `<Frame>`, `<video />`) er inert. Klassifisering skjer i
|
|
`active_tag_class`; `is_active_tag` er en tynn wrapper, og census patcher den
|
|
FØRSTE (en boolsk patch kan ikke uttrykke en regradering).
|
|
ZWJ (U+200D) dømmes på KONTEKST, ikke identitet — unntas kun mellom to emoji, på
|
|
begge flater (`sanitize` eier predikatet, `output` importerer det).
|
|
Start med `docs/BRIEF.md` for design, `README.md` for bruk, `docs/PLAN.md` for
|
|
byggerekkefølgen.
|
|
|
|
## Konvensjoner
|
|
|
|
- Norsk for dialog og planer, engelsk for kode og innhold (repoet er publisert).
|
|
- Ingen GitHub — kun Forgejo (`git.fromaitochitta.com`).
|
|
- Remote satt: offentlig `open/`-speil på Forgejo; push hver commit (durabelt autorisert).
|
|
- Minimal-dependency: stdlib-first kjerne; ML/judge-detektorer bak extras.
|
|
|
|
## Communication patterns
|
|
|
|
### Linking to local files
|
|
|
|
When pointing to local files in responses, always use markdown link syntax with a descriptive name:
|
|
|
|
- Use `[Human-friendly name](file:///absolute/path)` — never bare `file:///...` URLs or autolinks `<file://...>`.
|
|
- Always use absolute paths. Never `~/` or relative paths.
|
|
- For multiple files, render as a bullet list of named markdown links.
|
|
|
|
Why: bare `file://` URLs only render the first as clickable across multiple lines. Named markdown links make each entry independently clickable and look cleaner.
|
|
|
|
Example (the path is a placeholder — the checkout root is the reader's own, and
|
|
this file is published, so it must not carry one machine's directory layout):
|
|
|
|
- [Brief](file:///absolute/path/to/llm-ingestion-pipeline-security/docs/BRIEF.md)
|