1
0
Fork 0

feat(okf): resource-URL https allowlist reject-gate (T3, TDD, +10)

This commit is contained in:
Kjell Tore Guttormsen 2026-07-06 07:43:37 +02:00
commit f9a89938b4
2 changed files with 92 additions and 0 deletions

View file

@ -31,9 +31,11 @@ __all__ = [
"parse_frontmatter",
"scan_concept",
"validate_concept_path",
"validate_resource_url",
"OKFError",
"OKFFrontmatterError",
"OKFPathError",
"OKFResourceError",
]
_FENCE = "---"
@ -58,6 +60,13 @@ class OKFPathError(OKFError):
"""A concept path is unsafe (traversal, absolute, or reserved-name shadow)."""
class OKFResourceError(OKFError):
"""A ``resource`` URL is not on the https allowlist."""
_URL_SCHEME_RE = re.compile(r"^([A-Za-z][A-Za-z0-9+.\-]*):")
# `index.md` (directory listing) and `log.md` (update history) are reserved by
# the OKF spec and MUST NOT name concept documents — at any directory level.
_RESERVED_BASENAMES = frozenset({"index.md", "log.md"})
@ -159,6 +168,33 @@ def validate_concept_path(path):
return path[: -len(".md")]
def validate_resource_url(url):
"""Validate a concept's ``resource`` URL against the https allowlist (T3).
The OKF format places no constraint on the ``resource`` scheme (verified
against SPEC.md), so this default-deny allowlist is the only gate: it
**rejects** anything that is not ``https`` ``http``, ``data:``,
``javascript:``, ``file:``, ``blob:``, ``ftp:`` and schemeless/relative
strings *before commit*. This is reject, not defang: ``neutralize`` renders
dangerous schemes inert for human audit; this refuses to persist them at all.
Returns ``url`` unchanged on success; raises :class:`OKFResourceError`
otherwise.
"""
if not url or not isinstance(url, str):
raise OKFResourceError("empty or non-string resource URL: %r" % (url,))
stripped = url.strip()
if " " in stripped or any(ord(c) < 0x20 for c in stripped):
raise OKFResourceError("resource URL contains whitespace/control chars: %r" % url)
match = _URL_SCHEME_RE.match(stripped)
scheme = match.group(1).lower() if match else None
if scheme != "https":
raise OKFResourceError(
"resource URL must use the https scheme (got %r): %r" % (scheme, url)
)
return url
def _parse_flat(fm_lines):
result = {}
i = 0