Skip to content

fix(deps): make hackney an optional dependency (additionally: bump hackney to 4.x to address security advisories)#104

Merged
gjtorikian merged 6 commits into
workos:mainfrom
jtippett:fix/hackney-cve-bump
Jul 2, 2026
Merged

fix(deps): make hackney an optional dependency (additionally: bump hackney to 4.x to address security advisories)#104
gjtorikian merged 6 commits into
workos:mainfrom
jtippett:fix/hackney-cve-bump

Conversation

@jtippett

@jtippett jtippett commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

hackney releases <= 4.0.1 are affected by a batch of security advisories fixed in hackney 4.0.2+:

There is no 1.x backport, so the previous {:hackney, "~> 1.9"} constraint could only ever resolve to vulnerable releases (the lockfile sat at 1.20.1).

Changes

  • Make hackney optional and require a patched release:

    {:hackney, ">= 4.0.2", optional: true}
    • optional: true (per customer request) means hackney is no longer forced onto downstream consumers — they're free to bring their own HTTP client. This is safe because the SDK never wires hackney as the Tesla adapter; Tesla falls back to its built-in default (:httpc) unless the consumer configures otherwise.
    • The constraint drops the 1.x line entirely (it has no security backport), so consumers can no longer resolve to a vulnerable hackney through this SDK. There's no single ~> shorthand for "≥ 4.0.2 but < 5.0.0", and an unbounded floor is sufficient here — Tesla's own adapter constraint caps the upper bound at < 5.0.0, so the lock resolves to hackney 4.4.5.
  • mix.lock now resolves tesla to 1.20.0 (up from 1.8.0): older Tesla capped its optional hackney dependency at ~> 1.6, which blocked 4.x resolution. Recent Tesla's hackney adapter supports the 4.x API.

  • Pruned orphaned lock entries (:metrics, :unicode_util_compat) left over from the hackney 1.x tree, which otherwise trip mix deps.unlock --check-unused.

  • Merged main (feat: raise Elixir floor to 1.16, OTP to 26, and test through 1.20 #105), which raised the Elixir floor to 1.16 / OTP 26 and expanded the CI matrix through Elixir 1.20. hackney 4.x pulls in quic (HTTP/3), which requires OTP 26+ — the new floor satisfies that.

⚠️ Breaking changes

Why this is low-risk

  • The SDK uses hackney only as a possible Tesla adapter — it never calls hackney directly, and doesn't even configure it as the adapter (Tesla defaults to :httpc). Consumers who want hackney opt in explicitly.
  • The test suite runs against Tesla.Mock, so hackney isn't exercised in tests.

Verification

  • CI green across the full matrix — Elixir 1.16 / 1.17 / 1.18 / 1.19 / 1.20, plus Format & Credo, Dialyzer, and Socket Security.
  • MIX_ENV=prod mix compile --force --warnings-as-errors — clean compile with hackney 4.4.5 + tesla 1.20.0.
  • mix test113 passed.
  • mix hex.audit — no retired packages.

hackney <= 4.0.1 is affected by a batch of advisories fixed in 4.0.2+
(e.g. CVE-2026-47075 CRLF injection / HTTP request splitting,
CVE-2026-47066 infinite loop, CVE-2026-47070, CVE-2026-47077). There is
no 1.x backport, so the previous `~> 1.9` constraint could only resolve
to vulnerable releases.

Widen the constraint to `~> 1.21 or ~> 4.0 and >= 4.0.2`, mirroring
Tesla's own hackney adapter constraint, and update the lock so it
resolves to hackney 4.4.5. Tesla is bumped 1.8.0 -> 1.20.0 because older
Tesla capped its optional hackney dependency at `~> 1.6`, which blocked
4.x resolution.

workos uses hackney only as the default Tesla adapter (never directly),
and the test suite runs against Tesla.Mock, so the major bump is
absorbed by Tesla's adapter. Full suite passes (113 tests).
@jtippett jtippett requested a review from a team as a code owner June 28, 2026 11:16
@greptile-apps

greptile-apps Bot commented Jun 28, 2026

Copy link
Copy Markdown

Greptile Summary

This PR updates the optional HTTP adapter dependency path for the Elixir SDK. The main changes are:

  • Makes hackney an optional dependency in mix.exs.
  • Updates the resolved lockfile to hackney 4.4.5.
  • Bumps Tesla from 1.8.0 to 1.20.0 and refreshes related transitive dependencies.

Confidence Score: 4/5

Merge should wait for the dependency constraint to preserve compatibility with consumers that still resolve the supported 1.x hackney line.

The changed dependency declaration is small and localized, and the compatibility issue is directly tied to the version expression in mix.exs.

mix.exs

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex attempted to reproduce the Mix-based dependency-resolution flow, but the Mix executable was not installed, so the repro could not run.
  • I inspected the dependency-resolution compile step by capturing the before and after lockfiles, which showed base hackney 1.20.1/Tesla 1.8.0 and then a head state with hackney 4.4.5/Tesla 1.20.0, culminating in a failure to compile dependency :parse_trans.
  • I ran the tests-audit flow to verify runtime blockers and confirmed the environment reports Elixir 1.14.0/OTP 25 as installed.
  • I exercised the optional hackney consumer path and verified the resolution transitions to a state with no hackney entry, indicating the optionality was accepted.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Head production compile fails with the updated hackney lock

    • Bug
      • The PR's head lockfile contains the claimed hackney 4.4.5 and Tesla 1.20.0 versions, but the requested production compile contract is not met. In the isolated head worktree, after mix deps.get, MIX_ENV=prod mix compile --force fails while compiling parse_trans, a dependency pulled by the locked hackney 4.4.5 entry.
    • Cause
      • The changed mix.lock hackney entry at line 11 locks hackney 4.4.5 with parse_trans 3.4.2 in its dependency list; the executed production compile fails in that dependency chain with undefined function erl_syntax:string/1 and exits 1.
    • Fix
      • Update the locked dependency set or build/runtime support so the hackney 4.4.5 dependency chain compiles cleanly in production, then rerun mix deps.get and MIX_ENV=prod mix compile --force from a clean checkout.

    T-Rex Ran code and verified through T-Rex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
mix.exs:70-72
**Preserve downstream compatibility**
The new `>= 4.0.2` constraint excludes every `1.x` hackney release, so any consumer that still has `hackney` `1.21.x` in its dependency graph now gets a resolver conflict despite this dependency being optional. The lockfile shows Tesla `1.20.0` already accepts `~> 1.21 or >= 4.0.2 and < 5.0.0-0`, so matching that constraint keeps the intended compatibility while preferring patched `4.x` on a fresh resolve.

```suggestion
      # hackney is an optional Tesla adapter (the SDK never wires it up; Tesla
      # defaults to :httpc). Allow patched 4.x while preserving compatibility
      # with consumers still pinned to the latest 1.x line.
      {:hackney, "~> 1.21 or >= 4.0.2 and < 5.0.0-0", optional: true},
```

Reviews (4): Last reviewed commit: "refactor(deps): simplify hackney constra..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd57e85968

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread mix.lock
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"h2": {:hex, :h2, "0.10.2", "ea0146b9c8b5f3b5de16045765f5684db38ef1e66f1c60444890948cb1003e47", [:rebar3], [], "hexpm", "497a899f338b42e6a0b292524e635b0ce6f9379fa39395c8e38d06351cd9b9cf"},
"hackney": {:hex, :hackney, "4.4.5", "a908f620525bb886a16613532324762e5166287f8c00c9888a762edee11a30c0", [:rebar3], [{:certifi, "~> 2.17.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:h2, "~> 0.10.1", [hex: :h2, repo: "hexpm", optional: false]}, {:idna, "~> 7.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.2", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:quic, "~> 1.6.5", [hex: :quic, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:webtransport, "~> 0.4.1", [hex: :webtransport, repo: "hexpm", optional: false]}], "hexpm", "6d72bef4e135e94c522c271e11fbb6933efb0006ef235a3933807d0be73b71ec"},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid pulling quic into OTP 25 builds

The CI matrix still includes an OTP 25.3 job (.github/workflows/main.yml:30-31) and that job runs mix do deps.get, deps.compile (.github/workflows/main.yml:57-59), but this lock entry makes quic a non-optional transitive dependency of hackney 4.4.5. quic 1.6.x declares {minimum_otp_vsn, "26"}, so the OTP 25 job and users on that still-supported runtime will fail during dependency compilation even though mix.exs still advertises broad Elixir support. Please either drop OTP 25 support or avoid resolving a hackney version that pulls this OTP 26-only dependency for supported builds.

Useful? React with 👍 / 👎.

gjtorikian and others added 2 commits July 2, 2026 13:24
Brings in workos#105 (Elixir floor 1.16 / OTP 26, CI matrix through 1.20, split
lint.yml, credo 1.7 / dialyxir 1.4). The new OTP 26 floor satisfies
hackney 4.x's OTP 26+ requirement, so no further version bump is needed.

mix.lock conflict resolved by keeping this branch's hackney 4.4.5 +
tesla 1.20.0 while pulling main's credo 1.7.19 / dialyxir 1.4.7 forward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per customer request, hackney is now declared `optional: true` so it is no
longer forced onto downstream consumers. The SDK never wires hackney as the
Tesla adapter (Tesla defaults to :httpc), so consumers are free to bring
their own HTTP client and configure Tesla accordingly. When hackney is used
it must be a patched release (>= 4.0.2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gjtorikian gjtorikian changed the title fix(deps): bump hackney to 4.x to address security advisories fix(deps): make hackney an optional dependency (additionally: bump hackney to 4.x to address security advisories) Jul 2, 2026
@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown

T-Rex pricing update — T-Rex was free through June 2026. Effective July 1, 2026, T-Rex adds 2 credits on top of the standard 1-credit review (3 total). T-Rex settings

gjtorikian and others added 3 commits July 2, 2026 13:32
hackney 4.x no longer depends on :metrics or :unicode_util_compat (those
belonged to the hackney 1.x tree). They lingered in mix.lock and tripped
`mix deps.unlock --check-unused` in CI. Remove them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 1.x line has no backport for the CRLF/redirect/DoS advisories, so
allowing `~> 1.21` would let consumers resolve to a vulnerable hackney.
Narrow the constraint to the patched 4.x line only (>= 4.0.2, < 5.0.0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the `~> 4.0 and` half of the constraint. There is no single `~>`
shorthand for ">= 4.0.2, capped below 5.0.0", and hackney is optional and
never called directly by the SDK, so an unbounded floor is sufficient —
Tesla's own adapter constraint (< 5.0.0) already caps the upper bound.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread mix.exs
Comment on lines +70 to +72
# hackney is an optional Tesla adapter (the SDK never wires it up; Tesla
# defaults to :httpc). Require >= 4.0.2 — the 1.x line has no security backport.
{:hackney, ">= 4.0.2", optional: true},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Preserve downstream compatibility
The new >= 4.0.2 constraint excludes every 1.x hackney release, so any consumer that still has hackney 1.21.x in its dependency graph now gets a resolver conflict despite this dependency being optional. The lockfile shows Tesla 1.20.0 already accepts ~> 1.21 or >= 4.0.2 and < 5.0.0-0, so matching that constraint keeps the intended compatibility while preferring patched 4.x on a fresh resolve.

Suggested change
# hackney is an optional Tesla adapter (the SDK never wires it up; Tesla
# defaults to :httpc). Require >= 4.0.2 — the 1.x line has no security backport.
{:hackney, ">= 4.0.2", optional: true},
# hackney is an optional Tesla adapter (the SDK never wires it up; Tesla
# defaults to :httpc). Allow patched 4.x while preserving compatibility
# with consumers still pinned to the latest 1.x line.
{:hackney, "~> 1.21 or >= 4.0.2 and < 5.0.0-0", optional: true},
Prompt To Fix With AI
This is a comment left during a code review.
Path: mix.exs
Line: 70-72

Comment:
**Preserve downstream compatibility**
The new `>= 4.0.2` constraint excludes every `1.x` hackney release, so any consumer that still has `hackney` `1.21.x` in its dependency graph now gets a resolver conflict despite this dependency being optional. The lockfile shows Tesla `1.20.0` already accepts `~> 1.21 or >= 4.0.2 and < 5.0.0-0`, so matching that constraint keeps the intended compatibility while preferring patched `4.x` on a fresh resolve.

```suggestion
      # hackney is an optional Tesla adapter (the SDK never wires it up; Tesla
      # defaults to :httpc). Allow patched 4.x while preserving compatibility
      # with consumers still pinned to the latest 1.x line.
      {:hackney, "~> 1.21 or >= 4.0.2 and < 5.0.0-0", optional: true},
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@gjtorikian gjtorikian merged commit 1ba9809 into workos:main Jul 2, 2026
11 checks passed
@gjtorikian

Copy link
Copy Markdown
Contributor

thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants