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.
This commit is contained in:
vh
2026-09-28 10:37:36 -07:00
parent 50bfc7b4ec
commit 190a75a0e1
8 changed files with 361 additions and 6 deletions
+4 -1
View File
@@ -1,6 +1,6 @@
# Persistent memory — booth
_Last updated: 2026-09-27_
_Last updated: 2026-09-28_
> **Always check for `/tmp/booth-dev-handoff.md`** — if it exists and its
> `Written:` stamp is under 8 hours old, read it (it carries the in-flight
@@ -19,6 +19,8 @@ loop it turned out to actually be.
_As of 2026-09-27:_
- ✅ **DOCS CAN NO LONGER RUN SCRIPT** (2026-09-28, Prime ruled "escape"): raw HTML in a `.md` renders as text, doc hrefs go through `is_safe_href`, the board's backslash twin of `//host` is closed. design-dev's anti-slop fix slices (`design-dev/antislop-sN`, Prime said GO in design-dev's session) arrive one ref at a time for booth-dev's gate.
→ `persistent-memory.d/2026-09-28-a-posted-doc-could-run-script.md`
- ✅ **ONE SUBMIT SAVES EVERY ASK ON A PAGE** (2026-09-27, Prime's bug via
infra-ops, thread `01M3JED397G1SZH7580PCXNVVA`). Client-side on both
surfaces (embed.js, base.html's in-place script), no server change; a
@@ -87,6 +89,7 @@ _As of 2026-09-27:_
## Recent decisions
- `[2026-09-28]` ✅ **A posted doc could run script; raw HTML is now escaped and doc hrefs guarded** — Prime ruled ESCAPE; READ BEFORE RENDERING ANY AUTHOR TEXT `|safe` or touching `links.is_safe_href`, which now guards docs too → `persistent-memory.d/2026-09-28-a-posted-doc-could-run-script.md`
- `[2026-09-27]` ✅ **One submit saves every ask on the page; the heid panel found the async window** — READ BEFORE TOUCHING THE SUBMIT PATH OF embed.js OR base.html: a batch reads forms at the press while the page stays live, and only a test that acts inside the flight can see it → `persistent-memory.d/2026-09-27-one-submit-saves-every-ask.md`
- `[2026-09-24]` ⏸ **The upload route's three lifecycle gaps: DEFERRED** — the pickup-id `mkdir` sits outside the try (a FileExistsError race), `rmtree(ignore_errors=True)` hides its own failure, and `except Exception` misses CancelledError. All three are rare; the operator was told and merged without them. Tracked in `225ba32`'s commit message.
- `[2026-09-24]` ✅ **Upload names: two crashes found, then two holes in the fix** — READ BEFORE WRITING A SANITISER: drop everything droppable FIRST, then apply the structural rules; a NUL test through httpx `files=` proves nothing → `persistent-memory.d/2026-09-24-upload-names-two-crashes-then-two-holes.md`