From 90479b3415b6692cc5c903a606610a5371bf11fb Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 26 Oct 2026 21:48:31 +0400 Subject: [PATCH 1/5] Sec: Update bandit to version 1.14.0 --- changelog.d/bandit-1.14.0.security | 1 + mix.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/bandit-1.14.0.security diff --git a/changelog.d/bandit-1.14.0.security b/changelog.d/bandit-1.14.0.security new file mode 100644 index 000000000..f2161ae86 --- /dev/null +++ b/changelog.d/bandit-1.14.0.security @@ -0,0 +1 @@ +Update Bandit to version 1.14.0, which fixes HTTP/1.1, HTTP/2 and WebSocket denial-of-service and request smuggling vulnerabilities diff --git a/mix.lock b/mix.lock index 2f2cce9e2..2d47078da 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,7 @@ %{ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "argon2_elixir": {:hex, :argon2_elixir, "4.1.3", "4f28318286f89453364d7fbb53e03d4563fd7ed2438a60237eba5e426e97785f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "7c295b8d8e0eaf6f43641698f962526cdf87c6feb7d14bd21e599271b510608c"}, - "bandit": {:hex, :bandit, "1.13.1", "1ce25a7beb750ed3fa2e905962f1f9a679cb585f1da0ae45637d06e385ce2eae", [:mix], [{:hpax, "~> 1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.6", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "39028f0b5a21404661410a6c19275060a9822c9dcca70b7c3e4e4fa50b11da58"}, + "bandit": {:hex, :bandit, "1.14.0", "555af34f126edb98d8b6b95bbeba4d5f4c3af21e37535d791f32db2365c532fb", [:mix], [{:hpax, "~> 1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.6", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "2d07630e4bf77b55240881386f7629bcc2f87426a299860b2d31c4ad77a48746"}, "base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"}, From eabdde8a2086759c7f39976e3780974dc5196a9d Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 26 Oct 2026 21:52:03 +0400 Subject: [PATCH 2/5] Sec: Update hackney to version 4.9.1 Hackney 4.9 enforces max_body while it reads and returns {:error, {:closed, partial}} for a truncated body; the hackney regression tests now pin that. The certifi override goes, as hackney itself now asks for certifi ~> 2.18.0. --- changelog.d/hackney-4.9.1.security | 1 + mix.exs | 4 +--- mix.lock | 8 ++++---- test/pleroma/http/hackney_conn_leak_test.exs | 11 +++++----- .../http/hackney_max_body_regression_test.exs | 20 +++++++++---------- 5 files changed, 20 insertions(+), 24 deletions(-) create mode 100644 changelog.d/hackney-4.9.1.security diff --git a/changelog.d/hackney-4.9.1.security b/changelog.d/hackney-4.9.1.security new file mode 100644 index 000000000..568903273 --- /dev/null +++ b/changelog.d/hackney-4.9.1.security @@ -0,0 +1 @@ +Update Hackney to version 4.9.1, which fixes unbounded response body buffering and decompression, CA pinning, redirect credential leaks and other vulnerabilities diff --git a/mix.exs b/mix.exs index d6c2be21f..f2014ec3b 100644 --- a/mix.exs +++ b/mix.exs @@ -224,9 +224,7 @@ defmodule Pleroma.Mixfile do {:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false}, {:mock, "~> 0.3.9", only: :test}, {:covertool, "~> 2.0", only: :test}, - {:hackney, "~> 4.7.3", override: true}, - # Hackney 4.7.3 pins certifi ~> 2.17.0; 2.18.0 drops the distrusted emSign roots. - {:certifi, "~> 2.18.0", override: true}, + {:hackney, "~> 4.9.1", override: true}, {:mox, "~> 1.2", only: :test}, {:websockex, "~> 0.4.3", only: :test}, {:benchee, "~> 1.4", only: :benchmark}, diff --git a/mix.lock b/mix.lock index 2d47078da..f8ce53f4c 100644 --- a/mix.lock +++ b/mix.lock @@ -58,8 +58,8 @@ "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "gun": {:hex, :gun, "2.7.0", "8dfc20ff1b70f3c78f07122f7d82ac6950c40d33a8a9b35da0134f49fb35b334", [:make, :rebar3], [{:cowlib, ">= 2.21.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "2a90d073069e72654b6c9a91fae365b746ff5ce385a4a5bbf71ef53a8bf95d82"}, - "h2": {:hex, :h2, "0.11.0", "b8c9f7c86f400582c5cbed59f79c0efd6cf49ad6478c210bede91d2d15019b14", [:rebar3], [], "hexpm", "c61f24361d0dc76375eaf35c2bf0e01235d81b7685538df571db036467fac358"}, - "hackney": {:hex, :hackney, "4.7.3", "42feec1a4986000411cb5d389bbc9fb61d9dff84c5eef42b11fc40ea327e6f9a", [:rebar3], [{:certifi, "~> 2.17.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:h2, "~> 0.11.0", [hex: :h2, repo: "hexpm", optional: false]}, {:idna, "~> 7.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.5", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.2", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:webtransport, "~> 0.4.4", [hex: :webtransport, repo: "hexpm", optional: false]}], "hexpm", "3f460e01d5a1abba39c32d06e9a733bb76e35d1ab87a83868b6bb29d646fe25f"}, + "h2": {:hex, :h2, "0.12.5", "03d492b4cce77466c4c4a2b3fd74b2bb16c1d78f9618bbfeb50ea493c005accf", [:rebar3], [], "hexpm", "a4524bf80cafb86e599710c1d5c9f63aca0284607cb4d99fb53ae63fd55aa03f"}, + "hackney": {:hex, :hackney, "4.9.1", "1c1c3d75ae0f9c35df40f56ca63ba2f8c9986d988d67ae8ebe26f96697e81485", [:rebar3], [{:certifi, "~> 2.18.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:h2, "~> 0.12.5", [hex: :h2, repo: "hexpm", optional: false]}, {:idna, "~> 7.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.5", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.2", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:quic, "~> 2.1.2", [hex: :quic, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:webtransport, "~> 0.4.8", [hex: :webtransport, repo: "hexpm", optional: false]}], "hexpm", "093e1830e523c2b1972d570dcee83d000d41f7b39e7eb3c6b5240cf0f64a1973"}, "hpax": {:hex, :hpax, "1.1.0", "782931867cc23217c68fb5f68fe1a11f5e7544c7fda82c8a7019a5df5a4a1cdf", [:mix], [], "hexpm", "0b8d0f05832f55571d65ac720f79bf8994138ffbb133209dc4685eae0ad456a8"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "http_signatures": {:hex, :http_signatures, "0.1.3", "19f26aee35b4684e37efdce3ac4638605e6e41a04368186bd39d2b6138a60ea9", [:mix], [{:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20313a65516db88006f85b090f6f76cc5b04e9609b45943657e6781eb91174f4"}, @@ -130,7 +130,7 @@ "prometheus_phx": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git", "9cd8f248c9381ffedc799905050abce194a97514", [branch: "no-logging"]}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, "quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"}, - "quic": {:hex, :quic, "1.8.0", "7b074fc6f8a13b4a81e4e11254bf061db61c5c07b4366d568a79a38c5e8bb5f3", [:rebar3], [], "hexpm", "6b1f8f08a45412ac503bc6771efb8edc696bb14079bc48d2ca960b92933da8a4"}, + "quic": {:hex, :quic, "2.1.3", "c66861354d39b0f2b8ac0d7cde56d90db0168ac7b3ffa11d1cb8ab37237d1233", [:rebar3], [], "hexpm", "1520df50fc63093c0a4d209cb97056e132d06f0b8fd54c5cf933ddc0c0ea6fe2"}, "ranch": {:hex, :ranch, "2.2.1", "fc2bb0e800efbeab8ff82eb325450b9c08653b14c3f69e8b09bbea304973105d", [:make, :rebar3], [], "hexpm", "55f05cce20ec2da1d90de5d5981afb93dbfc01325fc7e933189aa5c62f037c24"}, "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"}, "remote_ip": {:hex, :remote_ip, "1.2.0", "fb078e12a44414f4cef5a75963c33008fe169b806572ccd17257c208a7bc760f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2ff91de19c48149ce19ed230a81d377186e4412552a597d6a5137373e5877cb7"}, @@ -158,7 +158,7 @@ "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"}, - "webtransport": {:hex, :webtransport, "0.4.4", "714180b340fbd30a7d666f3f593a612186ff1ba444566b3e8c9ce37fb2950638", [:rebar3], [{:h2, "~> 0.11.0", [hex: :h2, repo: "hexpm", optional: false]}, {:quic, "~> 1.8.0", [hex: :quic, repo: "hexpm", optional: false]}], "hexpm", "80a6641e0f6628938ceee4c9d7a4f6d48a6089890da1ae3288ca8dab13a6005e"}, + "webtransport": {:hex, :webtransport, "0.4.8", "3c340d7ea381e3f639ece90378d85fb46b1da4eece17bba3223051e833166501", [:rebar3], [{:h2, "~> 0.12", [hex: :h2, repo: "hexpm", optional: false]}, {:quic, "~> 2.0", [hex: :quic, repo: "hexpm", optional: false]}], "hexpm", "08477a2195ccbf944da0b78f85dc4e6ee4498790e3dd7fe84ee7a245938e1b26"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.12.2", "9dd1330fb4cd9a36a7b0f502e5b12486eff632792ee4a5f0eba52a4d4ec32c9c", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "e7c1b10122f973e6558462d51c39026ba0e14afbc6745318e990ea82cfe9e159"}, } diff --git a/test/pleroma/http/hackney_conn_leak_test.exs b/test/pleroma/http/hackney_conn_leak_test.exs index f83cad73d..ce1184453 100644 --- a/test/pleroma/http/hackney_conn_leak_test.exs +++ b/test/pleroma/http/hackney_conn_leak_test.exs @@ -14,7 +14,8 @@ # # Pleroma cannot work around this: since 4.0 Hackney ignores `with_body` and # always returns a fully read binary, so a synchronous request never yields a -# handle to close, and a truncated read still reports `{:ok, body}`. +# handle to close. Up to 4.8 a truncated read also reported `{:ok, body}`; +# since 4.9 it is `{:error, {:closed, partial_body}}`. # # Media proxy warming hits this whenever an attachment fetch is cut short and # the outbound proxy is used as a CONNECT tunnel, since tunnels are never @@ -50,11 +51,9 @@ defmodule Pleroma.HTTP.HackneyConnLeakTest do # pool: false takes hackney's connect_direct/4 path, the same unpooled shape # a CONNECT tunnel gets. A pooled connection is reaped by the pool's own # grace timer and would not show the leak. - assert {:ok, %Tesla.Env{status: 200, body: body}} = - request(server, pool: false) + assert {:error, {:closed, body}} = request(server, pool: false) - # The short read still reports success, which is why nothing downstream - # knows to clean up. + # The short read hands back what did arrive. assert byte_size(body) == @sent_bytes leaked = wait_for_cleanup(before) @@ -70,7 +69,7 @@ defmodule Pleroma.HTTP.HackneyConnLeakTest do test "a pooled request with a truncated body does not leak either", %{server: server} do before = conn_pids() - assert {:ok, %Tesla.Env{status: 200}} = request(server, pool: :media) + assert {:error, {:closed, _}} = request(server, pool: :media) assert wait_for_cleanup(before) == [] end diff --git a/test/pleroma/http/hackney_max_body_regression_test.exs b/test/pleroma/http/hackney_max_body_regression_test.exs index 26de0e021..0fc456755 100644 --- a/test/pleroma/http/hackney_max_body_regression_test.exs +++ b/test/pleroma/http/hackney_max_body_regression_test.exs @@ -4,8 +4,9 @@ # Hackney 4 ignores `with_body` and always returns a fully read binary, so # `Tesla.Adapter.Hackney` never reaches the clause that honours `:max_body`. -# The limit is enforced by Pleroma.Tesla.Middleware.MaxBody instead; these -# exercise it against the real adapter. +# Hackney 4.9 enforces `max_body` itself while reading (CVE-2026-97200); +# Pleroma.Tesla.Middleware.MaxBody stays as the check for every adapter. These +# exercise both against the real adapter. defmodule Pleroma.HTTP.HackneyMaxBodyRegressionTest do use ExUnit.Case, async: false @@ -62,12 +63,11 @@ defmodule Pleroma.HTTP.HackneyMaxBodyRegressionTest do assert byte_size(body) == @body_bytes end - # Pins why the middleware is needed. Tesla.Adapter.Hackney only honours - # :max_body on the branch where hackney hands back a ref, which 4.x never - # does for a synchronous request. Should this ever start failing, hackney or - # Tesla have regained streaming and the middleware can go. - test "the bare adapter does not enforce max_body", %{server: server} do - url = "#{server.base_url}/big" + # Hackney 4.0 to 4.8 read the whole body whatever `max_body` said; 4.9 + # stops reading once the body goes over it (CVE-2026-97200). Should this + # start failing, the hackney pin has gone back to a version without the fix. + test "the bare adapter enforces max_body", %{server: server} do + url = "#{server.base_url}/chunked" adapter_opts = HackneyAdapterHelper.options( @@ -77,10 +77,8 @@ defmodule Pleroma.HTTP.HackneyMaxBodyRegressionTest do client = Tesla.client([], Tesla.Adapter.Hackney) - assert {:ok, %Tesla.Env{status: 200, body: body}} = + assert {:error, :body_too_large} = Tesla.request(client, method: :get, url: url, opts: [adapter: adapter_opts]) - - assert byte_size(body) == @body_bytes end defp start_server do From 48ec8518a31f61203d7e8726a4da0a7244fa5695 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 26 Oct 2026 22:02:18 +0400 Subject: [PATCH 3/5] fix(http): drop the partial body from truncated-read errors Hackney 4.9 returns {:error, {:closed, partial}}. RelMe caches errors, so it would keep up to max_body bytes per URL. --- lib/pleroma/http.ex | 6 ++++++ test/pleroma/http_test.exs | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex index c230cea46..ca9f5368d 100644 --- a/lib/pleroma/http.ex +++ b/lib/pleroma/http.ex @@ -79,8 +79,14 @@ defmodule Pleroma.HTTP do adapter, adapter_opts ) + |> drop_partial_body() end + # Hackney 4.9 returns the bytes read before the peer closed. No caller wants + # them, and callers that cache errors would keep them. + defp drop_partial_body({:error, {:closed, body}}) when is_binary(body), do: {:error, :closed} + defp drop_partial_body(result), do: result + @spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()} def request(client, request), do: Tesla.request(client, request) diff --git a/test/pleroma/http_test.exs b/test/pleroma/http_test.exs index e673e6591..436a22447 100644 --- a/test/pleroma/http_test.exs +++ b/test/pleroma/http_test.exs @@ -29,6 +29,10 @@ defmodule Pleroma.HTTPTest do %{method: :post, url: "http://example.com/world"} -> %Tesla.Env{status: 200, body: "world"} + # Hackney 4.9 hands back the bytes it read before the peer closed. + %{method: :get, url: "http://example.com/truncated"} -> + {:error, {:closed, "partial body"}} + %{method: :get, url: "https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz"} -> %Tesla.Env{status: 200, body: "emoji data"} @@ -81,6 +85,12 @@ defmodule Pleroma.HTTPTest do end end + describe "get/1 with a truncated body" do + test "drops the partial body from the error" do + assert HTTP.get("http://example.com/truncated") == {:error, :closed} + end + end + describe "get/2 (with headers)" do test "returns successfully result for json content-type" do assert HTTP.get("http://example.com/hello", [{"content-type", "application/json"}]) == From 26486b041b577a5694684f3975736c839ee4ae5a Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 26 Oct 2026 22:02:41 +0400 Subject: [PATCH 4/5] docs(http): update hackney comments for 4.9 --- lib/pleroma/tesla/middleware/max_body.ex | 16 +++++++--------- test/pleroma/http/hackney_conn_leak_test.exs | 9 ++++----- .../http/hackney_max_body_regression_test.exs | 6 +++--- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/pleroma/tesla/middleware/max_body.ex b/lib/pleroma/tesla/middleware/max_body.ex index 7e74f0b56..04b1b620d 100644 --- a/lib/pleroma/tesla/middleware/max_body.ex +++ b/lib/pleroma/tesla/middleware/max_body.ex @@ -7,16 +7,14 @@ defmodule Pleroma.Tesla.Middleware.MaxBody do Enforces the `:max_body` adapter option for every adapter. `Pleroma.HTTP.Adapter.Gun` applies the limit as it accumulates a `:plain` - body, so an oversized response is abandoned mid-transfer. Hackney cannot: - since 4.0 it ignores `with_body` and always hands back a fully read binary - (`hackney:request/5`), so `Tesla.Adapter.Hackney` never takes the branch that - would honour `:max_body`. Without this middleware the option is silently - unenforced there. + body, so an oversized response is abandoned mid-transfer. Since 4.9 Hackney + does the same with the `max_body` request option. Before that it ignored + `with_body` and always handed back a fully read binary (`hackney:request/5`), + so `Tesla.Adapter.Hackney` never took the branch that would honour + `:max_body`. - The check therefore bounds what callers are handed, and refuses up front when - `content-length` already announces too much. It cannot bound peak memory on - the hackney path, because the adapter has read the body before any middleware - runs. + The check bounds what callers are handed, whatever the adapter does, and + refuses up front when `content-length` already announces too much. Streaming bodies are left alone: neither adapter limits those, and the caller that owns the stream is the one that has to drain and release it. diff --git a/test/pleroma/http/hackney_conn_leak_test.exs b/test/pleroma/http/hackney_conn_leak_test.exs index ce1184453..424385dac 100644 --- a/test/pleroma/http/hackney_conn_leak_test.exs +++ b/test/pleroma/http/hackney_conn_leak_test.exs @@ -14,16 +14,15 @@ # # Pleroma cannot work around this: since 4.0 Hackney ignores `with_body` and # always returns a fully read binary, so a synchronous request never yields a -# handle to close. Up to 4.8 a truncated read also reported `{:ok, body}`; -# since 4.9 it is `{:error, {:closed, partial_body}}`. +# handle to close. Up to 4.8.2 a truncated read also reported `{:ok, body}`; +# since 4.8.3 it is `{:error, {:closed, partial_body}}`. # # Media proxy warming hits this whenever an attachment fetch is cut short and # the outbound proxy is used as a CONNECT tunnel, since tunnels are never # pooled. That is how it was found. # -# Fixed upstream by https://github.com/benoitc/hackney/pull/919; mix.exs pins -# that commit until it lands in a release. Should this start failing, the pin -# has been moved to a version without the fix. +# Fixed upstream by https://github.com/benoitc/hackney/pull/919. Should this +# start failing, mix.exs has moved to a Hackney version without the fix. # # https://github.com/benoitc/hackney/issues/918 defmodule Pleroma.HTTP.HackneyConnLeakTest do diff --git a/test/pleroma/http/hackney_max_body_regression_test.exs b/test/pleroma/http/hackney_max_body_regression_test.exs index 0fc456755..27fe2cddb 100644 --- a/test/pleroma/http/hackney_max_body_regression_test.exs +++ b/test/pleroma/http/hackney_max_body_regression_test.exs @@ -44,8 +44,8 @@ defmodule Pleroma.HTTP.HackneyMaxBodyRegressionTest do request("#{server.base_url}/big", max_body: 1024) end - # Without a Content-Length the header pre-check cannot fire, so this is the - # case that proves hackney's fully-read binary is measured. + # Without a Content-Length the header pre-check cannot fire, so only a size + # check on the body can refuse this. test "a chunked body over max_body is refused on size alone", %{server: server} do assert {:error, :body_too_large} = request("#{server.base_url}/chunked", max_body: 1024) @@ -58,7 +58,7 @@ defmodule Pleroma.HTTP.HackneyMaxBodyRegressionTest do assert byte_size(body) == @body_bytes end - test "without max_body the body is returned whatever its size", %{server: server} do + test "without max_body the body is returned", %{server: server} do assert {:ok, %Tesla.Env{status: 200, body: body}} = request("#{server.base_url}/big", []) assert byte_size(body) == @body_bytes end From 006ad1c7d07c8b3036ebb3a026a955cd4136d267 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 26 Oct 2026 22:02:47 +0400 Subject: [PATCH 5/5] Prepare 2.11.1 release --- CHANGELOG.md | 7 +++++++ changelog.d/bandit-1.14.0.security | 1 - changelog.d/hackney-4.9.1.security | 1 - changelog.d/{release-2.11.0.skip => release-2.11.1.skip} | 0 mix.exs | 2 +- 5 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 changelog.d/bandit-1.14.0.security delete mode 100644 changelog.d/hackney-4.9.1.security rename changelog.d/{release-2.11.0.skip => release-2.11.1.skip} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f799a0ed..5b22c4164 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 2.11.1 + +### Security + +- Update Bandit to version 1.14.0, which fixes HTTP/1.1, HTTP/2 and WebSocket denial-of-service and request smuggling vulnerabilities +- Update Hackney to version 4.9.1, which fixes unbounded response body buffering and decompression, CA pinning, redirect credential leaks and other vulnerabilities + ## 2.11.0 ### Security diff --git a/changelog.d/bandit-1.14.0.security b/changelog.d/bandit-1.14.0.security deleted file mode 100644 index f2161ae86..000000000 --- a/changelog.d/bandit-1.14.0.security +++ /dev/null @@ -1 +0,0 @@ -Update Bandit to version 1.14.0, which fixes HTTP/1.1, HTTP/2 and WebSocket denial-of-service and request smuggling vulnerabilities diff --git a/changelog.d/hackney-4.9.1.security b/changelog.d/hackney-4.9.1.security deleted file mode 100644 index 568903273..000000000 --- a/changelog.d/hackney-4.9.1.security +++ /dev/null @@ -1 +0,0 @@ -Update Hackney to version 4.9.1, which fixes unbounded response body buffering and decompression, CA pinning, redirect credential leaks and other vulnerabilities diff --git a/changelog.d/release-2.11.0.skip b/changelog.d/release-2.11.1.skip similarity index 100% rename from changelog.d/release-2.11.0.skip rename to changelog.d/release-2.11.1.skip diff --git a/mix.exs b/mix.exs index f2014ec3b..e2af5fda6 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.11.0"), + version: version("2.11.1"), elixir: "~> 1.17", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(),