Skip to content

Security: sanitise Marked.js output with DOMPurify (XSS via javascript: links / data: URIs) #11

@timon0305

Description

@timon0305

Problem

marked.parse(...) is called twice in the frontend and its output is injected straight into the DOM (or into a downloadable HTML file) without sanitisation:

marked.parse does not sanitise. Any chat content reaching this path can carry XSS payloads — most commonly via javascript: URLs in markdown links, dangerous data: URIs, <script> blocks (which Marked passes through), and event-handler attributes on whitelisted tags. While chat content should be operator-authored, the threat model includes:

  • Shared .cursor-chat exports from third parties
  • Future Cursor extensions / plugins writing into the local chat store
  • Pasted content from external tools that retains attacker-controlled markdown
  • Anyone who can write to the user's ~/.cursor/ workspace storage directory

A successful XSS in the dashboard origin can read every chat in the local store and exfiltrate it.

Suggested fix

  • Load DOMPurify from CDN in templates/base.html alongside Marked.js and highlight.js.
  • Add a renderMarkdownSafe(text) helper in static/js/app.js that wraps marked.parse(...) with DOMPurify.sanitize(...). If either library failed to load, fall back to escapeHtml(text) rather than emitting raw markdown HTML.
  • Replace the two unsanitised call sites (workspace.html, download.js) with the new helper.
  • Ship regression tests that fail if any future change reintroduces a bare marked.parse(...) flowing into innerHTML or a download blob without DOMPurify.sanitize.

Severity

Critical — XSS in the dashboard origin reads the entire local chat store. Listed as a Critical / 5pt item in Will's eval week-1 plan for cppa-cursor-browser.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions