How Markdown Here Implements XSS Prevention Using DOMPurify

Markdown Here prevents XSS attacks by sanitizing all HTML through a bundled copy of DOMPurify before DOM insertion, ensuring malicious scripts and event handlers are stripped while preserving safe Markdown-rendered content.

Markdown Here is a browser extension that converts user-supplied Markdown into HTML for rendering in webmail clients and rich text editors. Because this process involves injecting HTML converted from untrusted user input directly into web pages, rigorous XSS prevention measures are critical to block cross-site scripting vulnerabilities. The codebase implements a multi-layered defense centered on a vetted, locally bundled version of DOMPurify.

Bundled DOMPurify Library for XSS Prevention

Vetted Sanitizer Location

The extension ships with a hardened copy of DOMPurify (version 3.2.6) located at src/common/vendor/dompurify.min.js. This local bundling eliminates external dependency risks and ensures the sanitizer operates under Apache-2.0/Mozilla-2.0 licensing compliance without requiring network requests to third-party CDNs that could compromise supply chain security.

DOMPurify Configuration

In src/common/utils.js, the safelyParseHTML function wraps DOMPurify with defensive checks and custom configuration. The implementation first verifies that the global DOMPurify object exists before proceeding. It configures the sanitizer with RETURN_DOM_FRAGMENT: true to return a DocumentFragment object rather than a serialized HTML string, and sets DOCUMENT to reference the owner document for proper node creation context.

Safe HTML Parsing Architecture

Fragment-Based Sanitization

The safelyParseHTML helper ensures that no script elements, event-handler attributes, or other dangerous markup can execute by returning only sanitized DocumentFragment objects. When callers explicitly require CSS support, the helper conditionally extends the configuration with ADD_TAGS: ['style'] and FORCE_BODY: true, ensuring style elements are only permitted when specifically requested and properly contained within a body context.

Secure DOM Insertion Methods

Rather than using dangerous innerHTML assignments directly, the codebase provides saferSetInnerHTML and saferSetOuterHTML in src/common/utils.js. These functions accept the sanitized DocumentFragment from safelyParseHTML and insert it using a Range object, guaranteeing safe replacement of existing content without executing injected payloads.

Integration with Markdown Rendering

The conversion flow in src/common/markdown-render.js orchestrates the security pipeline. Raw Markdown passes through the bundled marked library (src/common/marked.js) to generate HTML, but marked explicitly does not sanitize output. The resulting HTML then immediately passes through the DOMPurify wrapper helpers before any DOM insertion occurs, satisfying the extension's Content Security Policy requirements and browser restrictions on inline scripts.

Practical Code Examples

// Example: rendering user-provided Markdown safely
import { saferSetInnerHTML } from './utils.js';
import marked from './marked.js';

// The user's Markdown string (could contain HTML)
const userMarkdown = '<img src=x onerror=alert(1) /><b>Bold</b>';

// Convert to HTML (marked does not sanitise)
const rawHtml = marked(userMarkdown);

// Insert the HTML into a container element safely
const container = document.getElementById('md-preview');
saferSetInnerHTML(container, rawHtml);   // <style> tags are refused unless asked
// Example: allowing <style> tags (rare, but supported)
const htmlWithStyle = '<style>body{background:red}</style><p>Hello</p>';
saferSetInnerHTML(container, htmlWithStyle, true); // `allowStyleTags = true`
// Direct use of the low-level helper (rare)
import { safelyParseHTML } from './utils.js';
const fragment = safelyParseHTML('<svg/onload=alert(2)>', document);
document.body.appendChild(fragment); // No XSS – script removed by DOMPurify

Summary

  • Markdown Here bundles DOMPurify v3.2.6 at src/common/vendor/dompurify.min.js to sanitize all rendered output before DOM insertion.
  • The safelyParseHTML helper in src/common/utils.js configures DOMPurify to return DocumentFragment objects with dangerous content removed.
  • saferSetInnerHTML and saferSetOuterHTML provide safe DOM insertion using Range objects rather than raw HTML assignment.
  • The rendering pipeline in src/common/markdown-render.js ensures Markdown-to-HTML conversion via src/common/marked.js never bypasses sanitization.

Frequently Asked Questions

Does Markdown Here use DOMPurify for all HTML rendering?

Yes. According to the adam-p/markdown-here source code, every HTML insertion flows through the safelyParseHTML helper in src/common/utils.js, which invokes the bundled DOMPurify library. Even when using the marked converter in src/common/marked.js, the output is sanitized before any DOM insertion occurs.

Why does the extension bundle DOMPurify instead of using a CDN?

The repository includes a minified copy at src/common/vendor/dompurify.min.js (version 3.2.6) to ensure supply chain security and offline functionality. This prevents external network dependencies and guarantees the specific vetted version operates under Apache-2.0/Mozilla-2.0 licenses without exposure to CDN compromise risks.

Can malicious style tags bypass the XSS prevention?

No. The safelyParseHTML function explicitly requires callers to set allowStyleTags = true to enable ADD_TAGS: ['style'] configuration. By default, style tags are stripped alongside scripts and event handlers, preventing CSS-based attacks unless explicitly enabled by the caller with FORCE_BODY: true containment.

How does the extension handle inline event handlers like onerror?

DOMPurify's default configuration removes all event-handler attributes (such as onerror, onload, and onclick) during the sanitization process. When safelyParseHTML processes HTML through the bundled DOMPurify instance, these dangerous attributes are stripped before the fragment returns, ensuring injected markup cannot execute JavaScript.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →