A bouncer who checks IDs but reads them with glasses that can't see past a smudge
CVE-2026-105482 is a NUL byte truncation flaw in Node.js's dns.resolve() family of functions (including dns.resolve4(), dns.reverse(), and their Promise variants). Because these functions pass JavaScript strings to the underlying c-ares C library — which treats \0 as a string terminator — an embedded NUL byte silently chops the hostname. Resolving example.com\0.trusted.corp actually queries only example.com. All three supported release lines are affected: 22.x (before 22.23.4), 24.x (before 24.22.1), and 26.x (before 26.12.1). The fix, committed by James M Snell as 6b78ea171d, replaces standard string validation with validateStringWithoutNullBytes, throwing ERR_INVALID_ARG_VALUE on any NUL-containing input. The vulnerability was reported by researcher hasanfleyah and patched on October 21, 2026.
Node.js labeled this HIGH, but published no CVSS score. That label is defensible for the specific subset of applications that implement DNS-based hostname allowlisting before making server-side requests — classic SSRF-defense-bypass territory. However, the HIGH label overstates the risk for the average Node.js deployment, which either hardcodes DNS targets or uses higher-level HTTP clients (like fetch() or axios) that don't route through raw dns.resolve() with attacker-controlled input. The NUL byte must survive through input parsing into a dns.resolve*() call, and the application must be performing hostname validation *before* resolution — a meaningful but minority pattern. For most Node.js workloads, this is a MEDIUM-severity issue with a narrow exploitation window.
4 steps from start to impact.
Identify a Node.js service accepting user-supplied URLs
- Publicly reachable Node.js service accepting user-supplied URLs
- Application processes the URL server-side (SSRF-adjacent pattern)
- Not all Node.js apps accept user-supplied URLs for server-side processing
- Many apps use managed PaaS platforms that strip unusual bytes at the edge
Inject NUL byte into hostname via JSON or URL encoding
{"url": "https://evil.com\u0000.trusted.corp/callback"}. In JSON, \u0000 is a valid Unicode escape that produces a NUL byte in the JavaScript string. Alternatively, %00 in URL-encoded form data achieves the same. The NUL byte must survive through the application's input parsing layer into the dns.resolve*() call.- Input channel supports NUL byte delivery (JSON \u0000, URL-encoded %00)
- Application does not strip NUL bytes during input sanitization
- Many input validation libraries explicitly reject NUL bytes
- HTTP/1.1 headers cannot carry NUL bytes — delivery must be via body or query params
- Some URL parsers (WHATWG URL,
new URL()) reject NUL bytes in hostnames before dns.resolve is reached
%00 and \u0000 patterns; Nuclei template in Han5N/10-21-CVE-PoC repoApplication validates hostname — NUL byte passes the check
.trusted.corp (or matches an allowlist pattern). Because JavaScript's String.prototype.endsWith() and regex operations process the entire string *including* the NUL byte, the validation sees evil.com\0.trusted.corp and concludes it ends with .trusted.corp. The check passes.- Application performs hostname validation before DNS resolution
- Validation logic operates on the full JS string (standard behavior)
- Applications using
new URL()to parse the hostname may extract only the hostname portion, potentially catching malformed input - Some allowlist implementations check against resolved IPs rather than hostnames, making this bypass irrelevant
dns.resolve() truncates at NUL, resolves wrong host
dns.resolve4() or similar. Node.js passes it to c-ares as a C string. C-ares stops reading at \0, so it resolves evil.com instead of evil.com\0.trusted.corp. The application then connects to the IP address returned for evil.com, believing it connected to a trusted host. In SSRF scenarios, the attacker can direct this to an internal IP (e.g., 169.254.169.254 cloud metadata) by controlling the DNS response for evil.com.- Application uses dns.resolve* (not a higher-level client that handles DNS internally)
- Attacker controls DNS for the truncated domain
- Many Node.js HTTP clients (fetch, axios, got) perform DNS resolution internally without exposing dns.resolve() to attacker input
- Cloud providers increasingly block metadata endpoint access via IMDSv2 requiring tokens
- Network-level SSRF protections (egress filtering) may block connections to internal IPs regardless of DNS bypass
if (hostname.includes('\0')) throw new Error('invalid hostname') at every code path where user input reaches dns.resolve*(). This is the exact mitigation the patch applies. Since the noisgate verdict is MEDIUM, there is no mitigation SLA — proceed directly to the 365-day remediation window via patching. However, this one-line check can be deployed immediately as defense-in-depth.ERR_INVALID_ARG_VALUE across all dns.resolve*, dns.reverse(), and Promise variants.new URL(input) and extracting .hostname strips or rejects NUL bytes in many cases, preventing them from reaching dns.resolve*(). This is a code-level architectural change that provides defense-in-depth independent of the Node.js version. Audit URL-processing code for direct string-to-dns.resolve patterns.ssrf-req-filter or manual IP validation post-resolution eliminate this entire attack class.- WAF NUL-byte rules alone — While WAFs can detect
%00in URLs, NUL bytes in JSON request bodies (\u0000) may pass through WAF inspection that only examines URL/header layers. WAFs reduce but do not eliminate the attack surface. - Upgrading c-ares independently — The vulnerability is in Node.js's string-to-C-string conversion boundary, not in c-ares itself. c-ares correctly handles C strings; the bug is that Node.js passed a string that should have been rejected. Upgrading c-ares alone does nothing.
- Rate limiting or authentication — This is a logic bypass, not a brute-force or unauthorized-access issue. An authenticated user with legitimate access to a URL-processing endpoint can exploit this with a single request.
The supporting signals.
| In-the-Wild Exploitation | Not observed. Not on CISA KEV. No mass exploitation campaigns reported as of October 23, 2026. GreyNoise shows no scanning tags. |
|---|---|
| Proof of Concept | Public PoC available. Han5N/10-21-CVE-PoC repo contains a runnable PoC in the CVE-2026-105482/poc/ directory, classified as *Category A: third-party publicly executable*. The PoC demonstrates dns.resolve4('example.com\u0000.trusted.corp') resolving example.com. |
| EPSS Score | Not yet scored. FIRST EPSS API returns empty data — CVE was disclosed October 21, 2026 (2 days ago). Expect initial scoring within 1-2 weeks. |
| KEV Status | Not listed. No CISA Known Exploited Vulnerabilities entry. |
| CVSS Vector (noisgate estimate) | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N → 5.8 MEDIUM. Network-accessible, high attack complexity (requires specific app architecture), no privileges, changed scope (SSRF impacts downstream resources), low C/I impact. |
| Affected Versions | Node.js 22.x (all versions before 22.23.4), 24.x (before 24.22.1), 26.x (before 26.12.1). All three currently supported release lines affected. |
| Fixed Versions | 22.23.4, 24.22.1, 26.12.1 — released October 21, 2026. Fix commit: 6b78ea171d by James M Snell. Distro backports expected within 1-2 weeks. |
| Scanning / Exposure | Node.js has 350M+ monthly downloads in 2026, used by 98% of Fortune 500. However, the vulnerable code path (dns.resolve* with attacker-controlled hostnames) represents a narrow subset. No Shodan/Censys fingerprinting can identify vulnerable *application patterns* — this is a code-level issue, not an exposed-service issue. |
| Disclosure Timeline | Disclosed and patched October 21, 2026 as part of the Node.js October 2026 security release (10 CVEs total). Coordinated release across all three supported lines. |
| Reporter | hasanfleyah (independent researcher). Fix by James M Snell (jasnell), Node.js core collaborator. |
Sources.
- Node.js October 2026 Security Releases (Official Advisory)
- Node.js Fix Commit 6b78ea171d — dns: validate resolve inputs for null bytes
- Lilting Technical Analysis — Node.js v24.22.1 HIGH CVEs
- TuxCare — Node.js October 2026 Security Fixes and Brotli RCE
- Han5N/10-21-CVE-PoC — Public PoC Repository
- The Hacker News — Node.js Security Vulnerabilities Coverage
- CyberSecurityNews — Node.js TLS, DNS, and Brotli Vulnerabilities
- CVE.org Record
Why this verdict
- Attack complexity is genuinely high. The attacker needs a Node.js application that (a) accepts user-supplied hostnames, (b) validates them with string operations before DNS resolution, and (c) passes them through
dns.resolve*()rather than a higher-level HTTP client. Each condition narrows the vulnerable population significantly. - NUL byte delivery has real friction. While JSON
\u0000and URL-encoded%00can deliver NUL bytes, many frameworks, URL parsers (WHATWGnew URL()), and WAFs strip or reject them before they reach application code. HTTP headers cannot carry NUL bytes at all. - Impact is secondary, not primary. This CVE enables hostname validation bypass — it is an *SSRF enabler*, not SSRF itself. The actual blast radius depends entirely on what the application does after DNS resolution and whether downstream targets (internal services, cloud metadata) are reachable.
- Role multiplier: In the *low-value role* (dev servers, static generators, CLI tools), this vulnerability is irrelevant — no attacker-controlled DNS input. In the *typical role* (API servers, web backends), only apps with URL-processing/proxy patterns are exposed. In the *high-value role* (webhook handlers, SSRF-protection middleware, API gateways), the chain succeeds and could enable SSRF → cloud credential theft → lateral movement. However, the high-value role represents a minority of the Node.js installed base (estimated <5% of Node.js deployments are custom SSRF-defense-implementing proxies), and the chain requires *additional* exploitation steps beyond this CVE (reachable internal targets, missing network-level egress controls, IMDSv1). This does not meet the ≥1% threshold for fleet-scale/domain-takeover outcome from this CVE alone.
- No exploitation signal. No KEV listing, no observed in-the-wild exploitation, EPSS not yet scored. The public PoC demonstrates the truncation but not a weaponized SSRF chain.
Why not higher?
HIGH would be warranted if the NUL byte injection directly yielded SSRF without additional application-specific prerequisites, or if the vulnerability affected a component where attacker-controlled DNS input is *expected* (like a DNS server or proxy appliance). Here, the attacker must find a Node.js app that implements a specific hostname-validation-then-resolve pattern, survive NUL byte filtering, and have a useful target behind the SSRF. Each link in this chain has independent friction. The public PoC demonstrates truncation, not a full exploitation chain.
Why not lower?
LOW would undercount the real risk for the meaningful subset of Node.js applications that do perform DNS-based SSRF protection — webhook handlers, URL previewers, integration platforms. For those apps, this is a clean bypass of a security control with a trivial payload (\u0000). Node.js's massive installed base (98% of Fortune 500, 350M+ monthly downloads) means even a small percentage of vulnerable patterns translates to thousands of affected services. The public PoC lowers the attacker's research cost to zero.
Crowdsourced verification payload.
Run this script on any host where Node.js is installed to check if the running version is patched against CVE-2026-105482. No special privileges required — it only checks the Node.js version number. Example: chmod +x check_cve_2026_105482.sh && ./check_cve_2026_105482.sh
#!/usr/bin/env bash
# CVE-2026-105482 - Node.js dns.resolve NUL byte truncation check
# Checks whether the installed Node.js version is patched.
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
if ! command -v node &>/dev/null; then
echo "UNKNOWN - Node.js is not installed on this host."
exit 2
fi
NODE_VERSION=$(node -v 2>/dev/null | sed 's/^v//')
if [[ -z "$NODE_VERSION" ]]; then
echo "UNKNOWN - Could not determine Node.js version."
exit 2
fi
# Parse major.minor.patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$NODE_VERSION"
# Patched versions:
# 22.x >= 22.23.4
# 24.x >= 24.22.1
# 26.x >= 26.12.1
# Odd majors (23, 25) are EOL and unpatched.
check_patched() {
case "$MAJOR" in
22)
if [[ "$MINOR" -gt 23 ]] || { [[ "$MINOR" -eq 23 ]] && [[ "$PATCH" -ge 4 ]]; }; then
return 0
fi
return 1
;;
24)
if [[ "$MINOR" -gt 22 ]] || { [[ "$MINOR" -eq 22 ]] && [[ "$PATCH" -ge 1 ]]; }; then
return 0
fi
return 1
;;
26)
if [[ "$MINOR" -gt 12 ]] || { [[ "$MINOR" -eq 12 ]] && [[ "$PATCH" -ge 1 ]]; }; then
return 0
fi
return 1
;;
*)
# Majors 23, 25 are EOL (no patch available)
# Majors < 22 are EOL
# Majors > 26 are future (presumably patched)
if [[ "$MAJOR" -gt 26 ]]; then
return 0
fi
echo "VULNERABLE - Node.js v${NODE_VERSION} is on an EOL release line with no patch available."
exit 1
;;
esac
}
if check_patched; then
echo "PATCHED - Node.js v${NODE_VERSION} is not affected by CVE-2026-105482."
exit 0
else
echo "VULNERABLE - Node.js v${NODE_VERSION} is affected by CVE-2026-105482. Upgrade to 22.23.4 / 24.22.1 / 26.12.1 or later."
exit 1
fi