Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,19 @@ All notable changes to loopctl are documented here.

### Changed

- **Dependency security bumps: plug_crypto 2.2.1, mint 1.10.2, cowlib 2.21.0.** CI's
Security job failed on 2026-10-24 on EEF-CVE-2026-105963 (plug_crypto: the secret key
base lands in stack-trace arguments on a wrong-typed argument, reachable through
`Phoenix.Token.verify/4`; fixed in 2.2.1). mint 1.10.2 backports the fixes for all
three advisories 1.11.0 carried (EEF-CVE-2026-91043 HIGH, -92103, -94194), so the pin
below 1.11.0 — the Finch 0.23.0 receive-timeout crash, pinned by
`test/loopctl/net/finch_timeout_reuse_test.exs` — costs no security, and the three
mint `ignore_advisories` entries are gone. cowlib 2.21.0 fixes EEF-CVE-2026-43969 and
that entry goes too. The remaining ignores — cowlib CVE-2026-43966 and cloak
CVE-2026-95105 / CVE-2026-94206 — have no upstream release to fix them and are
unreachable from loopctl's code, each pinned by
`test/loopctl/vault/cloak_advisory_reachability_test.exs` or the mix.exs comment.

- **chg-server's keys never certify or implement (US-47.5).** A key whose MINTING lineage
(revoked or not) is rooted at a dispatch any intake source of the tenant registers as
`chg_root_dispatch_id` — live or revoked source, either mode — is refused 409
Expand Down
62 changes: 22 additions & 40 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -36,46 +36,24 @@ defmodule Loopctl.MixProject do
# declined fix from arriving silently.
hex: [
ignore_advisories: [
# cowlib 2.20.0 — no patched release exists for either advisory below:
# each is introduced at an old version with no `fixed` event, and as of
# 2026-09-28 2.20.0 (which fixed CVE-2026-43971) is the newest cowlib on hex.
# cowlib is compiled in as a hard transitive of telemetry_metrics_prometheus
# (via plug_cowboy and cowboy), but the ONLY Cowboy listener is that
# reporter on the internal :9568 metrics port (prod-only, Fly private 6PN);
# the public API serves on Bandit (config/config.exs, Bandit.PhoenixAdapter).
# Recheck when cowlib > 2.20.0.
# cowlib 2.21.0 (2026-10-08) fixed CVE-2026-43969 (cow_cookie:cookie/1
# client-side encoder; nothing in the release calls it — no gun, no
# cowlib-based HTTP client), so that ignore is gone with the bump.
# CVE-2026-43966 (GHSA-w4f7-4cxr-rv3c, MEDIUM): HTTP response splitting in
# cow_http_struct_hd:escape_string/2. The metrics endpoint emits no
# structured headers built from untrusted input.
# cow_http_struct_hd:escape_string/2 — still no patched release: OSV shows
# it introduced at 2.9.0 with no `fixed` event, and 2.21.0 is the newest
# cowlib on hex. cowlib is compiled in as a hard transitive of
# telemetry_metrics_prometheus (via plug_cowboy and cowboy), but the ONLY
# Cowboy listener is that reporter on the internal :9568 metrics port
# (prod-only, Fly private 6PN); the public API serves on Bandit
# (config/config.exs, Bandit.PhoenixAdapter). The metrics endpoint emits no
# structured headers built from untrusted input. Recheck when cowlib > 2.21.0.
"CVE-2026-43966",
# CVE-2026-43969 (GHSA-g2wm-735q-3f56, LOW): Cookie REQUEST header injection
# in cow_cookie:cookie/1, the client-side encoder. Nothing in the release
# calls it: there is no cowlib-based HTTP client (no gun).
"CVE-2026-43969",
# mint 1.10.1 — the fix, 1.11.0, is DECLINED and pinned out in deps/0.
# 1.11.0 stopped closing a connection on a receive timeout, and Finch 0.23.0
# (the newest as of 2026-09-28) returns that connection to its pool, so the
# next request on it crashes with a CaseClauseError on the late response
# (test/loopctl/net/finch_timeout_reuse_test.exs pins it). That would turn
# every outbound timeout into a crash. Recheck when a Finch release closes a
# connection on a receive timeout: that test then passes on mint >= 1.11.0.
# CVE-2026-91043 (HIGH): HTTP/2 HPACK cookie fields bypass
# max_header_list_size. HTTP/2 only; Req's Finch pools fall back to
# `protocols: [:http1]` and no Req call opts into :http2
# (test/loopctl/net/no_outbound_http2_test.exs fails if one does).
"CVE-2026-91043",
# CVE-2026-92103 (MEDIUM): HTTP/2 oversized frames buffered before
# max_frame_size is enforced. HTTP/2 only, as above.
"CVE-2026-92103",
# CVE-2026-94194 (MEDIUM): HTTP/1 chunked framing when chunked is not the
# final coding, enabling response smuggling THROUGH AN INTERMEDIARY. That is
# REACHABLE in principle: a tenant's webhook URL may sit behind a CDN or a
# relay that other tenants' URLs share (one Finch pool per host). The
# impact is bounded to the delivery log: a webhook response is recorded
# (status and body, `Loopctl.Webhooks.ReqDelivery`) and never acted on, so a
# smuggled response can at worst put one tenant's relay response in
# another's delivery record. Accepted against the crash above.
"CVE-2026-94194",
# No mint ignores remain: 1.10.2 (2026-09-29) backported the fixes for all
# three advisories 1.11.0 carried (CVE-2026-91043 HIGH, CVE-2026-92103,
# CVE-2026-94194), so the pin below 1.11.0 in deps/0 costs no security.
# The HTTP/1-only posture is still pinned by
# test/loopctl/net/no_outbound_http2_test.exs.
# bandit moved to 1.13.1 (2026-10-17), which OSV reports clean of every
# advisory 1.12.5 carried (1.13.0 fixed CVE-2026-105660, the HTTP/1
# request-target authority issue), so no bandit ignore remains.
Expand Down Expand Up @@ -185,8 +163,12 @@ defmodule Loopctl.MixProject do

# HTTP client
{:req, "~> 0.5"},
# Pinned below 1.11.0: see the mint entry in hex ignore_advisories above.
{:mint, ">= 1.10.1 and < 1.11.0"},
# Pinned below 1.11.0: mint 1.11.0 stopped closing a connection on a receive
# timeout and Finch 0.23.0 returns that connection to its pool, so the next
# request on it crashes (test/loopctl/net/finch_timeout_reuse_test.exs).
# 1.10.2 carries every advisory 1.11.0 fixed, so the pin costs no security;
# recheck it when a Finch release closes the connection on a receive timeout.
{:mint, ">= 1.10.2 and < 1.11.0"},

# Background jobs
{:oban, "~> 2.19"},
Expand Down
6 changes: 3 additions & 3 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"cowboy": {:hex, :cowboy, "2.19.0", "78b9d92d25a23e56d7341040b67946c04067cd77de966e2372865f673a8e6f59", [:make, :rebar3], [{:cowlib, ">= 2.20.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "986dae81f99fcb78ef2d8efc21d738cee189410840eeaf32eca84ada81dcf6d4"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.20.0", "bb525377ba634cd6d68bac7bff5d98571f738806139d335daca827717c5dc172", [:make, :rebar3], [], "hexpm", "7d41a0dd2c093041ff3779ac5fe8a1585a68ec7cb2dd1de0536bdd2452fd7ba1"},
"cowlib": {:hex, :cowlib, "2.21.0", "3ea757c79784bdbdebac7857ce3adf2290af6b8680d4248c75e40ced2e98c89e", [:make, :rebar3], [], "hexpm", "0960248773762a5319b3c17285028bc76f54f58ef0f9451367401bdf67b46274"},
"credo": {:hex, :credo, "1.7.17", "f92b6aa5b26301eaa5a35e4d48ebf5aa1e7094ac00ae38f87086c562caf8a22f", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1eb5645c835f0b6c9b5410f94b5a185057bcf6d62a9c2b476da971cde8749645"},
"db_connection": {:hex, :db_connection, "2.10.2", "ae391e803a5adff104da913c2fc1c0c14a37f8b10001dcef568796e1fb7bf95c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "510b14482330f1af6490a2fa0efd8d4f1435d1529b165647df22ac0f2df0fa93"},
"decimal": {:hex, :decimal, "3.1.2", "5111bb7f15ceb8412ad408f908025e7a5ec3080b9978612a647ac82430306726", [:mix], [], "hexpm", "6aaf9d352d1c80fd3983b82af666f7ea2f3fe225ae202c89055ca38f3582ba5b"},
Expand All @@ -37,7 +37,7 @@
"mdex": {:hex, :mdex, "0.13.4", "9e42874ee4e9f8be110a5c03a91f838068c1b089bc0c586243d83503b153a2b1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:lumis, "~> 0.1", [hex: :lumis, repo: "hexpm", optional: true]}, {:mdex_native, ">= 0.2.6", [hex: :mdex_native, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}], "hexpm", "915e579df919923c0685e6ea9851cf6e4a346d014795f03ce5a1a9c52455192b"},
"mdex_native": {:hex, :mdex_native, "0.2.6", "16c0d9ffca05990982ef8113c0a61950ffc250bf0792e17b95b28b74e1f572dc", [:mix], [{:rustler, "~> 0.32", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "eaaf2af27162c9879f5aef66344101dadf1898e71ea6ac9eb811c8d92d8e49ad"},
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
"mint": {:hex, :mint, "1.10.1", "c53e70867cf74017716884d8d33e0742b08b32e9cdb0031cbc69a429dc5555e3", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "0ba2a904605ed8406393444fb8b3356dc58eb59ee6c7fb94ac3f015e1be129e8"},
"mint": {:hex, :mint, "1.10.2", "fe6165c639379882ba9af943e81771c79e40f2800646120d65b12cde2e57072c", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3171931320e7abc6164093aa49e3b5f20dcd079b6dce4be23947dd1e42dbb68b"},
"mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_ownership": {:hex, :nimble_ownership, "1.0.2", "fa8a6f2d8c592ad4d79b2ca617473c6aefd5869abfa02563a77682038bf916cf", [:mix], [], "hexpm", "098af64e1f6f8609c6672127cfe9e9590a5d3fcdd82bc17a377b8692fd81a879"},
Expand All @@ -55,7 +55,7 @@
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"plug": {:hex, :plug, "1.20.3", "56c480c633ec2ce10140e236e15233bf576e1d323887d7c96711bd02ab5160db", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be266aee1b8536ef6409d58cf39a3121319f0ec47cfa1b24024485aa0e76ad76"},
"plug_cowboy": {:hex, :plug_cowboy, "2.9.0", "87e21e0d9054ced99c36d128f49e3ea2cd8b745fffb97de50bff99706087af4f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2002bafba4f3a45b55a58e68d70211b153a7ed18d37edb1ceb6e96e7a92c422e"},
"plug_crypto": {:hex, :plug_crypto, "2.2.0", "144014737daaf485407f5ed77daeaad74d651b216a28c87543f8cc7043f8efc8", [:mix], [], "hexpm", "83a95744ab1c75876542b6fab135fcc176280e0f301a111c1f757fddcec95d2c"},
"plug_crypto": {:hex, :plug_crypto, "2.2.1", "bd1d0358a2e2f5ffb818217e64c394f9d24ed191d862b7fa1bcce264ace2b8ea", [:mix], [], "hexpm", "4417ccbd8f85e627321cd07d2ab9c4b621f1fd883b9905af241f9b4b780b94b6"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.22.4", "d271f595dfd25230b6398354e19d17bb5e2d20130fd2d9bdca7e15f125d43552", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "4aae45a2d60e35b04eea2602440be152fae332901f1fc7a60fc7cb7f0f9a9c5a"},
"ranch": {:hex, :ranch, "2.3.0", "7de7b041a9a6a5091a3aa5898d66c0564be671d87db4f9d63b1b5ee775b097df", [:make, :rebar3], [], "hexpm", "6168ec49409d982f7cfbd83dd083144f6cbe67caa4036551d2f0a3ad67c9d023"},
Expand Down
7 changes: 4 additions & 3 deletions test/loopctl/net/finch_timeout_reuse_test.exs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
defmodule Loopctl.Net.FinchTimeoutReuseTest do
@moduledoc """
PINS why mint stays below 1.11.0 (mix.exs, `hex: ignore_advisories`, PR #926).
PINS why mint stays below 1.11.0 (mix.exs, `deps/0`, PR #926; the reasoning moved from
`hex: ignore_advisories` to the pin comment in PR #1013).

mint 1.11.0 stopped closing a connection on a receive timeout, and Finch 0.23.0 returns an
open connection to its pool, so the NEXT request on it reads the late response of the one
that timed out and crashes with a CaseClauseError. On mint 1.10.1 the timed-out connection
that timed out and crashes with a CaseClauseError. On mint < 1.11.0 the timed-out connection
is closed and the next request gets a fresh one. A pool of ONE forces the reuse.

This fails on mint >= 1.11.0 until Finch closes the connection itself; when it passes on a
newer mint, the pin and the mint advisory entries can go.
newer mint, the pin can go — the mint advisory entries already did (1.10.2 fixed them).
"""

use ExUnit.Case, async: true
Expand Down
12 changes: 7 additions & 5 deletions test/loopctl/net/no_outbound_http2_test.exs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
defmodule Loopctl.Net.NoOutboundHttp2Test do
@moduledoc """
The mint HTTP/2 advisories (CVE-2026-91043, -92103) are ignored in mix.exs ONLY because no
outbound client speaks HTTP/2: Req's Finch pools fall back to `protocols: [:http1]`. A call
opting into `:http2` would make them reachable while the ignore entries keep CI green, so
this fails first (PR #926).
Nothing outbound speaks HTTP/2: Req's Finch pools fall back to `protocols: [:http1]`.
This pinned the HTTP/2-only mint advisories (CVE-2026-91043, -92103) while mint sat at
1.10.1 with ignore entries in mix.exs (PR #926); mint 1.10.2 fixed them and the ignores
are gone (PR #1013). The posture stays pinned here because an h2 client re-exposes the
surface every future mint advisory will live on, and the mix.exs mint comment points
at this test as the standing guarantee.
"""

use ExUnit.Case, async: true
Expand All @@ -14,6 +16,6 @@ defmodule Loopctl.Net.NoOutboundHttp2Test do
|> Path.wildcard()
|> Enum.filter(fn path -> File.read!(path) =~ ~r/:http2\b/ end)

assert offenders == [], "outbound HTTP/2 makes the ignored mint advisories reachable"
assert offenders == [], "outbound HTTP/2 re-opens the surface the mint advisories lived on"
end
end
Loading