[SECURITY AUDIT] No Content-Security-Policy — stored-HTML XSS defense rests entirely on sanitization #34
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: Low
Confidence: High (verified); this is a defense-in-depth gap, not an active exploit
Scope area: User input handling / XSS (audit area 3)
What
Helmet is initialized with CSP explicitly disabled:
Meanwhile the app renders admin-authored HTML into the DOM via
dangerouslySetInnerHTMLin three public views:client/src/routes/wiki/WikiArticle.jsx:124client/src/routes/public/FiveOnFriday.jsx:40client/src/routes/public/NewsletterIssue.jsx:56Sanitization is done well and in two layers (server-side
sanitizeHtml.cleanBodyon save, client-sideDOMPurify.sanitizeon render), so there is no known bypass today. But with CSP off, if any sanitizer gap is ever found (a DOMPurify bypass, a config regression in the allowlist, a new render path that forgets to sanitize), injected script executes with nothing to stop it — noscript-srcrestriction, no blocking of inline handlers ordata:/remote script.Why it matters
CSP is the standard second line of defense specifically for the "our sanitizer had a hole" case. The allowlist in
server/src/utils/sanitizeHtml.jspermits<span>,<a>,<img>, tables, etc. with attributes — a rich surface where one misconfiguration (or a future relaxation) turns into stored XSS on public pages, executing in visitors' browsers and, if it hits an authenticated admin, against a cookie session that (per issue #30) cannot be revoked.Where
server/src/app.js:39-44(CSP disabled)client/src/routes/wiki/WikiArticle.jsx:124,client/src/routes/public/FiveOnFriday.jsx:40,client/src/routes/public/NewsletterIssue.jsx:56(HTML injection sinks)Suggested fix (not implemented)
The code comment says CSP was deferred to "the frontend phase" — that phase is now in place, so enable it. Define a restrictive policy for the SPA (
default-src 'self', a controlledimg-srcincluding/uploads,script-src 'self'— avoidunsafe-inline, using hashes/nonces if inline is unavoidable). Roll out viaContent-Security-Policy-Report-Onlyfirst to catch violations, then enforce.