Where
-Infinity
0

Vendor Risk Score

See how benoitc compares to other vendors in security performance

View Risk Score →
Severity
8.7
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

The WebSocket client in src/hackneyws.erl imposes no upper bound on memory consumption across three distinct code paths. In each case, an attacker-controlled WebSocket server can exhaust the connecting process's memory without any authentication or special client configuration.

Details

1. Handshake response buffer (readhandshakeresponse/3)

The function accumulates received bytes into a growing buffer waiting for \r\n\r\n. The per-receive timeout resets on every chunk, so a server that trickles bytes indefinitely without completing the HTTP upgrade response grows the buffer until OOM. No total-size cap exists.

2. Frame payload accumulation (parsepayload/9, parseactivepayload/8)

parsepayload/9 (lines 816–817 and 825–826) appends each received chunk into a Buffer binary via <<Buffer/binary, MoreData/binary>> whenever the frame parser returns {more, ...}. parseactivepayload/8 does the same in active mode by appending each incoming tcp/ssl message to #wsdata.buffer. RFC 6455 permits payload lengths up to 2⁶³-1 bytes, and neither path validates the declared Len against any limit. The recvtimeout applies per chunk, not to the whole frame, so a slow trickle never triggers it.

3. Fragmentation buffer (fragbuffer)

The fragbuffer field of #wsdata{} accumulates continuation frames. A server that sends an unbounded stream of non-final (nofin) fragments without ever sending a final (fin) frame grows fragbuffer without bound.

PoC

1. Stand up a WebSocket server and connect to it with hackney's WebSocket client. 2. Trigger any of the three paths: (a) never send \r\n\r\n during the handshake; (b) announce a very large frame payload and dribble bytes slowly; (c) send an endless stream of nofin continuation frames. 3. Observe the hackney process's memory growing until the BEAM OOM-kills it or the node crashes.

Impact

Denial of service via unbounded memory consumption. Affects hackney 2.0.0 through 4.0.0 for any application using the WebSocket client against an attacker-controlled server. No authentication or special configuration is required on the client side. CVSS v4.0: 8.7 (HIGH).

Resources

Introduction commit: https://github.com/benoitc/hackney/commit/690cecaf236fba49526da404a5bc889a24367a3e Patch commit: https://github.com/benoitc/hackney/commit/ce0109e2970ace6e20ff29bae9d05c3ac22ec6dc

1 / 2
Source: GitHub
First published (updated )
Severity
8.7
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

CVE-2026-47067 is an atom table exhaustion vulnerability (CWE-770) in hackney's URL parser (src/hackneyurl.erl). hackneyurl:parseurl/1 converts every URL scheme it encounters into a BEAM atom via binarytoatom/2. Because BEAM atoms are never garbage-collected and the atom table has a hard limit of 1,048,576 entries, an attacker who can feed URLs with attacker-chosen scheme prefixes — directly as request targets, as webhook/callback URLs, or via Location headers in redirect chains — can exhaust the atom table and crash the entire BEAM VM with systemlimit.

Details

1. Scheme extraction and conversion

