{ "dataType": "CVE_RECORD", "dataVersion": "5.2", "cveMetadata": { "cveId": "CVE-2026-96618", "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db", "state": "PUBLISHED", "assignerShortName": "EEF", "dateReserved": "2026-10-10T13:00:02.352Z", "datePublished": "2026-10-12T15:29:19.117Z", "dateUpdated": "2026-10-12T15:29:19.117Z" }, "containers": { "cna": { "affected": [ { "cpes": [ "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*" ], "defaultStatus": "unaffected", "modules": [ "'Elixir.Hex.HTTP'", "'Elixir.Hex.SCM'", "'Elixir.Mix.Tasks.Hex.Docs'" ], "packageName": "hex", "packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex&vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git", "product": "hex", "programFiles": [ "lib/hex/http.ex", "lib/hex/scm.ex", "lib/mix/tasks/hex.docs.ex" ], "programRoutines": [ { "name": "'Elixir.Hex.HTTP':request/5" }, { "name": "'Elixir.Hex.SCM':fetch/3" }, { "name": "'Elixir.Hex.SCM':update/1" }, { "name": "'Elixir.Mix.Tasks.Hex.Docs':run/1" } ], "repo": "https://github.com/hexpm/hex", "vendor": "hexpm", "versions": [ { "lessThan": "2.5.2", "status": "affected", "version": "0.11.2", "versionType": "semver" } ] }, { "collectionURL": "https://github.com", "cpes": [ "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*" ], "defaultStatus": "unaffected", "modules": [ "'Elixir.Hex.HTTP'", "'Elixir.Hex.SCM'", "'Elixir.Mix.Tasks.Hex.Docs'" ], "packageName": "hexpm/hex", "packageURL": "pkg:github/hexpm/hex", "product": "hex", "programFiles": [ "lib/hex/http.ex", "lib/hex/scm.ex", "lib/mix/tasks/hex.docs.ex" ], "programRoutines": [ { "name": "'Elixir.Hex.HTTP':request/5" }, { "name": "'Elixir.Hex.SCM':fetch/3" }, { "name": "'Elixir.Hex.SCM':update/1" }, { "name": "'Elixir.Mix.Tasks.Hex.Docs':run/1" } ], "repo": "https://github.com/hexpm/hex", "vendor": "hexpm", "versions": [ { "changes": [ { "at": "bb5189eade682c5fc3f98d9725973a7beb4e1deb", "status": "unaffected" }, { "at": "08956100a8283ba500a123e5539ac64d5d7e91d8", "status": "unaffected" } ], "lessThan": "*", "status": "affected", "version": "51ea632424fb6ea8765148f96dc25dfeb526b1f8", "versionType": "git" } ] } ], "configurations": [ { "lang": "en", "supportingMedia": [ { "base64": false, "type": "text/html", "value": "
Affects users who fetch packages or docs through a mirror set with HEX_MIRROR_URL, HEX_MIRROR or mirror_url. Fetches from repo.hex.pm over HTTPS aren't affected unless TLS verification is broken. A trusted mirror (HEX_TRUSTED_MIRROR_URL) or a self-hosted repository can do the same, but Hex already trusts those with credentials.
Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex allows an untrusted package mirror to exhaust the memory or disk space of the machine running mix deps.get, mix deps.update, mix hex.package fetch or mix hex.docs fetch.
Hex treats a mirror configured with HEX_MIRROR_URL, HEX_MIRROR or mirror_url as untrusted: it doesn't send credentials to it, and it checks registry signatures and package checksums that the mirror can't forge. Several response paths consume memory or disk in proportion to what the mirror sends before any of those checks run.
Hex.HTTP.request/5 reads the whole response body into memory and decompresses it with :zlib.gunzip/1 whenever the response's Content-Encoding contains gzip, with no limit on the compressed or decompressed size. Hex doesn't ask for compressed responses, so the server decides whether this step runs. It applies to registry, package, docs and API responses.
Package tarballs have no download size limit. Hex 2.5.1 holds the whole tarball in memory and writes it to the cache, and the development branch streams it to a temporary file; the 16 MiB tarball limit is checked only when the file is unpacked. The tarball is then unpacked, into deps/<app> in 2.5.1 and into a staging directory on the development branch, before its checksums are compared with the registry.
mix hex.docs fetch, and mix hex.docs offline when docs are missing, write the docs tarball to disk and extract it with :mix_hex_erl_tar.extract/2 without the 16 MiB compressed and 128 MiB decompressed limits that mix_hex_tarball:unpack_docs/3 applies. The signed registry doesn't cover docs tarballs, so nothing checks them.
Registry responses are also decompressed before their signature is checked, in hex_core code that Hex vendors; that part is tracked in https://github.com/hexpm/hex_core/security/advisories/GHSA-7c4r-cgf9-9jmq.
\nThis issue affects hex: from 0.11.2 before 2.5.2.
" }, { "base64": false, "type": "text/markdown", "value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex allows an untrusted package mirror to exhaust the memory or disk space of the machine running `mix deps.get`, `mix deps.update`, `mix hex.package fetch` or `mix hex.docs fetch`.\n\nHex treats a mirror configured with `HEX_MIRROR_URL`, `HEX_MIRROR` or `mirror_url` as untrusted: it doesn't send credentials to it, and it checks registry signatures and package checksums that the mirror can't forge. Several response paths consume memory or disk in proportion to what the mirror sends before any of those checks run.\n\n`Hex.HTTP.request/5` reads the whole response body into memory and decompresses it with `:zlib.gunzip/1` whenever the response's `Content-Encoding` contains `gzip`, with no limit on the compressed or decompressed size. Hex doesn't ask for compressed responses, so the server decides whether this step runs. It applies to registry, package, docs and API responses.\n\nPackage tarballs have no download size limit. Hex 2.5.1 holds the whole tarball in memory and writes it to the cache, and the development branch streams it to a temporary file; the 16 MiB tarball limit is checked only when the file is unpacked. The tarball is then unpacked, into `deps/A mirror operator, or an attacker who controls the mirror's responses, can make Hex allocate memory or write data far larger than what it sent: in tests against Hex main 851b938, a 65 KB gzip-encoded response decompressed to 64 MiB in memory, a 17 MiB tarball was downloaded in full despite the 16 MiB limit, and a 131 KB docs tarball wrote 129 MiB to disk. Larger responses stop the Erlang VM running Mix or fill the disk, failing builds and CI jobs, and a full disk affects other processes on the machine. No package code has to run, and the mirror doesn't need the repository's signing key.
" }, { "base64": false, "type": "text/markdown", "value": "A mirror operator, or an attacker who controls the mirror's responses, can make Hex allocate memory or write data far larger than what it sent: in tests against Hex main 851b938, a 65 KB gzip-encoded response decompressed to 64 MiB in memory, a 17 MiB tarball was downloaded in full despite the 16 MiB limit, and a 131 KB docs tarball wrote 129 MiB to disk. Larger responses stop the Erlang VM running Mix or fill the disk, failing builds and CI jobs, and a full disk affects other processes on the machine. No package code has to run, and the mirror doesn't need the repository's signing key." } ], "value": "A mirror operator, or an attacker who controls the mirror's responses, can make Hex allocate memory or write data far larger than what it sent: in tests against Hex main 851b938, a 65 KB gzip-encoded response decompressed to 64 MiB in memory, a 17 MiB tarball was downloaded in full despite the 16 MiB limit, and a 131 KB docs tarball wrote 129 MiB to disk. Larger responses stop the Erlang VM running Mix or fill the disk, failing builds and CI jobs, and a full disk affects other processes on the machine. No package code has to run, and the mirror doesn't need the repository's signing key." } ] } ], "metrics": [ { "cvssV4_0": { "Automatable": "NOT_DEFINED", "Recovery": "NOT_DEFINED", "Safety": "NOT_DEFINED", "attackComplexity": "LOW", "attackRequirements": "PRESENT", "attackVector": "NETWORK", "baseScore": 2.1, "baseSeverity": "LOW", "privilegesRequired": "NONE", "providerUrgency": "NOT_DEFINED", "subAvailabilityImpact": "NONE", "subConfidentialityImpact": "NONE", "subIntegrityImpact": "NONE", "userInteraction": "ACTIVE", "valueDensity": "NOT_DEFINED", "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N", "version": "4.0", "vulnAvailabilityImpact": "LOW", "vulnConfidentialityImpact": "NONE", "vulnIntegrityImpact": "NONE", "vulnerabilityResponseEffort": "NOT_DEFINED" }, "format": "CVSS", "scenarios": [ { "lang": "en", "value": "GENERAL" } ] } ], "problemTypes": [ { "descriptions": [ { "cweId": "CWE-409", "description": "CWE-409 Improper Handling of Highly Compressed Data (Data Amplification)", "lang": "en", "type": "CWE" } ] }, { "descriptions": [ { "cweId": "CWE-770", "description": "CWE-770 Allocation of Resources Without Limits or Throttling", "lang": "en", "type": "CWE" } ] } ], "providerMetadata": { "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db", "shortName": "EEF", "dateUpdated": "2026-10-12T15:29:19.117Z" }, "references": [ { "name": "GitHub Advisory", "tags": [ "related", "vendor-advisory" ], "url": "https://github.com/hexpm/hex/security/advisories/GHSA-gf42-pg2f-63jp" }, { "name": "EEF CNA record for CVE-2026-96618", "tags": [ "related" ], "url": "https://cna.erlef.org/cves/CVE-2026-96618.html" }, { "name": "OSV record EEF-CVE-2026-96618", "tags": [ "related" ], "url": "https://osv.dev/vulnerability/EEF-CVE-2026-96618" }, { "name": "Introducing commit 51ea632 in hexpm/hex", "tags": [ "related" ], "url": "https://github.com/hexpm/hex/commit/51ea632424fb6ea8765148f96dc25dfeb526b1f8" }, { "name": "Fix commit bb5189e in hexpm/hex", "tags": [ "patch" ], "url": "https://github.com/hexpm/hex/commit/bb5189eade682c5fc3f98d9725973a7beb4e1deb" }, { "name": "Fix commit 0895610 in hexpm/hex", "tags": [ "patch" ], "url": "https://github.com/hexpm/hex/commit/08956100a8283ba500a123e5539ac64d5d7e91d8" } ], "solutions": [ { "lang": "en", "supportingMedia": [ { "base64": false, "type": "text/html", "value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. Hex then stops downloading a registry file, package, docs tarball or other file from a repository or mirror once it's larger than 16 MiB, including after decompressing a gzip-encoded response, and checks packages against the registry checksum before writing them to the package cache or unpacking a cached copy.
Fetch from repo.hex.pm or a mirror you operate. Running Mix under operating system memory and disk limits, such as container limits, confines the effect to that container.
" }, { "base64": false, "type": "text/markdown", "value": "Fetch from repo.hex.pm or a mirror you operate. Running Mix under operating system memory and disk limits, such as container limits, confines the effect to that container." } ], "value": "Fetch from repo.hex.pm or a mirror you operate. Running Mix under operating system memory and disk limits, such as container limits, confines the effect to that container." } ], "x_proofOfConcept": [ { "lang": "en", "supportingMedia": [ { "base64": false, "type": "text/html", "value": "Content-Encoding: gzip. Hex.HTTP.request(:get, url, %{}, nil) returns a 67,108,864-byte body; peak RSS was 220 MiB, against 91 MiB for an uncompressed control.Hex.Repo.get_tarball/3 returns all of it, and only the following unpack fails with {:tarball, :too_big}.HEX_MIRROR_URL to the server. mix hex.docs fetch probe 1.0.0 writes the 129 MiB file and reports success.Hex.HTTP.request/5 calls :httpc.request/5 with body_format: :binary, so the whole body is held in memory, and then calls :zlib.gunzip/1 when the content-encoding header contains gzip. Hex doesn't send Accept-Encoding.mirror_url is set, Hex.Repo sends registry, tarball and docs requests to the mirror and marks it untrusted, so no credentials are sent to it.Hex.SCM.fetch/3 writes the body returned by Hex.Repo.get_tarball/3 to the cache. Hex.SCM.update/1 deletes deps/<app>, unpacks the tarball into it with Hex.Tar.unpack!/2 and compares the inner and outer checksums afterwards. Development branch: Hex.Repo.get_tarball_to_file/4 streams the tarball to a temporary file without counting bytes; the file is renamed into the cache and unpacked into a staging directory before the checksums are compared.Mix.Tasks.Hex.Docs writes the body returned by Hex.Repo.get_docs/3 to disk and calls :mix_hex_erl_tar.extract/2 with [:compressed, cwd: ...] and no max_size, while mix_hex_tarball:unpack_docs/3 enforces a 16 MiB compressed and 128 MiB decompressed limit.