Someone left a semicolon-sized gap in the cookie jar's lid, but you have to reach through a very specific shelf to exploit it
CVE-2026-16729 targets undici's setCookie() function — the cookie-management helper in Node.js's built-in HTTP client. Two injection paths exist: (1) the domain parameter is not checked for semicolons, so a value like example.com; SameSite=None passes through verbatim and appends an attacker-chosen attribute; (2) entries in the unparsed array are only checked for an equals sign without sanitizing for embedded semicolons, so X-Custom=val; HttpOnly injects an HttpOnly flag the caller never intended. Affected versions span undici < 6.28.0, 7.0.0–7.28.x, and 8.0.0–8.8.x. Because Node.js v18+ bundles undici for its global fetch() implementation, the vulnerable library is present on every active Node.js installation — but the *specific API* (setCookie) is far less commonly invoked than fetch() or request().
The vendor's MEDIUM 4.8 is broadly appropriate but slightly generous. The CVSS vector already encodes AC:H (high attack complexity), yet even that understates the real-world friction: the attacker must find an application that (a) imports and calls setCookie() directly, *and* (b) pipes user-controlled input into the domain or unparsed fields — a pattern almost exclusively found in multi-tenant reverse proxies or SSR frameworks that scope cookies to tenant-supplied domains. That is a small slice of the 139 million weekly npm downloads undici enjoys. The impact ceiling is cookie-security-attribute manipulation (stripping HttpOnly, downgrading SameSite, removing Secure), which is a *precondition* for CSRF or session-fixation — not a standalone compromise. noisgate downgrades slightly to 4.0.
4 steps from start to impact.
Identify a setCookie consumer with user-controlled domain input
setCookie(). Multi-tenant SaaS platforms that let tenants specify cookie-scoping domains, or reverse-proxy layers that reflect upstream Set-Cookie headers through undici's cookie jar, are the canonical targets. Without this specific code path, the vulnerability is unreachable.- Target application uses undici's
setCookie()API (not justfetch()) - User-controlled data flows into the
domainorunparsedparameter
- Most undici consumers use
fetch()/request()— setCookie is a niche cookie-management helper - Multi-tenant cookie-scoping patterns are rare outside purpose-built proxy / SSR frameworks
Craft a semicolon-injected domain value
tenant.example.com; SameSite=None; Secure through the identified input channel. Because validateCookieDomain does not reject semicolons (unlike validateCookiePath, which does), the entire string is emitted verbatim into the Set-Cookie header. Alternatively, the attacker injects through the unparsed array with a value like X-Custom=val; HttpOnly to toggle or strip cookie flags.- Input is not independently validated against RFC 1034 LDH character set before reaching setCookie
- Well-written apps validate domain inputs at the API boundary — semicolons are not legal in DNS names
- WAF rules blocking semicolons in domain-type parameters would stop this trivially
Victim browser receives manipulated cookie attributes
Set-Cookie header now carries attacker-injected attributes. The browser interprets the cookie with the manipulated SameSite, Secure, or HttpOnly flags. For example, stripping SameSite=Strict and injecting SameSite=None enables cross-site request forgery against the victim's session on the target application. Stripping HttpOnly allows JavaScript-based cookie theft via XSS if a separate XSS vulnerability exists.- Victim visits or is redirected to the affected application
- The manipulated Set-Cookie header reaches the browser without downstream sanitization
- Impact requires a *second* vulnerability (XSS or CSRF target endpoint) to produce meaningful compromise
- Modern browsers enforce Lax-by-default SameSite policy if no attribute is present, limiting the blast radius of attribute stripping
Exploit weakened cookie security (CSRF / session fixation)
document.cookie. The attacker can also perform session fixation by injecting a known session cookie value scoped to the manipulated domain. This is the terminal impact — account-level compromise of individual user sessions, not system-level access.- A CSRF-exploitable state-changing endpoint exists without secondary CSRF tokens
- OR an XSS vulnerability exists on the same origin to read non-HttpOnly cookies
- Applications using anti-CSRF tokens (e.g., Django CSRF, Express csurf, framework-provided double-submit) are not affected by SameSite downgrade alone
- The chain requires *two* independent vulnerabilities to reach meaningful impact
The supporting signals.
| In-the-wild exploitation | None observed. Not listed in CISA KEV. No campaigns or threat-actor usage reported as of 2026-07-30. Disclosed yesterday (2026-07-29). |
|---|---|
| Proof-of-concept | No public PoC. The GHSA advisory (GHSA-v3r7-h72x-cjcm) describes the injection mechanism (semicolons in domain, unsanitized unparsed entries) but no exploit code is published. Reporter: Zelys-DFKH. |
| EPSS score | Not yet scored — CVE was published 2026-07-29; FIRST EPSS data is not yet populated. Expected to be very low (<5th percentile) given AC:H and no public exploit. |
| KEV status | Not listed. No CISA Known Exploited Vulnerability catalog entry. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N — Network-reachable but high complexity. No privileges or user interaction required. Scope unchanged. Low C+I, no availability impact. The AC:H is driven by the requirement for user-controlled input flowing into a specific API. |
| Affected versions | undici < 6.28.0, 7.0.0 – 7.28.x, 8.0.0 – 8.8.x. The flaw has existed since setCookie was introduced. All three active major branches are affected. |
| Fixed versions | undici 6.28.0, 7.29.0, 8.9.0. Node.js bundled fixes: Node 22.23.2 (undici 6.28.0), Node 24.18.1 (undici 7.29.0), Node 26.5.1 (undici 8.9.0). Released 2026-07-29. |
| Scanning / exposure data | Not applicable for network scanning — this is a library-level logic flaw, not an exposed service. Shodan/GreyNoise/Censys cannot fingerprint it. Software composition analysis (SCA) is the detection vector. undici has ~139M weekly npm downloads but setCookie usage is a fraction of that. |
| Disclosure timeline | CVE reserved 2026-07-23. GHSA + Node.js coordinated release 2026-07-29. Part of a batch fixing 10+ CVEs across Node.js 22/24/26. |
| Reporter / credits | Zelys-DFKH (reporter). Remediation reviewed by mcollina and UlisesGascon (Node.js core team). |
noisgate verdict.
The single most decisive factor is the narrow code path required for exploitation: the attacker must find an application that calls undici's setCookie() with user-controlled domain or unparsed input — a pattern confined to multi-tenant cookie-scoping proxies, which represent a small fraction of undici's 139M-weekly-download install base. Even when the chain succeeds, the terminal impact is cookie-attribute manipulation (SameSite/HttpOnly/Secure downgrade), which is a precondition for CSRF or session-fixation, not a standalone compromise.
Why this verdict
- Narrow API surface: setCookie() is a cookie-management helper, not the primary fetch/request path. The vast majority of undici consumers never call it, and fewer still pass user-controlled data into
domainorunparsed. This is materially narrower than AC:H alone captures. - Two-vuln chain to meaningful impact: Stripping SameSite or HttpOnly is a *precondition*, not a *compromise*. The attacker still needs a CSRF-exploitable endpoint (without secondary tokens) or an XSS on the same origin to extract cookies. Modern frameworks ship anti-CSRF tokens by default.
- Role multiplier: undici is bundled in every Node.js 18+ runtime, giving it presence on CI/CD runners, SSR servers, API gateways, and internal tooling. However, the affected API (setCookie) is not invoked in any of these high-value roles by default — it requires deliberate, custom cookie-scoping code. On a CI/CD server or build runner, setCookie with tenant domains is essentially non-existent. On an SSR framework or reverse proxy that *does* scope cookies to tenant domains, the blast radius is tenant-level session integrity, not fleet compromise or supply-chain pivot. No high-value-role floor is triggered because the terminal impact (cookie attribute manipulation) does not reach domain takeover, fleet compromise, or supply-chain scale.
- Browser defaults as backstop: Chrome, Firefox, and Edge enforce
SameSite=Laxby default when no SameSite attribute is present. An attacker who *strips* SameSite still gets Lax enforcement, limiting the CSRF window to top-level GET navigations only. - Fresh disclosure, zero exploitation signal: Published 2026-07-29 with no PoC, no KEV listing, no campaign reports. The attack prerequisites make automated mass exploitation unlikely.
Why not higher?
The vulnerability cannot reach HIGH because the terminal impact is limited to cookie-attribute manipulation — there is no path to remote code execution, privilege escalation, or direct data access. Exploitation requires a second independent vulnerability (XSS or unprotected CSRF endpoint) to produce meaningful harm. The affected API (setCookie) is a niche helper within a library whose primary use case (HTTP requests via fetch/request) is unaffected. No high-value-role deployment pattern puts setCookie in a position where attribute injection escalates to domain or fleet compromise.
Why not lower?
The vulnerability cannot drop to LOW because the attack vector is network-reachable with no authentication or user interaction required (AV:N/PR:N/UI:N), undici's install base is genuinely enormous (~139M weekly downloads, bundled in all active Node.js lines), and the specific multi-tenant reverse-proxy pattern — while uncommon — is not hypothetical. Cookie security attribute manipulation can enable real CSRF bypass in applications that rely solely on SameSite for CSRF protection, which is an increasingly common (if incomplete) pattern. The coordinated Node.js emergency release treating this as part of a 10-CVE batch also signals the maintainers consider it actionable.
What to do — in priority order.
- Validate domain inputs against RFC 1034 LDH charset before calling setCookie — Reject any domain string containing semicolons, spaces, or non-LDH characters at your application's input boundary. This is the most targeted fix and eliminates the injection vector entirely. Implement within your normal MEDIUM remediation window (365 days per noisgate SLA), but sooner if you know you pass user input to setCookie.
- Audit codebase for setCookie usage with user-controlled parameters — Run
grep -rn 'setCookie\|set-cookie' --include='*.js' --include='*.ts'across your Node.js services. If no call sites pass user-controlled data todomainorunparsed, your exposure is zero and you can deprioritize. This triage step should take hours, not days. - Upgrade undici or Node.js to patched versions — Update to undici 6.28.0 / 7.29.0 / 8.9.0, or Node.js 22.23.2 / 24.18.1 / 26.5.1. This is the definitive fix. Schedule within the noisgate remediation SLA of 365 days for MEDIUM severity.
- Ensure anti-CSRF tokens are in place on all state-changing endpoints — If your application relies solely on SameSite cookies for CSRF protection, add framework-level CSRF tokens (e.g.,
csurf, Django CSRF middleware, Rails authenticity tokens). This neutralizes the downstream impact even if cookie attributes are manipulated.
- WAF rules blocking cookie injection — The injection happens *inside* the application's own setCookie call, not in inbound HTTP headers. A WAF inspecting request headers won't see the manipulated Set-Cookie being emitted in the response unless you have egress header inspection, which most WAFs don't perform.
- Upgrading Node.js without checking direct undici dependency — If your app has undici in
package.jsonas a direct dependency (not just the bundled version), upgrading Node.js alone won't fix the direct import. You must alsonpm update undicior pin the fixed version. - Content Security Policy (CSP) — CSP controls script execution and resource loading, not cookie attribute parsing. It does not prevent SameSite downgrade or HttpOnly stripping.
Crowdsourced verification payload.
Run this on any host or CI runner with Node.js installed. It checks both the bundled undici version (via process.versions.undici) and any directly-installed undici in node_modules. No special privileges needed. Example: bash check_cve_2026_16729.sh /path/to/your/project
#!/usr/bin/env bash
# check_cve_2026_16729.sh — Detect undici versions vulnerable to CVE-2026-16729
# Usage: bash check_cve_2026_16729.sh [project_dir]
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
PROJECT_DIR="${1:-.}"
VULNERABLE=0
echo "=== CVE-2026-16729 Check ==="
echo "Checking undici for cookie attribute injection via unsanitized setCookie fields"
echo ""
# Function to compare semver (major.minor.patch)
ver_lt() {
local IFS='.'
read -ra a <<< "$1"
read -ra b <<< "$2"
for i in 0 1 2; do
local ai="${a[$i]:-0}"
local bi="${b[$i]:-0}"
if (( ai < bi )); then return 0; fi
if (( ai > bi )); then return 1; fi
done
return 1 # equal means not less-than
}
# Check 1: Bundled undici in Node.js
echo "[1] Checking bundled undici version in Node.js..."
BUNDLED_VER=$(node -e 'console.log(process.versions.undici || "none")' 2>/dev/null || echo "none")
if [ "$BUNDLED_VER" = "none" ]; then
echo " Node.js does not expose bundled undici version (likely Node < 18). Skipping."
else
echo " Bundled undici version: $BUNDLED_VER"
MAJOR=$(echo "$BUNDLED_VER" | cut -d. -f1)
if [ "$MAJOR" -le 6 ] && ver_lt "$BUNDLED_VER" "6.28.0"; then
echo " RESULT: VULNERABLE (needs >= 6.28.0)"
VULNERABLE=1
elif [ "$MAJOR" -eq 7 ] && ver_lt "$BUNDLED_VER" "7.29.0"; then
echo " RESULT: VULNERABLE (needs >= 7.29.0)"
VULNERABLE=1
elif [ "$MAJOR" -eq 8 ] && ver_lt "$BUNDLED_VER" "8.9.0"; then
echo " RESULT: VULNERABLE (needs >= 8.9.0)"
VULNERABLE=1
else
echo " RESULT: PATCHED"
fi
fi
echo ""
# Check 2: Direct undici dependency in project
echo "[2] Checking direct undici dependency in $PROJECT_DIR ..."
PKG_JSON="$PROJECT_DIR/node_modules/undici/package.json"
if [ -f "$PKG_JSON" ]; then
DIRECT_VER=$(node -e "console.log(require('$PKG_JSON').version)" 2>/dev/null || echo "unknown")
if [ "$DIRECT_VER" = "unknown" ]; then
echo " Could not parse version from $PKG_JSON"
else
echo " Direct undici version: $DIRECT_VER"
MAJOR=$(echo "$DIRECT_VER" | cut -d. -f1)
if [ "$MAJOR" -le 6 ] && ver_lt "$DIRECT_VER" "6.28.0"; then
echo " RESULT: VULNERABLE (needs >= 6.28.0)"
VULNERABLE=1
elif [ "$MAJOR" -eq 7 ] && ver_lt "$DIRECT_VER" "7.29.0"; then
echo " RESULT: VULNERABLE (needs >= 7.29.0)"
VULNERABLE=1
elif [ "$MAJOR" -eq 8 ] && ver_lt "$DIRECT_VER" "8.9.0"; then
echo " RESULT: VULNERABLE (needs >= 8.9.0)"
VULNERABLE=1
else
echo " RESULT: PATCHED"
fi
fi
else
echo " No direct undici dependency found in $PROJECT_DIR/node_modules/"
fi
echo ""
# Check 3: Grep for setCookie usage (exposure check)
echo "[3] Scanning for setCookie usage in $PROJECT_DIR (exposure check)..."
SETCOOKIE_HITS=$(grep -rn 'setCookie\|setCookies' --include='*.js' --include='*.ts' --include='*.mjs' "$PROJECT_DIR/" 2>/dev/null | grep -v node_modules | head -20 || true)
if [ -n "$SETCOOKIE_HITS" ]; then
echo " WARNING: setCookie usage found — review for user-controlled domain/unparsed input:"
echo "$SETCOOKIE_HITS" | while IFS= read -r line; do echo " $line"; done
else
echo " No setCookie calls found outside node_modules. Exposure is likely ZERO."
fi
echo ""
echo "=== Summary ==="
if [ "$VULNERABLE" -eq 1 ]; then
echo "VULNERABLE — Update undici or Node.js to a patched version."
exit 1
else
if [ "$BUNDLED_VER" = "none" ] && [ ! -f "$PKG_JSON" ]; then
echo "UNKNOWN — Could not determine undici version."
exit 2
else
echo "PATCHED — No vulnerable undici versions detected."
exit 0
fi
fiIf you remember one thing.
npm audit or your SCA scanner across Node.js services to identify undici versions below 6.28.0 / 7.29.0 / 8.9.0. Then grep for setCookie calls with user-controlled input — if you find none, your actual exposure is zero and this becomes a routine dependency bump. There is no noisgate mitigation SLA for MEDIUM — go straight to the noisgate remediation SLA of 365 days and schedule the undici or Node.js upgrade (22.23.2 / 24.18.1 / 26.5.1) in your next planned maintenance window. If you *do* find setCookie calls accepting tenant-supplied domains, add RFC 1034 LDH validation on those inputs this sprint as a targeted fix while you plan the broader upgrade. No emergency patching is warranted.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.