1
0
Fork 0

feat(active-content,okf): bound the last two detection surfaces

`scan_active_content` called directly and `okf.link_graph` were the two surfaces
still reading attacker-supplied text with no cap — the first reached by an
adapter that wants the active-content classes alone, the second running a
`findall` over every body in a bundle. Both are detection-shaped, so they
truncate and flag rather than raise the way the transform surfaces do: what a
detector shortens is its own coverage, not the caller's content.

Truncation is only honest if it is visible, so neither goes quiet: the scanner
emits `active:oversize-input` (LLM10), and `link_graph` records
`(from_id, body_length)` in `LinkGraphResult.truncated` — the field that lets a
caller tell "no links past here" from "no links read past here".

Reached through `scan_output`, the text is already under that surface's cap and
`max_scan_chars` is now passed down, so the flag is raised once, there.
This commit is contained in:
Kjell Tore Guttormsen 2026-08-10 14:48:54 +02:00
commit b90233481a
6 changed files with 111 additions and 22 deletions

View file

@ -496,6 +496,26 @@ def test_link_graph_resolves_present_target():
assert graph.dangling == ()
def test_link_graph_caps_an_oversize_body_and_records_it():
# Self-safety (OWASP LLM10): the graph runs a `findall` over every body in
# the bundle, all of it attacker-supplied. It is detection-shaped, so it
# truncates and records rather than raising — the caller's documents are not
# what it returns.
body = "y" * 200 + "\nSee [later](/b/target.md).\n"
graph = link_graph({"a/main.md": "---\ntype: t\n---\n" + body}, max_scan_chars=50)
assert graph.truncated == (("a/main", len(body)),)
# The link past the cap was never read — that cost is what the record announces.
assert graph.dangling == ()
def test_link_graph_body_at_the_cap_is_not_recorded():
body = "See [later](/b/target.md).\n"
graph = link_graph({"a/main.md": "---\ntype: t\n---\n" + body}, max_scan_chars=len(body))
assert graph.truncated == ()
assert ("a/main", "b/target") in graph.dangling
def test_link_graph_records_rejected_dangerous_link():
bundle = {"a/main.md": "---\ntype: t\n---\n[x](javascript:alert(1))\n"}
graph = link_graph(bundle)