# How Markdown Here Implements XSS Prevention Using DOMPurify

> Markdown Here uses DOMPurify to prevent XSS attacks. Learn how it sanitizes HTML, strips malicious scripts, and preserves safe content for your security.

- Repository: [Adam Pritchard/markdown-here](https://github.com/adam-p/markdown-here)
- Tags: how-to-guide
- Published: 2026-03-05

---

**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`](https://github.com/adam-p/markdown-here/blob/main/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`](https://github.com/adam-p/markdown-here/blob/main/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`](https://github.com/adam-p/markdown-here/blob/main/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`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-render.js) orchestrates the security pipeline. Raw Markdown passes through the bundled `marked` library ([`src/common/marked.js`](https://github.com/adam-p/markdown-here/blob/main/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

```javascript
// 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

```

```javascript
// Example: allowing <style> tags (rare, but supported)
const htmlWithStyle = '<style>body{background:red}</style><p>Hello</p>';
saferSetInnerHTML(container, htmlWithStyle, true); // `allowStyleTags = true`

```

```javascript
// 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`](https://github.com/adam-p/markdown-here/blob/main/src/common/vendor/dompurify.min.js) to sanitize all rendered output before DOM insertion.
- The `safelyParseHTML` helper in [`src/common/utils.js`](https://github.com/adam-p/markdown-here/blob/main/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`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-render.js) ensures Markdown-to-HTML conversion via [`src/common/marked.js`](https://github.com/adam-p/markdown-here/blob/main/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`](https://github.com/adam-p/markdown-here/blob/main/src/common/utils.js), which invokes the bundled DOMPurify library. Even when using the `marked` converter in [`src/common/marked.js`](https://github.com/adam-p/markdown-here/blob/main/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`](https://github.com/adam-p/markdown-here/blob/main/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.