Fix #014: sanitize themed text kinds + script-blocking CSP (stored XSS)

markdown/deck/html_fragment rendered author-influenced content verbatim,
so <script>/<img onerror>/etc. executed on the Daybook origin — self-XSS
against the owner's session, and (post-sharing) unauthenticated via /s/.

Two layers, both in artifacts/views.py:
- Create-time allowlist sanitization via nh3 (maintained ammonia/Rust
  sanitizer, not deprecated bleach). Keeps formatting/tables/links/images
  (incl. relative /assets URLs) and codehilite markup; drops script, on*
  handlers, style, unknown tags, and javascript:/data: URLs. html_full is
  deliberately NOT sanitized (documented verbatim escape hatch).
- Serve-time CSP (script-src 'none') for the sanitized kinds, on both
  /a/{id} and the unauthenticated /s/{token} route. Being a response
  header, it also neutralizes scripts in artifacts stored before this fix.
  Not applied to html_full or json/csv (json/csv data is already escaped;
  their viewers need their own JS).

Verified live across all kinds; 7 regression tests added. 102 tests pass.

Not addressed (noted in the issue): CSV formula injection,
CSRF_COOKIE_HTTPONLY (moot with XSS closed), nonce CSP for json/csv/full.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
peter 2026-09-03 10:44:49 +01:00
parent 6d2fe2147c
commit e3c5773e8f
6 changed files with 196 additions and 19 deletions

View file

@ -50,6 +50,13 @@ DAYBOOK_SOURCE=agent
For images, PDFs, SVGs, and other files, use `--file`; the helper infers multipart and content type.
**HTML in `markdown`, `html_fragment`, and `deck` is sanitized** — ordinary
formatting, tables, links, and images are kept, but `<script>`, event
handlers, `<style>`, and `javascript:`/`data:` URLs are stripped. If you
need JavaScript (a chart library, an interactive widget), use `html_full`,
which is served verbatim — that's what it's for. Don't put scripts in the
themed kinds expecting them to run; they won't.
`--theme` (for `markdown`/`html_fragment` only) accepts `report` (default —
serif, warm background), `tasks`, or `base` (minimal, no color styling).
`deck`/`html_full`/`json`/`csv` ignore `--theme` — each has its own fixed