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.
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:templates/workspace.html:317—renderMarkdown(text)returnsmarked.parse(text, { breaks: true, gfm: true }), which then flows intomain.innerHTMLat line 248 viabubble.textrendering.static/js/download.js:191—marked.parse(md)is interpolated directly into a downloadable.htmlblob.marked.parsedoes not sanitise. Any chat content reaching this path can carry XSS payloads — most commonly viajavascript:URLs in markdown links, dangerousdata: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:.cursor-chatexports from third parties~/.cursor/workspace storage directoryA successful XSS in the dashboard origin can read every chat in the local store and exfiltrate it.
Suggested fix
templates/base.htmlalongside Marked.js and highlight.js.renderMarkdownSafe(text)helper instatic/js/app.jsthat wrapsmarked.parse(...)withDOMPurify.sanitize(...). If either library failed to load, fall back toescapeHtml(text)rather than emitting raw markdown HTML.workspace.html,download.js) with the new helper.marked.parse(...)flowing intoinnerHTMLor a download blob withoutDOMPurify.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.