GHSA-655f-mp8p-96gv
Fix: wojtekmach/req@84977e5GHSA-655f-mp8p-96gv is a CWE-409 vulnerability in req. O3 Security confirms whether GHSA-655f-mp8p-96gv is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
Req vulnerable to unbounded archive/compression extraction triggered by response content-type
Real-World Exposure
reqReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Hex packages — download data is not available via public APIs for these ecosystems.
Description
Summary
Req's default response pipeline auto-decodes archive and compressed bodies based on the server-supplied content-type (or URL extension) and materialises the full decompressed contents in memory with no size cap. An attacker who controls (or can redirect a victim into) an HTTP endpoint reached by Req.get!/1 can return a tiny "decompression bomb" that expands to many gigabytes on the client and exhausts the BEAM's memory.
Details
1. Archive auto-decoding. Req.Steps.decode_body/1 in lib/req/steps.ex dispatches on the response content-type (or URL extension) and calls Erlang's archive libraries with :memory, returning a [{name, bytes}] list of every entry fully decompressed in RAM: application/zip → :zip.extract(body, [:memory]), application/x-tar → :erl_tar.extract({:binary, body}, [:memory]), application/gzip / .tgz → :erl_tar.extract({:binary, body}, [:memory, :compressed]). No byte cap is enforced before decoding and no per-entry size limit is passed to :zip / :erl_tar.
2. content-encoding chaining. Req.Steps.decompress_body/1 walks the content-encoding header and chains :zlib / :brotli / :ezstd decoders, so a response advertising content-encoding: gzip, gzip, gzip, … inflates through multiple layers without bound.
3. Default-on, attacker-chosen decoder. Both steps are part of Req's default pipeline. The caller does not need to opt in, and the attacker chooses which decoder fires by setting content-type and content-encoding on their own server (or on any host reached via Req's automatic redirect following).
PoC
- Run an HTTP server that responds 200 with
content-type: application/zipand a body that is a zip archive whose single entry is ~400 MB of zero bytes (compressed wire payload: a few hundred KB). - From the victim process, call
Req.get!(url)against that server (no special options, no opt-in to archive decoding). decode_body/1dispatches oncontent-type, invokes:zip.extract(body, [:memory]), and the response body becomes[{~c"bomb.bin", <<400 MB of zero bytes>>}]. A sub-MB request produces hundreds of MB resident memory; layering gzip on thecontent-encodingpath or increasing entry size scales arbitrarily.
Impact
Memory-exhaustion denial of service against any Elixir application that uses Req with its default step pipeline to fetch URLs influenced by an untrusted party, including webhook senders, link previews, OAuth/OIDC discovery clients, package mirrors, image proxies, and any Req.get!/1 call that may follow redirects to attacker-controlled hosts. No authentication is required; a single response can crash the BEAM and take down unrelated workloads on the same VM.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 💧Hex | req | ≥ 0.1.0&&< 0.6.1 | 0.6.1 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for req. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update req to 0.6.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-655f-mp8p-96gv is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-655f-mp8p-96gv is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-655f-mp8p-96gv. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-655f-mp8p-96gv in your dependencies?
O3 detects GHSA-655f-mp8p-96gv across Hex dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.