Refine your search
19 vulnerabilities found for by hexpm
CVE-2026-96596 (GCVE-0-2026-96596)
Vulnerability from cvelistv5
Published
2026-10-14 16:56
Modified
2026-10-22 07:08
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-835 - Loop with Unreachable Exit Condition ('Infinite Loop')
Summary
Loop with Unreachable Exit Condition ('Infinite Loop') vulnerability in Erlang OTP stdlib (erl_tar module), hexpm hex_core (hex_erl_tar module), hexpm hex (mix_hex_erl_tar module) and erlang rebar3 (r3_hex_erl_tar module) allows an attacker who supplies a tar archive to cause a denial of service via a crafted GNU sparse member.
When erl_tar:extract/1,2 writes a member to disk, erl_tar:stream_to_file_loop/3 reads it in chunks of 65536 bytes by default. For a GNU sparse member whose sparse map ends before its real size, erl_tar:do_sparse_read/3 matches its trailing-hole clause before its zero-length clause. After the chunk is used up, the trailing-hole clause reads zero bytes, does not advance the position, and recurses forever. A 2048-byte archive keeps the extracting process and one scheduler busy until the process is killed. The {max_size, N} option prevents this only when N is below the real size of the member. Extraction with the memory option is not affected.
hex_core ships its own copy of erl_tar as hex_erl_tar, and Hex and rebar3 ship hex_core with prefixed module names (mix_hex_erl_tar and r3_hex_erl_tar). These copies contain the same code and do not call stdlib's erl_tar, so they are affected on any OTP version. OTP 28 is not affected, but an application running on OTP 28 is affected if it extracts archives through an affected version of hex_core, Hex or rebar3.
This issue affects OTP from OTPĀ 29.0 before OTPĀ 29.1.2, corresponding to rebar3 from 3.27.1 before 3.28.0, and stdlib from 8.0 before 8.1.1, and hex from 2.4.2 before 2.5.3; hex_core: from 0.14.0 before 0.20.1.
References
Impacted products
| Vendor | Product | Version | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| erlang | rebar3 |
Version: 3.27.1 ≤ cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:* |
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-96596",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-16T20:07:46.754736Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-16T20:08:17.542Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"r3_hex_erl_tar",
"r3_hex_tarball"
],
"packageName": "rebar3",
"packageURL": "pkg:otp/rebar3?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Frebar3\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Frebar3.git",
"product": "rebar3",
"programFiles": [
"apps/rebar/src/vendored/r3_hex_erl_tar.erl",
"apps/rebar/src/vendored/r3_hex_tarball.erl"
],
"programRoutines": [
{
"name": "r3_hex_erl_tar:do_sparse_read/3"
},
{
"name": "r3_hex_erl_tar:stream_to_file_loop/3"
},
{
"name": "r3_hex_erl_tar:extract/1"
},
{
"name": "r3_hex_erl_tar:extract/2"
},
{
"name": "r3_hex_tarball:unpack/2"
},
{
"name": "r3_hex_tarball:unpack/3"
},
{
"name": "r3_hex_tarball:unpack_docs/2"
},
{
"name": "r3_hex_tarball:unpack_docs/3"
}
],
"repo": "https://github.com/erlang/rebar3",
"vendor": "erlang",
"versions": [
{
"lessThan": "3.28.0",
"status": "affected",
"version": "3.27.1",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"r3_hex_erl_tar",
"r3_hex_tarball"
],
"packageName": "erlang/rebar3",
"packageURL": "pkg:github/erlang/rebar3",
"product": "rebar3",
"programFiles": [
"apps/rebar/src/vendored/r3_hex_erl_tar.erl",
"apps/rebar/src/vendored/r3_hex_tarball.erl"
],
"programRoutines": [
{
"name": "r3_hex_erl_tar:do_sparse_read/3"
},
{
"name": "r3_hex_erl_tar:stream_to_file_loop/3"
},
{
"name": "r3_hex_erl_tar:extract/1"
},
{
"name": "r3_hex_erl_tar:extract/2"
},
{
"name": "r3_hex_tarball:unpack/2"
},
{
"name": "r3_hex_tarball:unpack/3"
},
{
"name": "r3_hex_tarball:unpack_docs/2"
},
{
"name": "r3_hex_tarball:unpack_docs/3"
}
],
"repo": "https://github.com/erlang/rebar3",
"vendor": "erlang",
"versions": [
{
"lessThan": "310e2ec8c7dd3df05d67af2b7fae23f30fc523c4",
"status": "affected",
"version": "23dbdb5d1f87f56902aef296c88a20049cd0c6e4",
"versionType": "git"
}
]
},
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"hex_erl_tar",
"hex_tarball"
],
"packageName": "hex_core",
"packageURL": "pkg:hex/hex_core",
"product": "hex_core",
"programFiles": [
"src/hex_erl_tar.erl",
"src/hex_tarball.erl"
],
"programRoutines": [
{
"name": "hex_erl_tar:do_sparse_read/3"
},
{
"name": "hex_erl_tar:stream_to_file_loop/3"
},
{
"name": "hex_erl_tar:extract/1"
},
{
"name": "hex_erl_tar:extract/2"
},
{
"name": "hex_tarball:unpack/2"
},
{
"name": "hex_tarball:unpack/3"
},
{
"name": "hex_tarball:unpack_docs/2"
},
{
"name": "hex_tarball:unpack_docs/3"
}
],
"repo": "https://github.com/hexpm/hex_core",
"vendor": "hexpm",
"versions": [
{
"lessThan": "0.20.1",
"status": "affected",
"version": "0.14.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"hex_erl_tar",
"hex_tarball"
],
"packageName": "hexpm/hex_core",
"packageURL": "pkg:github/hexpm/hex_core",
"product": "hex_core",
"programFiles": [
"src/hex_erl_tar.erl",
"src/hex_tarball.erl"
],
"programRoutines": [
{
"name": "hex_erl_tar:do_sparse_read/3"
},
{
"name": "hex_erl_tar:stream_to_file_loop/3"
},
{
"name": "hex_erl_tar:extract/1"
},
{
"name": "hex_erl_tar:extract/2"
},
{
"name": "hex_tarball:unpack/2"
},
{
"name": "hex_tarball:unpack/3"
},
{
"name": "hex_tarball:unpack_docs/2"
},
{
"name": "hex_tarball:unpack_docs/3"
}
],
"repo": "https://github.com/hexpm/hex_core",
"vendor": "hexpm",
"versions": [
{
"lessThan": "a2ece683e6e88a3ec95ad20b09aca426e08b372e",
"status": "affected",
"version": "13bb9fbba418a406af614790b8d5cd458259ee70",
"versionType": "git"
}
]
},
{
"cpes": [
"cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"erl_tar"
],
"packageName": "otp",
"packageURL": "pkg:software-id/erlang.org/otp",
"product": "OTP",
"programFiles": [
"lib/stdlib/src/erl_tar.erl"
],
"programRoutines": [
{
"name": "erl_tar:do_sparse_read/3"
},
{
"name": "erl_tar:stream_to_file_loop/3"
},
{
"name": "erl_tar:extract/1"
},
{
"name": "erl_tar:extract/2"
}
],
"vendor": "Erlang",
"versions": [
{
"lessThan": "29.1.2",
"status": "affected",
"version": "29.0",
"versionType": "otp"
}
]
},
{
"cpes": [
"cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"erl_tar"
],
"packageName": "stdlib",
"packageURL": "pkg:otp/stdlib?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Fotp\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Fotp.git",
"product": "OTP",
"programFiles": [
"src/erl_tar.erl"
],
"programRoutines": [
{
"name": "erl_tar:do_sparse_read/3"
},
{
"name": "erl_tar:stream_to_file_loop/3"
},
{
"name": "erl_tar:extract/1"
},
{
"name": "erl_tar:extract/2"
}
],
"repo": "https://github.com/erlang/otp",
"vendor": "Erlang",
"versions": [
{
"lessThan": "8.1.1",
"status": "affected",
"version": "8.0",
"versionType": "otp"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"erl_tar"
],
"packageName": "erlang/otp",
"packageURL": "pkg:github/erlang/otp",
"product": "OTP",
"programFiles": [
"lib/stdlib/src/erl_tar.erl"
],
"programRoutines": [
{
"name": "erl_tar:do_sparse_read/3"
},
{
"name": "erl_tar:stream_to_file_loop/3"
},
{
"name": "erl_tar:extract/1"
},
{
"name": "erl_tar:extract/2"
}
],
"repo": "https://github.com/erlang/otp",
"vendor": "Erlang",
"versions": [
{
"lessThan": "3086307f311b78b38644a7787a3aca6a3f95a9d5",
"status": "affected",
"version": "fbf2c7194f6fce4b7b66dc0bcaa4f6b4b85e088f",
"versionType": "git"
}
]
},
{
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"mix_hex_erl_tar",
"mix_hex_tarball"
],
"packageName": "hex",
"packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
"product": "hex",
"programFiles": [
"src/mix_hex_erl_tar.erl",
"src/mix_hex_tarball.erl"
],
"programRoutines": [
{
"name": "mix_hex_erl_tar:do_sparse_read/3"
},
{
"name": "mix_hex_erl_tar:stream_to_file_loop/3"
},
{
"name": "mix_hex_erl_tar:extract/1"
},
{
"name": "mix_hex_erl_tar:extract/2"
},
{
"name": "mix_hex_tarball:unpack/2"
},
{
"name": "mix_hex_tarball:unpack/3"
},
{
"name": "mix_hex_tarball:unpack_docs/2"
},
{
"name": "mix_hex_tarball:unpack_docs/3"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2.5.3",
"status": "affected",
"version": "2.4.2",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"mix_hex_erl_tar",
"mix_hex_tarball"
],
"packageName": "hexpm/hex",
"packageURL": "pkg:github/hexpm/hex",
"product": "hex",
"programFiles": [
"src/mix_hex_erl_tar.erl",
"src/mix_hex_tarball.erl"
],
"programRoutines": [
{
"name": "mix_hex_erl_tar:do_sparse_read/3"
},
{
"name": "mix_hex_erl_tar:stream_to_file_loop/3"
},
{
"name": "mix_hex_erl_tar:extract/1"
},
{
"name": "mix_hex_erl_tar:extract/2"
},
{
"name": "mix_hex_tarball:unpack/2"
},
{
"name": "mix_hex_tarball:unpack/3"
},
{
"name": "mix_hex_tarball:unpack_docs/2"
},
{
"name": "mix_hex_tarball:unpack_docs/3"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"changes": [
{
"at": "8e1067131a13949eff30623f31648a945ffd5418",
"status": "unaffected"
},
{
"at": "7847c23d09ab52113614c3718e1e7d368743da5d",
"status": "unaffected"
}
],
"lessThan": "*",
"status": "affected",
"version": "5543a1919c709d8eb2b317f62aad76ca1eed375c",
"versionType": "git"
}
]
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*",
"versionEndExcluding": "3.28.0",
"versionStartIncluding": "3.27.1",
"vulnerable": true
},
{
"criteria": "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*",
"versionEndExcluding": "0.20.1",
"versionStartIncluding": "0.14.0",
"vulnerable": true
},
{
"criteria": "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*",
"versionEndExcluding": "29.1.2",
"versionStartIncluding": "29.0",
"vulnerable": true
},
{
"criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
"versionEndExcluding": "2.5.3",
"versionStartIncluding": "2.4.2",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "reporter",
"value": "Eric Meadows-J\u00f6nsson"
}
],
"dateAssigned": "2026-10-07T13:11:38.000Z",
"datePublic": "2026-10-14T16:56:18.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eLoop with Unreachable Exit Condition (\u0027Infinite Loop\u0027) vulnerability in Erlang OTP stdlib (\u003ccode\u003eerl_tar\u003c/code\u003e module), hexpm hex_core (\u003ccode\u003ehex_erl_tar\u003c/code\u003e module), hexpm hex (\u003ccode\u003emix_hex_erl_tar\u003c/code\u003e module) and erlang rebar3 (\u003ccode\u003er3_hex_erl_tar\u003c/code\u003e module) allows an attacker who supplies a tar archive to cause a denial of service via a crafted GNU sparse member.\u003c/p\u003e\n\u003cp\u003eWhen \u003ccode\u003eerl_tar:extract/1,2\u003c/code\u003e writes a member to disk, \u003ccode\u003eerl_tar:stream_to_file_loop/3\u003c/code\u003e reads it in chunks of 65536 bytes by default. For a GNU sparse member whose sparse map ends before its real size, \u003ccode\u003eerl_tar:do_sparse_read/3\u003c/code\u003e matches its trailing-hole clause before its zero-length clause. After the chunk is used up, the trailing-hole clause reads zero bytes, does not advance the position, and recurses forever. A 2048-byte archive keeps the extracting process and one scheduler busy until the process is killed. The \u003ccode\u003e{max_size, N}\u003c/code\u003e option prevents this only when N is below the real size of the member. Extraction with the \u003ccode\u003ememory\u003c/code\u003e option is not affected.\u003c/p\u003e\n\u003cp\u003ehex_core ships its own copy of \u003ccode\u003eerl_tar\u003c/code\u003e as \u003ccode\u003ehex_erl_tar\u003c/code\u003e, and Hex and rebar3 ship hex_core with prefixed module names (\u003ccode\u003emix_hex_erl_tar\u003c/code\u003e and \u003ccode\u003er3_hex_erl_tar\u003c/code\u003e). These copies contain the same code and do not call stdlib\u0027s \u003ccode\u003eerl_tar\u003c/code\u003e, so they are affected on any OTP version. OTP 28 is not affected, but an application running on OTP 28 is affected if it extracts archives through an affected version of hex_core, Hex or rebar3.\u003c/p\u003e\n\u003cp\u003eThis issue affects OTP from OTP\u0026nbsp;29.0 before OTP\u0026nbsp;29.1.2, corresponding to rebar3 from 3.27.1 before 3.28.0, and stdlib from 8.0 before 8.1.1, and hex from 2.4.2 before 2.5.3; hex_core: from 0.14.0 before 0.20.1.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Loop with Unreachable Exit Condition (\u0027Infinite Loop\u0027) vulnerability in Erlang OTP stdlib (`erl_tar` module), hexpm hex_core (`hex_erl_tar` module), hexpm hex (`mix_hex_erl_tar` module) and erlang rebar3 (`r3_hex_erl_tar` module) allows an attacker who supplies a tar archive to cause a denial of service via a crafted GNU sparse member.\n\nWhen `erl_tar:extract/1,2` writes a member to disk, `erl_tar:stream_to_file_loop/3` reads it in chunks of 65536 bytes by default. For a GNU sparse member whose sparse map ends before its real size, `erl_tar:do_sparse_read/3` matches its trailing-hole clause before its zero-length clause. After the chunk is used up, the trailing-hole clause reads zero bytes, does not advance the position, and recurses forever. A 2048-byte archive keeps the extracting process and one scheduler busy until the process is killed. The `{max_size, N}` option prevents this only when N is below the real size of the member. Extraction with the `memory` option is not affected.\n\nhex_core ships its own copy of `erl_tar` as `hex_erl_tar`, and Hex and rebar3 ship hex_core with prefixed module names (`mix_hex_erl_tar` and `r3_hex_erl_tar`). These copies contain the same code and do not call stdlib\u0027s `erl_tar`, so they are affected on any OTP version. OTP 28 is not affected, but an application running on OTP 28 is affected if it extracts archives through an affected version of hex_core, Hex or rebar3.\n\nThis issue affects OTP from OTP\u00a029.0 before OTP\u00a029.1.2, corresponding to rebar3 from 3.27.1 before 3.28.0, and stdlib from 8.0 before 8.1.1, and hex from 2.4.2 before 2.5.3; hex_core: from 0.14.0 before 0.20.1."
}
],
"value": "Loop with Unreachable Exit Condition (\u0027Infinite Loop\u0027) vulnerability in Erlang OTP stdlib (erl_tar module), hexpm hex_core (hex_erl_tar module), hexpm hex (mix_hex_erl_tar module) and erlang rebar3 (r3_hex_erl_tar module) allows an attacker who supplies a tar archive to cause a denial of service via a crafted GNU sparse member.\n\nWhen erl_tar:extract/1,2 writes a member to disk, erl_tar:stream_to_file_loop/3 reads it in chunks of 65536 bytes by default. For a GNU sparse member whose sparse map ends before its real size, erl_tar:do_sparse_read/3 matches its trailing-hole clause before its zero-length clause. After the chunk is used up, the trailing-hole clause reads zero bytes, does not advance the position, and recurses forever. A 2048-byte archive keeps the extracting process and one scheduler busy until the process is killed. The {max_size, N} option prevents this only when N is below the real size of the member. Extraction with the memory option is not affected.\n\nhex_core ships its own copy of erl_tar as hex_erl_tar, and Hex and rebar3 ship hex_core with prefixed module names (mix_hex_erl_tar and r3_hex_erl_tar). These copies contain the same code and do not call stdlib\u0027s erl_tar, so they are affected on any OTP version. OTP 28 is not affected, but an application running on OTP 28 is affected if it extracts archives through an affected version of hex_core, Hex or rebar3.\n\nThis issue affects OTP from OTP\u00a029.0 before OTP\u00a029.1.2, corresponding to rebar3 from 3.27.1 before 3.28.0, and stdlib from 8.0 before 8.1.1, and hex from 2.4.2 before 2.5.3; hex_core: from 0.14.0 before 0.20.1."
}
],
"impacts": [
{
"capecId": "CAPEC-130",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAn attacker who can get an application to extract a tar archive to disk keeps the extracting process and one scheduler busy forever. Each further archive occupies another scheduler, until the node stops responding.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "An attacker who can get an application to extract a tar archive to disk keeps the extracting process and one scheduler busy forever. Each further archive occupies another scheduler, until the node stops responding."
}
],
"value": "An attacker who can get an application to extract a tar archive to disk keeps the extracting process and one scheduler busy forever. Each further archive occupies another scheduler, until the node stops responding."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "LOCAL",
"baseScore": 5.9,
"baseSeverity": "MEDIUM",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-835",
"description": "CWE-835 Loop with Unreachable Exit Condition (\u0027Infinite Loop\u0027)",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-22T07:08:09.364Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/erlang/otp/security/advisories/GHSA-phm5-m6cj-5382"
},
{
"name": "EEF CNA record for CVE-2026-96596",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-96596.html"
},
{
"name": "OSV record EEF-CVE-2026-96596",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-96596"
},
{
"name": "Erlang/OTP version ordering",
"tags": [
"x_version-scheme"
],
"url": "https://www.erlang.org/doc/system/versions.html#order-of-versions"
},
{
"name": "Introducing commit 23dbdb5 in erlang/rebar3",
"tags": [
"related"
],
"url": "https://github.com/erlang/rebar3/commit/23dbdb5d1f87f56902aef296c88a20049cd0c6e4"
},
{
"name": "Fix commit 310e2ec in erlang/rebar3",
"tags": [
"patch"
],
"url": "https://github.com/erlang/rebar3/commit/310e2ec8c7dd3df05d67af2b7fae23f30fc523c4"
},
{
"name": "Introducing commit 13bb9fb in hexpm/hex_core",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hex_core/commit/13bb9fbba418a406af614790b8d5cd458259ee70"
},
{
"name": "Fix commit a2ece68 in hexpm/hex_core",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex_core/commit/a2ece683e6e88a3ec95ad20b09aca426e08b372e"
},
{
"name": "Introducing commit fbf2c71 in erlang/otp",
"tags": [
"related"
],
"url": "https://github.com/erlang/otp/commit/fbf2c7194f6fce4b7b66dc0bcaa4f6b4b85e088f"
},
{
"name": "Fix commit 3086307 in erlang/otp",
"tags": [
"patch"
],
"url": "https://github.com/erlang/otp/commit/3086307f311b78b38644a7787a3aca6a3f95a9d5"
},
{
"name": "Introducing commit 5543a19 in hexpm/hex",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hex/commit/5543a1919c709d8eb2b317f62aad76ca1eed375c"
},
{
"name": "Fix commit 8e10671 in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/8e1067131a13949eff30623f31648a945ffd5418"
},
{
"name": "Fix commit 7847c23 in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/7847c23d09ab52113614c3718e1e7d368743da5d"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "erl_tar:extract/2 loops forever on a GNU sparse member with a trailing hole",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003ePass \u003ccode\u003e{chunks, N}\u003c/code\u003e together with \u003ccode\u003e{max_size, N}\u003c/code\u003e, with the same value N, to \u003ccode\u003eerl_tar:extract/2\u003c/code\u003e. Extraction then rejects any member larger than N, and reads each remaining member in one call, so the loop cannot occur.\u003c/p\u003e\n\u003cp\u003eAlternatively, extract with the \u003ccode\u003ememory\u003c/code\u003e option and write the returned files to disk in the application.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Pass `{chunks, N}` together with `{max_size, N}`, with the same value N, to `erl_tar:extract/2`. Extraction then rejects any member larger than N, and reads each remaining member in one call, so the loop cannot occur.\n\nAlternatively, extract with the `memory` option and write the returned files to disk in the application."
}
],
"value": "Pass {chunks, N} together with {max_size, N}, with the same value N, to erl_tar:extract/2. Extraction then rejects any member larger than N, and reads each remaining member in one call, so the loop cannot occur.\n\nAlternatively, extract with the memory option and write the returned files to disk in the application."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eBuild a 2048-byte tar that holds one GNU sparse member (typeflag \u003ccode\u003eS\u003c/code\u003e). Give it a stored size of 4 bytes, one sparse map entry of 4 bytes at offset 0, and a real size of 1048576 bytes. Follow the 512-byte header with a 512-byte block that starts with the 4 data bytes, then 1024 zero bytes.\u003c/li\u003e\n\u003cli\u003eIn a separate process, call \u003ccode\u003eerl_tar:extract({binary, Tar}, [{cwd, Dir}])\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eThe call does not return. After 5 seconds the process has used more than 10^9 reductions. On OTP 28 the same call returns \u003ccode\u003eok\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Build a 2048-byte tar that holds one GNU sparse member (typeflag `S`). Give it a stored size of 4 bytes, one sparse map entry of 4 bytes at offset 0, and a real size of 1048576 bytes. Follow the 512-byte header with a 512-byte block that starts with the 4 data bytes, then 1024 zero bytes.\n2. In a separate process, call `erl_tar:extract({binary, Tar}, [{cwd, Dir}])`.\n3. The call does not return. After 5 seconds the process has used more than 10^9 reductions. On OTP 28 the same call returns `ok`."
}
],
"value": "* Build a 2048-byte tar that holds one GNU sparse member (typeflag S). Give it a stored size of 4 bytes, one sparse map entry of 4 bytes at offset 0, and a real size of 1048576 bytes. Follow the 512-byte header with a 512-byte block that starts with the 4 data bytes, then 1024 zero bytes.\n* In a separate process, call erl_tar:extract({binary, Tar}, [{cwd, Dir}]).\n* The call does not return. After 5 seconds the process has used more than 10^9 reductions. On OTP 28 the same call returns ok."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e1. Chunked extraction.\u003c/strong\u003e \u003ccode\u003eerl_tar:extract/2\u003c/code\u003e writes each member to disk through \u003ccode\u003estream_to_file_loop/3\u003c/code\u003e. This function calls \u003ccode\u003edo_read(Reader, ChunkSize)\u003c/code\u003e until the \u003ccode\u003enum_bytes\u003c/code\u003e field of the reader is 0. The default chunk size is 65536 bytes.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Clause order in \u003ccode\u003edo_sparse_read/3\u003c/code\u003e.\u003c/strong\u003e For a sparse member, \u003ccode\u003edo_read/2\u003c/code\u003e calls \u003ccode\u003edo_sparse_read/3\u003c/code\u003e. The clause for a trailing hole matches when \u003ccode\u003esparse_map\u003c/code\u003e is \u003ccode\u003e[]\u003c/code\u003e and \u003ccode\u003ePos \u0026lt; Size\u003c/code\u003e. It comes before the clause that returns when \u003ccode\u003eLen\u003c/code\u003e is 0.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Unbounded recursion.\u003c/strong\u003e The member is a GNU sparse member (typeflag \u003ccode\u003eS\u003c/code\u003e) whose real size is larger than one chunk and whose sparse map ends before the real size. After the data fragments, the trailing-hole clause reads the rest of the chunk and recurses with \u003ccode\u003eLen = 0\u003c/code\u003e. The same clause matches again, and \u003ccode\u003eread_sparse_hole/3\u003c/code\u003e returns an empty binary and leaves \u003ccode\u003ePos\u003c/code\u003e unchanged. The zero-length clause is never reached, so the call never returns.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4. No mitigation from \u003ccode\u003emax_size\u003c/code\u003e.\u003c/strong\u003e For a sparse member, \u003ccode\u003e{max_size, N}\u003c/code\u003e is checked against the real size from the header. It stops the loop only when N is below that size. \u003ccode\u003eerl_tar:table/2\u003c/code\u003e lists the member as \u003ccode\u003eregular\u003c/code\u003e, so a caller cannot skip sparse members by type.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "**1. Chunked extraction.** `erl_tar:extract/2` writes each member to disk through `stream_to_file_loop/3`. This function calls `do_read(Reader, ChunkSize)` until the `num_bytes` field of the reader is 0. The default chunk size is 65536 bytes.\n\n**2. Clause order in `do_sparse_read/3`.** For a sparse member, `do_read/2` calls `do_sparse_read/3`. The clause for a trailing hole matches when `sparse_map` is `[]` and `Pos \u003c Size`. It comes before the clause that returns when `Len` is 0.\n\n**3. Unbounded recursion.** The member is a GNU sparse member (typeflag `S`) whose real size is larger than one chunk and whose sparse map ends before the real size. After the data fragments, the trailing-hole clause reads the rest of the chunk and recurses with `Len = 0`. The same clause matches again, and `read_sparse_hole/3` returns an empty binary and leaves `Pos` unchanged. The zero-length clause is never reached, so the call never returns.\n\n**4. No mitigation from `max_size`.** For a sparse member, `{max_size, N}` is checked against the real size from the header. It stops the loop only when N is below that size. `erl_tar:table/2` lists the member as `regular`, so a caller cannot skip sparse members by type."
}
],
"value": "1. Chunked extraction. erl_tar:extract/2 writes each member to disk through stream_to_file_loop/3. This function calls do_read(Reader, ChunkSize) until the num_bytes field of the reader is 0. The default chunk size is 65536 bytes.\n\n2. Clause order in do_sparse_read/3. For a sparse member, do_read/2 calls do_sparse_read/3. The clause for a trailing hole matches when sparse_map is [] and Pos \u003c Size. It comes before the clause that returns when Len is 0.\n\n3. Unbounded recursion. The member is a GNU sparse member (typeflag S) whose real size is larger than one chunk and whose sparse map ends before the real size. After the data fragments, the trailing-hole clause reads the rest of the chunk and recurses with Len = 0. The same clause matches again, and read_sparse_hole/3 returns an empty binary and leaves Pos unchanged. The zero-length clause is never reached, so the call never returns.\n\n4. No mitigation from max_size. For a sparse member, {max_size, N} is checked against the real size from the header. It stops the loop only when N is below that size. erl_tar:table/2 lists the member as regular, so a caller cannot skip sparse members by type."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-96596",
"datePublished": "2026-10-14T16:56:18.530Z",
"dateReserved": "2026-09-27T12:00:01.540Z",
"dateUpdated": "2026-10-22T07:08:09.364Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-105274 (GCVE-0-2026-105274)
Vulnerability from cvelistv5
Published
2026-10-12 16:43
Modified
2026-10-15 18:05
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-409 - Improper Handling of Highly Compressed Data (Data Amplification)
Summary
Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex_core allows an untrusted repository mirror, or a server answering hex_core's API requests, to exhaust the memory of the Erlang VM running hex_core. The same code is vendored in Hex (mix_hex_repo, mix_hex_api) and rebar3 (r3_hex_repo, r3_hex_api).
hex_repo:get_names/1, hex_repo:get_versions/1, hex_repo:get_package/2 and hex_repo:get_policy/2 decompress the registry response with zlib:gunzip/1 and decode the signed envelope before verifying its signature. A mirror doesn't have the repository's private key, but it chooses the compressed bytes, so a small response decompresses into an envelope of any size before the signature check rejects it.
hex_api decodes responses with the application/vnd.hex+erlang content type through hex_safe_binary_to_term:safe_binary_to_term/1, which calls binary_to_term(Binary, [safe]). The safe option prevents atom creation, but it accepts compressed terms and doesn't limit the size of the decoded term, and the check that rejects functions and ports runs after the term has been built. The fix for CVE-2026-21619 added the safe option and that check without bounding the decoded size.
This issue affects rebar3: from 3.9.1 before 3.27.3; hex: from 2.1.0 before 2.5.2; hex_core: from 0.1.0 before 0.20.0.
References
| URL | Tags | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|||||||||||||||||||||||||||||
Impacted products
| Vendor | Product | Version | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| erlang | rebar3 |
Version: 3.9.1 ≤ cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:* |
|||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-105274",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-15T17:51:41.556015Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-15T18:05:12.446Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"r3_hex_repo",
"r3_hex_api"
],
"packageName": "rebar3",
"packageURL": "pkg:otp/rebar3?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Frebar3\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Frebar3.git",
"product": "rebar3",
"programFiles": [
"apps/rebar/src/vendored/r3_hex_repo.erl",
"apps/rebar/src/vendored/r3_hex_api.erl"
],
"programRoutines": [
{
"name": "r3_hex_repo:get_names/1"
},
{
"name": "r3_hex_repo:get_versions/1"
},
{
"name": "r3_hex_repo:get_package/2"
},
{
"name": "r3_hex_api:request/4"
}
],
"repo": "https://github.com/erlang/rebar3",
"vendor": "erlang",
"versions": [
{
"lessThan": "3.27.3",
"status": "affected",
"version": "3.9.1",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"r3_hex_repo",
"r3_hex_api"
],
"packageName": "erlang/rebar3",
"packageURL": "pkg:github/erlang/rebar3",
"product": "rebar3",
"programFiles": [
"apps/rebar/src/vendored/r3_hex_repo.erl",
"apps/rebar/src/vendored/r3_hex_api.erl"
],
"programRoutines": [
{
"name": "r3_hex_repo:get_names/1"
},
{
"name": "r3_hex_repo:get_versions/1"
},
{
"name": "r3_hex_repo:get_package/2"
},
{
"name": "r3_hex_api:request/4"
}
],
"repo": "https://github.com/erlang/rebar3",
"vendor": "erlang",
"versions": [
{
"lessThan": "360838a22f5b3a88c0358207f2923059bcbb3afb",
"status": "affected",
"version": "209c02ec57c2cc3207ee0174c3af3675b8dc8f79",
"versionType": "git"
}
]
},
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"hex_repo",
"hex_api",
"hex_safe_binary_to_term"
],
"packageName": "hex_core",
"packageURL": "pkg:hex/hex_core",
"product": "hex_core",
"programFiles": [
"src/hex_repo.erl",
"src/hex_api.erl",
"src/hex_safe_binary_to_term.erl"
],
"programRoutines": [
{
"name": "hex_repo:get_names/1"
},
{
"name": "hex_repo:get_versions/1"
},
{
"name": "hex_repo:get_package/2"
},
{
"name": "hex_repo:get_policy/2"
},
{
"name": "hex_api:request/4"
},
{
"name": "hex_safe_binary_to_term:safe_binary_to_term/1"
}
],
"repo": "https://github.com/hexpm/hex_core",
"vendor": "hexpm",
"versions": [
{
"lessThan": "0.20.0",
"status": "affected",
"version": "0.1.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"hex_repo",
"hex_api",
"hex_safe_binary_to_term"
],
"packageName": "hexpm/hex_core",
"packageURL": "pkg:github/hexpm/hex_core",
"product": "hex_core",
"programFiles": [
"src/hex_repo.erl",
"src/hex_api.erl",
"src/hex_safe_binary_to_term.erl"
],
"programRoutines": [
{
"name": "hex_repo:get_names/1"
},
{
"name": "hex_repo:get_versions/1"
},
{
"name": "hex_repo:get_package/2"
},
{
"name": "hex_repo:get_policy/2"
},
{
"name": "hex_api:request/4"
},
{
"name": "hex_safe_binary_to_term:safe_binary_to_term/1"
}
],
"repo": "https://github.com/hexpm/hex_core",
"vendor": "hexpm",
"versions": [
{
"lessThan": "ebe25d110717f6a3addba5232cd4a7dc1c76b628",
"status": "affected",
"version": "fbb6d9de472e5edb8d6fb6b32a91cb266137892e",
"versionType": "git"
}
]
},
{
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"mix_hex_repo",
"mix_hex_api"
],
"packageName": "hex",
"packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
"product": "hex",
"programFiles": [
"src/mix_hex_repo.erl",
"src/mix_hex_api.erl"
],
"programRoutines": [
{
"name": "mix_hex_repo:get_names/1"
},
{
"name": "mix_hex_repo:get_versions/1"
},
{
"name": "mix_hex_repo:get_package/2"
},
{
"name": "mix_hex_repo:get_policy/2"
},
{
"name": "mix_hex_api:request/4"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2.5.2",
"status": "affected",
"version": "2.1.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"mix_hex_repo",
"mix_hex_api"
],
"packageName": "hexpm/hex",
"packageURL": "pkg:github/hexpm/hex",
"product": "hex",
"programFiles": [
"src/mix_hex_repo.erl",
"src/mix_hex_api.erl"
],
"programRoutines": [
{
"name": "mix_hex_repo:get_names/1"
},
{
"name": "mix_hex_repo:get_versions/1"
},
{
"name": "mix_hex_repo:get_package/2"
},
{
"name": "mix_hex_repo:get_policy/2"
},
{
"name": "mix_hex_api:request/4"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "d8b60eee97abb119d525306f048969a217d793a5",
"status": "affected",
"version": "671f695ac548ed01f5828c2a739ecaa942b21007",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eRegistry decoding is affected when a repository\u0027s URL points at a mirror or another server the user doesn\u0027t trust with the availability of their builds, for example Hex\u0027s \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e or \u003ccode\u003emirror_url\u003c/code\u003e and rebar3\u0027s \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e. API decoding is affected when the API URL points at such a server or an attacker can alter API responses. Requests to repo.hex.pm and hex.pm over verified HTTPS aren\u0027t affected.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Registry decoding is affected when a repository\u0027s URL points at a mirror or another server the user doesn\u0027t trust with the availability of their builds, for example Hex\u0027s `HEX_MIRROR_URL` or `mirror_url` and rebar3\u0027s `HEX_MIRROR_URL`. API decoding is affected when the API URL points at such a server or an attacker can alter API responses. Requests to repo.hex.pm and hex.pm over verified HTTPS aren\u0027t affected."
}
],
"value": "Registry decoding is affected when a repository\u0027s URL points at a mirror or another server the user doesn\u0027t trust with the availability of their builds, for example Hex\u0027s HEX_MIRROR_URL or mirror_url and rebar3\u0027s HEX_MIRROR_URL. API decoding is affected when the API URL points at such a server or an attacker can alter API responses. Requests to repo.hex.pm and hex.pm over verified HTTPS aren\u0027t affected."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*",
"versionEndExcluding": "3.27.3",
"versionStartIncluding": "3.9.1",
"vulnerable": true
},
{
"criteria": "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*",
"versionEndExcluding": "0.20.0",
"versionStartIncluding": "0.1.0",
"vulnerable": true
},
{
"criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
"versionEndExcluding": "2.5.2",
"versionStartIncluding": "2.1.0",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Peter Ullrich"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "analyst",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-10T13:01:00.000Z",
"datePublic": "2026-10-12T16:43:59.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex_core allows an untrusted repository mirror, or a server answering hex_core\u0027s API requests, to exhaust the memory of the Erlang VM running hex_core. The same code is vendored in Hex (\u003ccode\u003emix_hex_repo\u003c/code\u003e, \u003ccode\u003emix_hex_api\u003c/code\u003e) and rebar3 (\u003ccode\u003er3_hex_repo\u003c/code\u003e, \u003ccode\u003er3_hex_api\u003c/code\u003e).\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ehex_repo:get_names/1\u003c/code\u003e, \u003ccode\u003ehex_repo:get_versions/1\u003c/code\u003e, \u003ccode\u003ehex_repo:get_package/2\u003c/code\u003e and \u003ccode\u003ehex_repo:get_policy/2\u003c/code\u003e decompress the registry response with \u003ccode\u003ezlib:gunzip/1\u003c/code\u003e and decode the signed envelope before verifying its signature. A mirror doesn\u0027t have the repository\u0027s private key, but it chooses the compressed bytes, so a small response decompresses into an envelope of any size before the signature check rejects it.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ehex_api\u003c/code\u003e decodes responses with the \u003ccode\u003eapplication/vnd.hex+erlang\u003c/code\u003e content type through \u003ccode\u003ehex_safe_binary_to_term:safe_binary_to_term/1\u003c/code\u003e, which calls \u003ccode\u003ebinary_to_term(Binary, [safe])\u003c/code\u003e. The \u003ccode\u003esafe\u003c/code\u003e option prevents atom creation, but it accepts compressed terms and doesn\u0027t limit the size of the decoded term, and the check that rejects functions and ports runs after the term has been built. The fix for CVE-2026-21619 added the \u003ccode\u003esafe\u003c/code\u003e option and that check without bounding the decoded size.\u003c/p\u003e\n\u003cp\u003eThis issue affects rebar3: from 3.9.1 before 3.27.3; hex: from 2.1.0 before 2.5.2; hex_core: from 0.1.0 before 0.20.0.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex_core allows an untrusted repository mirror, or a server answering hex_core\u0027s API requests, to exhaust the memory of the Erlang VM running hex_core. The same code is vendored in Hex (`mix_hex_repo`, `mix_hex_api`) and rebar3 (`r3_hex_repo`, `r3_hex_api`).\n\n`hex_repo:get_names/1`, `hex_repo:get_versions/1`, `hex_repo:get_package/2` and `hex_repo:get_policy/2` decompress the registry response with `zlib:gunzip/1` and decode the signed envelope before verifying its signature. A mirror doesn\u0027t have the repository\u0027s private key, but it chooses the compressed bytes, so a small response decompresses into an envelope of any size before the signature check rejects it.\n\n`hex_api` decodes responses with the `application/vnd.hex+erlang` content type through `hex_safe_binary_to_term:safe_binary_to_term/1`, which calls `binary_to_term(Binary, [safe])`. The `safe` option prevents atom creation, but it accepts compressed terms and doesn\u0027t limit the size of the decoded term, and the check that rejects functions and ports runs after the term has been built. The fix for CVE-2026-21619 added the `safe` option and that check without bounding the decoded size.\n\nThis issue affects rebar3: from 3.9.1 before 3.27.3; hex: from 2.1.0 before 2.5.2; hex_core: from 0.1.0 before 0.20.0."
}
],
"value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex_core allows an untrusted repository mirror, or a server answering hex_core\u0027s API requests, to exhaust the memory of the Erlang VM running hex_core. The same code is vendored in Hex (mix_hex_repo, mix_hex_api) and rebar3 (r3_hex_repo, r3_hex_api).\n\nhex_repo:get_names/1, hex_repo:get_versions/1, hex_repo:get_package/2 and hex_repo:get_policy/2 decompress the registry response with zlib:gunzip/1 and decode the signed envelope before verifying its signature. A mirror doesn\u0027t have the repository\u0027s private key, but it chooses the compressed bytes, so a small response decompresses into an envelope of any size before the signature check rejects it.\n\nhex_api decodes responses with the application/vnd.hex+erlang content type through hex_safe_binary_to_term:safe_binary_to_term/1, which calls binary_to_term(Binary, [safe]). The safe option prevents atom creation, but it accepts compressed terms and doesn\u0027t limit the size of the decoded term, and the check that rejects functions and ports runs after the term has been built. The fix for CVE-2026-21619 added the safe option and that check without bounding the decoded size.\n\nThis issue affects rebar3: from 3.9.1 before 3.27.3; hex: from 2.1.0 before 2.5.2; hex_core: from 0.1.0 before 0.20.0."
}
],
"impacts": [
{
"capecId": "CAPEC-130",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eA mirror can make every registry fetch allocate memory far beyond the size of its response: in a test against Hex main 851b938, which vendors this code, a 65 KB registry response decompressed into a 64 MiB envelope before the signature check failed. The API server, or an attacker who can alter its responses, can do the same with API responses: 8 KB of compressed Erlang term format decoded into a list of 4.2 million elements. Large enough responses stop the Erlang VM, which fails the build tool command or the program using hex_core. No package code has to run.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "A mirror can make every registry fetch allocate memory far beyond the size of its response: in a test against Hex main 851b938, which vendors this code, a 65 KB registry response decompressed into a 64 MiB envelope before the signature check failed. The API server, or an attacker who can alter its responses, can do the same with API responses: 8 KB of compressed Erlang term format decoded into a list of 4.2 million elements. Large enough responses stop the Erlang VM, which fails the build tool command or the program using hex_core. No package code has to run."
}
],
"value": "A mirror can make every registry fetch allocate memory far beyond the size of its response: in a test against Hex main 851b938, which vendors this code, a 65 KB registry response decompressed into a 64 MiB envelope before the signature check failed. The API server, or an attacker who can alter its responses, can do the same with API responses: 8 KB of compressed Erlang term format decoded into a list of 4.2 million elements. Large enough responses stop the Erlang VM, which fails the build tool command or the program using hex_core. No package code has to run."
}
]
}
],
"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"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-12T18:16:57.155Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/hexpm/hex_core/security/advisories/GHSA-7c4r-cgf9-9jmq"
},
{
"name": "EEF CNA record for CVE-2026-105274",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-105274.html"
},
{
"name": "OSV record EEF-CVE-2026-105274",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-105274"
},
{
"name": "Introducing commit 209c02e in erlang/rebar3",
"tags": [
"related"
],
"url": "https://github.com/erlang/rebar3/commit/209c02ec57c2cc3207ee0174c3af3675b8dc8f79"
},
{
"name": "Fix commit 360838a in erlang/rebar3",
"tags": [
"patch"
],
"url": "https://github.com/erlang/rebar3/commit/360838a22f5b3a88c0358207f2923059bcbb3afb"
},
{
"name": "Introducing commit fbb6d9d in hexpm/hex_core",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hex_core/commit/fbb6d9de472e5edb8d6fb6b32a91cb266137892e"
},
{
"name": "Fix commit ebe25d1 in hexpm/hex_core",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex_core/commit/ebe25d110717f6a3addba5232cd4a7dc1c76b628"
},
{
"name": "Introducing commit 671f695 in hexpm/hex",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hex/commit/671f695ac548ed01f5828c2a739ecaa942b21007"
},
{
"name": "Fix commit d8b60ee in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/d8b60eee97abb119d525306f048969a217d793a5"
}
],
"solutions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUpgrade hex_core to 0.20.0 or later. Registry resources larger than 16 MiB after decompression are rejected with \u003ccode\u003e{too_big_uncompressed, MaxSize}\u003c/code\u003e, configurable with \u003ccode\u003eregistry_max_uncompressed_size\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eUpgrade Hex to 2.5.2 or later, which vendors hex_core 0.20.0. No rebar3 release vendors hex_core 0.20.0 yet.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Upgrade hex_core to 0.20.0 or later. Registry resources larger than 16 MiB after decompression are rejected with `{too_big_uncompressed, MaxSize}`, configurable with `registry_max_uncompressed_size`.\n\nUpgrade Hex to 2.5.2 or later, which vendors hex_core 0.20.0. No rebar3 release vendors hex_core 0.20.0 yet."
}
],
"value": "Upgrade hex_core to 0.20.0 or later. Registry resources larger than 16 MiB after decompression are rejected with {too_big_uncompressed, MaxSize}, configurable with registry_max_uncompressed_size.\n\nUpgrade Hex to 2.5.2 or later, which vendors hex_core 0.20.0. No rebar3 release vendors hex_core 0.20.0 yet."
}
],
"source": {
"discovery": "UNKNOWN"
},
"title": "hex_core decompresses registry and Erlang term API responses without size limits",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003ePoint repository URLs at repo.hex.pm or a mirror you operate, and the API URL at hex.pm.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Point repository URLs at repo.hex.pm or a mirror you operate, and the API URL at hex.pm."
}
],
"value": "Point repository URLs at repo.hex.pm or a mirror you operate, and the API URL at hex.pm."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003ePoint a repository at a loopback mirror (in Hex, \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e) that answers \u003ccode\u003e/packages/probe\u003c/code\u003e with a 65,268-byte gzip stream decompressing to a 67,108,872-byte signed envelope with a 64 MiB payload and a one-byte signature.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHex.Repo.get_package(\"hexpm\", \"probe\", nil)\u003c/code\u003e, which calls the vendored \u003ccode\u003ehex_repo:get_package/2\u003c/code\u003e, returns \u003ccode\u003e{:error, :bad_signature}\u003c/code\u003e after allocating the envelope. Peak RSS was 234 MiB, against 96 MiB for a 1,030-byte invalid envelope.\u003c/li\u003e\n\u003cli\u003eAnswer an API request with \u003ccode\u003econtent-type: application/vnd.hex+erlang\u003c/code\u003e and 8,178 bytes of compressed Erlang term format encoding a list of 4,194,304 elements. \u003ccode\u003emix_hex_api:get/2\u003c/code\u003e accepts the decoded term, and BEAM memory rises from 58 MiB to 272 MiB.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Point a repository at a loopback mirror (in Hex, `HEX_MIRROR_URL`) that answers `/packages/probe` with a 65,268-byte gzip stream decompressing to a 67,108,872-byte signed envelope with a 64 MiB payload and a one-byte signature.\n2. `Hex.Repo.get_package(\"hexpm\", \"probe\", nil)`, which calls the vendored `hex_repo:get_package/2`, returns `{:error, :bad_signature}` after allocating the envelope. Peak RSS was 234 MiB, against 96 MiB for a 1,030-byte invalid envelope.\n3. Answer an API request with `content-type: application/vnd.hex+erlang` and 8,178 bytes of compressed Erlang term format encoding a list of 4,194,304 elements. `mix_hex_api:get/2` accepts the decoded term, and BEAM memory rises from 58 MiB to 272 MiB."
}
],
"value": "* Point a repository at a loopback mirror (in Hex, HEX_MIRROR_URL) that answers /packages/probe with a 65,268-byte gzip stream decompressing to a 67,108,872-byte signed envelope with a 64 MiB payload and a one-byte signature.\n* Hex.Repo.get_package(\"hexpm\", \"probe\", nil), which calls the vendored hex_repo:get_package/2, returns {:error, :bad_signature} after allocating the envelope. Peak RSS was 234 MiB, against 96 MiB for a 1,030-byte invalid envelope.\n* Answer an API request with content-type: application/vnd.hex+erlang and 8,178 bytes of compressed Erlang term format encoding a list of 4,194,304 elements. mix_hex_api:get/2 accepts the decoded term, and BEAM memory rises from 58 MiB to 272 MiB."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003ehex_repo:get_protobuf/3\u003c/code\u003e, used by \u003ccode\u003eget_names/1\u003c/code\u003e, \u003ccode\u003eget_versions/1\u003c/code\u003e, \u003ccode\u003eget_package/2\u003c/code\u003e and \u003ccode\u003eget_policy/2\u003c/code\u003e, calls \u003ccode\u003ezlib:gunzip/1\u003c/code\u003e on the whole response body with no output limit.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ehex_repo:decode/4\u003c/code\u003e then calls \u003ccode\u003ehex_registry:decode_and_verify_signed/2\u003c/code\u003e, which decodes the protobuf envelope, copying its payload and signature, before checking the signature.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ehex_api:request/4\u003c/code\u003e passes every response whose content type contains \u003ccode\u003eapplication/vnd.hex+erlang\u003c/code\u003e to \u003ccode\u003ehex_safe_binary_to_term:safe_binary_to_term/1\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003esafe_binary_to_term/1\u003c/code\u003e calls \u003ccode\u003ebinary_to_term(Binary, [safe])\u003c/code\u003e, which decompresses compressed terms and builds the whole term, and only then walks it to reject functions and ports.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. `hex_repo:get_protobuf/3`, used by `get_names/1`, `get_versions/1`, `get_package/2` and `get_policy/2`, calls `zlib:gunzip/1` on the whole response body with no output limit.\n2. `hex_repo:decode/4` then calls `hex_registry:decode_and_verify_signed/2`, which decodes the protobuf envelope, copying its payload and signature, before checking the signature.\n3. `hex_api:request/4` passes every response whose content type contains `application/vnd.hex+erlang` to `hex_safe_binary_to_term:safe_binary_to_term/1`.\n4. `safe_binary_to_term/1` calls `binary_to_term(Binary, [safe])`, which decompresses compressed terms and builds the whole term, and only then walks it to reject functions and ports."
}
],
"value": "* hex_repo:get_protobuf/3, used by get_names/1, get_versions/1, get_package/2 and get_policy/2, calls zlib:gunzip/1 on the whole response body with no output limit.\n* hex_repo:decode/4 then calls hex_registry:decode_and_verify_signed/2, which decodes the protobuf envelope, copying its payload and signature, before checking the signature.\n* hex_api:request/4 passes every response whose content type contains application/vnd.hex+erlang to hex_safe_binary_to_term:safe_binary_to_term/1.\n* safe_binary_to_term/1 calls binary_to_term(Binary, [safe]), which decompresses compressed terms and builds the whole term, and only then walks it to reject functions and ports."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-105274",
"datePublished": "2026-10-12T16:43:58.730Z",
"dateReserved": "2026-10-10T13:00:02.355Z",
"dateUpdated": "2026-10-15T18:05:12.446Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-81856 (GCVE-0-2026-81856)
Vulnerability from cvelistv5
Published
2026-10-12 15:46
Modified
2026-10-16 17:47
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-150 - Improper Neutralization of Escape, Meta, or Control Sequences
Summary
Improper Neutralization of Escape, Meta, or Control Sequences vulnerability in hexpm hex allows a malicious package publisher or a hostile repository server to inject terminal control sequences into the output of Mix tasks.
Hex prints strings supplied by the package repository and the documentation search service straight to the terminal through Hex.Shell.info/1 and Hex.Shell.error/1, neutralizing no control characters. Affected sinks span package metadata and configuration snippets in mix hex.info, search results in mix hex.package search and mix hex.search, owner and organization listings, publish and sponsor URLs, the authenticated username in mix hex.user, policy identities in mix hex.policy, and API error bodies rendered by Hex.Utils.print_error_result/1. A malicious package can embed ANSI escape sequences (cursor movement, line erase, carriage returns, OSC 52 clipboard writes) or Unicode bidirectional overrides in any of those fields, so the bytes reach the terminal unchanged, forging displayed URLs, package descriptions and suggested commands, concealing real output, or writing to the clipboard. No authentication or privileged position is required; only publishing a package.
This issue affects hex: from 0.0.1 before 2.5.2.
References
| URL | Tags | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|||||||||||||||||
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-81856",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-16T17:46:03.963539Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-16T17:47:15.339Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Mix.Tasks.Hex.Search\u0027"
],
"packageName": "hex",
"packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
"product": "hex",
"programFiles": [
"lib/mix/tasks/hex.search.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Mix.Tasks.Hex.Search\u0027:run/1"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2.5.2",
"status": "affected",
"version": "0.0.1",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Mix.Tasks.Hex.Search\u0027"
],
"packageName": "hexpm/hex",
"packageURL": "pkg:github/hexpm/hex",
"product": "hex",
"programFiles": [
"lib/mix/tasks/hex.search.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Mix.Tasks.Hex.Search\u0027:run/1"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "dd3305ddf0032f6d0b468fd40dbbc3072bfcb8b8",
"status": "affected",
"version": "211303e3f68f5d3ce06450c1ff1a7a770a9ab236",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eReachable only when a developer runs an affected Mix task in a terminal emulator that interprets ANSI escape sequences, and the printed content originates from an attacker-published package or a hostile repository server.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Reachable only when a developer runs an affected Mix task in a terminal emulator that interprets ANSI escape sequences, and the printed content originates from an attacker-published package or a hostile repository server."
}
],
"value": "Reachable only when a developer runs an affected Mix task in a terminal emulator that interprets ANSI escape sequences, and the printed content originates from an attacker-published package or a hostile repository server."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
"versionEndExcluding": "2.5.2",
"versionStartIncluding": "0.0.1",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "reporter",
"value": "Zach Daniel"
},
{
"lang": "en",
"type": "remediation reviewer",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Jonatan M\u00e4nnchen"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen"
}
],
"dateAssigned": "2026-09-09T16:47:17.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper Neutralization of Escape, Meta, or Control Sequences vulnerability in hexpm hex allows a malicious package publisher or a hostile repository server to inject terminal control sequences into the output of Mix tasks.\u003c/p\u003e\n\u003cp\u003eHex prints strings supplied by the package repository and the documentation search service straight to the terminal through \u003ccode\u003eHex.Shell.info/1\u003c/code\u003e and \u003ccode\u003eHex.Shell.error/1\u003c/code\u003e, neutralizing no control characters. Affected sinks span package metadata and configuration snippets in \u003ccode\u003emix hex.info\u003c/code\u003e, search results in \u003ccode\u003emix hex.package search\u003c/code\u003e and \u003ccode\u003emix hex.search\u003c/code\u003e, owner and organization listings, publish and sponsor URLs, the authenticated username in \u003ccode\u003emix hex.user\u003c/code\u003e, policy identities in \u003ccode\u003emix hex.policy\u003c/code\u003e, and API error bodies rendered by \u003ccode\u003eHex.Utils.print_error_result/1\u003c/code\u003e. A malicious package can embed ANSI escape sequences (cursor movement, line erase, carriage returns, OSC 52 clipboard writes) or Unicode bidirectional overrides in any of those fields, so the bytes reach the terminal unchanged, forging displayed URLs, package descriptions and suggested commands, concealing real output, or writing to the clipboard. No authentication or privileged position is required; only publishing a package.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 0.0.1 before 2.5.2.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Improper Neutralization of Escape, Meta, or Control Sequences vulnerability in hexpm hex allows a malicious package publisher or a hostile repository server to inject terminal control sequences into the output of Mix tasks.\n\nHex prints strings supplied by the package repository and the documentation search service straight to the terminal through `Hex.Shell.info/1` and `Hex.Shell.error/1`, neutralizing no control characters. Affected sinks span package metadata and configuration snippets in `mix hex.info`, search results in `mix hex.package search` and `mix hex.search`, owner and organization listings, publish and sponsor URLs, the authenticated username in `mix hex.user`, policy identities in `mix hex.policy`, and API error bodies rendered by `Hex.Utils.print_error_result/1`. A malicious package can embed ANSI escape sequences (cursor movement, line erase, carriage returns, OSC 52 clipboard writes) or Unicode bidirectional overrides in any of those fields, so the bytes reach the terminal unchanged, forging displayed URLs, package descriptions and suggested commands, concealing real output, or writing to the clipboard. No authentication or privileged position is required; only publishing a package.\n\nThis issue affects hex: from 0.0.1 before 2.5.2."
}
],
"value": "Improper Neutralization of Escape, Meta, or Control Sequences vulnerability in hexpm hex allows a malicious package publisher or a hostile repository server to inject terminal control sequences into the output of Mix tasks.\n\nHex prints strings supplied by the package repository and the documentation search service straight to the terminal through Hex.Shell.info/1 and Hex.Shell.error/1, neutralizing no control characters. Affected sinks span package metadata and configuration snippets in mix hex.info, search results in mix hex.package search and mix hex.search, owner and organization listings, publish and sponsor URLs, the authenticated username in mix hex.user, policy identities in mix hex.policy, and API error bodies rendered by Hex.Utils.print_error_result/1. A malicious package can embed ANSI escape sequences (cursor movement, line erase, carriage returns, OSC 52 clipboard writes) or Unicode bidirectional overrides in any of those fields, so the bytes reach the terminal unchanged, forging displayed URLs, package descriptions and suggested commands, concealing real output, or writing to the clipboard. No authentication or privileged position is required; only publishing a package.\n\nThis issue affects hex: from 0.0.1 before 2.5.2."
}
],
"impacts": [
{
"capecId": "CAPEC-93",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eA malicious package publisher can forge what a developer sees when running \u003ccode\u003emix hex.search\u003c/code\u003e, rewriting the displayed hexdocs URL or a suggested command, concealing real output, or writing attacker-chosen text to the clipboard through OSC 52. Any developer who runs a documentation search that surfaces the attacker\u0027s package is affected.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "A malicious package publisher can forge what a developer sees when running `mix hex.search`, rewriting the displayed hexdocs URL or a suggested command, concealing real output, or writing attacker-chosen text to the clipboard through OSC 52. Any developer who runs a documentation search that surfaces the attacker\u0027s package is affected."
}
],
"value": "A malicious package publisher can forge what a developer sees when running mix hex.search, rewriting the displayed hexdocs URL or a suggested command, concealing real output, or writing attacker-chosen text to the clipboard through OSC 52. Any developer who runs a documentation search that surfaces the attacker\u0027s package is affected."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 2.3,
"baseSeverity": "LOW",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "PASSIVE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "LOW",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-150",
"description": "CWE-150 Improper Neutralization of Escape, Meta, or Control Sequences",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-12T15:46:12.146Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/hexpm/hex/security/advisories/GHSA-fpmj-3cxg-p766"
},
{
"name": "EEF CNA record for CVE-2026-81856",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-81856.html"
},
{
"name": "OSV record EEF-CVE-2026-81856",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-81856"
},
{
"name": "Introducing commit 211303e in hexpm/hex",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hex/commit/211303e3f68f5d3ce06450c1ff1a7a770a9ab236"
},
{
"name": "Fix commit dd3305d in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/dd3305ddf0032f6d0b468fd40dbbc3072bfcb8b8"
}
],
"solutions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. Hex then escapes terminal control characters in text from hex.pm and the registry before printing it, and rejects registry dependencies whose package or repository name isn\u0027t a valid name.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. Hex then escapes terminal control characters in text from hex.pm and the registry before printing it, and rejects registry dependencies whose package or repository name isn\u0027t a valid name."
}
],
"value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. Hex then escapes terminal control characters in text from hex.pm and the registry before printing it, and rejects registry dependencies whose package or repository name isn\u0027t a valid name."
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Terminal escape sequence injection in Hex via unneutralized server-supplied output"
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-81856",
"datePublished": "2026-10-12T15:46:12.146Z",
"dateReserved": "2026-08-31T00:45:02.431Z",
"dateUpdated": "2026-10-16T17:47:15.339Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-96612 (GCVE-0-2026-96612)
Vulnerability from cvelistv5
Published
2026-10-12 15:39
Modified
2026-10-16 17:41
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-22 - Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Summary
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') vulnerability in hexpm hex allows a malicious package publisher to delete a directory outside the project's deps directory and extract package files into it when a developer fetches dependencies.
A package release's requirements carry an app label that names the dependency's OTP application. Hex takes this label from the repository's signed registry and uses it as the dependency's application name without validating it (Hex.RemoteConverger.deps/2, Hex.Mix.to_lock/1). Mix derives the dependency's checkout directory as deps/<app>, and Hex.SCM.update/1 runs File.rm_rf!/1 on that directory and unpacks the package into it. hex.pm only checks the label's length, so a publisher can set it to a value such as ../../target. When a project depends on such a release directly or transitively, mix deps.get or mix deps.update deletes the directory the label resolves to and writes the labelled package's files there. This happens before any dependency code is compiled.
This issue affects hex: from 0.6.0 before 2.5.2.
References
| URL | Tags | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-96612",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-16T17:41:34.850974Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-16T17:41:58.545Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hex.RemoteConverger\u0027",
"\u0027Elixir.Hex.Mix\u0027",
"\u0027Elixir.Hex.SCM\u0027"
],
"packageName": "hex",
"packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
"product": "hex",
"programFiles": [
"lib/hex/remote_converger.ex",
"lib/hex/mix.ex",
"lib/hex/scm.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hex.RemoteConverger\u0027:deps/2"
},
{
"name": "\u0027Elixir.Hex.RemoteConverger\u0027:converge/2"
},
{
"name": "\u0027Elixir.Hex.Mix\u0027:to_lock/1"
},
{
"name": "\u0027Elixir.Hex.SCM\u0027:update/1"
},
{
"name": "\u0027Elixir.Hex.SCM\u0027:checkout/1"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2.5.2",
"status": "affected",
"version": "0.6.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hex.RemoteConverger\u0027",
"\u0027Elixir.Hex.Mix\u0027",
"\u0027Elixir.Hex.SCM\u0027"
],
"packageName": "hexpm/hex",
"packageURL": "pkg:github/hexpm/hex",
"product": "hex",
"programFiles": [
"lib/hex/remote_converger.ex",
"lib/hex/mix.ex",
"lib/hex/scm.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hex.RemoteConverger\u0027:deps/2"
},
{
"name": "\u0027Elixir.Hex.RemoteConverger\u0027:converge/2"
},
{
"name": "\u0027Elixir.Hex.Mix\u0027:to_lock/1"
},
{
"name": "\u0027Elixir.Hex.SCM\u0027:update/1"
},
{
"name": "\u0027Elixir.Hex.SCM\u0027:checkout/1"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"changes": [
{
"at": "1b3e2ae8f0dd7273a2c2625b9e50b6c67c8568fb",
"status": "unaffected"
},
{
"at": "72279a8e2004b32b20d8767ff010c117193dd02c",
"status": "unaffected"
}
],
"lessThan": "*",
"status": "affected",
"version": "380f87054e1bc08ee13d2b5311bc01f98dad2fc6",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAffects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata carries a crafted dependency \u003ccode\u003eapp\u003c/code\u003e label, when dependencies are fetched with \u003ccode\u003emix deps.get\u003c/code\u003e or \u003ccode\u003emix deps.update\u003c/code\u003e. Packages on hex.pm are affected while hex.pm accepts such labels; packages on self-hosted repositories are affected when the repository operator signs such metadata.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Affects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata carries a crafted dependency `app` label, when dependencies are fetched with `mix deps.get` or `mix deps.update`. Packages on hex.pm are affected while hex.pm accepts such labels; packages on self-hosted repositories are affected when the repository operator signs such metadata."
}
],
"value": "Affects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata carries a crafted dependency app label, when dependencies are fetched with mix deps.get or mix deps.update. Packages on hex.pm are affected while hex.pm accepts such labels; packages on self-hosted repositories are affected when the repository operator signs such metadata."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
"versionEndExcluding": "2.5.2",
"versionStartIncluding": "0.6.0",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Peter Ullrich"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "analyst",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-07T22:03:18.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in hexpm hex allows a malicious package publisher to delete a directory outside the project\u0027s \u003ccode\u003edeps\u003c/code\u003e directory and extract package files into it when a developer fetches dependencies.\u003c/p\u003e\n\u003cp\u003eA package release\u0027s requirements carry an \u003ccode\u003eapp\u003c/code\u003e label that names the dependency\u0027s OTP application. Hex takes this label from the repository\u0027s signed registry and uses it as the dependency\u0027s application name without validating it (\u003ccode\u003eHex.RemoteConverger.deps/2\u003c/code\u003e, \u003ccode\u003eHex.Mix.to_lock/1\u003c/code\u003e). Mix derives the dependency\u0027s checkout directory as \u003ccode\u003edeps/\u0026lt;app\u0026gt;\u003c/code\u003e, and \u003ccode\u003eHex.SCM.update/1\u003c/code\u003e runs \u003ccode\u003eFile.rm_rf!/1\u003c/code\u003e on that directory and unpacks the package into it. hex.pm only checks the label\u0027s length, so a publisher can set it to a value such as \u003ccode\u003e../../target\u003c/code\u003e. When a project depends on such a release directly or transitively, \u003ccode\u003emix deps.get\u003c/code\u003e or \u003ccode\u003emix deps.update\u003c/code\u003e deletes the directory the label resolves to and writes the labelled package\u0027s files there. This happens before any dependency code is compiled.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 0.6.0 before 2.5.2.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in hexpm hex allows a malicious package publisher to delete a directory outside the project\u0027s `deps` directory and extract package files into it when a developer fetches dependencies.\n\nA package release\u0027s requirements carry an `app` label that names the dependency\u0027s OTP application. Hex takes this label from the repository\u0027s signed registry and uses it as the dependency\u0027s application name without validating it (`Hex.RemoteConverger.deps/2`, `Hex.Mix.to_lock/1`). Mix derives the dependency\u0027s checkout directory as `deps/\u003capp\u003e`, and `Hex.SCM.update/1` runs `File.rm_rf!/1` on that directory and unpacks the package into it. hex.pm only checks the label\u0027s length, so a publisher can set it to a value such as `../../target`. When a project depends on such a release directly or transitively, `mix deps.get` or `mix deps.update` deletes the directory the label resolves to and writes the labelled package\u0027s files there. This happens before any dependency code is compiled.\n\nThis issue affects hex: from 0.6.0 before 2.5.2."
}
],
"value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in hexpm hex allows a malicious package publisher to delete a directory outside the project\u0027s deps directory and extract package files into it when a developer fetches dependencies.\n\nA package release\u0027s requirements carry an app label that names the dependency\u0027s OTP application. Hex takes this label from the repository\u0027s signed registry and uses it as the dependency\u0027s application name without validating it (Hex.RemoteConverger.deps/2, Hex.Mix.to_lock/1). Mix derives the dependency\u0027s checkout directory as deps/\u003capp\u003e, and Hex.SCM.update/1 runs File.rm_rf!/1 on that directory and unpacks the package into it. hex.pm only checks the label\u0027s length, so a publisher can set it to a value such as ../../target. When a project depends on such a release directly or transitively, mix deps.get or mix deps.update deletes the directory the label resolves to and writes the labelled package\u0027s files there. This happens before any dependency code is compiled.\n\nThis issue affects hex: from 0.6.0 before 2.5.2."
}
],
"impacts": [
{
"capecId": "CAPEC-126",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eA publisher of any package in a project\u0027s dependency tree can make \u003ccode\u003emix deps.get\u003c/code\u003e delete a directory that the developer\u0027s account can write and that \u003ccode\u003e..\u003c/code\u003e segments relative to \u003ccode\u003edeps\u003c/code\u003e reach, such as the project directory or its parent, and fill it with the labelled package\u0027s files. Writing attacker-chosen files into a directory whose contents are later run or loaded can lead to code execution without the developer compiling the dependency. Developers who fetch dependencies to review them before compiling, CI jobs and dependency update bots that only run \u003ccode\u003emix deps.get\u003c/code\u003e or \u003ccode\u003emix deps.update\u003c/code\u003e are affected.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "A publisher of any package in a project\u0027s dependency tree can make `mix deps.get` delete a directory that the developer\u0027s account can write and that `..` segments relative to `deps` reach, such as the project directory or its parent, and fill it with the labelled package\u0027s files. Writing attacker-chosen files into a directory whose contents are later run or loaded can lead to code execution without the developer compiling the dependency. Developers who fetch dependencies to review them before compiling, CI jobs and dependency update bots that only run `mix deps.get` or `mix deps.update` are affected."
}
],
"value": "A publisher of any package in a project\u0027s dependency tree can make mix deps.get delete a directory that the developer\u0027s account can write and that .. segments relative to deps reach, such as the project directory or its parent, and fill it with the labelled package\u0027s files. Writing attacker-chosen files into a directory whose contents are later run or loaded can lead to code execution without the developer compiling the dependency. Developers who fetch dependencies to review them before compiling, CI jobs and dependency update bots that only run mix deps.get or mix deps.update are affected."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 7.5,
"baseSeverity": "HIGH",
"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:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-22",
"description": "CWE-22 Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027)",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-12T15:39:49.862Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/hexpm/hex/security/advisories/GHSA-2vjj-c5hr-6xc3"
},
{
"name": "EEF CNA record for CVE-2026-96612",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-96612.html"
},
{
"name": "OSV record EEF-CVE-2026-96612",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-96612"
},
{
"name": "Introducing commit 380f870 in hexpm/hex",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hex/commit/380f87054e1bc08ee13d2b5311bc01f98dad2fc6"
},
{
"name": "Fix commit 1b3e2ae in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/1b3e2ae8f0dd7273a2c2625b9e50b6c67c8568fb"
},
{
"name": "Fix commit 72279a8 in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/72279a8e2004b32b20d8767ff010c117193dd02c"
}
],
"solutions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. Hex then skips package releases that give a dependency an application name other than a letter followed by letters, digits, \u003ccode\u003e_\u003c/code\u003e and \u003ccode\u003e-\u003c/code\u003e, and warns about them. Resolution fails if no other release of the package fits the requirements.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. Hex then skips package releases that give a dependency an application name other than a letter followed by letters, digits, `_` and `-`, and warns about them. Resolution fails if no other release of the package fits the requirements."
}
],
"value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. Hex then skips package releases that give a dependency an application name other than a letter followed by letters, digits, _ and -, and warns about them. Resolution fails if no other release of the package fits the requirements."
}
],
"source": {
"discovery": "UNKNOWN"
},
"title": "Path traversal in Hex via unvalidated dependency app names",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eBefore running \u003ccode\u003emix deps.get\u003c/code\u003e on a \u003ccode\u003emix.lock\u003c/code\u003e from an untrusted change, check that every key in \u003ccode\u003emix.lock\u003c/code\u003e is a plain application name without \u003ccode\u003e/\u003c/code\u003e, \u003ccode\u003e\\\u003c/code\u003e or \u003ccode\u003e..\u003c/code\u003e. There\u0027s no workaround for new dependency resolution with \u003ccode\u003emix deps.update\u003c/code\u003e or \u003ccode\u003emix deps.get\u003c/code\u003e without a lock entry.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Before running `mix deps.get` on a `mix.lock` from an untrusted change, check that every key in `mix.lock` is a plain application name without `/`, `\\` or `..`. There\u0027s no workaround for new dependency resolution with `mix deps.update` or `mix deps.get` without a lock entry."
}
],
"value": "Before running mix deps.get on a mix.lock from an untrusted change, check that every key in mix.lock is a plain application name without /, \\ or ... There\u0027s no workaround for new dependency resolution with mix deps.update or mix deps.get without a lock entry."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003ePublish package \u003ccode\u003ebeta\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003ePublish package \u003ccode\u003ealpha\u003c/code\u003e with a requirement on \u003ccode\u003ebeta\u003c/code\u003e whose \u003ccode\u003eapp\u003c/code\u003e is \u003ccode\u003e../../escape_marker\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eIn a project that depends on \u003ccode\u003ealpha\u003c/code\u003e, create \u003ccode\u003e../escape_marker/canary.txt\u003c/code\u003e next to the project directory.\u003c/li\u003e\n\u003cli\u003eRun \u003ccode\u003emix deps.get\u003c/code\u003e. Hex prints \u003ccode\u003e* Updating ../../escape_marker (Hex package)\u003c/code\u003e, deletes \u003ccode\u003ecanary.txt\u003c/code\u003e and writes \u003ccode\u003ebeta\u003c/code\u003e\u0027s files into \u003ccode\u003e../escape_marker\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Publish package `beta`.\n2. Publish package `alpha` with a requirement on `beta` whose `app` is `../../escape_marker`.\n3. In a project that depends on `alpha`, create `../escape_marker/canary.txt` next to the project directory.\n4. Run `mix deps.get`. Hex prints `* Updating ../../escape_marker (Hex package)`, deletes `canary.txt` and writes `beta`\u0027s files into `../escape_marker`."
}
],
"value": "* Publish package beta.\n* Publish package alpha with a requirement on beta whose app is ../../escape_marker.\n* In a project that depends on alpha, create ../escape_marker/canary.txt next to the project directory.\n* Run mix deps.get. Hex prints * Updating ../../escape_marker (Hex package), deletes canary.txt and writes beta\u0027s files into ../escape_marker."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003ehex.pm stores each requirement\u0027s \u003ccode\u003eapp\u003c/code\u003e field with only a length check (255 codepoints) and copies it into the signed package registry resource when it differs from the dependency\u0027s package name.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHex.RemoteConverger.deps/2\u003c/code\u003e returns the dependency\u0027s children with \u003ccode\u003eString.to_atom/1\u003c/code\u003e of that label as the application name, and \u003ccode\u003eHex.Mix.to_lock/1\u003c/code\u003e writes it as the \u003ccode\u003emix.lock\u003c/code\u003e key.\u003c/li\u003e\n\u003cli\u003eMix computes the dependency destination as \u003ccode\u003ePath.join(Mix.Project.deps_path(), Atom.to_string(app))\u003c/code\u003e without validating the name.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHex.SCM.update/1\u003c/code\u003e calls \u003ccode\u003eFile.rm_rf!(dest)\u003c/code\u003e and then \u003ccode\u003eHex.Tar.unpack!(path, dest)\u003c/code\u003e, so a label containing \u003ccode\u003e..\u003c/code\u003e segments deletes and repopulates a directory outside \u003ccode\u003edeps\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eMix doesn\u0027t evaluate a Hex dependency\u0027s \u003ccode\u003emix.exs\u003c/code\u003e during \u003ccode\u003emix deps.get\u003c/code\u003e, so this is reachable before any package code runs.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. hex.pm stores each requirement\u0027s `app` field with only a length check (255 codepoints) and copies it into the signed package registry resource when it differs from the dependency\u0027s package name.\n2. `Hex.RemoteConverger.deps/2` returns the dependency\u0027s children with `String.to_atom/1` of that label as the application name, and `Hex.Mix.to_lock/1` writes it as the `mix.lock` key.\n3. Mix computes the dependency destination as `Path.join(Mix.Project.deps_path(), Atom.to_string(app))` without validating the name.\n4. `Hex.SCM.update/1` calls `File.rm_rf!(dest)` and then `Hex.Tar.unpack!(path, dest)`, so a label containing `..` segments deletes and repopulates a directory outside `deps`.\n5. Mix doesn\u0027t evaluate a Hex dependency\u0027s `mix.exs` during `mix deps.get`, so this is reachable before any package code runs."
}
],
"value": "* hex.pm stores each requirement\u0027s app field with only a length check (255 codepoints) and copies it into the signed package registry resource when it differs from the dependency\u0027s package name.\n* Hex.RemoteConverger.deps/2 returns the dependency\u0027s children with String.to_atom/1 of that label as the application name, and Hex.Mix.to_lock/1 writes it as the mix.lock key.\n* Mix computes the dependency destination as Path.join(Mix.Project.deps_path(), Atom.to_string(app)) without validating the name.\n* Hex.SCM.update/1 calls File.rm_rf!(dest) and then Hex.Tar.unpack!(path, dest), so a label containing .. segments deletes and repopulates a directory outside deps.\n* Mix doesn\u0027t evaluate a Hex dependency\u0027s mix.exs during mix deps.get, so this is reachable before any package code runs."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-96612",
"datePublished": "2026-10-12T15:39:49.862Z",
"dateReserved": "2026-10-01T09:00:02.103Z",
"dateUpdated": "2026-10-16T17:41:58.545Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-105668 (GCVE-0-2026-105668)
Vulnerability from cvelistv5
Published
2026-10-12 15:31
Modified
2026-10-16 17:38
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-693 - Protection Mechanism Failure
Summary
Protection Mechanism Failure vulnerability in hexpm hex allows the owner of a package, or an attacker who has taken over the owner's account, to get a newly published release past the release-age cooldown.
The cooldown (cooldown config, HEX_COOLDOWN) withholds versions younger than the configured age during dependency resolution. When the release of a package locked in mix.lock is retired or has a security advisory, Hex.RemoteConverger.build_cooldown_bypass/3 and Hex.RemoteConverger.unsafe_lock_bypass/1 lift the cooldown for every version of that package and of every package the locked release depends on, so that mix deps.update can move off the unsafe release. Retiring a release needs the same package owner permission as publishing one. An attacker who publishes a malicious release can retire the currently locked release, and the next mix deps.update that unlocks the package selects the new release regardless of its age. Hex.Registry.Cooldown.versions/2 checks the bypass by package name alone, so the bypass also covers packages with the same name in other repositories. Cooldown configured through an organization dependency policy is not affected.
This issue affects hex: from 2.5.0 before 2.5.2.
References
| URL | Tags | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-105668",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-16T17:37:56.690913Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-16T17:38:34.235Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hex.RemoteConverger\u0027",
"\u0027Elixir.Hex.Registry.Cooldown\u0027"
],
"packageName": "hex",
"packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
"product": "hex",
"programFiles": [
"lib/hex/remote_converger.ex",
"lib/hex/registry/cooldown.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hex.RemoteConverger\u0027:build_cooldown_bypass/3"
},
{
"name": "\u0027Elixir.Hex.RemoteConverger\u0027:unsafe_lock_bypass/1"
},
{
"name": "\u0027Elixir.Hex.Registry.Cooldown\u0027:versions/2"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2.5.2",
"status": "affected",
"version": "2.5.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hex.RemoteConverger\u0027",
"\u0027Elixir.Hex.Registry.Cooldown\u0027"
],
"packageName": "hexpm/hex",
"packageURL": "pkg:github/hexpm/hex",
"product": "hex",
"programFiles": [
"lib/hex/remote_converger.ex",
"lib/hex/registry/cooldown.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hex.RemoteConverger\u0027:build_cooldown_bypass/3"
},
{
"name": "\u0027Elixir.Hex.RemoteConverger\u0027:unsafe_lock_bypass/1"
},
{
"name": "\u0027Elixir.Hex.Registry.Cooldown\u0027:versions/2"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"changes": [
{
"at": "eed2bdf85320ae9e244629511c03f5f3f8c5d0b5",
"status": "unaffected"
},
{
"at": "1b6fddfeadd98ed26a143f60356efade0494e2ae",
"status": "unaffected"
}
],
"lessThan": "*",
"status": "affected",
"version": "b4e665d46be8a5f81e8cbbe4c3a67058a19cf133",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAffects projects that set the \u003ccode\u003ecooldown\u003c/code\u003e config or \u003ccode\u003eHEX_COOLDOWN\u003c/code\u003e and update dependencies with \u003ccode\u003emix deps.update\u003c/code\u003e, or otherwise unlock the package during resolution. Cooldowns enforced through an organization dependency policy aren\u0027t affected.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Affects projects that set the `cooldown` config or `HEX_COOLDOWN` and update dependencies with `mix deps.update`, or otherwise unlock the package during resolution. Cooldowns enforced through an organization dependency policy aren\u0027t affected."
}
],
"value": "Affects projects that set the cooldown config or HEX_COOLDOWN and update dependencies with mix deps.update, or otherwise unlock the package during resolution. Cooldowns enforced through an organization dependency policy aren\u0027t affected."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
"versionEndExcluding": "2.5.2",
"versionStartIncluding": "2.5.0",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Peter Ullrich"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "coordinator",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "analyst",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-10T13:01:56.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eProtection Mechanism Failure vulnerability in hexpm hex allows the owner of a package, or an attacker who has taken over the owner\u0027s account, to get a newly published release past the release-age cooldown.\u003c/p\u003e\n\u003cp\u003eThe cooldown (\u003ccode\u003ecooldown\u003c/code\u003e config, \u003ccode\u003eHEX_COOLDOWN\u003c/code\u003e) withholds versions younger than the configured age during dependency resolution. When the release of a package locked in \u003ccode\u003emix.lock\u003c/code\u003e is retired or has a security advisory, \u003ccode\u003eHex.RemoteConverger.build_cooldown_bypass/3\u003c/code\u003e and \u003ccode\u003eHex.RemoteConverger.unsafe_lock_bypass/1\u003c/code\u003e lift the cooldown for every version of that package and of every package the locked release depends on, so that \u003ccode\u003emix deps.update\u003c/code\u003e can move off the unsafe release. Retiring a release needs the same package owner permission as publishing one. An attacker who publishes a malicious release can retire the currently locked release, and the next \u003ccode\u003emix deps.update\u003c/code\u003e that unlocks the package selects the new release regardless of its age. \u003ccode\u003eHex.Registry.Cooldown.versions/2\u003c/code\u003e checks the bypass by package name alone, so the bypass also covers packages with the same name in other repositories. Cooldown configured through an organization dependency policy is not affected.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 2.5.0 before 2.5.2.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Protection Mechanism Failure vulnerability in hexpm hex allows the owner of a package, or an attacker who has taken over the owner\u0027s account, to get a newly published release past the release-age cooldown.\n\nThe cooldown (`cooldown` config, `HEX_COOLDOWN`) withholds versions younger than the configured age during dependency resolution. When the release of a package locked in `mix.lock` is retired or has a security advisory, `Hex.RemoteConverger.build_cooldown_bypass/3` and `Hex.RemoteConverger.unsafe_lock_bypass/1` lift the cooldown for every version of that package and of every package the locked release depends on, so that `mix deps.update` can move off the unsafe release. Retiring a release needs the same package owner permission as publishing one. An attacker who publishes a malicious release can retire the currently locked release, and the next `mix deps.update` that unlocks the package selects the new release regardless of its age. `Hex.Registry.Cooldown.versions/2` checks the bypass by package name alone, so the bypass also covers packages with the same name in other repositories. Cooldown configured through an organization dependency policy is not affected.\n\nThis issue affects hex: from 2.5.0 before 2.5.2."
}
],
"value": "Protection Mechanism Failure vulnerability in hexpm hex allows the owner of a package, or an attacker who has taken over the owner\u0027s account, to get a newly published release past the release-age cooldown.\n\nThe cooldown (cooldown config, HEX_COOLDOWN) withholds versions younger than the configured age during dependency resolution. When the release of a package locked in mix.lock is retired or has a security advisory, Hex.RemoteConverger.build_cooldown_bypass/3 and Hex.RemoteConverger.unsafe_lock_bypass/1 lift the cooldown for every version of that package and of every package the locked release depends on, so that mix deps.update can move off the unsafe release. Retiring a release needs the same package owner permission as publishing one. An attacker who publishes a malicious release can retire the currently locked release, and the next mix deps.update that unlocks the package selects the new release regardless of its age. Hex.Registry.Cooldown.versions/2 checks the bypass by package name alone, so the bypass also covers packages with the same name in other repositories. Cooldown configured through an organization dependency policy is not affected.\n\nThis issue affects hex: from 2.5.0 before 2.5.2."
}
],
"impacts": [
{
"capecId": "CAPEC-538",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eThe cooldown exists to keep a compromised release out of projects until it can be detected and retired. An attacker who controls a package owner account can defeat it by also retiring the currently locked release, so a malicious release reaches projects that rely on the local cooldown as soon as they run \u003ccode\u003emix deps.update\u003c/code\u003e, including automated dependency update jobs. The malicious release\u0027s code then runs when the dependency is compiled.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "The cooldown exists to keep a compromised release out of projects until it can be detected and retired. An attacker who controls a package owner account can defeat it by also retiring the currently locked release, so a malicious release reaches projects that rely on the local cooldown as soon as they run `mix deps.update`, including automated dependency update jobs. The malicious release\u0027s code then runs when the dependency is compiled."
}
],
"value": "The cooldown exists to keep a compromised release out of projects until it can be detected and retired. An attacker who controls a package owner account can defeat it by also retiring the currently locked release, so a malicious release reaches projects that rely on the local cooldown as soon as they run mix deps.update, including automated dependency update jobs. The malicious release\u0027s code then runs when the dependency is compiled."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 7.1,
"baseSeverity": "HIGH",
"privilegesRequired": "HIGH",
"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:H/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-693",
"description": "CWE-693 Protection Mechanism Failure",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-12T15:31:58.926Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/hexpm/hex/security/advisories/GHSA-pqhp-5x9x-69h7"
},
{
"name": "EEF CNA record for CVE-2026-105668",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-105668.html"
},
{
"name": "OSV record EEF-CVE-2026-105668",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-105668"
},
{
"name": "Introducing commit b4e665d in hexpm/hex",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hex/commit/b4e665d46be8a5f81e8cbbe4c3a67058a19cf133"
},
{
"name": "Fix commit eed2bdf in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/eed2bdf85320ae9e244629511c03f5f3f8c5d0b5"
},
{
"name": "Fix commit 1b6fddf in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/1b6fddfeadd98ed26a143f60356efade0494e2ae"
}
],
"solutions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. A retired locked release then no longer lifts the release-age cooldown for its package. A locked release with a security advisory still lifts it for its package and the direct dependencies of the locked release, only in the repository the package is locked from.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. A retired locked release then no longer lifts the release-age cooldown for its package. A locked release with a security advisory still lifts it for its package and the direct dependencies of the locked release, only in the repository the package is locked from."
}
],
"value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. A retired locked release then no longer lifts the release-age cooldown for its package. A locked release with a security advisory still lifts it for its package and the direct dependencies of the locked release, only in the repository the package is locked from."
}
],
"source": {
"discovery": "UNKNOWN"
},
"title": "Hex release-age cooldown is lifted package-wide when the locked release is retired",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eEnforce the cooldown through an organization dependency policy instead of the local \u003ccode\u003ecooldown\u003c/code\u003e setting, or run \u003ccode\u003emix hex.audit\u003c/code\u003e before \u003ccode\u003emix deps.update\u003c/code\u003e and don\u0027t update a package whose locked release was retired until its replacement has been reviewed.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Enforce the cooldown through an organization dependency policy instead of the local `cooldown` setting, or run `mix hex.audit` before `mix deps.update` and don\u0027t update a package whose locked release was retired until its replacement has been reviewed."
}
],
"value": "Enforce the cooldown through an organization dependency policy instead of the local cooldown setting, or run mix hex.audit before mix deps.update and don\u0027t update a package whose locked release was retired until its replacement has been reviewed."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eLock \u003ccode\u003efoo 1.0.0\u003c/code\u003e in a project with \u003ccode\u003eHEX_COOLDOWN=7d\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003ePublish \u003ccode\u003efoo 1.0.1\u003c/code\u003e; \u003ccode\u003emix deps.update foo\u003c/code\u003e reports it as filtered by cooldown and keeps 1.0.0.\u003c/li\u003e\n\u003cli\u003eRetire \u003ccode\u003efoo 1.0.0\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eRun \u003ccode\u003emix deps.update foo\u003c/code\u003e again; it upgrades to the 1-hour-old 1.0.1.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Lock `foo 1.0.0` in a project with `HEX_COOLDOWN=7d`.\n2. Publish `foo 1.0.1`; `mix deps.update foo` reports it as filtered by cooldown and keeps 1.0.0.\n3. Retire `foo 1.0.0`.\n4. Run `mix deps.update foo` again; it upgrades to the 1-hour-old 1.0.1."
}
],
"value": "* Lock foo 1.0.0 in a project with HEX_COOLDOWN=7d.\n* Publish foo 1.0.1; mix deps.update foo reports it as filtered by cooldown and keeps 1.0.0.\n* Retire foo 1.0.0.\n* Run mix deps.update foo again; it upgrades to the 1-hour-old 1.0.1."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003eHex.RemoteConverger.build_cooldown_bypass/3\u003c/code\u003e adds \u003ccode\u003eunsafe_lock_bypass(old_lock)\u003c/code\u003e to the set of packages exempt from the cooldown.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eunsafe_lock_bypass/1\u003c/code\u003e adds a package\u0027s name, and the names of its locked children, whenever \u003ccode\u003eRegistry.retired/3\u003c/code\u003e or \u003ccode\u003eRegistry.advisories/3\u003c/code\u003e reports the locked version as unsafe.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHex.Registry.Cooldown.versions/2\u003c/code\u003e returns every version unfiltered for any package name in that set, so freshly published versions become eligible as soon as the package is unlocked.\u003c/li\u003e\n\u003cli\u003ehex.pm lets the package owner retire any of their releases, so the owner controls the trigger. \u003ccode\u003eHex.Policy.Filter\u003c/code\u003e applies policy cooldowns without consulting the bypass set.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. `Hex.RemoteConverger.build_cooldown_bypass/3` adds `unsafe_lock_bypass(old_lock)` to the set of packages exempt from the cooldown.\n2. `unsafe_lock_bypass/1` adds a package\u0027s name, and the names of its locked children, whenever `Registry.retired/3` or `Registry.advisories/3` reports the locked version as unsafe.\n3. `Hex.Registry.Cooldown.versions/2` returns every version unfiltered for any package name in that set, so freshly published versions become eligible as soon as the package is unlocked.\n4. hex.pm lets the package owner retire any of their releases, so the owner controls the trigger. `Hex.Policy.Filter` applies policy cooldowns without consulting the bypass set."
}
],
"value": "* Hex.RemoteConverger.build_cooldown_bypass/3 adds unsafe_lock_bypass(old_lock) to the set of packages exempt from the cooldown.\n* unsafe_lock_bypass/1 adds a package\u0027s name, and the names of its locked children, whenever Registry.retired/3 or Registry.advisories/3 reports the locked version as unsafe.\n* Hex.Registry.Cooldown.versions/2 returns every version unfiltered for any package name in that set, so freshly published versions become eligible as soon as the package is unlocked.\n* hex.pm lets the package owner retire any of their releases, so the owner controls the trigger. Hex.Policy.Filter applies policy cooldowns without consulting the bypass set."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-105668",
"datePublished": "2026-10-12T15:31:58.926Z",
"dateReserved": "2026-10-07T22:15:01.885Z",
"dateUpdated": "2026-10-16T17:38:34.235Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-105280 (GCVE-0-2026-105280)
Vulnerability from cvelistv5
Published
2026-10-12 15:30
Modified
2026-10-16 17:37
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-354 - Improper Validation of Integrity Check Value
Summary
Improper Validation of Integrity Check Value vulnerability in hexpm hex allows a package mirror to substitute the tarball that mix hex.package fetch PACKAGE VERSION --output - writes to standard output.
Hex compares a fetched tarball's inner and outer checksums with the repository's signed registry before writing it to a file or unpacking it. The standard output branch of mix hex.package fetch writes the bytes returned by Hex.SCM.fetch/3 without that comparison, and Hex.SCM.fetch/3 doesn't verify freshly downloaded tarballs itself. When a mirror configured with HEX_MIRROR_URL or mirror_url serves altered bytes, the command prints them and exits successfully, while the same fetch with --output FILE fails with a checksum mismatch.
Hex also prints diagnostics to standard output while the command runs: the body of a failed HTTP response from the update check or a registry fetch, and API warnings from the x-hex-message header. A mirror that answers one of those requests with an error whose body is a tar archive gets that archive written to standard output ahead of the verified tarball, so a pipeline that unpacks standard output extracts the mirror's files. This happens even when the checksum check then fails, because the archive is already on standard output.
This issue affects hex: from 0.20.2 before 2.5.2.
References
| URL | Tags | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-105280",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-16T17:35:17.920015Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-16T17:37:21.498Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Mix.Tasks.Hex.Package\u0027",
"\u0027Elixir.Hex.SCM\u0027"
],
"packageName": "hex",
"packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
"product": "hex",
"programFiles": [
"lib/mix/tasks/hex.package.ex",
"lib/hex/scm.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Mix.Tasks.Hex.Package\u0027:run/1"
},
{
"name": "\u0027Elixir.Hex.SCM\u0027:fetch/3"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2.5.2",
"status": "affected",
"version": "0.20.2",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Mix.Tasks.Hex.Package\u0027",
"\u0027Elixir.Hex.SCM\u0027"
],
"packageName": "hexpm/hex",
"packageURL": "pkg:github/hexpm/hex",
"product": "hex",
"programFiles": [
"lib/mix/tasks/hex.package.ex",
"lib/hex/scm.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Mix.Tasks.Hex.Package\u0027:run/1"
},
{
"name": "\u0027Elixir.Hex.SCM\u0027:fetch/3"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"changes": [
{
"at": "8a14632ca6ba8a8afd2a3f2098bc267c13e13d93",
"status": "unaffected"
},
{
"at": "69489f8b74213f03db10169e6e5407e6d5fee512",
"status": "unaffected"
}
],
"lessThan": "*",
"status": "affected",
"version": "d1f5a085d9d4748e1f7d195e4bc39ebd77c28d9c",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAffects \u003ccode\u003emix hex.package fetch\u003c/code\u003e with \u003ccode\u003e--output -\u003c/code\u003e when packages are downloaded through a mirror set with \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e or \u003ccode\u003emirror_url\u003c/code\u003e. Downloads directly from the repository over HTTPS aren\u0027t affected unless TLS verification is broken.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Affects `mix hex.package fetch` with `--output -` when packages are downloaded through a mirror set with `HEX_MIRROR_URL` or `mirror_url`. Downloads directly from the repository over HTTPS aren\u0027t affected unless TLS verification is broken."
}
],
"value": "Affects mix hex.package fetch with --output - when packages are downloaded through a mirror set with HEX_MIRROR_URL or mirror_url. Downloads directly from the repository over HTTPS aren\u0027t affected unless TLS verification is broken."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
"versionEndExcluding": "2.5.2",
"versionStartIncluding": "0.20.2",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Peter Ullrich"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "analyst",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-10T13:01:30.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper Validation of Integrity Check Value vulnerability in hexpm hex allows a package mirror to substitute the tarball that \u003ccode\u003emix hex.package fetch PACKAGE VERSION --output -\u003c/code\u003e writes to standard output.\u003c/p\u003e\n\u003cp\u003eHex compares a fetched tarball\u0027s inner and outer checksums with the repository\u0027s signed registry before writing it to a file or unpacking it. The standard output branch of \u003ccode\u003emix hex.package fetch\u003c/code\u003e writes the bytes returned by \u003ccode\u003eHex.SCM.fetch/3\u003c/code\u003e without that comparison, and \u003ccode\u003eHex.SCM.fetch/3\u003c/code\u003e doesn\u0027t verify freshly downloaded tarballs itself. When a mirror configured with \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e or \u003ccode\u003emirror_url\u003c/code\u003e serves altered bytes, the command prints them and exits successfully, while the same fetch with \u003ccode\u003e--output FILE\u003c/code\u003e fails with a checksum mismatch.\u003c/p\u003e\n\u003cp\u003eHex also prints diagnostics to standard output while the command runs: the body of a failed HTTP response from the update check or a registry fetch, and API warnings from the \u003ccode\u003ex-hex-message\u003c/code\u003e header. A mirror that answers one of those requests with an error whose body is a tar archive gets that archive written to standard output ahead of the verified tarball, so a pipeline that unpacks standard output extracts the mirror\u0027s files. This happens even when the checksum check then fails, because the archive is already on standard output.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 0.20.2 before 2.5.2.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Improper Validation of Integrity Check Value vulnerability in hexpm hex allows a package mirror to substitute the tarball that `mix hex.package fetch PACKAGE VERSION --output -` writes to standard output.\n\nHex compares a fetched tarball\u0027s inner and outer checksums with the repository\u0027s signed registry before writing it to a file or unpacking it. The standard output branch of `mix hex.package fetch` writes the bytes returned by `Hex.SCM.fetch/3` without that comparison, and `Hex.SCM.fetch/3` doesn\u0027t verify freshly downloaded tarballs itself. When a mirror configured with `HEX_MIRROR_URL` or `mirror_url` serves altered bytes, the command prints them and exits successfully, while the same fetch with `--output FILE` fails with a checksum mismatch.\n\nHex also prints diagnostics to standard output while the command runs: the body of a failed HTTP response from the update check or a registry fetch, and API warnings from the `x-hex-message` header. A mirror that answers one of those requests with an error whose body is a tar archive gets that archive written to standard output ahead of the verified tarball, so a pipeline that unpacks standard output extracts the mirror\u0027s files. This happens even when the checksum check then fails, because the archive is already on standard output.\n\nThis issue affects hex: from 0.20.2 before 2.5.2."
}
],
"value": "Improper Validation of Integrity Check Value vulnerability in hexpm hex allows a package mirror to substitute the tarball that mix hex.package fetch PACKAGE VERSION --output - writes to standard output.\n\nHex compares a fetched tarball\u0027s inner and outer checksums with the repository\u0027s signed registry before writing it to a file or unpacking it. The standard output branch of mix hex.package fetch writes the bytes returned by Hex.SCM.fetch/3 without that comparison, and Hex.SCM.fetch/3 doesn\u0027t verify freshly downloaded tarballs itself. When a mirror configured with HEX_MIRROR_URL or mirror_url serves altered bytes, the command prints them and exits successfully, while the same fetch with --output FILE fails with a checksum mismatch.\n\nHex also prints diagnostics to standard output while the command runs: the body of a failed HTTP response from the update check or a registry fetch, and API warnings from the x-hex-message header. A mirror that answers one of those requests with an error whose body is a tar archive gets that archive written to standard output ahead of the verified tarball, so a pipeline that unpacks standard output extracts the mirror\u0027s files. This happens even when the checksum check then fails, because the archive is already on standard output.\n\nThis issue affects hex: from 0.20.2 before 2.5.2."
}
],
"impacts": [
{
"capecId": "CAPEC-186",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eHex treats mirrors as untrusted and relies on registry signatures and checksums for package integrity. Scripts that pipe \u003ccode\u003emix hex.package fetch --output -\u003c/code\u003e into another tool, such as an unpacker, a vendoring step or an installer, receive whatever the mirror served, so the mirror operator can deliver modified package contents that downstream steps treat as the published release.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Hex treats mirrors as untrusted and relies on registry signatures and checksums for package integrity. Scripts that pipe `mix hex.package fetch --output -` into another tool, such as an unpacker, a vendoring step or an installer, receive whatever the mirror served, so the mirror operator can deliver modified package contents that downstream steps treat as the published release."
}
],
"value": "Hex treats mirrors as untrusted and relies on registry signatures and checksums for package integrity. Scripts that pipe mix hex.package fetch --output - into another tool, such as an unpacker, a vendoring step or an installer, receive whatever the mirror served, so the mirror operator can deliver modified package contents that downstream steps treat as the published release."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 6.9,
"baseSeverity": "MEDIUM",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "HIGH",
"userInteraction": "ACTIVE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:H/VA:N/SC:N/SI:H/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-354",
"description": "CWE-354 Improper Validation of Integrity Check Value",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-12T15:30:14.126Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/hexpm/hex/security/advisories/GHSA-98jr-23pm-vj7c"
},
{
"name": "EEF CNA record for CVE-2026-105280",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-105280.html"
},
{
"name": "OSV record EEF-CVE-2026-105280",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-105280"
},
{
"name": "Introducing commit d1f5a08 in hexpm/hex",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hex/commit/d1f5a085d9d4748e1f7d195e4bc39ebd77c28d9c"
},
{
"name": "Fix commit 8a14632 in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/8a14632ca6ba8a8afd2a3f2098bc267c13e13d93"
},
{
"name": "Fix commit 69489f8 in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/69489f8b74213f03db10169e6e5407e6d5fee512"
}
],
"solutions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. \u003ccode\u003emix hex.package fetch PACKAGE VERSION --output -\u003c/code\u003e then verifies the registry checksum before writing the tarball to standard output and prints its messages to standard error.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. `mix hex.package fetch PACKAGE VERSION --output -` then verifies the registry checksum before writing the tarball to standard output and prints its messages to standard error."
}
],
"value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. mix hex.package fetch PACKAGE VERSION --output - then verifies the registry checksum before writing the tarball to standard output and prints its messages to standard error."
}
],
"source": {
"discovery": "UNKNOWN"
},
"title": "Hex writes unverified package tarballs to standard output in mix hex.package fetch",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eWrite the tarball to a file with \u003ccode\u003e--output FILE\u003c/code\u003e, which verifies the checksums, and read that file instead of standard output.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Write the tarball to a file with `--output FILE`, which verifies the checksums, and read that file instead of standard output."
}
],
"value": "Write the tarball to a file with --output FILE, which verifies the checksums, and read that file instead of standard output."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eSet \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e to a server that returns modified bytes for \u003ccode\u003etarballs/decimal-2.4.1.tar\u003c/code\u003e and proxies everything else to repo.hex.pm.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003emix hex.package fetch decimal 2.4.1 --output -\u003c/code\u003e prints the modified bytes and exits 0.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003emix hex.package fetch decimal 2.4.1 --output decimal.tar\u003c/code\u003e fails with \u003ccode\u003eChecksum mismatch against registry (outer)\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Set `HEX_MIRROR_URL` to a server that returns modified bytes for `tarballs/decimal-2.4.1.tar` and proxies everything else to repo.hex.pm.\n2. `mix hex.package fetch decimal 2.4.1 --output -` prints the modified bytes and exits 0.\n3. `mix hex.package fetch decimal 2.4.1 --output decimal.tar` fails with `Checksum mismatch against registry (outer)`."
}
],
"value": "* Set HEX_MIRROR_URL to a server that returns modified bytes for tarballs/decimal-2.4.1.tar and proxies everything else to repo.hex.pm.\n* mix hex.package fetch decimal 2.4.1 --output - prints the modified bytes and exits 0.\n* mix hex.package fetch decimal 2.4.1 --output decimal.tar fails with Checksum mismatch against registry (outer)."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003emix hex.package fetch PACKAGE VERSION --output -\u003c/code\u003e calls \u003ccode\u003efetch_tarball!/3\u003c/code\u003e, which calls \u003ccode\u003eHex.SCM.fetch/3\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eFor a fresh download, \u003ccode\u003eHex.SCM.fetch/3\u003c/code\u003e writes the response body to the cache and returns it without comparing it to the registry checksum.\u003c/li\u003e\n\u003cli\u003eThe standard output branch passes those bytes to \u003ccode\u003eIO.binwrite/1\u003c/code\u003e. The file and \u003ccode\u003e--unpack\u003c/code\u003e branches call \u003ccode\u003everify_inner_checksum!/4\u003c/code\u003e and \u003ccode\u003everify_outer_checksum!/4\u003c/code\u003e first.\u003c/li\u003e\n\u003cli\u003eWhile the task runs, Hex prints messages with \u003ccode\u003eHex.Shell.info/1\u003c/code\u003e, which writes to standard output. \u003ccode\u003eHex.Utils.print_error_result/1\u003c/code\u003e prints the body of an error response that way; the update checker calls it when its request for \u003ccode\u003einstalls/hex-1.x.csv\u003c/code\u003e fails, and \u003ccode\u003eHex.Registry.Server\u003c/code\u003e calls it when a registry fetch fails and the cached registry is used. \u003ccode\u003eHex.HTTP\u003c/code\u003e prints the \u003ccode\u003ex-hex-message\u003c/code\u003e warning of each response the same way. These are printed before the tarball is written, so the mirror chooses bytes that come before the tarball on standard output.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. `mix hex.package fetch PACKAGE VERSION --output -` calls `fetch_tarball!/3`, which calls `Hex.SCM.fetch/3`.\n2. For a fresh download, `Hex.SCM.fetch/3` writes the response body to the cache and returns it without comparing it to the registry checksum.\n3. The standard output branch passes those bytes to `IO.binwrite/1`. The file and `--unpack` branches call `verify_inner_checksum!/4` and `verify_outer_checksum!/4` first.\n4. While the task runs, Hex prints messages with `Hex.Shell.info/1`, which writes to standard output. `Hex.Utils.print_error_result/1` prints the body of an error response that way; the update checker calls it when its request for `installs/hex-1.x.csv` fails, and `Hex.Registry.Server` calls it when a registry fetch fails and the cached registry is used. `Hex.HTTP` prints the `x-hex-message` warning of each response the same way. These are printed before the tarball is written, so the mirror chooses bytes that come before the tarball on standard output."
}
],
"value": "* mix hex.package fetch PACKAGE VERSION --output - calls fetch_tarball!/3, which calls Hex.SCM.fetch/3.\n* For a fresh download, Hex.SCM.fetch/3 writes the response body to the cache and returns it without comparing it to the registry checksum.\n* The standard output branch passes those bytes to IO.binwrite/1. The file and --unpack branches call verify_inner_checksum!/4 and verify_outer_checksum!/4 first.\n* While the task runs, Hex prints messages with Hex.Shell.info/1, which writes to standard output. Hex.Utils.print_error_result/1 prints the body of an error response that way; the update checker calls it when its request for installs/hex-1.x.csv fails, and Hex.Registry.Server calls it when a registry fetch fails and the cached registry is used. Hex.HTTP prints the x-hex-message warning of each response the same way. These are printed before the tarball is written, so the mirror chooses bytes that come before the tarball on standard output."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-105280",
"datePublished": "2026-10-12T15:30:14.126Z",
"dateReserved": "2026-10-08T13:45:02.134Z",
"dateUpdated": "2026-10-16T17:37:21.498Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-96618 (GCVE-0-2026-96618)
Vulnerability from cvelistv5
Published
2026-10-12 15:29
Modified
2026-10-16 17:33
Severity ?
VLAI Severity ?
EPSS score ?
CWE
Summary
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.
This issue affects hex: from 0.11.2 before 2.5.2.
References
| URL | Tags | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-96618",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-16T17:33:32.316951Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-16T17:33:51.114Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hex.HTTP\u0027",
"\u0027Elixir.Hex.SCM\u0027",
"\u0027Elixir.Mix.Tasks.Hex.Docs\u0027"
],
"packageName": "hex",
"packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_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": "\u0027Elixir.Hex.HTTP\u0027:request/5"
},
{
"name": "\u0027Elixir.Hex.SCM\u0027:fetch/3"
},
{
"name": "\u0027Elixir.Hex.SCM\u0027:update/1"
},
{
"name": "\u0027Elixir.Mix.Tasks.Hex.Docs\u0027: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": [
"\u0027Elixir.Hex.HTTP\u0027",
"\u0027Elixir.Hex.SCM\u0027",
"\u0027Elixir.Mix.Tasks.Hex.Docs\u0027"
],
"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": "\u0027Elixir.Hex.HTTP\u0027:request/5"
},
{
"name": "\u0027Elixir.Hex.SCM\u0027:fetch/3"
},
{
"name": "\u0027Elixir.Hex.SCM\u0027:update/1"
},
{
"name": "\u0027Elixir.Mix.Tasks.Hex.Docs\u0027: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": "\u003cp\u003eAffects users who fetch packages or docs through a mirror set with \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e, \u003ccode\u003eHEX_MIRROR\u003c/code\u003e or \u003ccode\u003emirror_url\u003c/code\u003e. Fetches from repo.hex.pm over HTTPS aren\u0027t affected unless TLS verification is broken. A trusted mirror (\u003ccode\u003eHEX_TRUSTED_MIRROR_URL\u003c/code\u003e) or a self-hosted repository can do the same, but Hex already trusts those with credentials.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"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\u0027t 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."
}
],
"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\u0027t 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."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
"versionEndExcluding": "2.5.2",
"versionStartIncluding": "0.11.2",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Peter Ullrich"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "analyst",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-10T13:00:30.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper 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 \u003ccode\u003emix deps.get\u003c/code\u003e, \u003ccode\u003emix deps.update\u003c/code\u003e, \u003ccode\u003emix hex.package fetch\u003c/code\u003e or \u003ccode\u003emix hex.docs fetch\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eHex treats a mirror configured with \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e, \u003ccode\u003eHEX_MIRROR\u003c/code\u003e or \u003ccode\u003emirror_url\u003c/code\u003e as untrusted: it doesn\u0027t send credentials to it, and it checks registry signatures and package checksums that the mirror can\u0027t forge. Several response paths consume memory or disk in proportion to what the mirror sends before any of those checks run.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eHex.HTTP.request/5\u003c/code\u003e reads the whole response body into memory and decompresses it with \u003ccode\u003e:zlib.gunzip/1\u003c/code\u003e whenever the response\u0027s \u003ccode\u003eContent-Encoding\u003c/code\u003e contains \u003ccode\u003egzip\u003c/code\u003e, with no limit on the compressed or decompressed size. Hex doesn\u0027t ask for compressed responses, so the server decides whether this step runs. It applies to registry, package, docs and API responses.\u003c/p\u003e\n\u003cp\u003ePackage 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 \u003ccode\u003edeps/\u0026lt;app\u0026gt;\u003c/code\u003e in 2.5.1 and into a staging directory on the development branch, before its checksums are compared with the registry.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003emix hex.docs fetch\u003c/code\u003e, and \u003ccode\u003emix hex.docs offline\u003c/code\u003e when docs are missing, write the docs tarball to disk and extract it with \u003ccode\u003e:mix_hex_erl_tar.extract/2\u003c/code\u003e without the 16 MiB compressed and 128 MiB decompressed limits that \u003ccode\u003emix_hex_tarball:unpack_docs/3\u003c/code\u003e applies. The signed registry doesn\u0027t cover docs tarballs, so nothing checks them.\u003c/p\u003e\n\u003cp\u003eRegistry responses are also decompressed before their signature is checked, in hex_core code that Hex vendors; that part is tracked in \u003ca href=\"https://github.com/hexpm/hex_core/security/advisories/GHSA-7c4r-cgf9-9jmq\" rel=\"noopener noreferrer\"\u003ehttps://github.com/hexpm/hex_core/security/advisories/GHSA-7c4r-cgf9-9jmq\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 0.11.2 before 2.5.2.\u003c/p\u003e"
},
{
"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\u0027t send credentials to it, and it checks registry signatures and package checksums that the mirror can\u0027t 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\u0027s `Content-Encoding` contains `gzip`, with no limit on the compressed or decompressed size. Hex doesn\u0027t 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/\u003capp\u003e` in 2.5.1 and into a staging directory on the development branch, before its checksums are compared with the registry.\n\n`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\u0027t cover docs tarballs, so nothing checks them.\n\nRegistry 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.\n\nThis issue affects hex: from 0.11.2 before 2.5.2."
}
],
"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\u0027t send credentials to it, and it checks registry signatures and package checksums that the mirror can\u0027t forge. Several response paths consume memory or disk in proportion to what the mirror sends before any of those checks run.\n\nHex.HTTP.request/5 reads the whole response body into memory and decompresses it with :zlib.gunzip/1 whenever the response\u0027s Content-Encoding contains gzip, with no limit on the compressed or decompressed size. Hex doesn\u0027t 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/\u003capp\u003e in 2.5.1 and into a staging directory on the development branch, before its checksums are compared with the registry.\n\nmix 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\u0027t cover docs tarballs, so nothing checks them.\n\nRegistry 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.\n\nThis issue affects hex: from 0.11.2 before 2.5.2."
}
],
"impacts": [
{
"capecId": "CAPEC-130",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eA mirror operator, or an attacker who controls the mirror\u0027s 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\u0027t need the repository\u0027s signing key.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "A mirror operator, or an attacker who controls the mirror\u0027s 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\u0027t need the repository\u0027s signing key."
}
],
"value": "A mirror operator, or an attacker who controls the mirror\u0027s 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\u0027t need the repository\u0027s 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": {
"dateUpdated": "2026-10-12T15:29:19.117Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"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": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. Hex then stops downloading a registry file, package, docs tarball or other file from a repository or mirror once it\u0027s 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.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"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\u0027s 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."
}
],
"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\u0027s 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."
}
],
"source": {
"discovery": "UNKNOWN"
},
"title": "Hex exhausts memory and disk on oversized or compressed responses from an untrusted mirror",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eFetch 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.\u003c/p\u003e"
},
{
"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": "\u003col\u003e\n\u003cli\u003eRun a loopback HTTP server that answers with a 65,255-byte gzip stream decompressing to 64 MiB and \u003ccode\u003eContent-Encoding: gzip\u003c/code\u003e. \u003ccode\u003eHex.HTTP.request(:get, url, %{}, nil)\u003c/code\u003e returns a 67,108,864-byte body; peak RSS was 220 MiB, against 91 MiB for an uncompressed control.\u003c/li\u003e\n\u003cli\u003eServe a 17 MiB file as a package tarball. \u003ccode\u003eHex.Repo.get_tarball/3\u003c/code\u003e returns all of it, and only the following unpack fails with \u003ccode\u003e{:tarball, :too_big}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eServe a 131,574-byte docs tarball containing a 129 MiB file and set \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e to the server. \u003ccode\u003emix hex.docs fetch probe 1.0.0\u003c/code\u003e writes the 129 MiB file and reports success.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Run a loopback HTTP server that answers with a 65,255-byte gzip stream decompressing to 64 MiB and `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.\n2. Serve a 17 MiB file as a package tarball. `Hex.Repo.get_tarball/3` returns all of it, and only the following unpack fails with `{:tarball, :too_big}`.\n3. Serve a 131,574-byte docs tarball containing a 129 MiB file and set `HEX_MIRROR_URL` to the server. `mix hex.docs fetch probe 1.0.0` writes the 129 MiB file and reports success."
}
],
"value": "* Run a loopback HTTP server that answers with a 65,255-byte gzip stream decompressing to 64 MiB and 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.\n* Serve a 17 MiB file as a package tarball. Hex.Repo.get_tarball/3 returns all of it, and only the following unpack fails with {:tarball, :too_big}.\n* Serve a 131,574-byte docs tarball containing a 129 MiB file and set HEX_MIRROR_URL to the server. mix hex.docs fetch probe 1.0.0 writes the 129 MiB file and reports success."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003eHex.HTTP.request/5\u003c/code\u003e calls \u003ccode\u003e:httpc.request/5\u003c/code\u003e with \u003ccode\u003ebody_format: :binary\u003c/code\u003e, so the whole body is held in memory, and then calls \u003ccode\u003e:zlib.gunzip/1\u003c/code\u003e when the \u003ccode\u003econtent-encoding\u003c/code\u003e header contains \u003ccode\u003egzip\u003c/code\u003e. Hex doesn\u0027t send \u003ccode\u003eAccept-Encoding\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eWhen \u003ccode\u003emirror_url\u003c/code\u003e is set, \u003ccode\u003eHex.Repo\u003c/code\u003e sends registry, tarball and docs requests to the mirror and marks it untrusted, so no credentials are sent to it.\u003c/li\u003e\n\u003cli\u003eHex 2.5.1: \u003ccode\u003eHex.SCM.fetch/3\u003c/code\u003e writes the body returned by \u003ccode\u003eHex.Repo.get_tarball/3\u003c/code\u003e to the cache. \u003ccode\u003eHex.SCM.update/1\u003c/code\u003e deletes \u003ccode\u003edeps/\u0026lt;app\u0026gt;\u003c/code\u003e, unpacks the tarball into it with \u003ccode\u003eHex.Tar.unpack!/2\u003c/code\u003e and compares the inner and outer checksums afterwards. Development branch: \u003ccode\u003eHex.Repo.get_tarball_to_file/4\u003c/code\u003e 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.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eMix.Tasks.Hex.Docs\u003c/code\u003e writes the body returned by \u003ccode\u003eHex.Repo.get_docs/3\u003c/code\u003e to disk and calls \u003ccode\u003e:mix_hex_erl_tar.extract/2\u003c/code\u003e with \u003ccode\u003e[:compressed, cwd: ...]\u003c/code\u003e and no \u003ccode\u003emax_size\u003c/code\u003e, while \u003ccode\u003emix_hex_tarball:unpack_docs/3\u003c/code\u003e enforces a 16 MiB compressed and 128 MiB decompressed limit.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. `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\u0027t send `Accept-Encoding`.\n2. When `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.\n3. Hex 2.5.1: `Hex.SCM.fetch/3` writes the body returned by `Hex.Repo.get_tarball/3` to the cache. `Hex.SCM.update/1` deletes `deps/\u003capp\u003e`, 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.\n4. `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."
}
],
"value": "* 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\u0027t send Accept-Encoding.\n* When 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.\n* Hex 2.5.1: Hex.SCM.fetch/3 writes the body returned by Hex.Repo.get_tarball/3 to the cache. Hex.SCM.update/1 deletes deps/\u003capp\u003e, 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.\n* 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."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-96618",
"datePublished": "2026-10-12T15:29:19.117Z",
"dateReserved": "2026-10-10T13:00:02.352Z",
"dateUpdated": "2026-10-16T17:33:51.114Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-103503 (GCVE-0-2026-103503)
Vulnerability from cvelistv5
Published
2026-10-12 15:17
Modified
2026-10-16 17:32
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-295 - Improper Certificate Validation
Summary
Improper Certificate Validation vulnerability in hexpm hex allows an attacker who can intercept HTTPS connections, and holds a certificate for a Hex host issued by a CA in Hex's bundled CA store, to impersonate that host to users who set cacerts_path to a CA store that leaves that CA out.
cacerts_path (HEX_CACERTS_PATH) is documented as the path to the CA certificate store, used instead of the CA bundle that ships with Hex. Hex.HTTP.SSL.ssl_opts/1 passes the configured store as the cacerts option, but builds the partial_chain callback from the bundled store whether cacerts_path is set or not. Hex.HTTP.SSL.partial_chain/2 accepts a server chain when any certificate in it has the same public key as a bundled CA certificate, so a chain that leads to a bundled CA passes verification although the configured store doesn't contain that CA. Hostname verification still applies.
This issue affects hex: from 0.20.2 before 2.5.2.
References
| URL | Tags | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-103503",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-16T17:30:53.768172Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-16T17:32:45.735Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hex.HTTP.SSL\u0027"
],
"packageName": "hex",
"packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
"product": "hex",
"programFiles": [
"lib/hex/http/ssl.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hex.HTTP.SSL\u0027:ssl_opts/1"
},
{
"name": "\u0027Elixir.Hex.HTTP.SSL\u0027:partial_chain/2"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2.5.2",
"status": "affected",
"version": "0.20.2",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hex.HTTP.SSL\u0027"
],
"packageName": "hexpm/hex",
"packageURL": "pkg:github/hexpm/hex",
"product": "hex",
"programFiles": [
"lib/hex/http/ssl.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hex.HTTP.SSL\u0027:ssl_opts/1"
},
{
"name": "\u0027Elixir.Hex.HTTP.SSL\u0027:partial_chain/2"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"changes": [
{
"at": "5f5a2136c7cf5991b6e8a30d645ca9f8e74b0785",
"status": "unaffected"
},
{
"at": "6232a6498015e559743bec4f4fbec9138837cec8",
"status": "unaffected"
}
],
"lessThan": "*",
"status": "affected",
"version": "c9b26a46928bfcd546773571e09c1d05f4cd9fb7",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAffects users who set \u003ccode\u003ecacerts_path\u003c/code\u003e or \u003ccode\u003eHEX_CACERTS_PATH\u003c/code\u003e to a CA store that leaves out some of the CAs bundled with Hex. Users who don\u0027t set it trust the bundled store already and aren\u0027t affected.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Affects users who set `cacerts_path` or `HEX_CACERTS_PATH` to a CA store that leaves out some of the CAs bundled with Hex. Users who don\u0027t set it trust the bundled store already and aren\u0027t affected."
}
],
"value": "Affects users who set cacerts_path or HEX_CACERTS_PATH to a CA store that leaves out some of the CAs bundled with Hex. Users who don\u0027t set it trust the bundled store already and aren\u0027t affected."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
"versionEndExcluding": "2.5.2",
"versionStartIncluding": "0.20.2",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Peter Ullrich"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "analyst",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-10T12:59:48.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper Certificate Validation vulnerability in hexpm hex allows an attacker who can intercept HTTPS connections, and holds a certificate for a Hex host issued by a CA in Hex\u0027s bundled CA store, to impersonate that host to users who set \u003ccode\u003ecacerts_path\u003c/code\u003e to a CA store that leaves that CA out.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ecacerts_path\u003c/code\u003e (\u003ccode\u003eHEX_CACERTS_PATH\u003c/code\u003e) is documented as the path to the CA certificate store, used instead of the CA bundle that ships with Hex. \u003ccode\u003eHex.HTTP.SSL.ssl_opts/1\u003c/code\u003e passes the configured store as the \u003ccode\u003ecacerts\u003c/code\u003e option, but builds the \u003ccode\u003epartial_chain\u003c/code\u003e callback from the bundled store whether \u003ccode\u003ecacerts_path\u003c/code\u003e is set or not. \u003ccode\u003eHex.HTTP.SSL.partial_chain/2\u003c/code\u003e accepts a server chain when any certificate in it has the same public key as a bundled CA certificate, so a chain that leads to a bundled CA passes verification although the configured store doesn\u0027t contain that CA. Hostname verification still applies.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 0.20.2 before 2.5.2.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Improper Certificate Validation vulnerability in hexpm hex allows an attacker who can intercept HTTPS connections, and holds a certificate for a Hex host issued by a CA in Hex\u0027s bundled CA store, to impersonate that host to users who set `cacerts_path` to a CA store that leaves that CA out.\n\n`cacerts_path` (`HEX_CACERTS_PATH`) is documented as the path to the CA certificate store, used instead of the CA bundle that ships with Hex. `Hex.HTTP.SSL.ssl_opts/1` passes the configured store as the `cacerts` option, but builds the `partial_chain` callback from the bundled store whether `cacerts_path` is set or not. `Hex.HTTP.SSL.partial_chain/2` accepts a server chain when any certificate in it has the same public key as a bundled CA certificate, so a chain that leads to a bundled CA passes verification although the configured store doesn\u0027t contain that CA. Hostname verification still applies.\n\nThis issue affects hex: from 0.20.2 before 2.5.2."
}
],
"value": "Improper Certificate Validation vulnerability in hexpm hex allows an attacker who can intercept HTTPS connections, and holds a certificate for a Hex host issued by a CA in Hex\u0027s bundled CA store, to impersonate that host to users who set cacerts_path to a CA store that leaves that CA out.\n\ncacerts_path (HEX_CACERTS_PATH) is documented as the path to the CA certificate store, used instead of the CA bundle that ships with Hex. Hex.HTTP.SSL.ssl_opts/1 passes the configured store as the cacerts option, but builds the partial_chain callback from the bundled store whether cacerts_path is set or not. Hex.HTTP.SSL.partial_chain/2 accepts a server chain when any certificate in it has the same public key as a bundled CA certificate, so a chain that leads to a bundled CA passes verification although the configured store doesn\u0027t contain that CA. Hostname verification still applies.\n\nThis issue affects hex: from 0.20.2 before 2.5.2."
}
],
"impacts": [
{
"capecId": "CAPEC-94",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUsers set \u003ccode\u003ecacerts_path\u003c/code\u003e to limit which CAs can authenticate Hex\u0027s servers, for example to their organization\u0027s internal CA. Every CA in Hex\u0027s bundled store can still authenticate those servers, so a certificate mis-issued by one of those CAs, or obtained through compromised domain validation, lets an attacker on the network path impersonate hex.pm, repo.hex.pm, a self-hosted repository or a mirror. The attacker receives the API keys, OAuth tokens and repository keys Hex sends, and can alter API responses. Registry signatures and package checksums still prevent changes to registry contents and packages.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Users set `cacerts_path` to limit which CAs can authenticate Hex\u0027s servers, for example to their organization\u0027s internal CA. Every CA in Hex\u0027s bundled store can still authenticate those servers, so a certificate mis-issued by one of those CAs, or obtained through compromised domain validation, lets an attacker on the network path impersonate hex.pm, repo.hex.pm, a self-hosted repository or a mirror. The attacker receives the API keys, OAuth tokens and repository keys Hex sends, and can alter API responses. Registry signatures and package checksums still prevent changes to registry contents and packages."
}
],
"value": "Users set cacerts_path to limit which CAs can authenticate Hex\u0027s servers, for example to their organization\u0027s internal CA. Every CA in Hex\u0027s bundled store can still authenticate those servers, so a certificate mis-issued by one of those CAs, or obtained through compromised domain validation, lets an attacker on the network path impersonate hex.pm, repo.hex.pm, a self-hosted repository or a mirror. The attacker receives the API keys, OAuth tokens and repository keys Hex sends, and can alter API responses. Registry signatures and package checksums still prevent changes to registry contents and packages."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "HIGH",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 5.9,
"baseSeverity": "MEDIUM",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "ACTIVE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:A/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "LOW",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-295",
"description": "CWE-295 Improper Certificate Validation",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-12T15:17:45.545Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/hexpm/hex/security/advisories/GHSA-rw8q-hg7m-cxgr"
},
{
"name": "EEF CNA record for CVE-2026-103503",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-103503.html"
},
{
"name": "OSV record EEF-CVE-2026-103503",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-103503"
},
{
"name": "Introducing commit c9b26a4 in hexpm/hex",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hex/commit/c9b26a46928bfcd546773571e09c1d05f4cd9fb7"
},
{
"name": "Fix commit 5f5a213 in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/5f5a2136c7cf5991b6e8a30d645ca9f8e74b0785"
},
{
"name": "Fix commit 6232a64 in hexpm/hex",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/6232a6498015e559743bec4f4fbec9138837cec8"
}
],
"solutions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. With \u003ccode\u003ecacerts_path\u003c/code\u003e (\u003ccode\u003eHEX_CACERTS_PATH\u003c/code\u003e) set, Hex then only accepts server certificates that chain to a CA in that file, and it doesn\u0027t trust copies of CA certificates sent by the server.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. With `cacerts_path` (`HEX_CACERTS_PATH`) set, Hex then only accepts server certificates that chain to a CA in that file, and it doesn\u0027t trust copies of CA certificates sent by the server."
}
],
"value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. With cacerts_path (HEX_CACERTS_PATH) set, Hex then only accepts server certificates that chain to a CA in that file, and it doesn\u0027t trust copies of CA certificates sent by the server."
}
],
"source": {
"discovery": "UNKNOWN"
},
"title": "Hex trusts its bundled CA certificates when cacerts_path is set",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eThere\u0027s no Hex configuration that stops the bundled CAs from being trusted.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "There\u0027s no Hex configuration that stops the bundled CAs from being trusted."
}
],
"value": "There\u0027s no Hex configuration that stops the bundled CAs from being trusted."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eCreate a PEM file that contains only an unrelated test CA certificate and set \u003ccode\u003ecacerts_path\u003c/code\u003e to it.\u003c/li\u003e\n\u003cli\u003eOpen a TLS connection to hex.pm with the options from \u003ccode\u003eHex.HTTP.SSL.ssl_opts(\"https://hex.pm\")\u003c/code\u003e. The handshake succeeds, and the accepted chain leads to ISRG Root X2 cross-signed by ISRG Root X1, neither of which is in the PEM file.\u003c/li\u003e\n\u003cli\u003eRemove the \u003ccode\u003epartial_chain\u003c/code\u003e option and connect again. The handshake fails with \u003ccode\u003eunknown_ca\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Create a PEM file that contains only an unrelated test CA certificate and set `cacerts_path` to it.\n2. Open a TLS connection to hex.pm with the options from `Hex.HTTP.SSL.ssl_opts(\"https://hex.pm\")`. The handshake succeeds, and the accepted chain leads to ISRG Root X2 cross-signed by ISRG Root X1, neither of which is in the PEM file.\n3. Remove the `partial_chain` option and connect again. The handshake fails with `unknown_ca`."
}
],
"value": "* Create a PEM file that contains only an unrelated test CA certificate and set cacerts_path to it.\n* Open a TLS connection to hex.pm with the options from Hex.HTTP.SSL.ssl_opts(\"https://hex.pm\"). The handshake succeeds, and the accepted chain leads to ISRG Root X2 cross-signed by ISRG Root X1, neither of which is in the PEM file.\n* Remove the partial_chain option and connect again. The handshake fails with unknown_ca."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003eHex.HTTP.SSL.get_ca_certs/0\u003c/code\u003e returns the certificates from \u003ccode\u003ecacerts_path\u003c/code\u003e when it\u0027s set, and \u003ccode\u003essl_opts/1\u003c/code\u003e passes them as \u003ccode\u003ecacerts\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003essl_opts/1\u003c/code\u003e sets \u003ccode\u003epartial_chain\u003c/code\u003e to \u003ccode\u003e\u0026amp;partial_chain(Certs.cacerts(), \u0026amp;1)\u003c/code\u003e, which always uses the bundled store.\u003c/li\u003e\n\u003cli\u003eWhen the server\u0027s chain doesn\u0027t lead to a certificate in \u003ccode\u003ecacerts\u003c/code\u003e, OTP calls \u003ccode\u003epartial_chain\u003c/code\u003e with the chain. \u003ccode\u003epartial_chain/2\u003c/code\u003e returns \u003ccode\u003e{:trusted_ca, der}\u003c/code\u003e for the first certificate whose public key matches a bundled CA certificate, and OTP validates the chain with that certificate as the trust anchor.\u003c/li\u003e\n\u003cli\u003eChain validation and the hostname check from \u003ccode\u003ecustomize_hostname_check/1\u003c/code\u003e still run, so the attacker needs a certificate for the requested host that chains to a bundled CA.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. `Hex.HTTP.SSL.get_ca_certs/0` returns the certificates from `cacerts_path` when it\u0027s set, and `ssl_opts/1` passes them as `cacerts`.\n2. `ssl_opts/1` sets `partial_chain` to `\u0026partial_chain(Certs.cacerts(), \u00261)`, which always uses the bundled store.\n3. When the server\u0027s chain doesn\u0027t lead to a certificate in `cacerts`, OTP calls `partial_chain` with the chain. `partial_chain/2` returns `{:trusted_ca, der}` for the first certificate whose public key matches a bundled CA certificate, and OTP validates the chain with that certificate as the trust anchor.\n4. Chain validation and the hostname check from `customize_hostname_check/1` still run, so the attacker needs a certificate for the requested host that chains to a bundled CA."
}
],
"value": "* Hex.HTTP.SSL.get_ca_certs/0 returns the certificates from cacerts_path when it\u0027s set, and ssl_opts/1 passes them as cacerts.\n* ssl_opts/1 sets partial_chain to \u0026partial_chain(Certs.cacerts(), \u00261), which always uses the bundled store.\n* When the server\u0027s chain doesn\u0027t lead to a certificate in cacerts, OTP calls partial_chain with the chain. partial_chain/2 returns {:trusted_ca, der} for the first certificate whose public key matches a bundled CA certificate, and OTP validates the chain with that certificate as the trust anchor.\n* Chain validation and the hostname check from customize_hostname_check/1 still run, so the attacker needs a certificate for the requested host that chains to a bundled CA."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-103503",
"datePublished": "2026-10-12T15:17:45.545Z",
"dateReserved": "2026-10-08T15:45:02.372Z",
"dateUpdated": "2026-10-16T17:32:45.735Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-103502 (GCVE-0-2026-103502)
Vulnerability from cvelistv5
Published
2026-10-12 13:15
Modified
2026-10-16 16:58
Severity ?
VLAI Severity ?
EPSS score ?
Summary
Improper Certificate Validation vulnerability in hexpm hex_core allows an attacker who can intercept HTTPS connections to impersonate hex.pm, repo.hex.pm or any other repository or API server to programs that use hex_core's default HTTP adapter on Erlang/OTP 24, or on OTP 25 when the operating system's CA certificates can't be loaded.
hex_core:default_config/0 selects the built-in hex_http_httpc adapter. For an HTTPS request whose adapter config has no ssl option, hex_http_httpc:http_options/2 builds options that verify the server certificate against public_key:cacerts_get/0. When that call raises, the adapter prints a warning and sends the request without ssl options, so httpc uses the defaults of the ssl application, which before OTP 26 don't verify the server certificate. public_key:cacerts_get/0 doesn't exist on OTP 24, so every HTTPS request falls back there, and on OTP 25 it raises when no CA store is found. hex_core versions before 0.11.0 never set ssl options, so they don't verify the server certificate on OTP 25 and earlier whether or not a CA store is found.
The fallback was intended and documented, but the documentation didn't make clear that it meant no certificate verification. Since hex_core 0.8.2 the README has warned that the built-in httpc adapter "has insecure default settings" and recommended other HTTP clients, and the adapter prints "using default ssl options which are insecure" on every request that falls back. hex_core 0.11.0 added certificate verification with the changelog entry "Use secure SSL defaults for OTP 25+". Neither the README nor the warning says that the server certificate isn't verified, the README doesn't say which OTP versions the warning applies to, and on OTP 25 without CA certificates the warning tells users to upgrade to OTP 25 or later, which they already run. This advisory is published so that users who took those notices as general advice know their HTTPS requests weren't authenticated.
This issue affects hex_core: from 0.1.0 before 0.20.0.
References
| URL | Tags | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|||||||||||||||||
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-103502",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-16T16:55:40.731433Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-16T16:58:36.248Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"hex_http_httpc"
],
"packageName": "hex_core",
"packageURL": "pkg:hex/hex_core",
"product": "hex_core",
"programFiles": [
"src/hex_http_httpc.erl"
],
"programRoutines": [
{
"name": "hex_http_httpc:request/5"
},
{
"name": "hex_http_httpc:request_to_file/6"
}
],
"repo": "https://github.com/hexpm/hex_core",
"vendor": "hexpm",
"versions": [
{
"lessThan": "0.20.0",
"status": "affected",
"version": "0.1.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"hex_http_httpc"
],
"packageName": "hexpm/hex_core",
"packageURL": "pkg:github/hexpm/hex_core",
"product": "hex_core",
"programFiles": [
"src/hex_http_httpc.erl"
],
"programRoutines": [
{
"name": "hex_http_httpc:request/5"
},
{
"name": "hex_http_httpc:request_to_file/6"
}
],
"repo": "https://github.com/hexpm/hex_core",
"vendor": "hexpm",
"versions": [
{
"lessThan": "b7b3a42defb4ed8e20a5c68a4412ce68a95b7938",
"status": "affected",
"version": "fbb6d9de472e5edb8d6fb6b32a91cb266137892e",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAffects programs that use the \u003ccode\u003ehex_http_httpc\u003c/code\u003e adapter, the default in \u003ccode\u003ehex_core:default_config/0\u003c/code\u003e, without an \u003ccode\u003essl\u003c/code\u003e option in the adapter\u0027s \u003ccode\u003ehttp_options\u003c/code\u003e, on OTP 24, or on OTP 25 when \u003ccode\u003epublic_key:cacerts_get/0\u003c/code\u003e fails. hex_core versions before 0.11.0 are affected on OTP 25 and earlier whether or not the CA certificates load. On OTP 26 and later the \u003ccode\u003essl\u003c/code\u003e defaults verify the server certificate. Hex and rebar3 vendor hex_core but configure their own HTTP adapters, so they aren\u0027t affected.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Affects programs that use the `hex_http_httpc` adapter, the default in `hex_core:default_config/0`, without an `ssl` option in the adapter\u0027s `http_options`, on OTP 24, or on OTP 25 when `public_key:cacerts_get/0` fails. hex_core versions before 0.11.0 are affected on OTP 25 and earlier whether or not the CA certificates load. On OTP 26 and later the `ssl` defaults verify the server certificate. Hex and rebar3 vendor hex_core but configure their own HTTP adapters, so they aren\u0027t affected."
}
],
"value": "Affects programs that use the hex_http_httpc adapter, the default in hex_core:default_config/0, without an ssl option in the adapter\u0027s http_options, on OTP 24, or on OTP 25 when public_key:cacerts_get/0 fails. hex_core versions before 0.11.0 are affected on OTP 25 and earlier whether or not the CA certificates load. On OTP 26 and later the ssl defaults verify the server certificate. Hex and rebar3 vendor hex_core but configure their own HTTP adapters, so they aren\u0027t affected."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*",
"versionEndExcluding": "0.20.0",
"versionStartIncluding": "0.1.0",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Peter Ullrich"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "analyst",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-10T12:59:18.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper Certificate Validation vulnerability in hexpm hex_core allows an attacker who can intercept HTTPS connections to impersonate hex.pm, repo.hex.pm or any other repository or API server to programs that use hex_core\u0027s default HTTP adapter on Erlang/OTP 24, or on OTP 25 when the operating system\u0027s CA certificates can\u0027t be loaded.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ehex_core:default_config/0\u003c/code\u003e selects the built-in \u003ccode\u003ehex_http_httpc\u003c/code\u003e adapter. For an HTTPS request whose adapter config has no \u003ccode\u003essl\u003c/code\u003e option, \u003ccode\u003ehex_http_httpc:http_options/2\u003c/code\u003e builds options that verify the server certificate against \u003ccode\u003epublic_key:cacerts_get/0\u003c/code\u003e. When that call raises, the adapter prints a warning and sends the request without \u003ccode\u003essl\u003c/code\u003e options, so httpc uses the defaults of the \u003ccode\u003essl\u003c/code\u003e application, which before OTP 26 don\u0027t verify the server certificate. \u003ccode\u003epublic_key:cacerts_get/0\u003c/code\u003e doesn\u0027t exist on OTP 24, so every HTTPS request falls back there, and on OTP 25 it raises when no CA store is found. hex_core versions before 0.11.0 never set \u003ccode\u003essl\u003c/code\u003e options, so they don\u0027t verify the server certificate on OTP 25 and earlier whether or not a CA store is found.\u003c/p\u003e\n\u003cp\u003eThe fallback was intended and documented, but the documentation didn\u0027t make clear that it meant no certificate verification. Since hex_core 0.8.2 the README has warned that the built-in httpc adapter \"has insecure default settings\" and recommended other HTTP clients, and the adapter prints \"using default ssl options which are insecure\" on every request that falls back. hex_core 0.11.0 added certificate verification with the changelog entry \"Use secure SSL defaults for OTP 25+\". Neither the README nor the warning says that the server certificate isn\u0027t verified, the README doesn\u0027t say which OTP versions the warning applies to, and on OTP 25 without CA certificates the warning tells users to upgrade to OTP 25 or later, which they already run. This advisory is published so that users who took those notices as general advice know their HTTPS requests weren\u0027t authenticated.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex_core: from 0.1.0 before 0.20.0.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Improper Certificate Validation vulnerability in hexpm hex_core allows an attacker who can intercept HTTPS connections to impersonate hex.pm, repo.hex.pm or any other repository or API server to programs that use hex_core\u0027s default HTTP adapter on Erlang/OTP 24, or on OTP 25 when the operating system\u0027s CA certificates can\u0027t be loaded.\n\n`hex_core:default_config/0` selects the built-in `hex_http_httpc` adapter. For an HTTPS request whose adapter config has no `ssl` option, `hex_http_httpc:http_options/2` builds options that verify the server certificate against `public_key:cacerts_get/0`. When that call raises, the adapter prints a warning and sends the request without `ssl` options, so httpc uses the defaults of the `ssl` application, which before OTP 26 don\u0027t verify the server certificate. `public_key:cacerts_get/0` doesn\u0027t exist on OTP 24, so every HTTPS request falls back there, and on OTP 25 it raises when no CA store is found. hex_core versions before 0.11.0 never set `ssl` options, so they don\u0027t verify the server certificate on OTP 25 and earlier whether or not a CA store is found.\n\nThe fallback was intended and documented, but the documentation didn\u0027t make clear that it meant no certificate verification. Since hex_core 0.8.2 the README has warned that the built-in httpc adapter \"has insecure default settings\" and recommended other HTTP clients, and the adapter prints \"using default ssl options which are insecure\" on every request that falls back. hex_core 0.11.0 added certificate verification with the changelog entry \"Use secure SSL defaults for OTP 25+\". Neither the README nor the warning says that the server certificate isn\u0027t verified, the README doesn\u0027t say which OTP versions the warning applies to, and on OTP 25 without CA certificates the warning tells users to upgrade to OTP 25 or later, which they already run. This advisory is published so that users who took those notices as general advice know their HTTPS requests weren\u0027t authenticated.\n\nThis issue affects hex_core: from 0.1.0 before 0.20.0."
}
],
"value": "Improper Certificate Validation vulnerability in hexpm hex_core allows an attacker who can intercept HTTPS connections to impersonate hex.pm, repo.hex.pm or any other repository or API server to programs that use hex_core\u0027s default HTTP adapter on Erlang/OTP 24, or on OTP 25 when the operating system\u0027s CA certificates can\u0027t be loaded.\n\nhex_core:default_config/0 selects the built-in hex_http_httpc adapter. For an HTTPS request whose adapter config has no ssl option, hex_http_httpc:http_options/2 builds options that verify the server certificate against public_key:cacerts_get/0. When that call raises, the adapter prints a warning and sends the request without ssl options, so httpc uses the defaults of the ssl application, which before OTP 26 don\u0027t verify the server certificate. public_key:cacerts_get/0 doesn\u0027t exist on OTP 24, so every HTTPS request falls back there, and on OTP 25 it raises when no CA store is found. hex_core versions before 0.11.0 never set ssl options, so they don\u0027t verify the server certificate on OTP 25 and earlier whether or not a CA store is found.\n\nThe fallback was intended and documented, but the documentation didn\u0027t make clear that it meant no certificate verification. Since hex_core 0.8.2 the README has warned that the built-in httpc adapter \"has insecure default settings\" and recommended other HTTP clients, and the adapter prints \"using default ssl options which are insecure\" on every request that falls back. hex_core 0.11.0 added certificate verification with the changelog entry \"Use secure SSL defaults for OTP 25+\". Neither the README nor the warning says that the server certificate isn\u0027t verified, the README doesn\u0027t say which OTP versions the warning applies to, and on OTP 25 without CA certificates the warning tells users to upgrade to OTP 25 or later, which they already run. This advisory is published so that users who took those notices as general advice know their HTTPS requests weren\u0027t authenticated.\n\nThis issue affects hex_core: from 0.1.0 before 0.20.0."
}
],
"impacts": [
{
"capecId": "CAPEC-94",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAn attacker on the network path between a program using hex_core\u0027s default adapter and a Hex repository or API server completes the TLS handshake with any certificate. It receives the API keys, OAuth tokens and repository keys the program sends, and can alter API requests and responses. Registry signatures, which hex_core verifies by default, still prevent changes to registry contents. Hex and rebar3 aren\u0027t affected because they configure their own HTTP adapters.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "An attacker on the network path between a program using hex_core\u0027s default adapter and a Hex repository or API server completes the TLS handshake with any certificate. It receives the API keys, OAuth tokens and repository keys the program sends, and can alter API requests and responses. Registry signatures, which hex_core verifies by default, still prevent changes to registry contents. Hex and rebar3 aren\u0027t affected because they configure their own HTTP adapters."
}
],
"value": "An attacker on the network path between a program using hex_core\u0027s default adapter and a Hex repository or API server completes the TLS handshake with any certificate. It receives the API keys, OAuth tokens and repository keys the program sends, and can alter API requests and responses. Registry signatures, which hex_core verifies by default, still prevent changes to registry contents. Hex and rebar3 aren\u0027t affected because they configure their own HTTP adapters."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 5.9,
"baseSeverity": "MEDIUM",
"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:H/VI:L/VA:N/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "LOW",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-295",
"description": "CWE-295 Improper Certificate Validation",
"lang": "en",
"type": "CWE"
}
]
},
{
"descriptions": [
{
"cweId": "CWE-636",
"description": "CWE-636 Not Failing Securely (\u0027Failing Open\u0027)",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-12T13:15:29.040Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/hexpm/hex_core/security/advisories/GHSA-p26c-h8c2-rwqj"
},
{
"name": "EEF CNA record for CVE-2026-103502",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-103502.html"
},
{
"name": "OSV record EEF-CVE-2026-103502",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-103502"
},
{
"name": "Introducing commit fbb6d9d in hexpm/hex_core",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hex_core/commit/fbb6d9de472e5edb8d6fb6b32a91cb266137892e"
},
{
"name": "Fix commit b7b3a42 in hexpm/hex_core",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex_core/commit/b7b3a42defb4ed8e20a5c68a4412ce68a95b7938"
}
],
"solutions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUpgrade hex_core to 0.20.0 or later. On OTP 24, or on OTP 25 without the operating system\u0027s CA certificates, HTTPS requests through \u003ccode\u003ehex_http_httpc\u003c/code\u003e then fail with \u003ccode\u003e{error, {cacerts_unavailable, Reason}}\u003c/code\u003e unless the adapter config has \u003ccode\u003essl\u003c/code\u003e options.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Upgrade hex_core to 0.20.0 or later. On OTP 24, or on OTP 25 without the operating system\u0027s CA certificates, HTTPS requests through `hex_http_httpc` then fail with `{error, {cacerts_unavailable, Reason}}` unless the adapter config has `ssl` options."
}
],
"value": "Upgrade hex_core to 0.20.0 or later. On OTP 24, or on OTP 25 without the operating system\u0027s CA certificates, HTTPS requests through hex_http_httpc then fail with {error, {cacerts_unavailable, Reason}} unless the adapter config has ssl options."
}
],
"source": {
"discovery": "UNKNOWN"
},
"title": "hex_core\u0027s default HTTP adapter doesn\u0027t verify TLS certificates on OTP 24 and 25",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003ePass \u003ccode\u003essl\u003c/code\u003e options that verify the server certificate in the adapter config, for example \u003ccode\u003e{hex_http_httpc, #{http_options =\u0026gt; [{ssl, SslOpts}]}}\u003c/code\u003e, use another HTTP adapter, or run on OTP 26 or later.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Pass `ssl` options that verify the server certificate in the adapter config, for example `{hex_http_httpc, #{http_options =\u003e [{ssl, SslOpts}]}}`, use another HTTP adapter, or run on OTP 26 or later."
}
],
"value": "Pass ssl options that verify the server certificate in the adapter config, for example {hex_http_httpc, #{http_options =\u003e [{ssl, SslOpts}]}}, use another HTTP adapter, or run on OTP 26 or later."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eCompile \u003ccode\u003esrc/hex_http.erl\u003c/code\u003e and \u003ccode\u003esrc/hex_http_httpc.erl\u003c/code\u003e from hex_core main 4fbc5d1 in the \u003ccode\u003eerlang:24\u003c/code\u003e Docker image and run \u003ccode\u003ehex_http_httpc:request(get, \u0026lt;\u0026lt;\"https://self-signed.badssl.com/\"\u0026gt;\u0026gt;, #{}, undefined, #{profile =\u0026gt; default})\u003c/code\u003e after starting inets and ssl.\u003c/li\u003e\n\u003cli\u003eThe adapter prints \u003ccode\u003e[hex_http_httpc] using default ssl options which are insecure.\u003c/code\u003e and returns \u003ccode\u003e{ok, {200, Headers, Body}}\u003c/code\u003e from the server with the self-signed certificate.\u003c/li\u003e\n\u003cli\u003eIn the \u003ccode\u003eerlang:25\u003c/code\u003e image with an empty tmpfs mounted on \u003ccode\u003e/etc/ssl/certs\u003c/code\u003e, \u003ccode\u003epublic_key:cacerts_get/0\u003c/code\u003e raises and the request also returns \u003ccode\u003e{ok, {200, ...}}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eIn the \u003ccode\u003eerlang:25\u003c/code\u003e image with the CA store present (150 certificates), the same request fails with \u003ccode\u003e{error, {failed_connect, ...}}\u003c/code\u003e after a \u003ccode\u003eBad Certificate\u003c/code\u003e alert.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Compile `src/hex_http.erl` and `src/hex_http_httpc.erl` from hex_core main 4fbc5d1 in the `erlang:24` Docker image and run `hex_http_httpc:request(get, \u003c\u003c\"https://self-signed.badssl.com/\"\u003e\u003e, #{}, undefined, #{profile =\u003e default})` after starting inets and ssl.\n2. The adapter prints `[hex_http_httpc] using default ssl options which are insecure.` and returns `{ok, {200, Headers, Body}}` from the server with the self-signed certificate.\n3. In the `erlang:25` image with an empty tmpfs mounted on `/etc/ssl/certs`, `public_key:cacerts_get/0` raises and the request also returns `{ok, {200, ...}}`.\n4. In the `erlang:25` image with the CA store present (150 certificates), the same request fails with `{error, {failed_connect, ...}}` after a `Bad Certificate` alert."
}
],
"value": "* Compile src/hex_http.erl and src/hex_http_httpc.erl from hex_core main 4fbc5d1 in the erlang:24 Docker image and run hex_http_httpc:request(get, \u003c\u003c\"https://self-signed.badssl.com/\"\u003e\u003e, #{}, undefined, #{profile =\u003e default}) after starting inets and ssl.\n* The adapter prints [hex_http_httpc] using default ssl options which are insecure. and returns {ok, {200, Headers, Body}} from the server with the self-signed certificate.\n* In the erlang:25 image with an empty tmpfs mounted on /etc/ssl/certs, public_key:cacerts_get/0 raises and the request also returns {ok, {200, ...}}.\n* In the erlang:25 image with the CA store present (150 certificates), the same request fails with {error, {failed_connect, ...}} after a Bad Certificate alert."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003ehex_core:default_config/0\u003c/code\u003e sets \u003ccode\u003ehttp_adapter\u003c/code\u003e to \u003ccode\u003e{hex_http_httpc, #{profile =\u0026gt; default}}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ehex_http_httpc:request/5\u003c/code\u003e and \u003ccode\u003ehex_http_httpc:request_to_file/6\u003c/code\u003e build their httpc options with \u003ccode\u003ehttp_options/2\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eFor an \u003ccode\u003ehttps\u003c/code\u003e URI whose adapter config has no \u003ccode\u003essl\u003c/code\u003e entry in \u003ccode\u003ehttp_options\u003c/code\u003e, \u003ccode\u003ehttp_options/2\u003c/code\u003e adds \u003ccode\u003e{ssl, [{verify, verify_peer}, {cacerts, public_key:cacerts_get()}, ...]}\u003c/code\u003e inside a \u003ccode\u003etry\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eWhen \u003ccode\u003epublic_key:cacerts_get/0\u003c/code\u003e raises (\u003ccode\u003eundef\u003c/code\u003e on OTP 24; an error on OTP 25 when no CA store is found), the \u003ccode\u003ecatch\u003c/code\u003e prints a warning with \u003ccode\u003eio:format/1\u003c/code\u003e and returns the options without \u003ccode\u003essl\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003ehttpc then uses the \u003ccode\u003essl\u003c/code\u003e application\u0027s defaults, which are \u003ccode\u003everify_none\u003c/code\u003e before OTP 26, and completes the handshake with any certificate.\u003c/li\u003e\n\u003cli\u003ehex_core before 0.11.0 (e57b4fb) had no step 3: it never passed \u003ccode\u003essl\u003c/code\u003e options, so step 5 applied on every OTP version before 26.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. `hex_core:default_config/0` sets `http_adapter` to `{hex_http_httpc, #{profile =\u003e default}}`.\n2. `hex_http_httpc:request/5` and `hex_http_httpc:request_to_file/6` build their httpc options with `http_options/2`.\n3. For an `https` URI whose adapter config has no `ssl` entry in `http_options`, `http_options/2` adds `{ssl, [{verify, verify_peer}, {cacerts, public_key:cacerts_get()}, ...]}` inside a `try`.\n4. When `public_key:cacerts_get/0` raises (`undef` on OTP 24; an error on OTP 25 when no CA store is found), the `catch` prints a warning with `io:format/1` and returns the options without `ssl`.\n5. httpc then uses the `ssl` application\u0027s defaults, which are `verify_none` before OTP 26, and completes the handshake with any certificate.\n6. hex_core before 0.11.0 (e57b4fb) had no step 3: it never passed `ssl` options, so step 5 applied on every OTP version before 26."
}
],
"value": "* hex_core:default_config/0 sets http_adapter to {hex_http_httpc, #{profile =\u003e default}}.\n* hex_http_httpc:request/5 and hex_http_httpc:request_to_file/6 build their httpc options with http_options/2.\n* For an https URI whose adapter config has no ssl entry in http_options, http_options/2 adds {ssl, [{verify, verify_peer}, {cacerts, public_key:cacerts_get()}, ...]} inside a try.\n* When public_key:cacerts_get/0 raises (undef on OTP 24; an error on OTP 25 when no CA store is found), the catch prints a warning with io:format/1 and returns the options without ssl.\n* httpc then uses the ssl application\u0027s defaults, which are verify_none before OTP 26, and completes the handshake with any certificate.\n* hex_core before 0.11.0 (e57b4fb) had no step 3: it never passed ssl options, so step 5 applied on every OTP version before 26."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-103502",
"datePublished": "2026-10-12T13:15:29.040Z",
"dateReserved": "2026-10-09T16:45:02.344Z",
"dateUpdated": "2026-10-16T16:58:36.248Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-86698 (GCVE-0-2026-86698)
Vulnerability from cvelistv5
Published
2026-09-22 15:51
Modified
2026-09-22 18:09
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-613 - Insufficient Session Expiration
Summary
Insufficient Session Expiration vulnerability in OAuth token issuance in hexpm hexpm allows a user whose organization membership or session has ended to keep reading the organization's private packages and their documentation tarballs via a retained refresh token.
generate_refresh_token/4 in lib/hexpm/oauth/jwt.ex signs the refresh token with the same iss, aud and scope claims as the access token, so it carries the same repository:<org> scopes. The CDN service that serves private repositories verifies the signature and time claims and then authorizes from the scope claim, with no database lookup and no way to tell the two token kinds apart. Removing a member or revoking a session therefore takes effect at the CDN only when the 30 day refresh token expires, instead of after the 30 minute access token lifetime. Access is read-only and limited to organizations the account belonged to when the token was granted.
This issue affects hex.pm: from 2025-10-10 before 2026-09-22.
References
| URL | Tags | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|||||||||||||||||
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-86698",
"options": [
{
"Exploitation": "poc"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-09-22T18:05:56.100145Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-09-22T18:09:08.676Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"references": [
{
"tags": [
"exploit"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-vmw7-7g2w-gw2f"
}
],
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://hex.pm",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hexpm.OAuth.JWT\u0027",
"\u0027Elixir.Hexpm.OAuth.Tokens\u0027"
],
"packageName": "hex.pm",
"product": "hexpm",
"programFiles": [
"lib/hexpm/oauth/jwt.ex",
"lib/hexpm/oauth/tokens.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hexpm.OAuth.JWT\u0027:generate_refresh_token/4"
},
{
"name": "\u0027Elixir.Hexpm.OAuth.Tokens\u0027:create_for_user/6"
}
],
"vendor": "hexpm",
"versions": [
{
"lessThan": "2026-09-22",
"status": "affected",
"version": "2025-10-10",
"versionType": "date"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hexpm.OAuth.JWT\u0027",
"\u0027Elixir.Hexpm.OAuth.Tokens\u0027"
],
"packageName": "hexpm/hexpm",
"packageURL": "pkg:github/hexpm/hexpm",
"product": "hexpm",
"programFiles": [
"lib/hexpm/oauth/jwt.ex",
"lib/hexpm/oauth/tokens.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hexpm.OAuth.JWT\u0027:generate_refresh_token/4"
},
{
"name": "\u0027Elixir.Hexpm.OAuth.Tokens\u0027:create_for_user/6"
}
],
"repo": "https://github.com/hexpm/hexpm",
"vendor": "hexpm",
"versions": [
{
"lessThan": "db58cf3a86c7639708ef52ff86de0b4f938e6360",
"status": "affected",
"version": "650faa03af511e74c1b3b49ec12d35666b6984c4",
"versionType": "git"
}
]
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Peter Ullrich"
},
{
"lang": "en",
"type": "analyst",
"value": "Jonatan M\u00e4nnchen / EEF"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson"
}
],
"dateAssigned": "2026-09-21T12:06:25.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eInsufficient Session Expiration vulnerability in OAuth token issuance in hexpm hexpm allows a user whose organization membership or session has ended to keep reading the organization\u0027s private packages and their documentation tarballs via a retained refresh token.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003egenerate_refresh_token/4\u003c/code\u003e in \u003ccode\u003elib/hexpm/oauth/jwt.ex\u003c/code\u003e signs the refresh token with the same \u003ccode\u003eiss\u003c/code\u003e, \u003ccode\u003eaud\u003c/code\u003e and \u003ccode\u003escope\u003c/code\u003e claims as the access token, so it carries the same \u003ccode\u003erepository:\u0026lt;org\u0026gt;\u003c/code\u003e scopes. The CDN service that serves private repositories verifies the signature and time claims and then authorizes from the \u003ccode\u003escope\u003c/code\u003e claim, with no database lookup and no way to tell the two token kinds apart. Removing a member or revoking a session therefore takes effect at the CDN only when the 30 day refresh token expires, instead of after the 30 minute access token lifetime. Access is read-only and limited to organizations the account belonged to when the token was granted.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex.pm: from 2025-10-10 before 2026-09-22.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Insufficient Session Expiration vulnerability in OAuth token issuance in hexpm hexpm allows a user whose organization membership or session has ended to keep reading the organization\u0027s private packages and their documentation tarballs via a retained refresh token.\n\n`generate_refresh_token/4` in `lib/hexpm/oauth/jwt.ex` signs the refresh token with the same `iss`, `aud` and `scope` claims as the access token, so it carries the same `repository:\u003corg\u003e` scopes. The CDN service that serves private repositories verifies the signature and time claims and then authorizes from the `scope` claim, with no database lookup and no way to tell the two token kinds apart. Removing a member or revoking a session therefore takes effect at the CDN only when the 30 day refresh token expires, instead of after the 30 minute access token lifetime. Access is read-only and limited to organizations the account belonged to when the token was granted.\n\nThis issue affects hex.pm: from 2025-10-10 before 2026-09-22."
}
],
"value": "Insufficient Session Expiration vulnerability in OAuth token issuance in hexpm hexpm allows a user whose organization membership or session has ended to keep reading the organization\u0027s private packages and their documentation tarballs via a retained refresh token.\n\ngenerate_refresh_token/4 in lib/hexpm/oauth/jwt.ex signs the refresh token with the same iss, aud and scope claims as the access token, so it carries the same repository:\u003corg\u003e scopes. The CDN service that serves private repositories verifies the signature and time claims and then authorizes from the scope claim, with no database lookup and no way to tell the two token kinds apart. Removing a member or revoking a session therefore takes effect at the CDN only when the 30 day refresh token expires, instead of after the 30 minute access token lifetime. Access is read-only and limited to organizations the account belonged to when the token was granted.\n\nThis issue affects hex.pm: from 2025-10-10 before 2026-09-22."
}
],
"impacts": [
{
"capecId": "CAPEC-60",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eA user removed from an organization, or anyone holding a copy of a revoked session\u0027s refresh token, can keep downloading that organization\u0027s private packages and their documentation tarballs for up to 30 days after access was withdrawn.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "A user removed from an organization, or anyone holding a copy of a revoked session\u0027s refresh token, can keep downloading that organization\u0027s private packages and their documentation tarballs for up to 30 days after access was withdrawn."
}
],
"value": "A user removed from an organization, or anyone holding a copy of a revoked session\u0027s refresh token, can keep downloading that organization\u0027s private packages and their documentation tarballs for up to 30 days after access was withdrawn."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 2.3,
"baseSeverity": "LOW",
"privilegesRequired": "LOW",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "LOW",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "LOW",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-613",
"description": "CWE-613 Insufficient Session Expiration",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-09-22T15:51:57.998Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "Refresh tokens are accepted as private repository credentials at the CDN",
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-vmw7-7g2w-gw2f"
},
{
"name": "EEF CNA record for CVE-2026-86698",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-86698.html"
},
{
"name": "OSV record EEF-CVE-2026-86698",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-86698"
},
{
"name": "Introducing commit 650faa0 in hexpm/hexpm",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hexpm/commit/650faa03af511e74c1b3b49ec12d35666b6984c4"
},
{
"name": "Fix commit db58cf3 in hexpm/hexpm",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hexpm/commit/db58cf3a86c7639708ef52ff86de0b4f938e6360"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Refresh tokens accepted as private repository credentials at the CDN",
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eAuthenticate a CLI session with \u003ccode\u003emix hex.user auth\u003c/code\u003e for an account that is a member of an organization with private packages. \u003ccode\u003e~/.hex/hex.config\u003c/code\u003e then holds an access token and a refresh token.\u003c/li\u003e\n\u003cli\u003eCopy the refresh token out of that file.\u003c/li\u003e\n\u003cli\u003eRemove the account from the organization, or revoke the session.\u003c/li\u003e\n\u003cli\u003eRequest \u003ccode\u003ehttps://repo.hex.pm/repos/\u0026lt;org\u0026gt;/tarballs/\u0026lt;package\u0026gt;-\u0026lt;version\u0026gt;.tar\u003c/code\u003e with the header \u003ccode\u003eAuthorization: Bearer \u0026lt;refresh token\u0026gt;\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eThe CDN serves the tarball until the refresh token\u0027s 30 day expiry, while the access token from the same file stops working within 30 minutes.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Authenticate a CLI session with `mix hex.user auth` for an account that is a member of an organization with private packages. `~/.hex/hex.config` then holds an access token and a refresh token.\n2. Copy the refresh token out of that file.\n3. Remove the account from the organization, or revoke the session.\n4. Request `https://repo.hex.pm/repos/\u003corg\u003e/tarballs/\u003cpackage\u003e-\u003cversion\u003e.tar` with the header `Authorization: Bearer \u003crefresh token\u003e`.\n5. The CDN serves the tarball until the refresh token\u0027s 30 day expiry, while the access token from the same file stops working within 30 minutes."
}
],
"value": "* Authenticate a CLI session with mix hex.user auth for an account that is a member of an organization with private packages. ~/.hex/hex.config then holds an access token and a refresh token.\n* Copy the refresh token out of that file.\n* Remove the account from the organization, or revoke the session.\n* Request https://repo.hex.pm/repos/\u003corg\u003e/tarballs/\u003cpackage\u003e-\u003cversion\u003e.tar with the header Authorization: Bearer \u003crefresh token\u003e.\n* The CDN serves the tarball until the refresh token\u0027s 30 day expiry, while the access token from the same file stops working within 30 minutes."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e1. Token issuance\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eHexpm.OAuth.JWT.generate_refresh_token/4\u003c/code\u003e builds its claim set from the same template as \u003ccode\u003egenerate_access_token/4\u003c/code\u003e, changing only \u003ccode\u003eexp\u003c/code\u003e, so a refresh token carries the \u003ccode\u003escope\u003c/code\u003e claim of the access token it is issued with. hexpm itself never reads that claim: the refresh grant resolves scopes from the database, and \u003ccode\u003eHexpm.OAuth.Tokens.lookup/3\u003c/code\u003e matches an access token on the \u003ccode\u003ejti\u003c/code\u003e column and a refresh token on \u003ccode\u003erefresh_jti\u003c/code\u003e, so hexpm\u0027s API rejects a refresh token presented as a bearer credential.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Edge authorization\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThe CDN service in front of private repositories consults no database. It verifies the ES256 signature, \u003ccode\u003eiss\u003c/code\u003e, \u003ccode\u003eaud\u003c/code\u003e, \u003ccode\u003enbf\u003c/code\u003e and \u003ccode\u003eexp\u003c/code\u003e, then grants access when \u003ccode\u003escope\u003c/code\u003e contains \u003ccode\u003erepository:\u0026lt;org\u0026gt;\u003c/code\u003e. A refresh token sent as \u003ccode\u003eAuthorization: Bearer\u003c/code\u003e passes every check and is authorized like an access token.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Effect\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eMember removal, session revocation and an organization single sign-on session lapsing rely on the 30 minute access token lifetime to take effect at the CDN, because the CDN has no revocation list. A copy of the refresh token extends that window to its 30 day lifetime.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "**1. Token issuance**\n\n`Hexpm.OAuth.JWT.generate_refresh_token/4` builds its claim set from the same template as `generate_access_token/4`, changing only `exp`, so a refresh token carries the `scope` claim of the access token it is issued with. hexpm itself never reads that claim: the refresh grant resolves scopes from the database, and `Hexpm.OAuth.Tokens.lookup/3` matches an access token on the `jti` column and a refresh token on `refresh_jti`, so hexpm\u0027s API rejects a refresh token presented as a bearer credential.\n\n**2. Edge authorization**\n\nThe CDN service in front of private repositories consults no database. It verifies the ES256 signature, `iss`, `aud`, `nbf` and `exp`, then grants access when `scope` contains `repository:\u003corg\u003e`. A refresh token sent as `Authorization: Bearer` passes every check and is authorized like an access token.\n\n**3. Effect**\n\nMember removal, session revocation and an organization single sign-on session lapsing rely on the 30 minute access token lifetime to take effect at the CDN, because the CDN has no revocation list. A copy of the refresh token extends that window to its 30 day lifetime."
}
],
"value": "1. Token issuance\n\nHexpm.OAuth.JWT.generate_refresh_token/4 builds its claim set from the same template as generate_access_token/4, changing only exp, so a refresh token carries the scope claim of the access token it is issued with. hexpm itself never reads that claim: the refresh grant resolves scopes from the database, and Hexpm.OAuth.Tokens.lookup/3 matches an access token on the jti column and a refresh token on refresh_jti, so hexpm\u0027s API rejects a refresh token presented as a bearer credential.\n\n2. Edge authorization\n\nThe CDN service in front of private repositories consults no database. It verifies the ES256 signature, iss, aud, nbf and exp, then grants access when scope contains repository:\u003corg\u003e. A refresh token sent as Authorization: Bearer passes every check and is authorized like an access token.\n\n3. Effect\n\nMember removal, session revocation and an organization single sign-on session lapsing rely on the 30 minute access token lifetime to take effect at the CDN, because the CDN has no revocation list. A copy of the refresh token extends that window to its 30 day lifetime."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-86698",
"datePublished": "2026-09-22T15:51:57.998Z",
"dateReserved": "2026-09-17T14:30:02.046Z",
"dateUpdated": "2026-09-22T18:09:08.676Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-75554 (GCVE-0-2026-75554)
Vulnerability from cvelistv5
Published
2026-08-24 20:15
Modified
2026-08-25 19:53
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-613 - Insufficient Session Expiration
Summary
Insufficient Session Expiration vulnerability in the OAuth token refresh grant in hexpm hexpm allows a user removed from an organization to keep reading its private packages.
expand_repositories_scope/3 in lib/hexpm/permissions.ex only rewrites the literal repositories scope, so an explicitly granted repository:<org> or docs:<org> scope passes through it untouched. The refresh grant re-derives a new token from the stored granted_scopes, which holds that expanded form, so every refresh reproduces the organization scope without revisiting membership. Because both CDN edges authorize repository access from the token claim without querying the database, an account removed from an organization retains read access for as long as it keeps refreshing, bounded by the 30 day refresh token lifetime rather than the 30 minute access token lifetime.
This issue affects hex.pm: from 2025-10-10 before 2026-08-24.
References
| URL | Tags | |
|---|---|---|
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-75554",
"options": [
{
"Exploitation": "poc"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-08-25T19:52:35.290936Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-08-25T19:53:55.535Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"references": [
{
"tags": [
"exploit"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-24ww-j3f4-p49c"
}
],
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://hex.pm",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hexpm.Permissions\u0027",
"\u0027Elixir.Hexpm.OAuth.Tokens\u0027",
"\u0027Elixir.HexpmWeb.API.OAuthController\u0027"
],
"packageName": "hex.pm",
"product": "hexpm",
"programFiles": [
"lib/hexpm/permissions.ex",
"lib/hexpm/oauth/tokens.ex",
"lib/hexpm_web/controllers/api/oauth_controller.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hexpm.Permissions\u0027:expand_repositories_scope/2"
},
{
"name": "\u0027Elixir.Hexpm.OAuth.Tokens\u0027:create_for_user/6"
},
{
"name": "\u0027Elixir.HexpmWeb.API.OAuthController\u0027:handle_refresh_token_grant/2"
}
],
"vendor": "hexpm",
"versions": [
{
"lessThan": "2026-08-24",
"status": "affected",
"version": "2025-10-10",
"versionType": "date"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hexpm.Permissions\u0027",
"\u0027Elixir.Hexpm.OAuth.Tokens\u0027",
"\u0027Elixir.HexpmWeb.API.OAuthController\u0027"
],
"packageName": "hexpm/hexpm",
"packageURL": "pkg:github/hexpm/hexpm",
"product": "hexpm",
"programFiles": [
"lib/hexpm/permissions.ex",
"lib/hexpm/oauth/tokens.ex",
"lib/hexpm_web/controllers/api/oauth_controller.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hexpm.Permissions\u0027:expand_repositories_scope/2"
},
{
"name": "\u0027Elixir.Hexpm.OAuth.Tokens\u0027:create_for_user/6"
},
{
"name": "\u0027Elixir.HexpmWeb.API.OAuthController\u0027:handle_refresh_token_grant/2"
}
],
"repo": "https://github.com/hexpm/hexpm",
"vendor": "hexpm",
"versions": [
{
"lessThan": "50cffd206490c49c92d7b63c8505949682abedb0",
"status": "affected",
"version": "650faa03af511e74c1b3b49ec12d35666b6984c4",
"versionType": "git"
}
]
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eInsufficient Session Expiration vulnerability in the OAuth token refresh grant in hexpm hexpm allows a user removed from an organization to keep reading its private packages.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eexpand_repositories_scope/3\u003c/code\u003e in \u003ccode\u003elib/hexpm/permissions.ex\u003c/code\u003e only rewrites the literal \u003ccode\u003erepositories\u003c/code\u003e scope, so an explicitly granted \u003ccode\u003erepository:\u0026lt;org\u0026gt;\u003c/code\u003e or \u003ccode\u003edocs:\u0026lt;org\u0026gt;\u003c/code\u003e scope passes through it untouched. The refresh grant re-derives a new token from the stored \u003ccode\u003egranted_scopes\u003c/code\u003e, which holds that expanded form, so every refresh reproduces the organization scope without revisiting membership. Because both CDN edges authorize repository access from the token claim without querying the database, an account removed from an organization retains read access for as long as it keeps refreshing, bounded by the 30 day refresh token lifetime rather than the 30 minute access token lifetime.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex.pm: from 2025-10-10 before 2026-08-24.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Insufficient Session Expiration vulnerability in the OAuth token refresh grant in hexpm hexpm allows a user removed from an organization to keep reading its private packages.\n\n`expand_repositories_scope/3` in `lib/hexpm/permissions.ex` only rewrites the literal `repositories` scope, so an explicitly granted `repository:\u003corg\u003e` or `docs:\u003corg\u003e` scope passes through it untouched. The refresh grant re-derives a new token from the stored `granted_scopes`, which holds that expanded form, so every refresh reproduces the organization scope without revisiting membership. Because both CDN edges authorize repository access from the token claim without querying the database, an account removed from an organization retains read access for as long as it keeps refreshing, bounded by the 30 day refresh token lifetime rather than the 30 minute access token lifetime.\n\nThis issue affects hex.pm: from 2025-10-10 before 2026-08-24."
}
],
"value": "Insufficient Session Expiration vulnerability in the OAuth token refresh grant in hexpm hexpm allows a user removed from an organization to keep reading its private packages.\n\nexpand_repositories_scope/3 in lib/hexpm/permissions.ex only rewrites the literal repositories scope, so an explicitly granted repository:\u003corg\u003e or docs:\u003corg\u003e scope passes through it untouched. The refresh grant re-derives a new token from the stored granted_scopes, which holds that expanded form, so every refresh reproduces the organization scope without revisiting membership. Because both CDN edges authorize repository access from the token claim without querying the database, an account removed from an organization retains read access for as long as it keeps refreshing, bounded by the 30 day refresh token lifetime rather than the 30 minute access token lifetime.\n\nThis issue affects hex.pm: from 2025-10-10 before 2026-08-24."
}
],
"impacts": [
{
"capecId": "CAPEC-122",
"descriptions": [
{
"lang": "en",
"value": "CAPEC-122 Privilege Abuse"
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 2.3,
"baseSeverity": "LOW",
"privilegesRequired": "LOW",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "LOW",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "LOW",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-613",
"description": "CWE-613 Insufficient Session Expiration",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-08-24T20:15:05.791Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-24ww-j3f4-p49c"
},
{
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-75554.html"
},
{
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-75554"
},
{
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hexpm/commit/50cffd206490c49c92d7b63c8505949682abedb0"
}
],
"source": {
"discovery": "INTERNAL"
},
"title": "Explicit organization scopes survive token refresh after membership ends"
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-75554",
"datePublished": "2026-08-24T20:15:05.791Z",
"dateReserved": "2026-08-18T06:00:02.163Z",
"dateUpdated": "2026-08-25T19:53:55.535Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-75542 (GCVE-0-2026-75542)
Vulnerability from cvelistv5
Published
2026-08-24 20:14
Modified
2026-08-25 19:53
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-863 - Incorrect Authorization
Summary
Incorrect Authorization vulnerability in the OAuth token endpoint in hexpm hexpm allows an API key holding the repositories permission to read another organization's private packages.
When an API key is exchanged for a token through the OAuth client_credentials grant, validate_scopes_against_key/2 in lib/hexpm_web/controllers/api/oauth_controller.ex admits a requested scope whenever the key carries the repositories permission and the scope string begins with repository:. The organization name is never resolved against the principal, and expand_repositories_scope/3 only rewrites the literal repositories scope, so an explicit repository:<name> passes through untouched. Both CDN edges authorize repository access from the token claim without querying the database, so the minted token is read access to that organization's private packages until it expires.
This issue affects hex.pm: from 2025-10-18 before 2026-08-24.
References
| URL | Tags | |
|---|---|---|
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-75542",
"options": [
{
"Exploitation": "poc"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-08-25T19:53:10.499908Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-08-25T19:53:26.196Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"references": [
{
"tags": [
"exploit"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-rfx8-w654-8cpr"
}
],
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://hex.pm",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.HexpmWeb.API.OAuthController\u0027",
"\u0027Elixir.Hexpm.OAuth.Tokens\u0027"
],
"packageName": "hex.pm",
"product": "hexpm",
"programFiles": [
"lib/hexpm_web/controllers/api/oauth_controller.ex",
"lib/hexpm/oauth/tokens.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.HexpmWeb.API.OAuthController\u0027:validate_scopes_against_key/2"
},
{
"name": "\u0027Elixir.Hexpm.OAuth.Tokens\u0027:create_for_org/6"
}
],
"vendor": "hexpm",
"versions": [
{
"lessThan": "2026-08-24",
"status": "affected",
"version": "2025-10-18",
"versionType": "date"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.HexpmWeb.API.OAuthController\u0027",
"\u0027Elixir.Hexpm.OAuth.Tokens\u0027"
],
"packageName": "hexpm/hexpm",
"packageURL": "pkg:github/hexpm/hexpm",
"product": "hexpm",
"programFiles": [
"lib/hexpm_web/controllers/api/oauth_controller.ex",
"lib/hexpm/oauth/tokens.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.HexpmWeb.API.OAuthController\u0027:validate_scopes_against_key/2"
},
{
"name": "\u0027Elixir.Hexpm.OAuth.Tokens\u0027:create_for_org/6"
}
],
"repo": "https://github.com/hexpm/hexpm",
"vendor": "hexpm",
"versions": [
{
"lessThan": "bf0fb9d208f0acfabf7a2f7467c8231659e322a8",
"status": "affected",
"version": "71829cb6f6559bcceb1ef4e43a2fb8cdd3af654b",
"versionType": "git"
}
]
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eIncorrect Authorization vulnerability in the OAuth token endpoint in hexpm hexpm allows an API key holding the \u003ccode\u003erepositories\u003c/code\u003e permission to read another organization\u0027s private packages.\u003c/p\u003e\n\u003cp\u003eWhen an API key is exchanged for a token through the OAuth \u003ccode\u003eclient_credentials\u003c/code\u003e grant, \u003ccode\u003evalidate_scopes_against_key/2\u003c/code\u003e in \u003ccode\u003elib/hexpm_web/controllers/api/oauth_controller.ex\u003c/code\u003e admits a requested scope whenever the key carries the \u003ccode\u003erepositories\u003c/code\u003e permission and the scope string begins with \u003ccode\u003erepository:\u003c/code\u003e. The organization name is never resolved against the principal, and \u003ccode\u003eexpand_repositories_scope/3\u003c/code\u003e only rewrites the literal \u003ccode\u003erepositories\u003c/code\u003e scope, so an explicit \u003ccode\u003erepository:\u0026lt;name\u0026gt;\u003c/code\u003e passes through untouched. Both CDN edges authorize repository access from the token claim without querying the database, so the minted token is read access to that organization\u0027s private packages until it expires.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex.pm: from 2025-10-18 before 2026-08-24.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Incorrect Authorization vulnerability in the OAuth token endpoint in hexpm hexpm allows an API key holding the `repositories` permission to read another organization\u0027s private packages.\n\nWhen an API key is exchanged for a token through the OAuth `client_credentials` grant, `validate_scopes_against_key/2` in `lib/hexpm_web/controllers/api/oauth_controller.ex` admits a requested scope whenever the key carries the `repositories` permission and the scope string begins with `repository:`. The organization name is never resolved against the principal, and `expand_repositories_scope/3` only rewrites the literal `repositories` scope, so an explicit `repository:\u003cname\u003e` passes through untouched. Both CDN edges authorize repository access from the token claim without querying the database, so the minted token is read access to that organization\u0027s private packages until it expires.\n\nThis issue affects hex.pm: from 2025-10-18 before 2026-08-24."
}
],
"value": "Incorrect Authorization vulnerability in the OAuth token endpoint in hexpm hexpm allows an API key holding the repositories permission to read another organization\u0027s private packages.\n\nWhen an API key is exchanged for a token through the OAuth client_credentials grant, validate_scopes_against_key/2 in lib/hexpm_web/controllers/api/oauth_controller.ex admits a requested scope whenever the key carries the repositories permission and the scope string begins with repository:. The organization name is never resolved against the principal, and expand_repositories_scope/3 only rewrites the literal repositories scope, so an explicit repository:\u003cname\u003e passes through untouched. Both CDN edges authorize repository access from the token claim without querying the database, so the minted token is read access to that organization\u0027s private packages until it expires.\n\nThis issue affects hex.pm: from 2025-10-18 before 2026-08-24."
}
],
"impacts": [
{
"capecId": "CAPEC-122",
"descriptions": [
{
"lang": "en",
"value": "CAPEC-122 Privilege Abuse"
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"attackVector": "NETWORK",
"baseScore": 8.3,
"baseSeverity": "HIGH",
"privilegesRequired": "LOW",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "HIGH",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-863",
"description": "CWE-863 Incorrect Authorization",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-08-24T20:14:30.191Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-rfx8-w654-8cpr"
},
{
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-75542.html"
},
{
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-75542"
},
{
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hexpm/commit/bf0fb9d208f0acfabf7a2f7467c8231659e322a8"
}
],
"source": {
"discovery": "INTERNAL"
},
"title": "OAuth token exchange grants repository scopes for organizations the principal cannot access"
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-75542",
"datePublished": "2026-08-24T20:14:30.191Z",
"dateReserved": "2026-08-18T11:00:02.414Z",
"dateUpdated": "2026-08-25T19:53:26.196Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-32148 (GCVE-0-2026-32148)
Vulnerability from cvelistv5
Published
2026-04-30 18:17
Modified
2026-05-01 04:33
Severity ?
VLAI Severity ?
EPSS score ?
CWE
Summary
Insufficient Verification of Data Authenticity vulnerability in hexpm hex (Hex.RemoteConverger module) allows dependency integrity bypass via unverified lockfile checksums.
Hex stores checksums for dependencies in the mix.lock file to ensure reproducible and integrity-checked builds. However, Hex.RemoteConverger.verify_resolved/2 never executes checksum verification because the lock data returned by Hex.Utils.lock/1 uses string-based dependency names, while the verification logic compares against atom-based names. This type mismatch causes the verification code path to be silently skipped. Checksums are still validated when packages are initially downloaded from the registry, but mismatches between the lockfile and resolved dependencies are not detected.
An attacker who can influence cached packages (e.g., via local cache poisoning or a compromised registry) can provide modified dependency contents that will be accepted without detection. The mix.lock file is silently rewritten with the checksum values from the registry, erasing evidence of tampering.
This issue affects hex: from 0.16.0 before 2.4.2.
References
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-32148",
"options": [
{
"Exploitation": "poc"
},
{
"Automatable": "no"
},
{
"Technical Impact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-04-30T19:03:06.385106Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-04-30T19:03:24.858Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"references": [
{
"tags": [
"exploit"
],
"url": "https://github.com/hexpm/hex/security/advisories/GHSA-hmv9-4mfr-m92v"
}
],
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hex.RemoteConverger\u0027"
],
"packageName": "hex",
"packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=git%20https:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
"product": "hex",
"programFiles": [
"lib/hex/remote_converger.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hex.RemoteConverger\u0027:verify_resolved/2"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2.4.2",
"status": "affected",
"version": "0.16.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hex.RemoteConverger\u0027"
],
"packageName": "hexpm/hex",
"packageURL": "pkg:github/hexpm/hex",
"product": "hex",
"programFiles": [
"lib/hex/remote_converger.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hex.RemoteConverger\u0027:verify_resolved/2"
}
],
"repo": "https://github.com/hexpm/hex.git",
"vendor": "hexpm",
"versions": [
{
"lessThan": "d7528c8199a1144511508bf3a6460026a5a14c8e",
"status": "affected",
"version": "e01576f28c64af9fae6eb17e2dad30f6efcb303c",
"versionType": "git"
}
]
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
"versionEndExcluding": "2.4.2",
"versionStartIncluding": "0.16.0",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Paul Fleischer"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Jonatan M\u00e4nnchen / EEF"
},
{
"lang": "en",
"type": "remediation reviewer",
"value": "Eric Meadows-J\u00f6nsson / Hex.pm"
}
],
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "Insufficient Verification of Data Authenticity vulnerability in hexpm hex (\u003ctt\u003eHex.RemoteConverger\u003c/tt\u003e module) allows dependency integrity bypass via unverified lockfile checksums.\u003cp\u003eHex stores checksums for dependencies in the \u003ctt\u003emix.lock\u003c/tt\u003e file to ensure reproducible and integrity-checked builds. However, \u003ctt\u003eHex.RemoteConverger.verify_resolved/2\u003c/tt\u003e never executes checksum verification because the lock data returned by \u003ctt\u003eHex.Utils.lock/1\u003c/tt\u003e uses string-based dependency names, while the verification logic compares against atom-based names. This type mismatch causes the verification code path to be silently skipped. Checksums are still validated when packages are initially downloaded from the registry, but mismatches between the lockfile and resolved dependencies are not detected.\u003c/p\u003e\u003cp\u003eAn attacker who can influence cached packages (e.g., via local cache poisoning or a compromised registry) can provide modified dependency contents that will be accepted without detection. The \u003ctt\u003emix.lock\u003c/tt\u003e file is silently rewritten with the checksum values from the registry, erasing evidence of tampering.\u003c/p\u003e\u003cp\u003eThis issue affects hex: from 0.16.0 before 2.4.2.\u003c/p\u003e"
}
],
"value": "Insufficient Verification of Data Authenticity vulnerability in hexpm hex (Hex.RemoteConverger module) allows dependency integrity bypass via unverified lockfile checksums.\n\nHex stores checksums for dependencies in the mix.lock file to ensure reproducible and integrity-checked builds. However, Hex.RemoteConverger.verify_resolved/2 never executes checksum verification because the lock data returned by Hex.Utils.lock/1 uses string-based dependency names, while the verification logic compares against atom-based names. This type mismatch causes the verification code path to be silently skipped. Checksums are still validated when packages are initially downloaded from the registry, but mismatches between the lockfile and resolved dependencies are not detected.\n\nAn attacker who can influence cached packages (e.g., via local cache poisoning or a compromised registry) can provide modified dependency contents that will be accepted without detection. The mix.lock file is silently rewritten with the checksum values from the registry, erasing evidence of tampering.\n\nThis issue affects hex: from 0.16.0 before 2.4.2."
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 8.9,
"baseSeverity": "HIGH",
"exploitMaturity": "NOT_DEFINED",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "HIGH",
"subConfidentialityImpact": "HIGH",
"subIntegrityImpact": "HIGH",
"userInteraction": "ACTIVE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-354",
"description": "CWE-354 Improper Validation of Integrity Check Value",
"lang": "en",
"type": "CWE"
}
]
},
{
"descriptions": [
{
"cweId": "CWE-494",
"description": "CWE-494 Download of Code Without Integrity Check",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-05-01T04:33:38.198Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/hexpm/hex/security/advisories/GHSA-hmv9-4mfr-m92v"
},
{
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-32148.html"
},
{
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-32148"
},
{
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/d7528c8199a1144511508bf3a6460026a5a14c8e"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Lockfile checksums not verified in Hex allows dependency integrity bypass",
"x_generator": {
"engine": "cvelib 1.8.0"
}
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-32148",
"datePublished": "2026-04-30T18:17:03.783Z",
"dateReserved": "2026-03-10T22:37:29.213Z",
"dateUpdated": "2026-05-01T04:33:38.198Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-23940 (GCVE-0-2026-23940)
Vulnerability from cvelistv5
Published
2026-03-13 16:07
Modified
2026-09-08 13:32
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-400 - Uncontrolled Resource Consumption
Summary
Uncontrolled Resource Consumption vulnerability in hexpm hexpm/hexpm allows Excessive Allocation.
Publishing an oversized package can cause Hex.pm to run out of memory while extracting the uploaded package tarball. This can terminate the affected application instance and result in a denial of service for package publishing and potentially other package-processing functionality.
This issue affects hex.pm: before 2026-03-10.
References
| URL | Tags | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|||||||||||||||||
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-23940",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "yes"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-03-16T17:06:18.779960Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-03-16T17:06:25.539Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://hex.pm",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"packageName": "hex.pm",
"product": "hexpm",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2026-03-10",
"status": "affected",
"version": "0",
"versionType": "date"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"packageName": "hexpm/hexpm",
"packageURL": "pkg:github/hexpm/hexpm",
"product": "hexpm",
"repo": "https://github.com/hexpm/hexpm",
"vendor": "hexpm",
"versions": [
{
"lessThan": "495f01607d3eae4aed7ad09b2f54f31ec7a7df01",
"status": "affected",
"version": "82911daf5f8fb2ab44f298e3ba22b90bc1ae3746",
"versionType": "git"
}
]
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Joud Zakharia / zentrust partners GmbH"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Eric Meadows-J\u00f6nsson / Hex.pm"
}
],
"datePublic": "2026-09-08T13:25:58.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUncontrolled Resource Consumption vulnerability in hexpm hexpm/hexpm allows Excessive Allocation.\u003c/p\u003e\n\u003cp\u003ePublishing an oversized package can cause Hex.pm to run out of memory while extracting the uploaded package tarball. This can terminate the affected application instance and result in a denial of service for package publishing and potentially other package-processing functionality.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex.pm: before 2026-03-10.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Uncontrolled Resource Consumption vulnerability in hexpm hexpm/hexpm allows Excessive Allocation.\n\nPublishing an oversized package can cause Hex.pm to run out of memory while extracting the uploaded package tarball. This can terminate the affected application instance and result in a denial of service for package publishing and potentially other package-processing functionality.\n\nThis issue affects hex.pm: before 2026-03-10."
}
],
"value": "Uncontrolled Resource Consumption vulnerability in hexpm hexpm/hexpm allows Excessive Allocation.\n\nPublishing an oversized package can cause Hex.pm to run out of memory while extracting the uploaded package tarball. This can terminate the affected application instance and result in a denial of service for package publishing and potentially other package-processing functionality.\n\nThis issue affects hex.pm: before 2026-03-10."
}
],
"impacts": [
{
"capecId": "CAPEC-130",
"descriptions": [
{
"lang": "en",
"value": "CAPEC-130 Excessive Allocation"
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"attackVector": "NETWORK",
"baseScore": 7.1,
"baseSeverity": "HIGH",
"privilegesRequired": "LOW",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-400",
"description": "CWE-400 Uncontrolled Resource Consumption",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-09-08T13:32:42.745Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-jp8w-gxf6-8hcr"
},
{
"name": "EEF CNA record for CVE-2026-23940",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-23940.html"
},
{
"name": "OSV record EEF-CVE-2026-23940",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-23940"
},
{
"name": "Introducing commit 82911da in hexpm/hexpm",
"tags": [
"related"
],
"url": "https://github.com/hexpm/hexpm/commit/82911daf5f8fb2ab44f298e3ba22b90bc1ae3746"
},
{
"name": "Fix commit 495f016 in hexpm/hexpm",
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hexpm/commit/495f01607d3eae4aed7ad09b2f54f31ec7a7df01"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Denial of Service via Oversized Package Upload",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cul\u003e\n\u003cli\u003ePrevent large package uploads by enforcing upload size limits at the reverse proxy or load balancer level.\u003c/li\u003e\n\u003c/ul\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "* Prevent large package uploads by enforcing upload size limits at the reverse proxy or load balancer level."
}
],
"value": "* Prevent large package uploads by enforcing upload size limits at the reverse proxy or load balancer level."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-23940",
"datePublished": "2026-03-13T16:07:53.328Z",
"dateReserved": "2026-01-19T14:23:14.343Z",
"dateUpdated": "2026-09-08T13:32:42.745Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-21622 (GCVE-0-2026-21622)
Vulnerability from cvelistv5
Published
2026-03-05 21:18
Modified
2026-04-21 04:15
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-613 - Insufficient Session Expiration
Summary
Insufficient Session Expiration vulnerability in hexpm hexpm/hexpm ('Elixir.Hexpm.Accounts.PasswordReset' module) allows Account Takeover.
Password reset tokens generated via the "Reset your password" flow do not expire. When a user requests a password reset, Hex sends an email containing a reset link with a token. This token remains valid indefinitely until used. There is no time-based expiration enforced.
If a user's historical emails are exposed through a data breach (e.g., a leaked mailbox archive), any unused password reset email contained in that dataset could be used by an attacker to reset the victim's password. The attacker does not need current access to the victim's email account, only access to a previously leaked copy of the reset email.
This vulnerability is associated with program files lib/hexpm/accounts/password_reset.ex and program routines 'Elixir.Hexpm.Accounts.PasswordReset':can_reset?/3.
This issue affects hexpm: from 617e44c71f1dd9043870205f371d375c5c4d886d before bb0e42091995945deef10556f58d046a52eb7884.
References
| URL | Tags | |
|---|---|---|
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-21622",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "yes"
},
{
"Technical Impact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-03-10T14:35:49.366785Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-04-21T02:43:06.195Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hexpm.Accounts.PasswordReset\u0027"
],
"packageName": "hexpm/hexpm",
"packageURL": "pkg:github/hexpm/hexpm",
"product": "hexpm",
"programFiles": [
"lib/hexpm/accounts/password_reset.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hexpm.Accounts.PasswordReset\u0027:can_reset?/3"
}
],
"repo": "https://github.com/hexpm/hexpm.git",
"vendor": "hexpm",
"versions": [
{
"lessThan": "bb0e42091995945deef10556f58d046a52eb7884",
"status": "affected",
"version": "617e44c71f1dd9043870205f371d375c5c4d886d",
"versionType": "git"
}
]
},
{
"defaultStatus": "unaffected",
"product": "hex.pm",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2026-03-05",
"status": "affected",
"version": "2025-08-01",
"versionType": "date"
}
]
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*",
"versionEndExcluding": "bb0e42091995945deef10556f58d046a52eb7884",
"versionStartIncluding": "617e44c71f1dd9043870205f371d375c5c4d886d",
"vulnerable": true
}
],
"negate": false,
"operator": "AND"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Michael Lubas / Paraxial.io"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Jonatan M\u00e4nnchen / EEF"
},
{
"lang": "en",
"type": "remediation reviewer",
"value": "Eric Meadows-J\u00f6nsson / Hex.pm"
}
],
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "Insufficient Session Expiration vulnerability in hexpm hexpm/hexpm (\u0027Elixir.Hexpm.Accounts.PasswordReset\u0027 module) allows Account Takeover.\u003cp\u003ePassword reset tokens generated via the \"Reset your password\" flow do not expire. When a user requests a password reset, Hex sends an email containing a reset link with a token. This token remains valid indefinitely until used. There is no time-based expiration enforced.\u003c/p\u003e\u003cp\u003eIf a user\u0027s historical emails are exposed through a data breach (e.g., a leaked mailbox archive), any unused password reset email contained in that dataset could be used by an attacker to reset the victim\u0027s password. The attacker does not need current access to the victim\u0027s email account, only access to a previously leaked copy of the reset email.\u003c/p\u003e\u003cp\u003eThis vulnerability is associated with program files \u003ctt\u003elib/hexpm/accounts/password_reset.ex\u003c/tt\u003e and program routines \u003ctt\u003e\u0027Elixir.Hexpm.Accounts.PasswordReset\u0027:can_reset?/3\u003c/tt\u003e.\u003c/p\u003e\u003cp\u003eThis issue affects hexpm: from 617e44c71f1dd9043870205f371d375c5c4d886d before bb0e42091995945deef10556f58d046a52eb7884.\u003c/p\u003e"
}
],
"value": "Insufficient Session Expiration vulnerability in hexpm hexpm/hexpm (\u0027Elixir.Hexpm.Accounts.PasswordReset\u0027 module) allows Account Takeover.\n\nPassword reset tokens generated via the \"Reset your password\" flow do not expire. When a user requests a password reset, Hex sends an email containing a reset link with a token. This token remains valid indefinitely until used. There is no time-based expiration enforced.\n\nIf a user\u0027s historical emails are exposed through a data breach (e.g., a leaked mailbox archive), any unused password reset email contained in that dataset could be used by an attacker to reset the victim\u0027s password. The attacker does not need current access to the victim\u0027s email account, only access to a previously leaked copy of the reset email.\n\nThis vulnerability is associated with program files lib/hexpm/accounts/password_reset.ex and program routines \u0027Elixir.Hexpm.Accounts.PasswordReset\u0027:can_reset?/3.\n\nThis issue affects hexpm: from 617e44c71f1dd9043870205f371d375c5c4d886d before bb0e42091995945deef10556f58d046a52eb7884."
}
],
"impacts": [
{
"capecId": "CAPEC-21",
"descriptions": [
{
"lang": "en",
"value": "CAPEC-21 Exploitation of Session Variables, Resource IDs and other Trusted Credentials"
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 9.5,
"baseSeverity": "CRITICAL",
"exploitMaturity": "NOT_DEFINED",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "LOW",
"subConfidentialityImpact": "HIGH",
"subIntegrityImpact": "HIGH",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:L/SC:H/SI:H/SA:L",
"version": "4.0",
"vulnAvailabilityImpact": "LOW",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-613",
"description": "CWE-613 Insufficient Session Expiration",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-04-21T04:15:20.750Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-6r94-pvwf-mxqm"
},
{
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-21622.html"
},
{
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-21622"
},
{
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hexpm/commit/bb0e42091995945deef10556f58d046a52eb7884"
}
],
"source": {
"discovery": "INTERNAL"
},
"title": "Password Reset Tokens Do Not Expire",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUsers who suspect email exposure should:\u003c/p\u003e\u003cul\u003e\u003cli\u003eImmediately reset their password.\u003c/li\u003e\u003cli\u003eEnable and enforce 2FA.\u003c/li\u003e\u003c/ul\u003e\u003cp\u003eThere is no complete mitigation without implementing token expiration.\u003c/p\u003e"
}
],
"value": "Users who suspect email exposure should:\n\n* Immediately reset their password.\n* Enable and enforce 2FA.\n\nThere is no complete mitigation without implementing token expiration."
}
],
"x_generator": {
"engine": "Vulnogram 0.2.0"
}
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-21622",
"datePublished": "2026-03-05T21:18:03.883Z",
"dateReserved": "2026-01-01T03:46:45.934Z",
"dateUpdated": "2026-04-21T04:15:20.750Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-21621 (GCVE-0-2026-21621)
Vulnerability from cvelistv5
Published
2026-03-05 19:20
Modified
2026-04-06 16:44
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-863 - Incorrect Authorization
Summary
Incorrect Authorization vulnerability in hexpm hexpm/hexpm ('Elixir.HexpmWeb.API.OAuthController' module) allows Privilege Escalation.
An API key created with read-only permissions (domain: "api", resource: "read") can be escalated to full write access under specific conditions.
When exchanging a read-only API key via the OAuth client_credentials grant, the resource qualifier is ignored. The resulting JWT receives the broad "api" scope instead of the expected "api:read" scope. This token is therefore treated as having full API access.
If an attacker is able to obtain a victim's read-only API key and a valid 2FA (TOTP) code for the victim account, they can use the incorrectly scoped JWT to create a new full-access API key with unrestricted API permissions that does not expire by default and can perform write operations such as publishing, retiring, or modifying packages.
This vulnerability is associated with program files lib/hexpm_web/controllers/api/oauth_controller.ex and program routines 'Elixir.HexpmWeb.API.OAuthController':validate_scopes_against_key/2.
This issue affects hexpm: from 71829cb6f6559bcceb1ef4e43a2fb8cdd3af654b before 71c127afebb7ed7cc637eb231b98feb802d62999.
References
| URL | Tags | |
|---|---|---|
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-21621",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-03-06T18:03:45.435445Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-03-06T18:03:52.971Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.HexpmWeb.API.OAuthController\u0027"
],
"packageName": "hexpm/hexpm",
"packageURL": "pkg:github/hexpm/hexpm",
"product": "hexpm",
"programFiles": [
"lib/hexpm_web/controllers/api/oauth_controller.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.HexpmWeb.API.OAuthController\u0027:validate_scopes_against_key/2"
}
],
"repo": "https://github.com/hexpm/hexpm.git",
"vendor": "hexpm",
"versions": [
{
"lessThan": "71c127afebb7ed7cc637eb231b98feb802d62999",
"status": "affected",
"version": "71829cb6f6559bcceb1ef4e43a2fb8cdd3af654b",
"versionType": "git"
}
]
},
{
"defaultStatus": "unaffected",
"product": "hex.pm",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2026-03-05",
"status": "affected",
"version": "2025-08-18",
"versionType": "date"
}
]
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*",
"versionEndExcluding": "71c127afebb7ed7cc637eb231b98feb802d62999",
"versionStartIncluding": "71829cb6f6559bcceb1ef4e43a2fb8cdd3af654b",
"vulnerable": true
}
],
"negate": false,
"operator": "AND"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Michael Lubas / Paraxial.io"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "Incorrect Authorization vulnerability in hexpm hexpm/hexpm (\u0027Elixir.HexpmWeb.API.OAuthController\u0027 module) allows Privilege Escalation.\u003cp\u003eAn API key created with read-only permissions (domain: \"api\", resource: \"read\") can be escalated to full write access under specific conditions.\u003c/p\u003e\u003cp\u003eWhen exchanging a read-only API key via the OAuth client_credentials grant, the resource qualifier is ignored. The resulting JWT receives the broad \"api\" scope instead of the expected \"api:read\" scope. This token is therefore treated as having full API access.\u003c/p\u003e\u003cp\u003eIf an attacker is able to obtain a victim\u0027s read-only API key and a valid 2FA (TOTP) code for the victim account, they can use the incorrectly scoped JWT to create a new full-access API key with unrestricted API permissions that does not expire by default and can perform write operations such as publishing, retiring, or modifying packages.\u003c/p\u003e\u003cp\u003eThis vulnerability is associated with program files \u003ctt\u003elib/hexpm_web/controllers/api/oauth_controller.ex\u003c/tt\u003e and program routines \u003ctt\u003e\u0027Elixir.HexpmWeb.API.OAuthController\u0027:validate_scopes_against_key/2\u003c/tt\u003e.\u003c/p\u003e\u003cp\u003eThis issue affects hexpm: from 71829cb6f6559bcceb1ef4e43a2fb8cdd3af654b before 71c127afebb7ed7cc637eb231b98feb802d62999.\u003c/p\u003e"
}
],
"value": "Incorrect Authorization vulnerability in hexpm hexpm/hexpm (\u0027Elixir.HexpmWeb.API.OAuthController\u0027 module) allows Privilege Escalation.\n\nAn API key created with read-only permissions (domain: \"api\", resource: \"read\") can be escalated to full write access under specific conditions.\n\nWhen exchanging a read-only API key via the OAuth client_credentials grant, the resource qualifier is ignored. The resulting JWT receives the broad \"api\" scope instead of the expected \"api:read\" scope. This token is therefore treated as having full API access.\n\nIf an attacker is able to obtain a victim\u0027s read-only API key and a valid 2FA (TOTP) code for the victim account, they can use the incorrectly scoped JWT to create a new full-access API key with unrestricted API permissions that does not expire by default and can perform write operations such as publishing, retiring, or modifying packages.\n\nThis vulnerability is associated with program files lib/hexpm_web/controllers/api/oauth_controller.ex and program routines \u0027Elixir.HexpmWeb.API.OAuthController\u0027:validate_scopes_against_key/2.\n\nThis issue affects hexpm: from 71829cb6f6559bcceb1ef4e43a2fb8cdd3af654b before 71c127afebb7ed7cc637eb231b98feb802d62999."
}
],
"impacts": [
{
"capecId": "CAPEC-233",
"descriptions": [
{
"lang": "en",
"value": "CAPEC-233 Privilege Escalation"
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 7,
"baseSeverity": "HIGH",
"exploitMaturity": "NOT_DEFINED",
"privilegesRequired": "LOW",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "HIGH",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:H/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "LOW",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-863",
"description": "CWE-863 Incorrect Authorization",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-04-06T16:44:09.535Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-739m-8727-j6w3"
},
{
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-21621.html"
},
{
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-21621"
},
{
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hexpm/commit/71c127afebb7ed7cc637eb231b98feb802d62999"
}
],
"source": {
"discovery": "INTERNAL"
},
"title": "Improper Scope Enforcement in OAuth client_credentials Flow Allows Read-Only API Key to Escalate to Full Access",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cul\u003e\u003cli\u003eRevoke and reissue exposed API keys immediately if compromise is suspected.\u003c/li\u003e\u003cli\u003eAvoid relying on read-only API keys as a strict security boundary in high-risk environments.\u003c/li\u003e\u003cli\u003eClosely monitor audit logs for unexpected API key creation events.\u003c/li\u003e\u003cli\u003eEnforce strong 2FA hygiene and protect TOTP seeds carefully.\u003c/li\u003e\u003c/ul\u003e\u003cp\u003eThere is no complete mitigation without upgrading, as the issue exists in server-side scope validation logic.\u003c/p\u003e"
}
],
"value": "* Revoke and reissue exposed API keys immediately if compromise is suspected.\n* Avoid relying on read-only API keys as a strict security boundary in high-risk environments.\n* Closely monitor audit logs for unexpected API key creation events.\n* Enforce strong 2FA hygiene and protect TOTP seeds carefully.\n\nThere is no complete mitigation without upgrading, as the issue exists in server-side scope validation logic."
}
],
"x_generator": {
"engine": "Vulnogram 0.2.0"
}
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-21621",
"datePublished": "2026-03-05T19:20:05.831Z",
"dateReserved": "2026-01-01T03:46:45.934Z",
"dateUpdated": "2026-04-06T16:44:09.535Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-21619 (GCVE-0-2026-21619)
Vulnerability from cvelistv5
Published
2026-02-27 17:57
Modified
2026-05-27 15:40
Severity ?
VLAI Severity ?
EPSS score ?
Summary
Uncontrolled Resource Consumption, Deserialization of Untrusted Data vulnerability in hexpm hex_core (hex_api modules), hexpm hex (mix_hex_api modules), erlang rebar3 (r3_hex_api modules) allows Object Injection, Excessive Allocation. This vulnerability is associated with program files src/hex_api.erl, src/mix_hex_api.erl, apps/rebar/src/vendored/r3_hex_api.erl and program routines hex_core:request/4, mix_hex_api:request/4, r3_hex_api:request/4.
This issue affects hex_core: from 0.1.0 before 0.12.1; hex: from 2.3.0 before 2.3.2; rebar3: from 3.9.1 before 3.27.0.
References
| URL | Tags | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
Impacted products
| Vendor | Product | Version | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hexpm | hex_core |
Version: eb327f8edfe45507351e38cc0805aa12fa647f0b cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:* |
|||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-21619",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-02-27T19:08:49.652728Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-02-27T19:08:57.019Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"hex_api"
],
"packageName": "hexpm/hex_core",
"packageURL": "pkg:github/hexpm/hex_core",
"product": "hex_core",
"programFiles": [
"src/hex_api.erl"
],
"programRoutines": [
{
"name": "hex_core:request/4"
}
],
"repo": "https://github.com/hexpm/hex_core",
"vendor": "hexpm",
"versions": [
{
"lessThan": "cdf726095bca85ad2549d146df1e831ae93c2b13",
"status": "affected",
"version": "eb327f8edfe45507351e38cc0805aa12fa647f0b",
"versionType": "git"
}
]
},
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"hex_api"
],
"packageName": "hex_core",
"packageURL": "pkg:hex/hex_core",
"product": "hex_core",
"programFiles": [
"src/hex_api.erl"
],
"programRoutines": [
{
"name": "hex_core:request/4"
}
],
"repo": "https://github.com/hexpm/hex_core",
"vendor": "hexpm",
"versions": [
{
"lessThan": "0.12.1",
"status": "affected",
"version": "0.1.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"mix_hex_api"
],
"packageName": "hexpm/hex",
"packageURL": "pkg:github/hexpm/hex",
"product": "hex",
"programFiles": [
"src/mix_hex_api.erl"
],
"programRoutines": [
{
"name": "mix_hex_api:request/4"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "636739f3322514e9303ca335fb630696fcbb3c95",
"status": "affected",
"version": "314546ac432229518714cc8e3336e916b9da6305",
"versionType": "git"
}
]
},
{
"cpes": [
"cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"mix_hex_api"
],
"packageName": "hex",
"packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex.git\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
"product": "hex",
"programFiles": [
"src/mix_hex_api.erl"
],
"programRoutines": [
{
"name": "mix_hex_api:request/4"
}
],
"repo": "https://github.com/hexpm/hex",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2.3.2",
"status": "affected",
"version": "2.3.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"r3_hex_api"
],
"packageName": "erlang/rebar3",
"packageURL": "pkg:github/erlang/rebar3",
"product": "rebar3",
"programFiles": [
"apps/rebar/src/vendored/r3_hex_api.erl"
],
"programRoutines": [
{
"name": "r3_hex_api:request/4"
}
],
"repo": "https://github.com/erlang/rebar3",
"vendor": "erlang",
"versions": [
{
"lessThan": "1d4478f527e373de0b225951e53115450e0d9b9d",
"status": "affected",
"version": "209c02ec57c2cc3207ee0174c3af3675b8dc8f79",
"versionType": "git"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"r3_hex_api"
],
"packageName": "rebar3",
"packageURL": "pkg:otp/rebar3?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Frebar3.git\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Frebar3.git",
"product": "rebar3",
"programFiles": [
"apps/rebar/src/vendored/r3_hex_api.erl"
],
"programRoutines": [
{
"name": "r3_hex_api:request/4"
}
],
"repo": "https://github.com/erlang/rebar3",
"vendor": "erlang",
"versions": [
{
"lessThan": "3.27.0",
"status": "affected",
"version": "3.9.1",
"versionType": "semver"
}
]
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*",
"versionEndExcluding": "0.12.1",
"versionStartIncluding": "0.1.0",
"vulnerable": true
}
],
"negate": false,
"operator": "AND"
},
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
"versionEndExcluding": "2.3.2",
"versionStartIncluding": "2.3.0",
"vulnerable": true
}
],
"negate": false,
"operator": "AND"
},
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*",
"versionEndExcluding": "3.27.0",
"versionStartIncluding": "3.9.1",
"vulnerable": true
}
],
"negate": false,
"operator": "AND"
}
],
"operator": "OR"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Michael Lubas / Paraxial.io"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Jonatan M\u00e4nnchen / EEF"
},
{
"lang": "en",
"type": "remediation reviewer",
"value": "Eric Meadows-J\u00f6nsson / Hex.pm"
}
],
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "Uncontrolled Resource Consumption, Deserialization of Untrusted Data vulnerability in hexpm hex_core (hex_api modules), hexpm hex (mix_hex_api modules), erlang rebar3 (r3_hex_api modules) allows Object Injection, Excessive Allocation.\u003cp\u003e This vulnerability is associated with program files \u003ctt\u003esrc/hex_api.erl\u003c/tt\u003e, \u003ctt\u003esrc/mix_hex_api.erl\u003c/tt\u003e, \u003ctt\u003eapps/rebar/src/vendored/r3_hex_api.erl\u003c/tt\u003e and program routines \u003ctt\u003ehex_core:request/4\u003c/tt\u003e, \u003ctt\u003emix_hex_api:request/4\u003c/tt\u003e, \u003ctt\u003er3_hex_api:request/4\u003c/tt\u003e.\u003c/p\u003e\u003cp\u003eThis issue affects hex_core: from 0.1.0 before 0.12.1; hex: from 2.3.0 before 2.3.2; rebar3: from 3.9.1 before 3.27.0.\u003c/p\u003e"
}
],
"value": "Uncontrolled Resource Consumption, Deserialization of Untrusted Data vulnerability in hexpm hex_core (hex_api modules), hexpm hex (mix_hex_api modules), erlang rebar3 (r3_hex_api modules) allows Object Injection, Excessive Allocation. This vulnerability is associated with program files src/hex_api.erl, src/mix_hex_api.erl, apps/rebar/src/vendored/r3_hex_api.erl and program routines hex_core:request/4, mix_hex_api:request/4, r3_hex_api:request/4.\n\nThis issue affects hex_core: from 0.1.0 before 0.12.1; hex: from 2.3.0 before 2.3.2; rebar3: from 3.9.1 before 3.27.0."
}
],
"impacts": [
{
"capecId": "CAPEC-586",
"descriptions": [
{
"lang": "en",
"value": "CAPEC-586 Object Injection"
}
]
},
{
"capecId": "CAPEC-130",
"descriptions": [
{
"lang": "en",
"value": "CAPEC-130 Excessive Allocation"
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 2,
"baseSeverity": "LOW",
"exploitMaturity": "NOT_DEFINED",
"privilegesRequired": "LOW",
"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:L/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-400",
"description": "CWE-400 Uncontrolled Resource Consumption",
"lang": "en",
"type": "CWE"
}
]
},
{
"descriptions": [
{
"cweId": "CWE-502",
"description": "CWE-502 Deserialization of Untrusted Data",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-05-27T15:40:33.166Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/hexpm/hex_core/security/advisories/GHSA-hx9w-f2w9-9g96"
},
{
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-21619.html"
},
{
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-21619"
},
{
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex_core/commit/cdf726095bca85ad2549d146df1e831ae93c2b13"
},
{
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hex/commit/636739f3322514e9303ca335fb630696fcbb3c95"
},
{
"tags": [
"patch"
],
"url": "https://github.com/erlang/rebar3/commit/1d4478f527e373de0b225951e53115450e0d9b9d"
}
],
"source": {
"discovery": "INTERNAL"
},
"title": "Unsafe Deserialization of Erlang Terms in hex_core",
"x_generator": {
"engine": "Vulnogram 0.5.0"
}
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-21619",
"datePublished": "2026-02-27T17:57:11.513Z",
"dateReserved": "2026-01-01T03:46:45.933Z",
"dateUpdated": "2026-05-27T15:40:33.166Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-23939 (GCVE-0-2026-23939)
Vulnerability from cvelistv5
Published
2026-02-26 19:41
Modified
2026-09-16 15:36
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-22 - Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Summary
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') vulnerability in hexpm hexpm/hexpm ('Elixir.Hexpm.Store.Local' module) allows Relative Path Traversal. This vulnerability is associated with program files lib/hexpm/store/local.ex and program routines 'Elixir.Hexpm.Store.Local':get/3, 'Elixir.Hexpm.Store.Local':put/4, 'Elixir.Hexpm.Store.Local':delete/2, 'Elixir.Hexpm.Store.Local':delete_many/2.
This issue does NOT affect hex.pm the service. Only self-hosted deployments using the Local Storage backend are affected.
This issue affects hexpm: from 931ee0ed46fa89218e0400a4f6e6d15f96406050 before 5d2ccd2f14f45a63225a73fb5b1c937baf36fdc0.
References
| URL | Tags | |
|---|---|---|
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-23939",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "yes"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-02-26T20:24:13.786958Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-02-26T20:24:24.772Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Hexpm.Store.Local\u0027"
],
"packageName": "hexpm/hexpm",
"packageURL": "pkg:github/hexpm/hexpm",
"product": "hexpm",
"programFiles": [
"lib/hexpm/store/local.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Hexpm.Store.Local\u0027:get/3"
},
{
"name": "\u0027Elixir.Hexpm.Store.Local\u0027:put/4"
},
{
"name": "\u0027Elixir.Hexpm.Store.Local\u0027:delete/2"
},
{
"name": "\u0027Elixir.Hexpm.Store.Local\u0027:delete_many/2"
}
],
"repo": "https://github.com/hexpm/hexpm.git",
"vendor": "hexpm",
"versions": [
{
"lessThan": "5d2ccd2f14f45a63225a73fb5b1c937baf36fdc0",
"status": "affected",
"version": "931ee0ed46fa89218e0400a4f6e6d15f96406050",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "Only self-hosted hexpm deployments using the \u003ctt\u003eHexpm.Store.Local\u003c/tt\u003e storage backend are affected. The hex.pm service and deployments using S3-compatible object storage backends are not affected."
}
],
"value": "Only self-hosted hexpm deployments using the Hexpm.Store.Local storage backend are affected. The hex.pm service and deployments using S3-compatible object storage backends are not affected."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*",
"versionEndExcluding": "5d2ccd2f14f45a63225a73fb5b1c937baf36fdc0",
"versionStartIncluding": "931ee0ed46fa89218e0400a4f6e6d15f96406050",
"vulnerable": true
}
],
"negate": false,
"operator": "AND"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Michael Lubas / Paraxial.io"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Jonatan M\u00e4nnchen / EEF"
},
{
"lang": "en",
"type": "remediation reviewer",
"value": "Eric Meadows-J\u00f6nsson / Hex.pm"
}
],
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in hexpm hexpm/hexpm (\u0027Elixir.Hexpm.Store.Local\u0027 module) allows Relative Path Traversal.\u003cp\u003eThis vulnerability is associated with program files \u003ctt\u003elib/hexpm/store/local.ex\u003c/tt\u003e and program routines \u003ctt\u003e\u0027Elixir.Hexpm.Store.Local\u0027:get/3\u003c/tt\u003e, \u003ctt\u003e\u0027Elixir.Hexpm.Store.Local\u0027:put/4\u003c/tt\u003e, \u003ctt\u003e\u0027Elixir.Hexpm.Store.Local\u0027:delete/2\u003c/tt\u003e, \u003ctt\u003e\u0027Elixir.Hexpm.Store.Local\u0027:delete_many/2\u003c/tt\u003e.\u003c/p\u003e\u003cp\u003eThis issue does NOT affect hex.pm the service. Only self-hosted deployments using the Local Storage backend are affected.\u003c/p\u003e\u003cp\u003eThis issue affects hexpm: from 931ee0ed46fa89218e0400a4f6e6d15f96406050 before 5d2ccd2f14f45a63225a73fb5b1c937baf36fdc0.\u003c/p\u003e"
}
],
"value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in hexpm hexpm/hexpm (\u0027Elixir.Hexpm.Store.Local\u0027 module) allows Relative Path Traversal. This vulnerability is associated with program files lib/hexpm/store/local.ex and program routines \u0027Elixir.Hexpm.Store.Local\u0027:get/3, \u0027Elixir.Hexpm.Store.Local\u0027:put/4, \u0027Elixir.Hexpm.Store.Local\u0027:delete/2, \u0027Elixir.Hexpm.Store.Local\u0027:delete_many/2.\n\nThis issue does NOT affect hex.pm the service. Only self-hosted deployments using the Local Storage backend are affected.\n\nThis issue affects hexpm: from 931ee0ed46fa89218e0400a4f6e6d15f96406050 before 5d2ccd2f14f45a63225a73fb5b1c937baf36fdc0."
}
],
"impacts": [
{
"capecId": "CAPEC-139",
"descriptions": [
{
"lang": "en",
"value": "CAPEC-139 Relative Path Traversal"
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"attackVector": "NETWORK",
"baseScore": 6.9,
"baseSeverity": "MEDIUM",
"exploitMaturity": "NOT_DEFINED",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "LOW",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-22",
"description": "CWE-22 Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027)",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-09-16T15:36:19.353Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-42mv-r64p-4869"
},
{
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-23939.html"
},
{
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-23939"
},
{
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hexpm/commit/5d2ccd2f14f45a63225a73fb5b1c937baf36fdc0"
}
],
"source": {
"discovery": "INTERNAL"
},
"title": "Path Traversal in Local File Store Backend",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cul\u003e\u003cli\u003eAvoid the local file store backend in any exposed environment.\u003c/li\u003e\u003cli\u003eRestrict network access to the registry when using the local backend.\u003c/li\u003e\u003cli\u003eProduction deployments should use object storage (e.g., S3-compatible backends) instead of the local filesystem store.\u003c/li\u003e\u003c/ul\u003e"
}
],
"value": "* Avoid the local file store backend in any exposed environment.\n* Restrict network access to the registry when using the local backend.\n* Production deployments should use object storage (e.g., S3-compatible backends) instead of the local filesystem store."
}
],
"x_generator": {
"engine": "Vulnogram 0.2.0"
}
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-23939",
"datePublished": "2026-02-26T19:41:18.762Z",
"dateReserved": "2026-01-19T14:23:14.343Z",
"dateUpdated": "2026-09-16T15:36:19.353Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-21618 (GCVE-0-2026-21618)
Vulnerability from cvelistv5
Published
2026-01-19 14:22
Modified
2026-07-24 14:16
Severity ?
VLAI Severity ?
EPSS score ?
CWE
- CWE-79 - Improper Neutralization of Input During Web Page Generation (XSS or 'Cross-site Scripting')
Summary
Improper Neutralization of Input During Web Page Generation (XSS or 'Cross-site Scripting') vulnerability in hexpm hexpm/hexpm ('Elixir.HexpmWeb.SharedAuthorizationView' modules) allows Cross-Site Scripting (XSS). This vulnerability is associated with program files lib/hexpm_web/views/shared_authorization_view.ex and program routines 'Elixir.HexpmWeb.SharedAuthorizationView':render_grouped_scopes/3.
This issue affects hexpm: from 617e44c71f1dd9043870205f371d375c5c4d886d before c692438684ead90c3bcbfb9ccf4e63c768c668a8; hex.pm: from 2025-10-01 before 2026-01-19.
References
| URL | Tags | |
|---|---|---|
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-21618",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "yes"
},
{
"Technical Impact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-01-20T16:16:45.709727Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-01-20T16:16:57.225Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.HexpmWeb.SharedAuthorizationView\u0027"
],
"packageName": "hexpm/hexpm",
"packageURL": "pkg:github/hexpm/hexpm",
"product": "hexpm",
"programFiles": [
"lib/hexpm_web/views/shared_authorization_view.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.HexpmWeb.SharedAuthorizationView\u0027:render_grouped_scopes/3"
}
],
"repo": "https://github.com/hexpm/hexpm.git",
"vendor": "hexpm",
"versions": [
{
"lessThan": "c692438684ead90c3bcbfb9ccf4e63c768c668a8",
"status": "affected",
"version": "617e44c71f1dd9043870205f371d375c5c4d886d",
"versionType": "git"
}
]
},
{
"defaultStatus": "unaffected",
"product": "hex.pm",
"vendor": "hexpm",
"versions": [
{
"lessThan": "2026-01-19",
"status": "affected",
"version": "2025-10-01",
"versionType": "date"
}
]
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:hexpm:hexpm:*:*:*:*:*:*:*:*",
"versionEndExcluding": "c692438684ead90c3bcbfb9ccf4e63c768c668a8",
"versionStartIncluding": "617e44c71f1dd9043870205f371d375c5c4d886d",
"vulnerable": true
}
],
"negate": false,
"operator": "AND"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Joud Zakharia / zentrust partners GmbH"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Jonatan M\u00e4nnchen / EEF"
},
{
"lang": "en",
"type": "remediation reviewer",
"value": "Eric Meadows-J\u00f6nsson / Hex.pm"
}
],
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "Improper Neutralization of Input During Web Page Generation (XSS or \u0027Cross-site Scripting\u0027) vulnerability in hexpm hexpm/hexpm (\u0027Elixir.HexpmWeb.SharedAuthorizationView\u0027 modules) allows Cross-Site Scripting (XSS).\u003cp\u003e This vulnerability is associated with program files \u003ctt\u003elib/hexpm_web/views/shared_authorization_view.ex\u003c/tt\u003e and program routines \u003ctt\u003e\u0027Elixir.HexpmWeb.SharedAuthorizationView\u0027:render_grouped_scopes/3\u003c/tt\u003e.\u003c/p\u003e\u003cp\u003eThis issue affects hexpm: from 617e44c71f1dd9043870205f371d375c5c4d886d before c692438684ead90c3bcbfb9ccf4e63c768c668a8; hex.pm: from 2025-10-01 before 2026-01-19.\u003c/p\u003e"
}
],
"value": "Improper Neutralization of Input During Web Page Generation (XSS or \u0027Cross-site Scripting\u0027) vulnerability in hexpm hexpm/hexpm (\u0027Elixir.HexpmWeb.SharedAuthorizationView\u0027 modules) allows Cross-Site Scripting (XSS). This vulnerability is associated with program files lib/hexpm_web/views/shared_authorization_view.ex and program routines \u0027Elixir.HexpmWeb.SharedAuthorizationView\u0027:render_grouped_scopes/3.\n\nThis issue affects hexpm: from 617e44c71f1dd9043870205f371d375c5c4d886d before c692438684ead90c3bcbfb9ccf4e63c768c668a8; hex.pm: from 2025-10-01 before 2026-01-19."
}
],
"impacts": [
{
"capecId": "CAPEC-63",
"descriptions": [
{
"lang": "en",
"value": "CAPEC-63 Cross-Site Scripting (XSS)"
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"attackVector": "NETWORK",
"baseScore": 8.5,
"baseSeverity": "HIGH",
"exploitMaturity": "NOT_DEFINED",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "LOW",
"subIntegrityImpact": "LOW",
"userInteraction": "ACTIVE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:N/SC:L/SI:L/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-79",
"description": "CWE-79 Improper Neutralization of Input During Web Page Generation (XSS or \u0027Cross-site Scripting\u0027)",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-07-24T14:16:04.791Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/hexpm/hexpm/security/advisories/GHSA-6cw9-5gg4-rhpj"
},
{
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-21618.html"
},
{
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-21618"
},
{
"tags": [
"patch"
],
"url": "https://github.com/hexpm/hexpm/commit/c692438684ead90c3bcbfb9ccf4e63c768c668a8"
}
],
"source": {
"discovery": "UNKNOWN"
},
"title": "Cross-site scripting (XSS) in OAuth Device Authorization screen",
"x_generator": {
"engine": "Vulnogram 0.5.0"
}
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-21618",
"datePublished": "2026-01-19T14:22:46.770Z",
"dateReserved": "2026-01-01T03:46:45.933Z",
"dateUpdated": "2026-07-24T14:16:04.791Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}