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>
|
||
|---|---|---|
| .. | ||
| 007-handover-to-llm-test-2026-09-02.md | ||
| 013-handover-to-llm-test-round-2.md | ||
| 014-stored-xss-in-rendered-text-artifacts.md | ||
| 015-deeply-nested-json-recursionerror-500.md | ||
| 016-csv-ragged-rows-silent-data-loss-in-render.md | ||
| README.md | ||
Issues
One file per issue: NNN-short-slug.md, numbered sequentially. Each file
has a title, status (Open / Fixed / By design), description, and resolution
once closed. Numbering keeps climbing — archived numbers are never reused.
Resolved issues are moved to _archive_/ to keep this directory focused on
what's open. That subdirectory is gitignored (it matches the repo-wide
_archive_/ rule), so archived issues stay on local disk and in git
history but are not carried in the committed tree. This directory is read
by the sibling AGENT_DAYBOOK__LLM_TEST project via its local path, so it
still sees the archived files.