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:
parent
6d2fe2147c
commit
e3c5773e8f
6 changed files with 196 additions and 19 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# 014 — Stored XSS: markdown (and deck/html_fragment) render unsanitized script; no CSP; now reachable unauthenticated via public share links
|
||||
|
||||
**Status:** Open
|
||||
**Status:** Fixed
|
||||
**Severity:** High — stored XSS executing on the Daybook origin, with a
|
||||
concrete escalation chain against the owner's session, and (since the
|
||||
sharing feature shipped in `91ea19d`) reachable by unauthenticated third
|
||||
|
|
@ -129,4 +129,41 @@ Two things raise the stakes well past "you attacked your own account":
|
|||
|
||||
## Resolution
|
||||
|
||||
_(pending)_
|
||||
Fixed with two layers (both in `artifacts/views.py`):
|
||||
|
||||
1. **Allowlist sanitization at create time** for the themed kinds
|
||||
(`markdown`, `deck`, `html_fragment`) via `nh3` (the maintained
|
||||
`ammonia`/Rust sanitizer, per the issue's recommendation — not the
|
||||
deprecated `bleach`). `sanitize_html()` keeps ordinary formatting,
|
||||
tables, images, links, and the codehilite/Pygments markup, and drops
|
||||
`<script>`, `on*` handlers, `<style>`, unknown tags, and
|
||||
`javascript:`/`data:` URLs (`url_schemes` limited to http/https/mailto;
|
||||
relative `/assets/<id>` image URLs pass through). Verified live that
|
||||
markdown/deck/html_fragment strip script/onerror/onload/svg while tables,
|
||||
syntax highlighting, and relative images survive. `html_full` is
|
||||
deliberately **not** sanitized — it remains the documented verbatim
|
||||
escape hatch.
|
||||
|
||||
2. **Script-blocking CSP at serve time** for the sanitized kinds:
|
||||
`default-src 'self'; script-src 'none'; object-src 'none'; base-uri
|
||||
'none'`, set in `serve_artifact_file` (so it covers both `/a/{id}` and
|
||||
the unauthenticated `/s/{token}` share route). Because it's a response
|
||||
header, it also neutralizes any script in artifacts stored *before* this
|
||||
fix — closing the retroactive gap that create-time sanitization alone
|
||||
would miss. Not applied to `html_full` (its scripts are by contract) or
|
||||
`json`/`csv` (their viewers need JS; their user data is separately
|
||||
escaped via `escape_script_body`).
|
||||
|
||||
Regression tests added in `tests/api/test_artifacts.py` (7): script/handler
|
||||
stripping for each themed kind, formatting/highlighting/relative-image
|
||||
preservation, html_full staying verbatim, and the CSP header being present
|
||||
for sanitized kinds and absent for html_full.
|
||||
|
||||
**Not addressed here (separate, lower-priority items the issue noted):**
|
||||
- CSV formula injection (`=cmd|…` in a cell) — left as documented behavior.
|
||||
- `CSRF_COOKIE_HTTPONLY` — moot now that the XSS vector is closed, but a
|
||||
cheap extra hardening if wanted (our JS reads the token from a form input,
|
||||
not the cookie, so enabling it wouldn't break anything).
|
||||
- A nonce/hash CSP for the `json`/`csv`/`html_full` pages — those keep their
|
||||
current behavior; the strict CSP is scoped to the no-JS themed kinds where
|
||||
it's provably safe.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue