CVE-2026-61704: link-preview-js DNS Rebinding SSRF Bypass
link-preview-js versions up to 4.0.3 let an attacker bypass the documented SSRF protection by controlling DNS so the first resolution returns a public IP (passing the check) and the second returns an…

The problem
The library's resolveDNSHost option resolves and validates a hostname once, but then calls the native fetch() against the original hostname. The OS re-resolves the name at connect time, creating a TOCTOU window.
An attacker who controls the authoritative DNS for a submitted hostname can serve a public IP during validation and a loopback or internal IP during the real connection. This defeats the only documented SSRF mitigation and allows server-side fetches to reach 127.0.0.1, private RFC-1918 ranges, or cloud metadata endpoints (e.g., 169.254.169.254).
Proof of concept
A working proof-of-concept for CVE-2026-61704 in link-preview-js, with the exact payload below.
// Attacker controls DNS for make-PUB-rebind-127.0.0.1-rr.1u.ms
// First resolution → PUB IP (passes resolveDNSHost check)
// Second resolution → 127.0.0.1 (used by fetch())
import { getLinkPreview } from 'link-preview-js';
await getLinkPreview(
'http://make-1.2.3.4-rebind-127.0.0.1-rr.1u.ms/',
{
resolveDNSHost: async (url) => {
const { hostname } = new URL(url);
const { resolve4 } = await import('dns/promises');
const [ip] = await resolve4(hostname);
return ip; // returns '1.2.3.4' (public) — check passes
// fetch() re-resolves independently → gets 127.0.0.1
},
}
);The root cause is a classic TOCTOU: validate one result, act on a second. The resolveDNSHost callback returns an IP that the library checks against an internal-address denylist, but the validated IP is then discarded. The library proceeds to call fetch(url) with the original hostname, so the Node.js HTTP stack re-resolves it independently.
The patch (PR #181) eliminates the window by adding undici as a dependency and creating a pinned dispatcher that dials the pre-validated IP at the TCP layer rather than letting the HTTP client re-resolve. The fix also replaces regex-based private-address checks with node:net BlockList checks and adds IPv6 embedded-IPv4 normalization, closing a secondary bypass path.
CWE-918 (SSRF) via CWE-362 (TOCTOU race).
The fix
Upgrade to link-preview-js 4.0.4. The patched version uses an undici pinned dispatcher so the TCP connection is forced to the already-validated IP, removing the re-resolution window entirely. No config change is needed, resolveDNSHost works correctly in 4.0.4.
Reported by ahmet-sahiner.
Related research
- high · 8.2CVE-2026-65842CVE-2026-65842: @platejs/docx-io Server-Side Request Forgery via Remote Image Fetch
- high · 7.5CVE-2026-75899CVE-2026-75899: fast-uri SSRF via Double Hostname Percent-Decoding
- high · 7.5CVE-2026-75975CVE-2026-75975: fast-uri SSRF via Malformed IPv6 Normalization
- high · 7.1CVE-2026-62680CVE-2026-62680: Orval Generation-Time SSRF and Local File Inclusion via Unvalidated $ref