← Back to Feed CACHED · 2026-11-02 11:35:46 · CACHE_KEY CVE-2026-120216
CVE-2026-120216 · CWE-248 · Disclosed 2026-11-02

fastify handlerTimeout uncaught ERR_HTTP_HEADERS_SENT streaming response DoS

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A kitchen timer that, instead of dinging, flips the breaker on the whole restaurant

Fastify 5.8.0 through 5.12.7 has a process-killing bug when handlerTimeout is set to a non-zero value and a route returns a streaming response. If the response headers have already been sent and the stream stays open past the timeout, Fastify attempts to send a second error response on the same HTTP reply, throws ERR_HTTP_HEADERS_SENT, and crashes the entire Node.js process. An unauthenticated remote attacker only needs to make one request to a streaming endpoint and then stall reading the response.

The vendor rates this MEDIUM (CVSS 5.9) because handlerTimeout is non-default and the impact is availability only. That matches reality for most Fastify deployments: many apps never enable the timeout and not all routes stream. But for services that enabled handlerTimeout as a slowloris defense and expose streaming endpoints (SSE, file downloads, proxies), a trivial script can repeatedly crash the process. The severity is not enough to panic, but it is not a simple misconfig to ignore.

"Non-default handlerTimeout + streaming route = single request crashes the whole Node process; patch it before it finds your public API."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Identify a reachable Fastify streaming endpoint

The attacker finds a route that streams a response body, such as file downloads, SSE, or upstream proxy responses. This can be done by probing endpoints or reading API documentation. The route must be reachable over the network, ideally without authentication.
Conditions required:
  • Target runs Fastify 5.8.0–5.12.7
  • Application exposes a route that sends streaming output
  • Attacker can reach the route (publicly or via internal network)
Where this breaks in practice:
  • Streaming routes are not universal; many Fastify apps are JSON-only APIs
  • The route may require authentication or be behind a gateway
  • Fingerprinting Fastify is possible but not always reliable if headers are stripped
Detection/coverage: No public scanner for this CVE; Fastify can be fingerprinted via x-powered-by or error pages if not removed.
STEP 02

Confirm handlerTimeout is enabled

The vulnerable code path only exists when the server is configured with handlerTimeout: <n> where n > 0. The attacker cannot read the config directly, but can infer it by observing how long the server waits before sending a 503 on a slow handler.
Conditions required:
  • Server configuration sets handlerTimeout to a non-zero value
  • Default handlerTimeout: 0 is not affected
Where this breaks in practice:
  • handlerTimeout is non-default; many deployments never change it
  • Enabling handlerTimeout is usually done deliberately for slowloris mitigation
  • If not enabled, the attack path stops here
Detection/coverage: No external way to know; runtime behavior can be tested with slow non-streaming requests.
STEP 03

Send a request and stall the response stream

Using curl, slowhttptest, or a small script, the attacker sends a request to the streaming endpoint. The server begins sending the response headers and starts streaming, but the attacker reads the response very slowly or not at all, keeping the response open past the configured handlerTimeout.
Conditions required:
  • The streaming endpoint does not require authentication (or attacker has valid creds)
  • Attacker can keep a TCP/TLS connection open beyond the timeout
  • No intermediate proxy times out the idle connection first
Where this breaks in practice:
  • Load balancers or WAFs may have their own idle timeouts shorter than the configured handlerTimeout
  • Rate limiting may throttle repeated attempts, but one request is enough to crash
  • HTTP/2 connection coalescing can complicate but not prevent the attack
Detection/coverage: WAF or gateway logs may show unusually long response times; SIEM rules for ERR_HTTP_HEADERS_SENT crashes can alert.
STEP 04

Trigger timeout and crash the Node.js process

