Safeguard
DevSecOps

node-html-parser: How to Parse HTML Safely in Node.js

node-html-parser is a fast, dependency-light HTML parser for Node.js. Here is how to use it without opening the door to injection or denial-of-service bugs.

Karan Patel
Platform Engineer
6 min read

node-html-parser is a fast, pure-JavaScript HTML parser for Node.js that turns markup into a queryable DOM without pulling in a browser engine, and using it safely comes down to how you treat the parsed output, not the parser itself. If you have ever needed to scrape a page, rewrite a fragment of markup, or pull text out of user-submitted HTML on the server, this is one of the libraries you reach for. It is small, it is quick, and it sees millions of weekly downloads on npm.

The parser does one job well: it reads a string of HTML and gives you back a node tree you can walk with CSS-selector-style queries. The security questions start after that, when your code decides what to do with the nodes it found.

What node-html-parser actually does

At its core the library exposes a parse() function that accepts an HTML string and returns a root node. From there you get querySelector, querySelectorAll, .text, .innerHTML, .getAttribute(), and the usual traversal helpers. A typical read-only use looks like this:

import { parse } from 'node-html-parser';

const root = parse('<ul><li class="item">Alpha</li><li class="item">Beta</li></ul>');
const items = root.querySelectorAll('.item').map((el) => el.text);
// ['Alpha', 'Beta']

Because it is a server-side parser, there is no DOM, no window, and crucially no script execution. Parsing a string that contains <script>alert(1)</script> does not run anything. That is the first thing people worry about with a "node html parser" and it is a non-issue at parse time.

The risk moves downstream, to the moment you take parsed content and hand it to something that does execute or render it.

The real risk is what you do with the output

The most common way node-html-parser shows up in a security review is as a step in a pipeline that ends with untrusted markup being sent to a browser. Imagine a comment system that lets users submit rich text. You parse it, strip a few tags you do not like, then store root.innerHTML and render it later. If your allow-list is wrong, you have built a stored cross-site scripting (XSS) vulnerability, and the parser was just the tool that carried the payload from input to output.

node-html-parser is not a sanitizer. It will happily preserve <img src=x onerror=alert(1)> or a javascript: URL in an href. Removing dangerous tags and attributes is your job. For anything user-controlled that will eventually render as HTML, run the output through a purpose-built sanitizer such as DOMPurify (with jsdom on the server) or sanitize-html, and keep an explicit allow-list of tags and attributes rather than a block-list.

import sanitizeHtml from 'sanitize-html';

const clean = sanitizeHtml(userInput, {
  allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'li'],
  allowedAttributes: { a: ['href'] },
  allowedSchemes: ['http', 'https', 'mailto'],
});

Parse for structure, sanitize for safety. Those are two different steps and one library should not be expected to do both.

Denial of service from pathological input

Any parser that walks attacker-controlled strings can become a denial-of-service surface. Two patterns matter. The first is regular-expression denial of service (ReDoS): if a parser (or a library in the same request path) uses a regex with catastrophic backtracking, a small crafted input can pin a CPU core for seconds. The second is memory pressure from deeply nested or enormous documents, where a few kilobytes of <div> nesting expands into a large node tree.

The defensive posture is the same regardless of which parser you use:

  • Cap the size of any HTML you accept before parsing. A hard byte limit on the request body kills the easy cases.
  • Parse untrusted input off the main event loop where practical, or behind a timeout, so one bad document cannot stall every other request on the process.
  • Keep the dependency current. Parser maintainers fix backtracking and edge-case bugs over time, and running an old version means running without those fixes.

Do not assume a library is immune because you have not seen a headline about it. Assume any code that processes untrusted strings can be made slow, and bound its resource use.

Keep the dependency patched

The version story here is straightforward: node-html-parser is actively maintained and releases regularly. The practical rule is to pin a recent version, watch for advisories, and let an automated tool tell you when you have fallen behind rather than checking by hand.

This is exactly the kind of thing software composition analysis is built for. An SCA tool resolves your full dependency tree, matches it against known-vulnerability databases, and flags a parser (or anything it pulls in transitively) the moment a fix lands upstream. An SCA scanner such as Safeguard can surface a transitive issue you would never spot by reading your own package.json. If you are choosing between tools, our comparison with Snyk walks through how coverage and remediation differ.

A safe usage checklist

Before you ship code that parses HTML on the server, confirm:

  1. The parsed output that reaches a browser is sanitized with an allow-list, not just tag-stripped by hand.
  2. Input size is bounded before parsing, and parsing runs under a timeout for untrusted sources.
  3. Attribute values that become URLs are scheme-checked (http, https, mailto), so javascript: cannot slip through.
  4. The dependency is on a current release and covered by automated advisory scanning.
  5. You are not relying on the parser to "clean" anything; that responsibility lives in a dedicated sanitizer.

Handle those five points and node-html-parser is a safe, boring, useful part of your stack, which is exactly what you want from a parsing library.

FAQ

Does node-html-parser execute scripts in the HTML it parses?

No. It is a server-side parser with no DOM and no JavaScript engine, so <script> tags are parsed as inert nodes. Script execution can only happen later if you render the parsed markup in a real browser without sanitizing it first.

Is node-html-parser vulnerable to XSS?

The library itself does not introduce XSS, but it will faithfully preserve dangerous tags and attributes. XSS happens when you take its output and render it as HTML without sanitizing. Always run untrusted parsed content through a sanitizer like DOMPurify or sanitize-html.

How do I stop a malicious document from hanging my server?

Cap input size before parsing, run parsing under a timeout for untrusted sources, and keep the package updated so you inherit fixes for backtracking and edge-case bugs. Bounding resource use matters more than trusting any single library to be immune.

How do I know if my version of node-html-parser is safe?

Pin a current release and let automated dependency scanning watch for advisories. Manual version checks do not scale across a real dependency tree; an SCA scanner will alert you when a fix ships upstream, including for packages your parser depends on.

Never miss an update

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