{"affected":[{"ranges":[{"events":[{"introduced":"c9b26a46928bfcd546773571e09c1d05f4cd9fb7"},{"fixed":"5f5a2136c7cf5991b6e8a30d645ca9f8e74b0785"},{"fixed":"6232a6498015e559743bec4f4fbec9138837cec8"}],"repo":"https://github.com/hexpm/hex","type":"GIT"}]}],"aliases":["GHSA-rw8q-hg7m-cxgr","CVE-2026-103503"],"credits":[{"name":"Peter Ullrich","type":"FINDER"},{"name":"Eric Meadows-Jönsson","type":"REMEDIATION_DEVELOPER"},{"name":"Jonatan Männchen / EEF","type":"ANALYST"}],"database_specific":{"capec_ids":["CAPEC-94"],"cpe_ids":["cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"],"cwe_ids":["CWE-295"]},"details":"## Summary\n\nImproper 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.\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't contain that CA. Hostname verification still applies.\n\nThis issue affects hex: from 0.20.2 before 2.5.2.\n\n## Details\n\n1. `Hex.HTTP.SSL.get_ca_certs/0` returns the certificates from `cacerts_path` when it's set, and `ssl_opts/1` passes them as `cacerts`.\n2. `ssl_opts/1` sets `partial_chain` to `&partial_chain(Certs.cacerts(), &1)`, which always uses the bundled store.\n3. When the server's chain doesn't 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.\n\n## Proof of concept\n\n1. 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`.\n\n## Impact\n\nUsers set `cacerts_path` to limit which CAs can authenticate Hex's servers, for example to their organization's internal CA. Every CA in Hex's 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.\n\n## Workarounds\n\nThere's no Hex configuration that stops the bundled CAs from being trusted.\n\n## Configurations\n\nAffects 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't set it trust the bundled store already and aren't affected.\n\n## Solutions\n\nUpgrade 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't trust copies of CA certificates sent by the server.","id":"EEF-CVE-2026-103503","modified":"2026-10-12T15:17:45.883724Z","published":"2026-10-12T15:17:45.545Z","references":[{"type":"ADVISORY","url":"https://github.com/hexpm/hex/security/advisories/GHSA-rw8q-hg7m-cxgr"},{"type":"WEB","url":"https://cna.erlef.org/cves/CVE-2026-103503.html"},{"type":"WEB","url":"https://github.com/hexpm/hex/commit/c9b26a46928bfcd546773571e09c1d05f4cd9fb7"},{"type":"FIX","url":"https://github.com/hexpm/hex/commit/5f5a2136c7cf5991b6e8a30d645ca9f8e74b0785"},{"type":"FIX","url":"https://github.com/hexpm/hex/commit/6232a6498015e559743bec4f4fbec9138837cec8"}],"related":[],"schema_version":"1.7.3","severity":[{"score":"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","type":"CVSS_V4"}],"summary":"Hex trusts its bundled CA certificates when cacerts_path is set","upstream":[]}