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>
Moves the 11 Fixed issues (#001-006, #008-012) into issues/_archive_/,
which is gitignored — they stay on local disk and in git history but drop
out of the committed tree, keeping the issues/ dir focused on what's open.
The sibling test project reads this dir by local path, so it still sees
the archived files. Adds the three new open issues found in round-3
testing: #014 (stored XSS in markdown/deck/html_fragment — High), #015
(RecursionError -> 500 on deeply nested JSON), #016 (ragged CSV rows lose
data in the rendered table). Documents the archive convention in the README.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>