In src/hackneyurl.erl, parseurl/1 extracts the scheme binary (the part before ://), validates it with isvalidscheme/1 (RFC 3986 alphabet: alpha-led, <=19 bytes, alphanumeric/+/-/. body), lowercases it, then calls:

erlang binarytoatom(SchemeLower, utf8)

The resulting atom is stored on the #hackneyurl{} record and returned to the caller.

2. Permanent atom accumulation

The validation constrains the alphabet but not uniqueness. The allowed scheme space is enormous (≈52·65¹⁸ values), far exceeding the default atom limit of 1,048,576. Each distinct scheme mints a new permanent atom. Even when hackney subsequently rejects an unsupported scheme with {error, {unsupportedscheme, }}, the atom has already been interned and is never reclaimed.

3. Crash vector

The most dangerous path is redirect following: when hackney follows a Location header, the redirect target URL is re-parsed by the same function. An attacker-controlled server can serve a sequence of redirects — or a batch of URLs from an upstream feed — each with a fresh unique scheme, driving the atom count monotonically upward. At the limit the BEAM emits systemlimit and the node terminates; recovery requires a full restart.

PoC

1. Call hackneyurl:parseurl/1 (or :hackney.request/5) repeatedly with URLs whose scheme prefixes are unique on each call: aaaa://x, aaab://x, aaac://x, … 2. After enough iterations, observe erlang:systeminfo(:atomcount) climbing by one per unique scheme. 3. At 1,048,576 atoms the VM crashes with systemlimit.

Alternatively, point hackney at a server that replies with a feed of ~1M URLs with distinct schemes (or uses redirect chains with rotating schemes); the atom table is exhausted and the node crashes without the client being able to intervene.

Impact

Unauthenticated remote denial of service via permanent resource exhaustion leading to VM termination. Any application using hackney 2.0.0 through 4.0.0 that processes attacker-influenced URLs — direct request targets, webhook URLs, or Location headers in followed redirects — is affected. No authentication or special configuration is required. CVSS v4.0 score: 8.7 (HIGH).

References

Introduction commit: https://github.com/benoitc/hackney/commit/d9713695c0d99855d12c73fd8a0b4be0543950c4 Patch commit: https://github.com/benoitc/hackney/commit/31f6f0e27e096ad88743dfded4f030a3ee74972e

1 / 2
Source: GitHub
First published (updated )
Severity
6.9
CRLF Injection
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

CRLF injection in hackney's WebSocket upgrade request builder (src/hackneyws.erl). init/1 copies the host, path, headers, and protocols options from the caller-supplied opts map verbatim into #wsdata{}, and dohandshake/1 splices them directly into the raw HTTP/1.1 upgrade request by binary concatenation with no \r\n or \0 stripping. A caller that passes any of these fields from untrusted input can inject arbitrary header lines into the outbound upgrade request.

Details

dohandshake/1 builds the upgrade request at several concatenation sites:

- Host header (lines 583–590): the host binary is written straight into Host: <host>:<port>\r\n. - Sec-WebSocket-Protocol (lines 601–602): protocol tokens are joined with , and appended as a header line. - Extra headers (line 606): caller-supplied {Name, Value} tuples are concatenated as Name: Value\r\n with no sanitization of either component. - Request path (line 611): the path is interpolated into the GET <path> HTTP/1.1\r\n request line.

None of these sites reject \r, \n, or \0. A header value like <<"benign\r\nAuthorization: Bearer token">> produces two distinct header lines on the wire. A path with an embedded \r\n rewrites the request line itself.

PoC

1. Call :hackneyws.startlink/1 with headers: [{"X-User", "v\r\nAuthorization: Bearer attacker"}]. 2. Connect to a raw TCP listener and capture the bytes hackney writes. 3. The request contains a standalone Authorization: Bearer attacker line that the upstream WebSocket server parses as a legitimate header.

Impact

Header injection / request smuggling in outbound WebSocket upgrades. Affects hackney 2.0.0 through 4.0.0 wherever host, path, headers, or protocols options are populated from network or user input. Consequences include forging authentication headers toward the upstream server, log and cache poisoning, and request smuggling through intermediary proxies. CVSS v4.0: 6.9 (MEDIUM).

Resources

Introduction commit: https://github.com/benoitc/hackney/commit/690cecaf236fba49526da404a5bc889a24367a3e Patch commit: https://github.com/benoitc/hackney/commit/52310ca807e7b48441ba0e9129171f535313fdd1

1 / 2
Source: GitHub
First published (updated )
Severity
6.9
SSRF
CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

hackneyurl:normalize/2 URL-decodes the host component of a parsed URL, but the caller's SSRF allowlist runs before normalization using OTP's uristring:parse/1 and inet:parseaddress/1, neither of which decodes percent-escapes in hostnames. A URL like http://%31%32%37%2E%30%2E%30%2E%31/ presents an encoded, non-IP-looking host to the validator, which passes the allowlist check; hackney's normalizer then decodes it to 127.0.0.1 and connects to loopback. Because hackney:request/5 always calls normalize/2 with no opt-out, every request path that accepts a binary or list URL is affected. This is a parser-differential SSRF in the same class as CVE-2025-1211, but in a different function.

Details

In src/hackneyurl.erl (lines 161–186), normalize/2 checks whether the parsed host is already a dotted-quad or IPv6 literal via inetparse:address/1. Percent-encoded forms like %31%32%37%2E%30%2E%30%2E%31 fail that check and fall into the catch-all branch, where urldecode/1 decodes the host before passing it to IDNA conversion:

erlang Host1 = binarytolist( urldecode(unicode:characterstobinary(Host0)) ),

The decoded host ("127.0.0.1") replaces the original in the returned #hackneyurl{} record. hackney:request/5 at src/hackney.erl:463 always calls normalize/2, so the decoded host is what dodispatch/1 and addhostheader/2 ultimately use. The on-wire Host: header and the TCP connect target both reflect the decoded value.

The same payload pattern reaches the AWS/GCP/Azure IMDS (169.254.169.254), RFC1918 ranges, and any localhost admin endpoint. The 1.21.0 patch for CVE-2025-1211 fixed a separate differential in parseurl/1 and did not touch normalize/2.

PoC

1. Validate the URL with the canonical Erlang SSRF allowlist: uristring:parse/1 returns host <<"%31%32%37%2E%30%2E%30%2E%31">>, inet:parseaddress/1 returns {error, einval}, so the allowlist accepts it. 2. Pass the same URL to hackney:get/1. 3. hackney's normalize/2 decodes the host to "127.0.0.1" and connects to 127.0.0.1:80. The internal service receives the request with Host: 127.0.0.1.

Impact

Unauthenticated SSRF bypassing the canonical Erlang allowlist pattern. Affects hackney 0.13.0 through 4.0.0 for any application that accepts attacker-supplied URLs. Targets include cloud IMDS endpoints, localhost admin interfaces, and RFC1918 backends. CVSS v4.0: 6.9 (MEDIUM).

Resources

Introduction commit: https://github.com/benoitc/hackney/commit/4d725507588942fd00efca15b86da3273656510a Patch commit: https://github.com/benoitc/hackney/commit/452620a92ec1da2e6b4862a049a2a4f04b42068f

1 / 2
Source: GitHub
First published (updated )
Severity
6
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

The HTTP/3 redirect handler in src/hackneyh3.erl forwards the original request headers (Authorization, Cookie, Proxy-Authorization) and, for 307/308 responses, the original request body to the redirect target without checking whether the target host matches the origin. When followredirect is enabled and a server responds with a cross-origin Location, hackney delivers the caller's credentials verbatim to the attacker-controlled host. The main hackney HTTP/1 client has maybestripauthonredirect/2 (the fix for CVE-2018-1000007); the H3 client was added later without it.

Details

In src/hackneyh3.erl, handleredirect/11 (line 165) extracts the redirect target from the server-controlled Location header via getredirectlocation/1 and resolves it with resolveredirecturl/2, which accepts any absolute http:// or https:// URL. It then calls dorequestwithredirect/8 passing the original Headers list unchanged. For 307/308 responses, redirectmethod/2 preserves the original method and body, so the POST body is also forwarded.

No comparison is made between the original URL's scheme, host, or port and the redirect target. The downstream connect/3 opens a new QUIC connection to whatever the Location header named, and buildrequestheaders/4 serializes the unmodified headers into the QPACK-encoded request.

PoC

1. Issue an HTTP/3 POST to an attacker-controlled origin with followredirect => true and an Authorization: Bearer ... header. 2. The attacker's server responds 307 Location: https://other.host/collect. 3. hackney opens a new connection to other.host and re-sends the original headers and body, including the bearer token and any Cookie headers.

Impact

Credential and request-body disclosure to attacker-controlled origins. Affects hackney 3.1.1 through 4.0.0 when using the HTTP/3 client with followredirect enabled. Any upstream that is malicious, compromised, or reachable via DNS/MITM can steal session tokens, bearer credentials, and POST bodies. CVSS v4.0: 6.0 (MEDIUM).

Resources

Introduction commit: https://github.com/benoitc/hackney/commit/e61b7d04b7826847e1efe614106ef4d580c78eab Patch commit: https://github.com/benoitc/hackney/commit/c58d5b50bade146360b85caf3dc8065807b08246

1 / 2
Source: GitHub
First published (updated )
Severity
6.8
CRLF Injection
CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:A/VC:N/VI:H/VA:N/SC:N/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

hackneyurl:makeurl/3 passes the URL query component directly into the HTTP/1.1 request target without percent-encoding \r or \n. RFC 3986 §3.4 requires characters outside the query grammar to be percent-encoded, but no validation or escaping occurs. An attacker who controls any portion of the URL passed to hackney can inject raw CRLF sequences into the request line, enabling HTTP header injection or full request splitting.

Details

hackneyurl:makeurl/3 in src/hackneyurl.erl builds the request target by concatenating the path and query binaries. The query string is used as-is — no character-class check, no percent-encoding of \r or \n. When hackney serializes the request, the query value lands verbatim in the GET <path>?<query> HTTP/1.1\r\n request line. A query value containing \r\n terminates the request line early; subsequent bytes are parsed by the server (or any intermediary proxy) as additional header lines or a second request.

A concrete example: a URL with query ?q=x HTTP/1.1\r\nX-Injected: yes\r\nX: produces the following on the wire:

GET /?q=x HTTP/1.1 X-Injected: yes X: HTTP/1.1 Host: ...

The server sees X-Injected: yes as a legitimate request header that the client never intended to send.

PoC

1. Listen on a raw TCP port: nc -lvnp 8080. 2. Issue: :hackney.get("http://127.0.0.1:8080/?q=x HTTP/1.1\r\nX-Injected: yes\r\nX:"). 3. Observe the listener receives X-Injected: yes as a standalone header line in the request.

Impact

HTTP header injection and request splitting against any server hackney connects to. Affects all released versions of hackney before 4.0.1. Exploitation requires an attacker-controlled URL (or URL component) to reach hackney without prior sanitization. Consequences include injecting arbitrary headers (Authorization, Host, X-Forwarded-For) and splitting requests through proxies. CVSS v4.0: 6.8 (MEDIUM).

Resources

Introduction commit: https://github.com/benoitc/hackney/commit/8bb1a359a81ae58567c84f8d24564e9742e6f2bd Patch commit: https://github.com/benoitc/hackney/commit/ca73dd0aba0ed557449c18288bf07241671a43c9

1 / 3
Source: GitHub
First published (updated )
Severity
8.2
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

hackneyh3:awaitresponseloop/6 in src/hackneyh3.erl accumulates the HTTP/3 response body in memory without any size cap. The after Timeout clause is a per-message inactivity timer, not a wall-clock deadline: every received streamdata chunk, housekeeping select message, or settings frame resets it. A malicious HTTP/3 server that drips one small chunk every Timeout - 1 ms with Fin = false and never terminates the stream keeps the loop alive indefinitely while the accumulation buffer grows without bound, eventually exhausting the BEAM process heap.

Details

In src/hackneyh3.erl, awaitresponseloop/6 (line 430) builds the body with:

erlang NewBody = <<AccBody/binary, Data/binary>>

There is no maxbody check and no monotonic deadline. The after Timeout clause at line 463 is restarted on each loop iteration. A server that ensures at least one message arrives within Timeout ms indefinitely (one small chunk per interval is sufficient) prevents the timeout from firing while AccBody grows linearly. The same module's waitconnected/3 (lines 388-389) shows the correct pattern: track an absolute start time and pass a shrinking Remaining budget into each receive. This loop does not.

Configurations

Only the HTTP/3 transport is affected. Applications using the default TCP/TLS hackney transport are not vulnerable. The vulnerability requires using hackneyh3 directly or passing {transport, h3} to hackney:request/5.

PoC

1. Stand up an HTTP/3 server that responds with 200 OK headers (Fin = false), then emits a small streamdata chunk every Timeout - margin ms with Fin = false indefinitely. 2. Issue hackney:request(get, Url, [], <<>>, [{transport, h3}]) against it. 3. Watch the client process heap grow monotonically. The configured timeout never fires; the process is eventually killed by maxheapsize or the OS OOM killer.

Impact

Remote denial of service via unbounded memory consumption. Affects hackney 2.0.0 through 4.0.0 when using the HTTP/3 transport against an attacker-controlled or attacker-influenced server. Each affected request consumes unbounded memory until the BEAM is killed. CVSS v4.0: 8.2 (HIGH).

Resources

Introduction commit: https://github.com/benoitc/hackney/commit/0334af206d5099fdf510ed9eda18e34396f065ad Patch commit: https://github.com/benoitc/hackney/commit/3d25f9fea26c90609de9d64366fedfe5065413bc

1 / 2
Source: GitHub
First published (updated )
Severity
8.2
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

The SOCKS5 transport in src/hackneysocks5.erl correctly applies the caller-supplied timeout to the SOCKS5 negotiation phase, but then upgrades the tunnel to TLS using ssl:connect/2 (the two-argument form), which defaults to infinity. The Timeout value is in scope at that call site but is never forwarded. A hostile or man-in-the-middled SOCKS5 proxy that completes the SOCKS5 handshake normally and then stalls the TLS exchange will pin the connecting Erlang process and socket indefinitely, regardless of any connecttimeout or recvtimeout options the caller set.

Details

In src/hackneysocks5.erl, line 65, after the SOCKS5 negotiation succeeds, the code calls:

erlang ssl:connect(Socket, SSLOpts)

The three-argument form ssl:connect/3 takes a timeout; the two-argument form used here defaults to infinity. The variable Timeout (already used for SOCKS5 recv calls earlier in the same function) is simply not passed. The bytes that drive the TLS handshake on the upstream side of the tunnel come from whatever endpoint the proxy connects to. A hostile proxy can complete SOCKS5 normally, then either stay silent or send a partial ServerHello and stop, keeping ssl:connect/2 blocked forever. No certificate forgery is needed.

PoC

1. Stand up a SOCKS5 proxy that completes the SOCKS5 greeting and CONNECT reply normally, then goes silent (never sends a TLS ServerHello). 2. Issue an HTTPS request through it via hackney with connecttimeout and recvtimeout set to a short value (e.g. 2000 ms). 3. Observe the calling process remains blocked well past the configured timeout, consuming a process and socket until killed externally.

Impact

Denial of service via unbounded process and socket consumption. Affects hackney 0.10.0 through 4.0.0 for any HTTPS request routed through a SOCKS5 proxy. The connecttimeout and recvtimeout options give a false sense of safety since they are not honored during the TLS upgrade. CVSS v4.0: 8.2 (HIGH).

Resources

Introduction commit: https://github.com/benoitc/hackney/commit/34cdbd1d20a282aacc286a89327465a3925b4c5d Patch commit: https://github.com/benoitc/hackney/commit/5ccdab725c561a6f03d05a51f2d0664f98236dae

1 / 2
Source: GitHub
First published (updated )
Severity
2.1
CRLF Injection
CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

CRLF injection in hackneycookie:setcookie/3 (src/hackneycookie.erl). The function validates Name and Value against CR/LF and control characters but concatenates the domain and path options verbatim into the output binary. If either option carries attacker-controlled data, a Host header forwarded as the cookie domain, a request URI forwarded as the cookie path, a \r\n in the value splits the Set-Cookie header and lets the attacker inject additional headers into the HTTP response.

Details

1. Asymmetric validation

Lines 27–34 of hackneycookie.erl run binary:match on Name and Value, rejecting =, ,, ;, whitespace, \r, \n, \013, and \014. The Domain and Path options (lines 47 and 51) skip this check entirely and land straight in the result iolist:

erlang [<<"; Domain=">>, Domain] [<<"; Path=">>, Path]

iolisttobinary(...) on line 63 flattens everything and returns it to the caller.

2. Injection

A Path of <<"/x\r\nSet-Cookie: admin=1; Path=/">> produces a binary with a literal \r\n. Written into a Set-Cookie response header, the receiving HTTP parser splits it into two headers — one legitimate, one attacker-controlled.

3. Realistic trigger

Common patterns: keying the cookie domain off Host, deriving the path from the request URI, or copying a Location path into a cookie. Any of these lets a remote attacker control the injected content.

PoC

1. Call hackneycookie:setcookie(<<"sid">>, <<"abc">>, [{path, <<"/x\r\nSet-Cookie: admin=1; Path=/">>}]). 2. The returned binary contains a literal \r\n followed by a second Set-Cookie: line. 3. Write the result into a Set-Cookie response header — the client parses two headers, including admin=1.

Impact

Cookie injection / HTTP response splitting at the hackneycookie API boundary. Affects hackney 0.9.0 through 4.0.0 wherever domain or path options are populated from request data. Exploitation can overwrite session/auth cookies, fix cookies, or strip Secure/HttpOnly flags. CVSS v4.0: 2.1 (LOW) — requires attacker-controlled input to reach the domain or path option.

Resources

Introduction commit: https://github.com/benoitc/hackney/commit/602d5c7f2ea4acbc83ed75230655d935a0750ebc Patch commit: https://github.com/benoitc/hackney/commit/8e02b99c28aea1b3fa2ddc0e66f51fe5bb0ac540

1 / 2
Source: GitHub
First published (updated )
Severity
8.7
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

CVE-2026-47066 is an infinite loop (CWE-835) in hackney's Alt-Svc response header parser (src/hackneyaltsvc.erl). When an HTTP server returns an Alt-Svc header whose value begins with a non-token byte (e.g. !, @, =, ;), the parser enters a tight tail-recursive loop that pins an Erlang scheduler at 100% CPU and permanently hangs the calling connection process. Because the parser is invoked synchronously on every HTTP response, any attacker-controlled origin can trigger the hang with a single-byte header value.

Details

1. Parser dispatch

parseandcache/3 is called inside the hackney connection process on each HTTP response. It collects all Alt-Svc header values via collectaltsvcheaders/1, concatenates them, and passes the result to parse/1, which calls parseentries(Header, []).

2. Failed token consumption

parseentries/2 → parseentry/1 → parseprotocol/1 → parsetoken(Data, <<>>). The function parsetoken/2 pattern-matches leading bytes: alphanumeric, -, , whitespace, and comma all have explicit clauses. Any other byte (e.g. !) falls through to the catch-all:

erlang parsetoken(Rest, <<>>) -> {undefined, Rest}.

This returns the input unchanged — no byte is consumed.

3. No-progress loop

parseentry propagates {undefined, Rest} back to parseentries/2, which calls skipcomma(Rest). Because the first byte is not ,, skipcomma also returns Rest unchanged. parseentries then recurses with the identical buffer:

erlang parseentries(Data, Acc) % Data identical to previous iteration

Erlang tail recursion never preempts on a pure CPU loop, so the scheduler is pinned and the process never yields or returns.

4. Root cause

parseentries/2 has no guard that detects zero-byte progress after a failed parseentry call and no fallback to advance past the offending byte.

PoC

1. Start an HTTP server that responds with the header Alt-Svc: ! (any single non-token byte suffices). 2. Issue any HTTP GET request via hackney to that server: erlang hackney:request(get, "http://attacker.example/", [], <<>>, []) 3. Observe that the call never returns; the Erlang scheduler hosting the connection process is pinned at 100% CPU indefinitely.

Alternatively, call the parser directly: hackneyaltsvc:parse(<<"!">>) — the process hangs immediately.

Impact

Denial of service via unbounded CPU consumption. Any application using hackney 2.0.0-beta.1 through 4.0.0 that connects to attacker-controlled HTTP endpoints is affected. No authentication is required; a single response header byte is sufficient to hang the connection process. Fixed in hackney 4.0.1. CVSS v4.0 score: 8.7 (HIGH).

Resources

Introduction commit: https://github.com/benoitc/hackney/commit/408e5fe20302226ea8c74dde2bcbd452d712b5b2 Patch commit: https://github.com/benoitc/hackney/commit/e548aba1f97ffa3f4750da7b772998fb78c01894

1 / 2
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203