Safeguard
Open Source

Is turndown on npm Safe? A Security Review

turndown converts HTML to Markdown with no known CVEs, but the real risk is what you do with its input and output. Here is how to use it safely.

Aisha Rahman
Security Analyst
5 min read

The turndown npm package has no known published vulnerabilities as of its 7.x releases, so the security question is not whether turndown is exploitable but whether you handle its input and output safely. turndown is a small, well-scoped library that converts HTML into Markdown, and like most conversion tools it trusts you to sanitize at the right boundary.

This review covers what turndown does, where the real risk lives, and the patterns that keep an HTML-to-Markdown pipeline from becoming an injection path.

What turndown does

Install it the usual way:

npm install turndown

Then convert a fragment:

const TurndownService = require('turndown');
const turndownService = new TurndownService();
const markdown = turndownService.turndown('<h1>Hello</h1><p>World</p>');
// "# Hello\n\nWorld"

turndown walks the DOM of the HTML you give it and emits Markdown. In Node it uses a DOM implementation; in the browser it uses the native DOM. That is the whole job. It does not fetch anything, it does not execute scripts, and it has no network surface of its own. This narrow scope is why npm turndown scores well on security dashboards and has stayed quiet on the CVE front.

Why "no known CVEs" is not the whole story

A clean advisory record tells you the library code has not had a reported bug. It does not tell you that your use of it is safe. Two failure modes matter.

The first is trusting Markdown output as if it were sanitized. turndown converts HTML to Markdown; it does not strip dangerous content. If your source HTML contains a link like [click](javascript:alert(1)), turndown will faithfully produce Markdown that, when later rendered back to HTML by a permissive renderer, can execute. The conversion itself is not the vulnerability. The vulnerability is a pipeline that assumes "it went through turndown, so it is clean."

The second is the round trip. Many apps take user HTML, convert it to Markdown with turndown for storage, then render that Markdown back to HTML for display with a library like marked or markdown-it. Each stage preserves content it was never asked to sanitize. If neither the ingest nor the render stage runs a sanitizer, you have built an XSS pipeline out of two individually blameless libraries.

The correct place to sanitize

Sanitize the HTML that is actually rendered to a browser, using an allowlist sanitizer, as the last step before output. DOMPurify is the standard choice:

const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const DOMPurify = createDOMPurify(new JSDOM('').window);

// After Markdown has been rendered back to HTML:
const safeHtml = DOMPurify.sanitize(renderedHtml);

If you want to be defensive on the way in as well, sanitize the incoming HTML before you hand it to turndown. That limits what ever reaches your storage. But the non-negotiable sanitizer is the one closest to the browser, because that is where injected script actually runs.

A subtle point: sanitizing only the Markdown text is not enough, because Markdown syntax itself can carry active content (the javascript: link above, or raw HTML embedded in Markdown that many renderers pass through by default). Sanitize the rendered HTML.

A safe turndown pipeline

Putting it together, a defensible flow looks like this:

  1. Receive untrusted HTML from the user.
  2. Optionally sanitize it with DOMPurify to trim obvious junk.
  3. Convert to Markdown with turndown for storage and diffing.
  4. On display, render the stored Markdown back to HTML.
  5. Sanitize that rendered HTML with DOMPurify configured to your allowlist.
  6. Insert the sanitized string into the DOM.

The turndown step in the middle is the one part you do not have to worry about from a CVE standpoint. Everything around it is where your controls belong.

Keeping turndown itself current

Even a quiet package deserves a place in your dependency scan. turndown releases infrequently, which is normal for a stable library, but "infrequent releases" and "abandoned" look identical until you check. Confirm the package still has a maintainer responding to issues, and pin it in your lockfile so a future compromised release cannot silently roll in. Automated software composition analysis will surface turndown alongside your other dependencies and alert you if an advisory is ever filed against it. An SCA tool such as Safeguard can also flag the renderer you pair with turndown, which is statistically far more likely to carry a real XSS advisory.

FAQ

Does turndown have any known vulnerabilities?

As of its 7.x line, turndown has no known published CVEs. It is a small HTML-to-Markdown converter with no network surface. The security risk in a turndown-based feature comes from missing sanitization elsewhere in the pipeline, not from turndown's own code.

Does turndown sanitize HTML input for me?

No. turndown converts HTML to Markdown; it does not strip scripts, event handlers, or javascript: URLs in a security sense. You must sanitize with a tool like DOMPurify, and the critical place to do it is on the HTML that is finally rendered to the browser.

Is it safe to render Markdown produced by turndown?

Only if you sanitize the resulting HTML. Rendering stored Markdown back to HTML with a permissive renderer can reintroduce active content that survived the round trip. Run the rendered output through an allowlist sanitizer before inserting it into the page.

How do I install and pin turndown safely?

Run npm install turndown, commit your lockfile so the exact version is reproducible, and include it in your dependency scanning. Pinning matters because it prevents a future malicious or broken release from being pulled in automatically on the next install.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.