1
0
Fork 0

feat(okf): whole-concept scan surface — frontmatter values + resource + body (T1, TDD, +6)

This commit is contained in:
Kjell Tore Guttormsen 2026-07-06 07:39:18 +02:00
commit f8bc5db547
2 changed files with 87 additions and 2 deletions

View file

@ -24,7 +24,15 @@ lost; richer scalar forms are a future refinement, not a silent parse.
import re
__all__ = ["parse_frontmatter", "OKFError", "OKFFrontmatterError"]
from .output import scan_output
from .report import Report, Source
__all__ = [
"parse_frontmatter",
"scan_concept",
"OKFError",
"OKFFrontmatterError",
]
_FENCE = "---"
_KEY_RE = re.compile(r"^[A-Za-z0-9_][A-Za-z0-9_-]*$")
@ -71,6 +79,37 @@ def parse_frontmatter(document):
return frontmatter, body
def scan_concept(document, *, source=Source.OUTPUT):
"""Scan every scannable region of one OKF concept, merged into one Report.
T1 whole-concept scan surface. The body is not the only injectable region:
OKF frontmatter *values* (notably ``description``, which propagates into
``index.md`` and is read first under progressive disclosure), ``tags`` items
and the ``resource`` string are all attacker-controlled and must go through
the same ``scan_output`` path as the body. Findings from all regions are
merged so nothing in the frontmatter escapes the gate.
Frontmatter is parsed with the strict :func:`parse_frontmatter` gate first,
so a parse-safety violation (T2) raises before any scanning.
"""
frontmatter, body = parse_frontmatter(document)
report = Report()
for region in _scannable_regions(frontmatter, body):
report.extend(scan_output(region, source=source).findings)
return report
def _scannable_regions(frontmatter, body):
"""The text regions of a concept that carry attacker-controlled content."""
regions = [body]
for value in frontmatter.values():
if isinstance(value, list):
regions.extend(value)
elif value:
regions.append(value)
return regions
def _parse_flat(fm_lines):
result = {}
i = 0