docs(okf): capture OKF-ingestion security brief as v0.2 future work

Google's Open Knowledge Format (OKF v0.1) is the LLM-wiki pattern this
library guards. Capture a security brief for OKF ingestion as tracked
future work -- NOT shipped scope. The v0.1 core is format-agnostic text;
OKF surfaces beyond the body (YAML frontmatter, resource URLs, cross-link
graph, file path / reserved names, format-level authenticity) are uncovered.

- Coverage claims verified against the code at 5397ba1 (brief section 9): no
  YAML parse in src, neutralize defangs but has no reject-gate, no path/graph
  logic, disposition machinery exists.
- OKF confirmed real at the format level (Google Cloud, 2026-06-12; spec
  GoogleCloudPlatform/knowledge-catalog).
- Design move: an OKF adapter ON TOP of the format-agnostic core; the core
  stays text->findings.
- Section 8 lists 8 v0.2 tasks (T1-T8); T8 (surface two OKF residual risks in
  README honest-limits) can land independently.
This commit is contained in:
Kjell Tore Guttormsen 2026-07-06 07:05:45 +02:00
commit 5cd9b451fb

198
docs/OKF-INGESTION-BRIEF.md Normal file
View file

@ -0,0 +1,198 @@
# Security brief: OKF ingestion with `llm-ingestion-guard`
**Status:** Draft v0.1 — captured 2026-07-06.
**Applies to:** write-time ingestion pipelines for LLM wikis based on Google's
Open Knowledge Format (OKF v0.1).
**Inputs:** internal and external data, ingested automatically or manually.
> This document is a **future-work brief**, not shipped scope. The `v0.1` core
> (`docs/PLAN.md`, all modules built + hardened at commit `5397ba1`) is
> **format-agnostic text** (`prepare_input(text)` / `screen_output(text, policy)`).
> None of the OKF-specific controls below are implemented yet; §8 is the v0.2
> task list. The coverage claims in §4 were verified against the code on
> 2026-07-06 — see the verification log in §9.
---
## 1. Summary
OKF is, in practice, exactly the "LLM-wiki" pattern `llm-ingestion-guard` was
built to protect: untrusted content is enriched/persisted into a markdown corpus
that a *downstream* agent later reads as **trusted context**. The guard therefore
belongs at the persist gate, and the existing contract (carrier-strip → fence →
tool-less transform → capability isolation → output scan → fail-secure) covers the
markdown *body* directly.
OKF, however, introduces five attack surfaces **beyond** the body that must be
covered explicitly: YAML frontmatter, `resource` URLs, the cross-link graph, file
path / reserved names, and the absence of format-level authenticity. The largest
residual risk is **semantic poisoning**, which is out of scope by design (§7).
## 2. Scope
**In scope:** everything written into or merged into an OKF bundle that the
pipeline controls — both its own enrichment output and received external bundles.
**Out of scope:** query-time / runtime guardrails, the vector/embedding layer, and
semantic truth-assessment of concepts (see §7). These match the existing
out-of-scope boundary in `README.md` and `docs/PLAN.md`.
## 3. Why OKF is the right place to enforce
- OKF formalizes the LLM-wiki pattern into a portable markdown format; concepts
are meant to be read directly by agents as curated, authoritative context.
- The format has **no schema registry, no central authority, and no
signing/authenticity**. A received bundle's claimed origin is not verifiable at
the format level. (Verified at the format level — OKF v0.1 is a directory of
markdown files with YAML frontmatter, minimal by design; §9.)
- Consequence: **your ingestion pipeline *is* the trust boundary.** Provenance and
disposition must be stamped by you at ingestion, not assumed from the format.
## 4. Attack surfaces and required controls
Status column verified against the code at commit `5397ba1` on 2026-07-06 (§9).
| Surface | Vector | Required control | Status today |
|---|---|---|---|
| **Markdown body** | Prompt injection, hidden carriers (zero-width, BIDI, Unicode-tag, HTML comments, `data:` URIs) | Carrier-strip → fence → tool-less transform → output scan → fail-secure | ✅ Covered (core contract) |
| **YAML frontmatter** | Injection in `title`/`description`/`tags` + arbitrary unknown keys; `description` propagates into `index.md` (read **first** under progressive disclosure); YAML anchor/alias DoS + dangerous type coercion | Same sanitize/scan on frontmatter *values*; parse YAML with a safe loader | ⚠️ **Partial** — values are scanned **iff** the caller passes the whole document (frontmatter included) as text; the core **never parses YAML**, so the safe-loader is a genuinely new gate at an OKF-adapter boundary (§9) |
| **`resource` URL** | `data:`/`javascript:`/`file:`/SSRF target that a consumer or visualizer fetches | Scheme allowlist (`https` only), validate before commit | ❌ **New control**`neutralize` defangs schemes for human audit but there is **no reject-gate** and no `resource`-field concept (§9) |
| **Cross-link graph** | "Dead links are valid" → *dormant* injection: plant a link to a non-existent concept-ID now, write the poisoned target later | Constrain link targets to relative in-bundle paths + scheme check; re-scan on write of a link target | ❌ **New control** (graph level) |
| **File path / reserved names** | Concept-ID = file path minus `.md`; path traversal (`../`) and shadowing of reserved `index.md`/`log.md` | Sanitize/normalize paths; reject `..` and reserved filenames as concept names | ❌ **New control** — no path validation in the core (§9) |
| **`log.md` / provenance** | No authenticity at the format level | Stamp disposition + trust tier per concept | ↔️ **Machinery exists**`Trust` × `Provenance` × `Disposition` types are built; emission-to-`log.md` + an origin/channel stamp is new wiring on top (§9) |
## 5. Trust-tier model
Trust follows the data's **origin**, not the insertion channel. A manual paste of
external material is still external. This maps onto the existing `Trust` enum
(`TRUSTED` / `UNTRUSTED`) plus a channel dimension the pipeline records.
| Origin | Channel | Tier | Treatment |
|---|---|---|---|
| External | Automatic | Lowest | Full contract + fail-secure |
| External | Manual | Low | Full contract (channel grants *no* discount) |
| Internal | Automatic | Medium | Full contract; may relax the lexicon threshold with logging |
| Internal | Manual (trusted author) | Highest | Keep carrier-strip + scan; **note residual risk §7** |
**Nuance:** "manual internal" is not safe in itself. In-place edits by a trusted
author are outside the guard's scope — the channel does not authenticate the
content's origin. This is the same insider-edit limitation already in
`README.md` honest-limitations.
## 6. Two ingestion modes
**(a) Own enrichment output** — your agent writes concepts. The guard runs on your
own output before commit. One transform per concept. This is the existing
`prepare_input` → tool-less transform → `screen_output` bookend flow, applied
per concept.
**(b) Received external bundle** — you merge a whole third-party OKF bundle (the
format is explicitly built for cross-organization exchange). The guard must
**iterate over each concept** and validate body + frontmatter + links + `resource`
before merge — not treat the bundle as one unit. This is a **new iterator layer**
(§8 T7); the core has no bundle concept.
## 7. Residual risk (explicitly out of scope)
Cross-referenced to `README.md` honest-limitations. Items 1, 3, 5 are **already
documented there**; items 2 and 4 are OKF-specific and **not yet surfaced** (→ §8 T8).
1. **Semantic / factual poisoning** — a plausible-but-wrong concept (wrong
join-path, wrong metric definition, wrong runbook step) passes entropy + lexicon
cleanly and is treated as ground truth. **Highest impact for a wiki.** Needs
human review or source verification, not token analysis. *(Already in README;
the `grounding` module ships the `SourceGroundingCheck` seam for it.)*
2. **Dormant broken-link injection** — see §4; a per-concept write-time scan does
not see it over time. Needs graph / re-scan logic. *(Not yet in README.)*
3. **Trusted-author in-place edits** — normal OKF workflow (git-native editing) is
not gated by the guard. *(Already in README as the insider-edit limitation.)*
4. **Own security content as a false positive** — concepts that *document*
prompt-injection payloads will trip the carrier-strip / fail-secure path. Needs
a deliberate escaped path for "this concept is *about* attacks." *(Not yet in
README; also tracked as the corpus false-positive tension.)*
5. **Multimodal stego, embedding layer, query-time** — refer to the existing
threat-model docs. *(Already in README / out-of-scope boundary.)*
## 8. Recommendations → v0.2 task list
The unifying design move: add an **OKF adapter** *on top of* the format-agnostic
core. The core stays `text -> findings` (design principle 3); the adapter knows OKF
structure (frontmatter, paths, links, `resource`, bundles) and feeds scannable text
regions into the existing `sanitize` / `scan_output` / `disposition` machinery. No
YAML/format awareness leaks into the core.
| # | Task | Builds on | New? |
|---|---|---|---|
| **T1** | Whole-concept scan surface — route frontmatter *values* + `resource` strings through the same `sanitize`/`scan_output` path as the body | `sanitize`, `output` | adapter layer |
| **T2** | YAML parse-safety gate — safe loader (block anchor/alias DoS + dangerous type coercion) at the adapter boundary | — | **new** |
| **T3** | `resource`-URL allowlist gate — hard-reject non-`https` (and `data:`/`javascript:`/`file:`) *before commit* | `neutralize` scheme detection | **new** (reject, not defang) |
| **T4** | Path / reserved-name validation — reject `..` traversal + `index.md`/`log.md` shadowing; normalize concept-ID → path | — | **new** |
| **T5** | Cross-link graph re-scan — constrain targets to relative in-bundle paths + scheme check; re-scan on write of a link target (dormant-injection, §7.2) | — | **new** (graph state) |
| **T6** | Provenance stamping — origin + channel → tier + disposition per concept, emitted to `log.md` | `Trust`/`Provenance`/`Disposition` | new emission on existing types |
| **T7** | Bundle-import iterator (mode b) — iterate concepts; validate body + frontmatter + links + `resource` per concept | §6 bookends | **new** |
| **T8** | Docs — surface §7.2 (dormant broken-link) + §7.4 (own security content) in README honest-limitations; §7.1/§7.3/§7.5 already there | `README.md` | doc |
**Sequencing note:** T1T4 + T6 are per-concept and composable with the existing
bookends; T7 is the mode-(b) wrapper around them; T5 is the hardest (needs graph
state across writes) and the one the residual risk §7.2 explicitly warns a
per-concept scan cannot cover. T8 is cheap and can land independently.
**Naming caveat:** the illustrative stamp vocabulary in an earlier draft
(`SANITIZED`/`FENCED`/`QUARANTINED`/`FAIL_SECURE`) is **not** the actual
`Disposition` enum (which is `WARN` / `QUARANTINE_REVIEW` / `FAIL_SECURE`). A
provenance stamp (T6) would compose from `Trust` × `Provenance` × `Disposition`,
not add new disposition values.
## 9. Verification log (2026-07-06, commit `5397ba1`)
Per the operator's verification duty — what was checked against ground truth, and
what remains the operator's premise.
**Verified against the code:**
- **The guard is format-agnostic text.** Public API is `prepare_input(text)` /
`screen_output(text, policy)` (`src/llm_ingestion_guard/__init__.py`). A grep of
`src/` finds **no YAML import and no frontmatter parsing** anywhere. Implication
for the §4 "YAML frontmatter" row: frontmatter *values* are scanned **only if the
caller includes them in the text** passed to the scanner; the safe-loader is a
genuinely new gate, because the core never parses YAML.
- **`resource` URL — no reject-gate today.** `neutralize.py` recognizes dangerous
schemes (`javascript|data|vbscript|file|blob` defanged; `http(s)`/`ftp``hxxp`)
but this is *neutralization for human audit*, not an allowlist that **rejects**
non-`https` before commit, and there is no `resource`-field concept. §4 "New
control" confirmed.
- **Cross-link graph, file path / reserved names — absent.** No graph logic, no
path normalization / traversal check / reserved-name (`index.md`/`log.md`) check
in `src/`. §4 "New control" confirmed for both.
- **Provenance machinery exists.** `disposition.py` defines `Disposition`
(`WARN`/`QUARANTINE_REVIEW`/`FAIL_SECURE`), `Trust` (`TRUSTED`/`UNTRUSTED`),
`Provenance` (`PROSE`/`CODE_FENCE`/`LOCALIZED`) + presets. Per-concept stamping
to `log.md` is new emission on top; the types are ready. §4 "Machinery exists"
confirmed.
- **README already covers residual risks 1, 3, 5** (semantic poisoning + grounding
seam; insider in-place edits; multimodal/query-time). Residual risks 2 and 4 are
**not** yet in README → T8.
**Verified externally:**
- **OKF is real, format level.** Google Cloud's Open Knowledge Format v0.1,
announced 2026-06-12: a directory of markdown files with YAML frontmatter,
minimal by design (only a `type` field required), vendor-neutral,
producer/consumer-independent — which confirms the LLM-wiki framing, the
cross-org exchange mode (b), and the "no central authority / no signing"
premise in §1/§3. Spec: `GoogleCloudPlatform/knowledge-catalog/okf/SPEC.md`.
**Operator premise — not independently verified this session:**
- OKF *concept-level* specifics used above (concept-ID = file path minus `.md`;
reserved `index.md`/`log.md`; a `resource` field; `description` propagating into
`index.md` under progressive disclosure) are taken from the operator's brief
citing the OKF spec. They are consistent with the format-level facts but were not
each checked against `SPEC.md`. A focused pass over the OKF spec should confirm
the reserved-filename and `resource`-field details before T3/T4/T5 are built.
## Sources
- [Google Cloud — How the Open Knowledge Format can improve data sharing](https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing/)
- [GoogleCloudPlatform/knowledge-catalog — okf/SPEC.md](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md)
- [MarkTechPost — Google Cloud Introduces Open Knowledge Format (OKF)](https://www.marktechpost.com/2026/06/16/google-cloud-introduces-open-knowledge-format-okf-a-vendor-neutral-markdown-spec-for-giving-ai-agents-curated-context/)