Files
Kordant/tasks/security-fixes/01-fix-stored-xss-blog-rendering.md
2026-05-29 09:03:47 -04:00

2.5 KiB

01. Fix stored XSS via unsanitized innerHTML in blog rendering

meta: id: security-fixes-01 feature: security-fixes priority: P0 depends_on: [] tags: [implementation, tests-required, high-severity]

objective:

  • Eliminate stored XSS in blog post rendering by replacing raw innerHTML with a sanitization pipeline

deliverables:

  • Replace contentToHtml() in web/src/routes/blog/[slug].tsx with a safe HTML rendering approach
  • Add DOMPurify (or equivalent) as a dependency to sanitize HTML before innerHTML binding
  • Unit tests for the sanitization pipeline covering script injection, event handler injection, and data URI vectors

steps:

  1. Install dompurify and isomorphic-dompurify (for SSR compatibility) in the web app
  2. Examine contentToHtml() at web/src/routes/blog/[slug].tsx:14-46 and the innerHTML binding at line 121
  3. Create a sanitizeHtml(content: string): string utility that runs DOMPurify on the rendered HTML
  4. Replace the innerHTML binding with innerHTML={sanitizeHtml(contentToHtml(post.content))}
  5. Consider replacing the custom markdown-to-HTML parser with a library (e.g., marked + DOMPurify) if the custom implementation is fragile
  6. Add unit tests covering XSS vectors: <script>, onerror=, javascript:, data:text/html

tests:

  • Unit: sanitizeHtml() strips <script> tags, event handlers (onclick, onerror, etc.), javascript: URIs, and data:text/html URIs
  • Unit: sanitizeHtml() preserves legitimate HTML (headings, paragraphs, links, lists, code blocks)
  • Integration: Blog post with embedded script renders without executing JavaScript (verify via headless test or DOM inspection)

acceptance_criteria:

  • innerHTML is never bound with unsanitized content
  • DOMPurify (or equivalent) is called on all HTML before it reaches the DOM
  • Unit tests pass for all XSS vector categories (script, event handlers, data URIs, javascript: URIs)
  • Legitimate blog formatting (headings, links, bold, italic, code) still renders correctly

validation:

  • cd web && bun test — all tests pass
  • Manually create a blog post with <script>alert(1)</script> content and verify it does not execute
  • Review the rendered HTML output to confirm sanitization is applied

notes:

  • Finding p8-001 is the only HIGH severity finding — prioritize this first
  • isomorphic-dompurify is needed because DOMPurify requires a DOM environment (not available in Node SSR)
  • If SolidStart supports client-only rendering for this route, standard DOMPurify suffices