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.
5 steps from start to impact.
Identify a reachable Fastify streaming endpoint
- 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)
- 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
x-powered-by or error pages if not removed.Confirm handlerTimeout is enabled
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.- Server configuration sets
handlerTimeoutto a non-zero value - Default
handlerTimeout: 0is not affected
handlerTimeoutis non-default; many deployments never change it- Enabling
handlerTimeoutis usually done deliberately for slowloris mitigation - If not enabled, the attack path stops here
Send a request and stall the response stream
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.- 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
- 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
ERR_HTTP_HEADERS_SENT crashes can alert.Trigger timeout and crash the Node.js process
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.- The handler timeout expires while the response stream is still open
- No
uncaughtExceptionhandler that swallows the error and keeps the process alive - Node.js process is not protected by a supervisor that restarts it instantly
- Some process managers (systemd, pm2, Kubernetes) will restart the process quickly
- Custom
uncaughtExceptionhandlers may keep the process up, though state may be inconsistent - A single crash is noisy but not severe; repeated crashes matter
ERR_HTTP_HEADERS_SENT stack trace.Repeat to sustain an availability outage
- 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
- 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
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.- Web application firewalls: a single slow-read request to a legitimate streaming endpoint looks normal; most WAFs won't block it.
- Node.js
uncaughtExceptionhandlers: they may prevent immediate process exit but leave the connection state inconsistent and can mask the root cause; not a reliable mitigation.
The supporting signals.
| In-the-wild status | No known active exploitation campaign or mass scanning observed; not listed in CISA KEV. |
|---|---|
| Public PoC availability | No 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. |
| EPSS | EPSS score not available; predicted exploitation probability likely low (<10%) given non-default config and no observed attack activity. |
| KEV status | Not in CISA Known Exploited Vulnerabilities catalog as of 2026-11-02. |
| CVSS vector | CVSS: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 versions | fastify 5.8.0 through 5.12.7 inclusive. The default handlerTimeout: 0 is not affected. |
| Fixed versions | fastify 5.12.8 or later. Workaround: set handlerTimeout: 0 to disable the vulnerable timeout path. |
| Scanning/exposure data | No Shodan/Censys/GreyNoise fingerprint exists for handlerTimeout; Fastify is widespread among Node.js services, but the vulnerable configuration is not externally visible. |
| Disclosure date | 2026-11-02 |
| Reporting researcher/org | Fastify maintainers via GitHub Security Advisory (no individual researcher credited in the official record). |
Sources.
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>0with 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.
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
#!/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