flawopen.com/XSS/Markdown rendering
Is Markdown rendering safe from XSS?
Treat rendered Markdown like untrusted HTML. A parser may preserve raw HTML because the syntax permits it; parsing Markdown does not sanitize the resulting HTML. Think of the parser as a translator that faithfully copies dangerous markup into another format, not as a security guard.
Where the trust boundary is
The CommonMark specification says tag-shaped raw HTML is rendered without escaping. Actual behavior depends on the parser, options, plugins, and host framework. For example, Marked explicitly warns that it does not sanitize generated HTML. Markdown syntax itself does not prove that input is safe, and a sanitizer must match the final HTML sink.
A safe rendering pipeline
const html = marked.parse(userMarkdown); container.innerHTML = html; // parser output is not sanitized
import DOMPurify from "dompurify";
const html = marked.parse(userMarkdown);
container.innerHTML = DOMPurify.sanitize(html, {
USE_PROFILES: { html: true }
});
The example uses DOMPurify's HTML-only profile because this view needs HTML, not SVG or MathML. Choose the smallest feature set your product needs. Do not mutate sanitized markup or pass it through another HTML-transforming library before insertion; either step can invalidate the sanitizer's guarantees. Prefer a framework's safe text rendering when rich HTML is unnecessary. Add Content Security Policy and Trusted Types as defense in depth, not as substitutes for sanitization.
Checks for a production review
- ✓Pin the Markdown parser and explicitly review its raw-HTML, plugin, and URL-handling options.
- ✓Sanitize the final generated HTML with a maintained allow-list sanitizer immediately before the HTML sink.
- ✓Restrict allowed elements, attributes, and URL protocols to the features the product needs.
- ✓Do not edit, concatenate, or reparse sanitized markup before insertion; re-sanitize after any required transformation.
- ✓Test the exact parser configuration and rendering sink, and keep the sanitizer patched.
FAQ
It removes one input feature, but does not by itself establish safety. Review links, images, extensions, generated HTML, and the final insertion context; sanitize output when it reaches an HTML sink.
Avoid it. Changes after sanitization can introduce unsafe markup or invalidate the sanitizer's decisions. Perform transformations before sanitizing, then insert the sanitized result without further HTML rewriting.
No sanitizer is context-independent. Use it for HTML fragments according to its documented threat model, keep it current, choose a narrow policy, and do not reuse the result in a different context such as a JavaScript or CSS string.
References
- CommonMark specification — Raw HTML
- Marked documentation — Security warning
- DOMPurify — README and usage guidance
- OWASP Cross Site Scripting Prevention Cheat Sheet
- flawopen.com — Cross-Site Scripting