Custom cacerts or cacertfile do not restrict trust to the pinned CA in hackney
CVE-2026-115693 · GHSA-g7gg-9jhv-68h6 · EUVD-2026-109836 · EEF-CVE-2026-115693 · published 26 Oct 2026
How serious is it
- Severity: critical, CVSS score 9.1 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-115693?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
hackney(Hex) >= 1.24.0, < 4.9.0
How to check
- Look for
hackneyin your lockfile and compare its version with the range above. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
hackneyto 4.9.0 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
## Summary
Improper Certificate Validation vulnerability in `hackney_ssl` in benoitc hackney allows an on-path attacker to impersonate a server pinned to a private certificate authority via a certificate chain that ends in a publicly trusted root. hackney adds `{partial_chain, fun hackney_ssl:partial_chain/1}` to every `verify_peer` configuration. The callback returns any certificate in the peer chain whose public key matches a root in the bundled certifi store as the trust anchor, and OTP honours that callback over the configured `cacerts`. `hackney_ssl:merge_ssl_opts/3` kept the callback when the caller replaced the trust store with `cacerts` or `cacertfile` in `ssl_options`, so a connection pinned to a private CA still accepted any chain that carried a publicly trusted root.
The hostname check still applies, so the attacker needs a publicly trusted certificate for the pinned hostname.
This issue affects hackney: from 1.24.0 before 4.9.0.
## Details
**1. Default trust options.** `hackney_ssl:check_hostname_opts/1` builds the `verify_peer` options for every TLS connection: the certifi `cacerts`, a hostname `verify_fun`, and `{partial_chain, fun hackney_ssl:partial_chain/1}`. The callback walks the peer chain and returns `{trusted_ca, Cert}` for the first certificate whose public key matches a root in the bundled certifi store, which lets OTP accept chains that end in a cross-signed root.
**2. Merge keeps the callback.** When the caller passes `ssl_options`, `hackney_ssl:merge_ssl_opts/3` merges them over the defaults with the caller's values winning per key. `cacerts` or `cacertfile` replace the trust store, and a `cacertfile` removes the default `cacerts`, but `partial_chain` has no counterpart in the caller's options and stays.
**3. Validation.** OTP consults `partial_chain` before the configured trust store. A peer chain that carries any publicly trusted root therefore gets that root as its trust anchor, and the pinned `cacerts` are never consulted. Only the hostname `verify_fun` still constrains the attacker.
## Impact
An attacker positioned between the client and a server pinned to a private CA, holding a publicly trusted certificate for the pinned hostname, can impersonate the server and read or modify the traffic the client sends and receives.
## Workarounds
Pass your own `{partial_chain, Fun}` in `ssl_options` alongside `cacerts` or `cacertfile`. A caller-supplied `partial_chain` replaces hackney's in the merge, and a callback that returns `unknown_ca` makes OTP validate against the configured `cacerts` only.
## Configurations
The caller passes `cacerts` or `cacertfile` in `ssl_options` to restrict trust to a private CA, without a `partial_chain` of its own. Connections that use hackney's default certifi trust store are not affected.
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/benoitc/hackney/pull/978
- cna.erlef.org/cves/CVE-2026-115693.html
- github.com/benoitc/hackney/releases/tag/4.9.0
- github.com/benoitc/hackney/commit/d8a9afcf5c3849904274e2eba1df56c63d1a1a0c
- github.com/benoitc/hackney/commit/915eb5737e9c489d8fc22eec8cdae30e9b027be4
- hex.pm/packages/hackney
- osv.dev/vulnerability/EEF-CVE-2026-115693
Merged from OSV, ENISA EUVD. Last updated 3 Nov 2026. All vulnerabilities · RSS · JSON