When handlerTimeout fires, Fastify tries to send an error response on the same HTTP reply that has already sent headers. This raises an uncaught ERR_HTTP_HEADERS_SENT exception. In the default Node.js configuration, the process exits, interrupting all other in-flight requests and taking the service offline.
Conditions required:
  • The handler timeout expires while the response stream is still open
  • No uncaughtException handler that swallows the error and keeps the process alive
  • Node.js process is not protected by a supervisor that restarts it instantly
Where this breaks in practice:
  • Some process managers (systemd, pm2, Kubernetes) will restart the process quickly
  • Custom uncaughtException handlers may keep the process up, though state may be inconsistent
  • A single crash is noisy but not severe; repeated crashes matter
Detection/coverage: Process monitoring and crash dumps; Node.js logs show ERR_HTTP_HEADERS_SENT stack trace.
STEP 05

Repeat to sustain an availability outage

If the process restarts automatically, the attacker can simply resend the same request to crash it again. This turns a one-shot crash into a sustained DoS against the service, causing availability impact for all clients of that Fastify instance.
Conditions required:
  • The service is not patched or restarted with handlerTimeout: 0
  • Attacker can continue sending requests past any restart backoff
  • No IP blocking, rate limiting, or authentication stops the repeated triggers
Where this breaks in practice:
  • Kubernetes health checks may remove the pod after repeated crashes, but service remains degraded
  • CDN or WAF may cache static responses, but streaming endpoints usually bypass cache
  • Monitoring/SRE teams may respond quickly once alerts fire
Detection/coverage: SIEM alerts for repeated crash-restart loops; Node.js process supervisor logs.
03 · Compensating Control

1
MEDIUM 4.5→IGNORE 0.0
SEVERITY REDUCED
Upgrade fastify to 5.12.8 or later — Remove the bug entirely. This is the noisgate remediation for MEDIUM: apply within 365 days. If the service is public-facing with streaming endpoints, prioritize as HIGH and patch within 180 days.
2
MEDIUM 4.5→IGNORE 0.0
SEVERITY REDUCED
If you cannot upgrade immediately, set handlerTimeout to 0 — Disables the affected timeout path and prevents the crash. Apply as a temporary workaround during the standard 365-day remediation window; no mitigation SLA for MEDIUM, but roll out at next maintenance.
3
MEDIUM 4.5→LOW 3.0
SEVERITY REDUCED
Harden streaming endpoints behind authentication and network segmentation — Reduces the unauthenticated remote exposure that makes exploitation trivial, even on unpatched builds. For internal services, require mTLS or service mesh mTLS; for public APIs, use gateway-level auth and IP allowlists.
4
MEDIUM 4.5→MEDIUM 4.0
Configure process supervision and crash alerting — A supervisor (systemd, pm2, Kubernetes restartPolicy) restarts the process quickly. This does not prevent the crash but reduces mean time to recovery and makes repeated exploitation noisy, allowing defenders to respond.
What doesn't work
  • Web application firewalls: a single slow-read request to a legitimate streaming endpoint looks normal; most WAFs won't block it.
  • Node.js uncaughtException handlers: they may prevent immediate process exit but leave the connection state inconsistent and can mask the root cause; not a reliable mitigation.
04 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo known active exploitation campaign or mass scanning observed; not listed in CISA KEV.
Public PoC availabilityNo dedicated public repo confirmed at assessment time; exploitation is trivial with curl or a slow-read script. pocindex.io shows no standing PoC entry for this CVE.
EPSSEPSS score not available; predicted exploitation probability likely low (<10%) given non-default config and no observed attack activity.
KEV statusNot in CISA Known Exploited Vulnerabilities catalog as of 2026-11-02.
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H — network attack, high complexity due required configuration, no privileges, no user interaction, unchanged scope, availability impact high.
Affected versionsfastify 5.8.0 through 5.12.7 inclusive. The default handlerTimeout: 0 is not affected.
Fixed versionsfastify 5.12.8 or later. Workaround: set handlerTimeout: 0 to disable the vulnerable timeout path.
Scanning/exposure dataNo Shodan/Censys/GreyNoise fingerprint exists for handlerTimeout; Fastify is widespread among Node.js services, but the vulnerable configuration is not externally visible.
Disclosure date2026-11-02
Reporting researcher/orgFastify maintainers via GitHub Security Advisory (no individual researcher credited in the official record).

