Files
booth/persistent-memory.d/2026-09-28-a-posted-doc-could-run-script.md
T
vh 190a75a0e1 fix(docs): a posted doc cannot run script on the Booth's origin
Found by design-dev's impeccable run and confirmed at source. Python-Markdown
passes raw HTML through, and doc.html and booth.html render the result |safe.
A <script> in any session's .md ran on the Booth's origin, and a contract that
quoted <pre> opened a real one and swallowed the rest of the doc.

Operator ruling: escape raw HTML (not an allowlist).
- render_doc deregisters Python-Markdown's block and inline HTML processors,
  so raw HTML reaches the serializer as text and is escaped there. Fenced and
  inline code are unchanged.
- Every link href in a doc goes through links.is_safe_href after
  browser-style decoding. Python-Markdown keeps character references in
  attributes, so `java&#115;cript:` reached the browser as `javascript:`.
- is_safe_href reads a backslash as a slash, as a browser does in an http(s)
  URL: `/\evil.test` is `//evil.test`. This also closes the hole on the
  link board.
- A render that raises falls back to raw text, which the template escapes.

Two of 19 live .md files render differently. One is a contract losing the
quoted <pre> that swallowed it. The other is links.md, which renders as a
board, not through render_doc.

heid bug-hunt panel (4/4): the core claim held. Its two concrete edges (the
backslash twin, the unbounded render) are fixed here. Table
tests/mutations/doc_html.toml: 9/9 proved. Suite 951 -> 975.
2026-09-28 10:37:36 -07:00

2.3 KiB

2026-09-28 — A posted doc could run script on the Booth's origin

Found by design-dev's impeccable run (the whole-surface audit Prime asked for, report booth booth-antislop), confirmed at source by booth-dev: Python-Markdown passes raw HTML through and doc.html / booth.html render it |safe. Any session's .md could carry a <script>; a contract that merely QUOTED <pre> opened a real one and swallowed the rest of the doc.

Operator ruling (Prime, in this session: "A"; and in design-dev's): ESCAPE raw HTML, not an allowlist — the live docs that carry tags mean the literal tag, and an allowlist would still turn a quoted <pre> into a real one. Measured before shipping: 2 of 19 live .md files render differently; one is booth-redesign/03-u1-item-record.contract.md losing exactly the swallowing <pre>, the other is links/links.md, which renders as a board and never through render_doc.

Found while fixing it, same class: markdown link hrefs were never checked, and Python-Markdown keeps character references in attributes, so [x](java&#115;cript:...) reached the browser as javascript:. Every doc href now goes through links.is_safe_href after browser-style decoding (_browser_href). mailto autolinks lose their href as a result; accepted.

The heid panel (4/4, thread 01M3MFG07JCAJTQXRBC1GKS2FG) said the core claim holds and found its edges: /\evil.test passed is_safe_href as a relative path although a browser reads it as //evil.test (fixed in the ONE predicate, so the board is closed too); no bound around the render (fixed: a raising render falls back to escaped raw text, which also covers a markdown upgrade renaming the deregistered processors — the tests would go red on that upgrade). Declined: emphasis still applying inside quoted HTML (**x** in a quoted attribute renders bold) — that is prose getting markdown; quote in a code span for byte-literal. Accepted: img@src unguarded (inert in current browsers; data: images are legitimate), html.unescape as a superset of attribute decoding (over-refuses at worst).

Guards not claimed as falsifiers: the tab/CR/LF drop and C0 trim in _browser_href, because Python 3.13's urlsplit does the same; the AMP_SUBSTITUTE restore, reachable only through automail (always mailto:). Table: tests/mutations/doc_html.toml, 9/9 proved.