Skip to content

Back to the catalog

Happier DOMs: The perils of running untrusted JavaScript code outside of a web browser  | Blog | Endor Labs
endorlabs.comwriteup

Happier DOMs: The perils of running untrusted JavaScript code outside of a web browser | Blog | Endor Labs

Endor Labs explains CVE-2025-61927, an RCE in the happy-dom headless browser caused by relying on Node.js's vm module, which is not a security boundary. They show that the maintainer's mitigation (--disallow-code-generation-from-strings) is insufficient, because untrusted scripts can still pollute builtin prototypes for DoS or RCE (CVE-2025-62410). The fix adds --frozen-intrinsics. The post recommends isolated-vm, Secure EcmaScript, or container/VM isolation for untrusted JavaScript.

TL;DR happy-dom's vm-based sandbox escapes (CVE-2025-61927) and a prototype-pollution bypass of its mitigation (CVE-2025-62410); use real isolation like isolated-vm.
Document

What it is

CVE-2025-61927 is a remote code execution flaw in happy-dom, a headless browser for Node.js used for crawling and server-side rendering. CVE-2025-62410 is a follow-up advisory showing that the maintainer's first mitigation still leaves a prototype-pollution route to DoS and RCE. The root cause is that happy-dom runs page scripts in Node's vm module, which Node documents as "not a security mechanism."

How it works

Initial escape. Script in the page reaches the host Function constructor through this, builds a function outside the vm, and grabs process:

const process = this.constructor.constructor('return process')();
const require = process.mainModule.require;

From there it can call require('fs') and other Node APIs.

Pollution escape. The --disallow-code-generation-from-strings flag blocks the payload above, but this.constructor.constructor.__proto__.__proto__ still resolves to the host's root prototype. Script can overwrite builtins there:

this.constructor.constructor.__proto__.__proto__.toString = () => {};

The page's PoC for this causes DoS. The CVE-2025-62410 advisory has a more sophisticated payload that reaches arbitrary code execution. The page does not reproduce that payload.

flowchart TD
    A[Untrusted page script in vm] --> B{Mitigation applied?}
    B -->|None| C[Function constructor escape: RCE]
    B -->|disallow-code-generation-from-strings| D[Prototype pollution: DoS or RCE]
    B -->|frozen-intrinsics| E[Pollution blocked]
    E --> F[Global reads and side-channels may remain]
    F --> G[Add container or VM isolation]

Preconditions / Affected

  • You feed happy-dom pages or scripts you do not fully trust (crawlers, scrapers, SSR of partially trusted React components).
  • JavaScript evaluation is on. After the first advisory the maintainer disabled it by default. The pollution PoC explicitly sets enableJavaScriptEvaluation: true.
  • The page gives no affected or fixed version numbers. Check the two advisories for those.

What to do

  • Avoid running untrusted JavaScript in happy-dom at all, if you can.
  • If you must run it, start Node with the maintainer's recommended flags. The page names --frozen-intrinsics as the addition from CVE-2025-62410, alongside --disallow-code-generation-from-strings from the first advisory:
node --disallow-code-generation-from-strings --frozen-intrinsics

The page does not show the two flags combined in one command line. The command above is assembled from the flags it names.

  • Wrap happy-dom in a Docker container or VM when scraping untrusted content. Endor Labs recommends this as extra protection.
  • For any use of vm on untrusted code, migrate to a real sandbox:
  • isolated-vm exposes V8 Isolates and gives stronger isolation, but struggles with arbitrary open-source libraries.
  • Secure EcmaScript (Agoric's implementation of a Stage 1 proposal) freezes intrinsics and disallows dangerous constructs. It integrates third-party code well but isolation is more fragile.

Caveats

  • Endor Labs believes --frozen-intrinsics stops pollution but that malicious code may still read global values from the host context or use side-channels.
  • The maintainer declined Endor Labs' suggestion to move to a secure sandbox because of the refactoring required, so the architecture remains vm-based.
  • The same class of vm escape led to the deprecation of vm2 (for example CVE-2021-23555) and realms-shim.

Organisations named here

  • Agoric
  • Secure EcmaScript

CVEs named here

  • CVE-2025-61927
  • CVE-2025-62410
  • CVE-2021-23555

Technologies named here

  • happy-dom - vulnerable to CVE-2025-61927 and CVE-2025-62410
  • happy-dom
  • Node.js vm module
  • vm2
  • Node.js flag --disallow-code-generation-from-strings
  • realms-shim
  • Node.js flag --frozen-intrinsics
  • Docker container or virtual machine
  • Node.js
  • JavaScript
  • isolated-vm
  • Endor Labs

Techniques named here

  • Prototype pollution gadgets leading to remote code execution
  • Exception object references used to escape vm
  • Prototype Pollution
  • Sandbox escape