Sources.

  1. NVD entry for CVE-2026-120216
  2. Fastify release v5.12.8
  3. Fastify security advisories
  4. Fastify server options: handlerTimeout
  5. CWE-248 Uncaught Exception
  6. CISA Known Exploited Vulnerabilities Catalog
  7. pocindex.io
  8. Node.js ERR_HTTP_HEADERS_SENT error docs
05 · The Call

Final Verdict
= UNCHANGED to MEDIUM (4.5/10)

Why this verdict

  • Friction audit: The only substantive prerequisite is handlerTimeout>0; if set, the remaining steps are trivial. Most deployments leave it default, so a large portion of Fastify installs are not exposed.
  • Role multiplier: Fastify is an application-tier framework, not an identity provider, hypervisor, PKI, or network edge device. Worst plausible outcome is availability loss for one service, not domain, fleet, or supply-chain compromise.
  • Deployment spectrum: Workstations/dev sandboxes are low impact; internal APIs are medium; public streaming APIs are medium-high availability impact. No high-value-role floor applies because Fastify does not canonically occupy a high-value role.
  • Bottom line: For MEDIUM severity, there is no noisgate mitigation SLA — go straight to the 365-day noisgate remediation SLA. If you run handlerTimeout>0 with public streaming endpoints, treat it as HIGH and patch within 30/180 days.

Why not higher?

Even when the chain lands, it only crashes a single Node.js process; it does not compromise data or code. The vulnerable path requires a non-default timeout and a streaming route, which filters out many Fastify deployments. There is no evidence of active exploitation or mass scanning, and a supervising process manager can restart the service quickly.

Why not lower?

If handlerTimeout is enabled and a streaming endpoint is reachable anonymously, one request causes a full process crash and can be replayed to keep the service down. Streaming routes are common enough (file downloads, SSE, proxies) that the affected population is not negligible. Availability-only impact still matters for production APIs.

06 · Verification

Crowdsourced verification payload.

Run on the application host inside the project directory (or CI runner with the app checked out). Needs read access to package.json/node_modules and Node.js/npm. Example: bash check_cve-2026-120216.sh /opt/my-fastify-app

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# CVE-2026-120216 fastify version checker
# Run from app root. Usage: ./check_cve-2026-120216.sh [path]
set -euo pipefail
APP_DIR="${1:-.}"
cd "$APP_DIR" || { echo 'UNKNOWN: cannot cd'; exit 3; }

VERSION=''
if command -v node >/dev/null 2>&1; then
  VERSION=$(node -e 'try{console.log(require("./node_modules/fastify/package.json").version)}catch(e){process.exit(1)}' 2>/dev/null || true)
fi
if [ -z "$VERSION" ] && command -v npm >/dev/null 2>&1; then
  VERSION=$(npm list fastify --depth=0 --parseable 2>/dev/null | awk -F'@' '/fastify@/{print $2}' | head -n1)
fi
if [ -z "$VERSION" ]; then
  echo 'UNKNOWN: fastify not found or version cannot be determined'
  exit 3
fi

FASTIFY_VERSION="$VERSION" node <<'NODE'
const v = process.env.FASTIFY_VERSION;
const a = v.split('.').map(Number);
const vulnerable = a[0] === 5 && ((a[1] > 8 && a[1] < 12) || (a[1] === 8 && a[2] >= 0) || (a[1] === 12 && a[2] <= 7));
if (vulnerable) {
  console.log('VULNERABLE: fastify ' + v + ' is in affected range 5.8.0-5.12.7');
  process.exit(1);
} else {
  console.log('PATCHED: fastify ' + v + ' is outside affected range');
  process.exit(0);
}
NODE