Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/remarkjs/remark/llms.txt

Use this file to discover all available pages before exploring further.

remark is a general-purpose tool and, like any tool that processes user-supplied input and produces output that may be rendered in a browser, it comes with security responsibilities. Understanding the attack surfaces — and how to mitigate them — is an important part of deploying remark safely in production. This page covers the three main areas: cross-site scripting (XSS) risks when converting to HTML, denial-of-service (DDoS) risks when handling large or malformed input, and the supply-chain risk introduced by third-party plugins.

Cross-site scripting (XSS)

Markdown can contain raw HTML. When you convert markdown to HTML, any HTML embedded in the source document is passed through to the output. This means a malicious author can embed <script> tags, javascript: URLs, or event handlers like onerror directly in markdown content. If that output is rendered in a browser without sanitization, it will execute.
Always sanitize HTML output when processing markdown from untrusted sources, such as user-submitted content, form inputs, database records written by users, or third-party data feeds. Failing to do so can expose your application and its users to XSS attacks.
The recommended mitigation is to add rehype-sanitize to your unified pipeline immediately before rehype-stringify. It traverses the hast (HTML syntax tree) and removes elements and attributes that are not on its allowlist. The default schema follows GitHub’s sanitization rules:
npm install rehype-sanitize
import rehypeSanitize from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import {unified} from 'unified'

const file = await unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeSanitize)   // <-- sanitize before serializing
  .use(rehypeStringify)
  .process(untrustedMarkdown)

console.log(String(file))
The position of rehype-sanitize in the chain matters: it must come after remark-rehype (which produces the hast) and before rehype-stringify (which serializes it to an HTML string). Placing it after rehype-stringify would have no effect because the HTML string has already been produced. If the default sanitization schema is too restrictive or too permissive for your use case, rehype-sanitize accepts a custom schema object. See the rehype-sanitize documentation for details.

DDoS and resource exhaustion

Even without malicious HTML, an attacker can craft markdown that exhausts server resources, causing slowdowns or crashes.

Large files

Processing a very large markdown file consumes proportionally large amounts of memory and CPU time. A single multi-megabyte file could tie up a server for seconds. The recommendation is to cap the accepted size of input before it reaches the remark pipeline. According to the remark project’s own guidance, 500 KB is a reasonable upper bound — it is large enough to hold the text of an entire book, yet small enough to prevent runaway processing.
const MAX_INPUT_BYTES = 500_000 // 500 KB

if (Buffer.byteLength(input, 'utf8') > MAX_INPUT_BYTES) {
  throw new Error('Input exceeds maximum allowed size')
}

Deeply nested structures

Crashes and slowdowns can also originate from smaller payloads. Thousands of deeply nested constructs — such as heavily nested lists, many unclosed link brackets, or large numbers of reference definitions — can cause exponential processing time or stack overflows in the parser. This category of attack is harder to defend against purely by size-capping, because the payload may be small in bytes but expensive to process. The most effective mitigation is to process content in a separate thread or worker so that a runaway parse operation can be terminated without taking down the main application process. In Node.js, you can use worker_threads or a child process with a timeout:
import { Worker } from 'worker_threads'

function processMarkdownWithTimeout(input, timeoutMs = 5000) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./markdown-worker.js', {
      workerData: { input }
    })

    const timer = setTimeout(() => {
      worker.terminate()
      reject(new Error('Markdown processing timed out'))
    }, timeoutMs)

    worker.on('message', (result) => {
      clearTimeout(timer)
      resolve(result)
    })

    worker.on('error', (err) => {
      clearTimeout(timer)
      reject(err)
    })
  })
}
Isolating processing this way also limits the blast radius if a plugin throws an unhandled exception.

Plugin security

Each plugin you add to a remark pipeline is an additional dependency and a potential attack surface. A plugin runs arbitrary JavaScript with full access to the syntax tree, the virtual file, and any data you have attached to the processor. A compromised or malicious plugin could exfiltrate content, introduce output vulnerabilities, or modify the tree in unexpected ways. Before adding a plugin to your project:
  • Review the plugin’s source code, especially if it is small and simple enough to audit quickly.
  • Check the npm package for known vulnerabilities with npm audit.
  • Prefer plugins maintained within the @remarkjs and @unifiedjs organizations, which have consistent maintenance and security practices.
  • Pin your dependency versions and review updates before upgrading.
The remark monorepo itself, as well as the wider unified collective, follows a responsible disclosure process for security vulnerabilities.
To report a security vulnerability in remark or any package maintained by the unified collective, follow the security policy in the remarkjs/.github repository. Do not open a public issue for security-sensitive reports.

Summary

XSS

Always use rehype-sanitize before rehype-stringify when processing untrusted content.

DDoS

Cap input size to ~500 KB and process markdown in a worker thread with a timeout.

Plugins

Audit each plugin before adopting it. Prefer packages from the unified collective.

Next steps

Markdown to HTML

See the full pipeline with rehype-sanitize integrated correctly.

Creating Plugins

Understand what plugins can access and do inside the pipeline.

Build docs developers (and LLMs) love