From 835ef8c4d97045bd5ca4f35b219e4cb7ee1218e1 Mon Sep 17 00:00:00 2001 From: mkreyman Date: Fri, 23 Oct 2026 20:42:24 -0600 Subject: [PATCH 1/2] Bump plug_crypto/mint/cowlib for October advisories; drop five ignores MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI's scheduled Security job failed 2026-10-24 on plug_crypto 2.2.0 (EEF-CVE-2026-105963, fixed in 2.2.1). The same audit window carried mint 1.10.1 (three advisories) and cowlib 2.20.0 (two, ignored) — mint 1.10.2 backports all three fixes without the 1.11.0 behavior change that Finch 0.23.0 breaks on, and cowlib 2.21.0 fixes CVE-2026-43969, so five ignore_advisories entries come out and the mint pin moves to >= 1.10.2. The cloak CVE-2026-94206 in the report needs no code: it affects Cloak.Ecto.PBKDF2, which loopctl never uses, and that was already reasoned in mix.exs and pinned by cloak_advisory_reachability_test.exs. --- CHANGELOG.md | 13 +++++++++++ mix.exs | 62 +++++++++++++++++++--------------------------------- mix.lock | 6 ++--- 3 files changed, 38 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3a30f59..6fb38baf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/mix.exs b/mix.exs index 9dffbc05..6d4063c0 100644 --- a/mix.exs +++ b/mix.exs @@ -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. @@ -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"}, diff --git a/mix.lock b/mix.lock index 7e191bd3..99adbbeb 100644 --- a/mix.lock +++ b/mix.lock @@ -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"}, @@ -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"}, @@ -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"}, From ae1e423c12ec356f00a455d84aead83636f4f501 Mon Sep 17 00:00:00 2001 From: mkreyman Date: Fri, 23 Oct 2026 20:52:23 -0600 Subject: [PATCH 2/2] Refresh the two mint-pinning test docstrings The review of #1013 found both stale after the ignores came out: no_outbound_http2_test.exs still justified itself by mint ignore entries that no longer exist, and finch_timeout_reuse_test.exs pointed at the wrong section of mix.exs, described mint 1.10.1 (locked is 1.10.2), and told the reader the mint advisory entries could still go. Behaviour unchanged; both tests still pin it. --- test/loopctl/net/finch_timeout_reuse_test.exs | 7 ++++--- test/loopctl/net/no_outbound_http2_test.exs | 12 +++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/test/loopctl/net/finch_timeout_reuse_test.exs b/test/loopctl/net/finch_timeout_reuse_test.exs index 419cbee0..85e5c388 100644 --- a/test/loopctl/net/finch_timeout_reuse_test.exs +++ b/test/loopctl/net/finch_timeout_reuse_test.exs @@ -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 diff --git a/test/loopctl/net/no_outbound_http2_test.exs b/test/loopctl/net/no_outbound_http2_test.exs index 70635e05..c491311c 100644 --- a/test/loopctl/net/no_outbound_http2_test.exs +++ b/test/loopctl/net/no_outbound_http2_test.exs @@ -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 @@ -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