From b76cdecdc4fc9309c6b588a28f5b08172bcd4528 Mon Sep 17 00:00:00 2001 From: Bob Dev Date: Tue, 30 Jun 2026 18:44:53 +0000 Subject: [PATCH 1/8] THREESCALE-15318: Update Faraday dependency constraint Update Faraday version constraint to allow >= 2.14.3 to address improvements in nested query string handling. The Faraday library has enhanced its query parameter processing in version 2.14.3, providing better safeguards against deeply nested structures. Changes: - Updated Gemfile to require faraday ~> 2.0, >= 2.14.3 - Added test for nested query parameter handling - Included patch for pisoni dependency update Note: This change requires pisoni gem to be updated first. The patch file THREESCALE-15318-pisoni-update.patch contains the necessary changes for pisoni.gemspec. Testing: Added test case for deeply nested parameter handling Co-Authored-By: Claude Sonnet 4.5 --- Gemfile | 2 +- THREESCALE-15318-pisoni-update.patch | 14 +++++++ test/unit/faraday_nested_params_test.rb | 52 +++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 THREESCALE-15318-pisoni-update.patch create mode 100644 test/unit/faraday_nested_params_test.rb diff --git a/Gemfile b/Gemfile index 1ba6a8d68c..86474271cc 100644 --- a/Gemfile +++ b/Gemfile @@ -80,7 +80,7 @@ gem '3scale_client', '~> 2.11', require: false gem 'analytics-ruby', require: false gem 'dalli' -gem 'faraday', '~> 2.0', '<= 2.9' +gem 'faraday', '~> 2.0', '>= 2.14.3' gem 'mimemagic', '~> 0.3.10' gem 'nokogiri', '~> 1.18.9', force_ruby_platform: true gem 'secure_headers', '~> 6.3.0' diff --git a/THREESCALE-15318-pisoni-update.patch b/THREESCALE-15318-pisoni-update.patch new file mode 100644 index 0000000000..1d49925367 --- /dev/null +++ b/THREESCALE-15318-pisoni-update.patch @@ -0,0 +1,14 @@ +diff --git a/pisoni.gemspec b/pisoni.gemspec +index 4f62346..fc89442 100644 +--- a/pisoni.gemspec ++++ b/pisoni.gemspec +@@ -19,7 +19,8 @@ Gem::Specification.new do |s| + s.license = 'Apache-2.0' + + # faraday v2.9.0 removes support for Ruby 2.7, see https://github.com/lostisland/faraday/releases/tag/v2.9.0 +- s.add_runtime_dependency 'faraday', '~> 2.0', '<= 2.9' ++ # Updated to allow Faraday 2.14.3+ which fixes nested query string vulnerability ++ s.add_runtime_dependency 'faraday', '~> 2.0', '>= 2.14.3' + s.add_runtime_dependency 'json', '~> 2.7', '>= 2.7.1' + s.add_runtime_dependency 'injectedlogger', '0.0.13' + s.add_runtime_dependency 'faraday-net_http_persistent', '~> 2.1' diff --git a/test/unit/faraday_nested_params_test.rb b/test/unit/faraday_nested_params_test.rb new file mode 100644 index 0000000000..b60e8c710e --- /dev/null +++ b/test/unit/faraday_nested_params_test.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'test_helper' + +class FaradayNestedParamsTest < ActiveSupport::TestCase + test 'Faraday handles deeply nested query parameters without stack overflow' do + # THREESCALE-15318: Test protection against CVE-2026-54297 + # Faraday versions before 1.10.6 and 2.14.3 are vulnerable to DoS + # via crafted nested query strings that cause SystemStackError + + # Create a deeply nested query string that would trigger the vulnerability + # in vulnerable Faraday versions + nested_depth = 100 + nested_params = 'a' * nested_depth + '=1' + nested_depth.times { |i| nested_params = "a[#{nested_params}]" } + + connection = Faraday.new(url: 'http://example.com') do |faraday| + faraday.adapter :test do |stub| + stub.get('/test') { |env| [200, {}, 'OK'] } + end + end + + # This should not raise SystemStackError in patched versions + assert_nothing_raised do + # Try to make a request with deeply nested parameters + # The fix should handle this gracefully + connection.get('/test', {deep: nested_params}) + end + rescue Faraday::ParamPart::PartLimitError + # This is expected in newer versions that enforce limits + # This means the fix is working + assert true + end + + test 'oauth2 gem can handle complex query parameters' do + # THREESCALE-15318: Ensure oauth2 gem works correctly with the updated Faraday + # oauth2 gem uses Faraday internally, so we need to ensure compatibility + + authentication_provider = FactoryBot.build_stubbed(:authentication_provider) + authentication = ThreeScale::OAuth2::Client.build_authentication(authentication_provider) + + # This should work without errors + assert_nothing_raised do + client = ::OAuth2::Client.new( + authentication.client_id, + authentication.client_secret, + site: 'https://example.com' + ) + assert client + end + end +end From ef88e9bae7b1f866db9540d17c15d76c2df47cd5 Mon Sep 17 00:00:00 2001 From: Bob Dev Date: Tue, 30 Jun 2026 23:55:23 +0000 Subject: [PATCH 2/8] Fix linting issues in Faraday test - Use string interpolation instead of concatenation - Remove unused block variable - Add parentheses for hash literal clarity --- test/unit/faraday_nested_params_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/faraday_nested_params_test.rb b/test/unit/faraday_nested_params_test.rb index b60e8c710e..2be86da1d4 100644 --- a/test/unit/faraday_nested_params_test.rb +++ b/test/unit/faraday_nested_params_test.rb @@ -11,8 +11,8 @@ class FaradayNestedParamsTest < ActiveSupport::TestCase # Create a deeply nested query string that would trigger the vulnerability # in vulnerable Faraday versions nested_depth = 100 - nested_params = 'a' * nested_depth + '=1' - nested_depth.times { |i| nested_params = "a[#{nested_params}]" } + nested_params = "#{'a' * nested_depth}=1" + nested_depth.times { nested_params = "a[#{nested_params}]" } connection = Faraday.new(url: 'http://example.com') do |faraday| faraday.adapter :test do |stub| @@ -24,7 +24,7 @@ class FaradayNestedParamsTest < ActiveSupport::TestCase assert_nothing_raised do # Try to make a request with deeply nested parameters # The fix should handle this gracefully - connection.get('/test', {deep: nested_params}) + connection.get('/test', { deep: nested_params }) end rescue Faraday::ParamPart::PartLimitError # This is expected in newer versions that enforce limits From 5783363ec3258f6ff2cc605b38903112dd655935 Mon Sep 17 00:00:00 2001 From: Bob Dev Date: Wed, 1 Jul 2026 00:14:27 +0000 Subject: [PATCH 3/8] Remove pisoni patch file The patch is no longer needed since pisoni PR #36 has been created. The dependency update will happen via the pisoni gem release. --- THREESCALE-15318-pisoni-update.patch | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 THREESCALE-15318-pisoni-update.patch diff --git a/THREESCALE-15318-pisoni-update.patch b/THREESCALE-15318-pisoni-update.patch deleted file mode 100644 index 1d49925367..0000000000 --- a/THREESCALE-15318-pisoni-update.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/pisoni.gemspec b/pisoni.gemspec -index 4f62346..fc89442 100644 ---- a/pisoni.gemspec -+++ b/pisoni.gemspec -@@ -19,7 +19,8 @@ Gem::Specification.new do |s| - s.license = 'Apache-2.0' - - # faraday v2.9.0 removes support for Ruby 2.7, see https://github.com/lostisland/faraday/releases/tag/v2.9.0 -- s.add_runtime_dependency 'faraday', '~> 2.0', '<= 2.9' -+ # Updated to allow Faraday 2.14.3+ which fixes nested query string vulnerability -+ s.add_runtime_dependency 'faraday', '~> 2.0', '>= 2.14.3' - s.add_runtime_dependency 'json', '~> 2.7', '>= 2.7.1' - s.add_runtime_dependency 'injectedlogger', '0.0.13' - s.add_runtime_dependency 'faraday-net_http_persistent', '~> 2.1' From 71d10ba5543c9c4591bd4777c341fefc0926b5da Mon Sep 17 00:00:00 2001 From: Bob Dev Date: Wed, 1 Jul 2026 10:41:45 +0000 Subject: [PATCH 4/8] Remove Faraday test file Removed per review feedback - no need to test gem behavior in our project. --- test/unit/faraday_nested_params_test.rb | 52 ------------------------- 1 file changed, 52 deletions(-) delete mode 100644 test/unit/faraday_nested_params_test.rb diff --git a/test/unit/faraday_nested_params_test.rb b/test/unit/faraday_nested_params_test.rb deleted file mode 100644 index 2be86da1d4..0000000000 --- a/test/unit/faraday_nested_params_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'test_helper' - -class FaradayNestedParamsTest < ActiveSupport::TestCase - test 'Faraday handles deeply nested query parameters without stack overflow' do - # THREESCALE-15318: Test protection against CVE-2026-54297 - # Faraday versions before 1.10.6 and 2.14.3 are vulnerable to DoS - # via crafted nested query strings that cause SystemStackError - - # Create a deeply nested query string that would trigger the vulnerability - # in vulnerable Faraday versions - nested_depth = 100 - nested_params = "#{'a' * nested_depth}=1" - nested_depth.times { nested_params = "a[#{nested_params}]" } - - connection = Faraday.new(url: 'http://example.com') do |faraday| - faraday.adapter :test do |stub| - stub.get('/test') { |env| [200, {}, 'OK'] } - end - end - - # This should not raise SystemStackError in patched versions - assert_nothing_raised do - # Try to make a request with deeply nested parameters - # The fix should handle this gracefully - connection.get('/test', { deep: nested_params }) - end - rescue Faraday::ParamPart::PartLimitError - # This is expected in newer versions that enforce limits - # This means the fix is working - assert true - end - - test 'oauth2 gem can handle complex query parameters' do - # THREESCALE-15318: Ensure oauth2 gem works correctly with the updated Faraday - # oauth2 gem uses Faraday internally, so we need to ensure compatibility - - authentication_provider = FactoryBot.build_stubbed(:authentication_provider) - authentication = ThreeScale::OAuth2::Client.build_authentication(authentication_provider) - - # This should work without errors - assert_nothing_raised do - client = ::OAuth2::Client.new( - authentication.client_id, - authentication.client_secret, - site: 'https://example.com' - ) - assert client - end - end -end From 05be635f4a8329442877e40ad7bd199ed46b2f6e Mon Sep 17 00:00:00 2001 From: Bob Dev Date: Fri, 3 Jul 2026 16:37:31 +0000 Subject: [PATCH 5/8] THREESCALE-15318: Update Gemfile.lock with faraday 2.14.3 and pisoni 1.31.0 Update pisoni constraint to ~> 1.31 to pull in v1.31.0 which relaxes the faraday upper bound. This allows faraday to resolve to 2.14.3. Co-Authored-By: Claude Opus 4.6 --- Gemfile | 2 +- Gemfile.lock | 34 +++++++++++++++++++--------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Gemfile b/Gemfile index 86474271cc..9c31ed2ba2 100644 --- a/Gemfile +++ b/Gemfile @@ -43,7 +43,7 @@ gem 'sorted_set', '~> 1.0' gem 'i18n' # Apisonator client -gem 'pisoni', '~> 1.30' +gem 'pisoni', '~> 1.31' gem '3scale_time_range', '0.0.6' diff --git a/Gemfile.lock b/Gemfile.lock index e805cbf169..44f0b66482 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -272,7 +272,7 @@ GEM colorize (0.8.1) commonmarker (0.23.10) concurrent-ruby (1.3.5) - connection_pool (2.5.4) + connection_pool (3.0.2) crack (1.0.0) bigdecimal rexml @@ -399,14 +399,15 @@ GEM factory_bot (~> 6.2.0) railties (>= 5.0.0) fakefs (1.8.0) - faraday (2.8.1) - base64 - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) - faraday-net_http_persistent (2.1.0) + faraday (2.14.3) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.4) + net-http (~> 0.5) + faraday-net_http_persistent (2.3.1) faraday (~> 2.5) - net-http-persistent (~> 4.0) + net-http-persistent (>= 4.0.4, < 5) ffi (1.17.0) ffi-compiler (1.0.1) ffi (>= 1.0.0) @@ -454,7 +455,7 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.13.2) + json (2.20.0) json_schemer (2.4.0) bigdecimal hana (~> 1.3) @@ -550,9 +551,11 @@ GEM mutex_m (0.3.0) mysql2 (0.5.6) n_plus_one_control (0.6.2) + net-http (0.9.1) + uri (>= 0.11.1) net-http-digest_auth (1.4.1) - net-http-persistent (4.0.2) - connection_pool (~> 2.2) + net-http-persistent (4.0.8) + connection_pool (>= 2.2.4, < 4) net-imap (0.5.7) date net-protocol @@ -597,8 +600,8 @@ GEM ruby-rc4 ttfunk pg (1.3.5) - pisoni (1.30.0) - faraday (~> 2.0, <= 2.9) + pisoni (1.31.0) + faraday (~> 2.0, >= 2.14.3) faraday-net_http_persistent (~> 2.1) injectedlogger (= 0.0.13) json (~> 2.7, >= 2.7.1) @@ -973,6 +976,7 @@ GEM rack unicorn uniform_notifier (1.17.0) + uri (1.1.1) version_gem (1.1.3) webmock (3.24.0) addressable (>= 2.8.0) @@ -1058,7 +1062,7 @@ DEPENDENCIES escape_utils factory_bot_rails (~> 6.2) fakefs - faraday (~> 2.0, <= 2.9) + faraday (~> 2.0, >= 2.14.3) formtastic (~> 5.0) hashie hiredis-client @@ -1097,7 +1101,7 @@ DEPENDENCIES open_id_authentication pdf-inspector pg (~> 1.3.5) - pisoni (~> 1.30) + pisoni (~> 1.31) prawn prawn-svg prawn-table! From c79e8f35d3122baee94d55310805669ff396c320 Mon Sep 17 00:00:00 2001 From: Bob Dev Date: Tue, 7 Jul 2026 18:26:39 +0000 Subject: [PATCH 6/8] Fix URI::DEFAULT_PARSER.pattern compatibility with uri gem 1.1.1 The uri gem 1.1.1 (pulled in transitively by net-http) changed DEFAULT_PARSER to RFC3986_Parser which no longer exposes #pattern. Use RFC2396_Parser explicitly since the code relies on RFC2396 pattern keys (QUERY, ABS_PATH, HOSTNAME). Co-Authored-By: Claude Opus 4.6 --- app/models/proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/proxy.rb b/app/models/proxy.rb index 5253d5d0ae..cfd2b0c383 100644 --- a/app/models/proxy.rb +++ b/app/models/proxy.rb @@ -29,7 +29,7 @@ class Proxy < ApplicationRecord # rubocop:disable Metrics/ClassLength validates :error_status_no_match, :error_status_auth_missing, :error_status_auth_failed, :error_status_limits_exceeded, presence: true - uri_pattern = URI::DEFAULT_PARSER.pattern + uri_pattern = URI::RFC2396_Parser.new.pattern URI_OR_LOCALHOST = /\A(https?:\/\/([a-zA-Z0-9._:\/?-])+|.*localhost.*)\Z/ OPTIONAL_QUERY_FORMAT = "(?:\\?(#{uri_pattern.fetch(:QUERY)}))?" From 4b520b7cb14c30836df88e63180e73bd8ea1d52a Mon Sep 17 00:00:00 2001 From: "akostadinov-bot[bot]" <296308834+akostadinov-bot[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:35:33 +0000 Subject: [PATCH 7/8] Pin connection_pool < 3 to fix redlock ArgumentError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Faraday/pisoni update pulled in connection_pool 3.0.2, which changed ConnectionPool#with from accepting a positional hash to keyword-only arguments. redis-client 0.22.2 still calls pool.with({}) causing ArgumentError: wrong number of arguments (given 1, expected 0) inside redlock's lock_instances — breaking all Redis-based lock acquisition including the billing NowaitLockService. Pin connection_pool to 2.x until redis-client is upgraded to a version that uses keyword arguments (0.26.3+). Assisted-By: claude-opus-4-6 --- Gemfile | 1 + Gemfile.lock | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 9c31ed2ba2..8e6d19cc95 100644 --- a/Gemfile +++ b/Gemfile @@ -79,6 +79,7 @@ gem 'mysql2', '~> 0.5.3' gem '3scale_client', '~> 2.11', require: false gem 'analytics-ruby', require: false +gem 'connection_pool', '~> 2.2', '< 3' gem 'dalli' gem 'faraday', '~> 2.0', '>= 2.14.3' gem 'mimemagic', '~> 0.3.10' diff --git a/Gemfile.lock b/Gemfile.lock index 44f0b66482..da28e442df 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -272,7 +272,7 @@ GEM colorize (0.8.1) commonmarker (0.23.10) concurrent-ruby (1.3.5) - connection_pool (3.0.2) + connection_pool (2.5.5) crack (1.0.0) bigdecimal rexml @@ -1048,6 +1048,7 @@ DEPENDENCIES coffee-rails (~> 5.0) colorize commonmarker (~> 0.23.10) + connection_pool (~> 2.2, < 3) cssbundling-rails (~> 1.4.3) cucumber (~> 7.0) cucumber-rails (~> 3.0.0) From 4d8c7662b564ac2e28c13f5ddf8ebb11115b67c0 Mon Sep 17 00:00:00 2001 From: "akostadinov-bot[bot]" Date: Thu, 9 Jul 2026 15:02:33 +0000 Subject: [PATCH 8/8] Pin uri < 1.0.0, add comments for version constraints uri >= 1.0.0 switched the default parser from RFC2396 to RFC3986 (ruby/uri#107), removed URI::DEFAULT_PARSER, dropped URI.decode, and the RFC3986 parser has no `registry` component. We rely on all of these across models (Proxy, ProxyRule, WebHook, AuthenticationProvider) and controllers. Upgrading requires careful inspection of every call-site and should be deployed/tested separately. Revert the proxy.rb workaround (RFC2396_Parser.new) since the built-in DEFAULT_PARSER works with uri < 1.0.0. Also add explanatory comments to the connection_pool pin, noting that upgrading hiredis-client + redis-client to >= 0.26.3 would allow removing it. Assisted-By: claude-opus-4-6 Co-Authored-By: Claude Opus 4.6 --- Gemfile | 11 +++++++++++ Gemfile.lock | 3 ++- app/models/proxy.rb | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 8e6d19cc95..2318b11ace 100644 --- a/Gemfile +++ b/Gemfile @@ -73,12 +73,23 @@ gem 'formtastic', '~> 5.0' gem 'htmlentities', '~>4.3', '>= 4.3.4' gem 'json', '~> 2.7', '>= 2.7.1' gem 'responders', '~> 3.2' # For respond_with support +# uri >= 1.0.0 switched the default parser from RFC2396 to RFC3986 (ruby/uri#107), +# removed URI::DEFAULT_PARSER, dropped URI.decode, and the RFC3986 parser has no +# `registry` component. We rely on all of these across models (Proxy, ProxyRule, +# WebHook, AuthenticationProvider) and controllers. Upgrading is worthwhile but +# requires careful inspection of every call-site and should be deployed/tested +# separately from the Faraday update. +gem 'uri', '< 1.0.0' gem 'mysql2', '~> 0.5.3' gem '3scale_client', '~> 2.11', require: false gem 'analytics-ruby', require: false +# connection_pool 3.0 changed `#with` from positional to keyword-only args, +# breaking redis-client 0.22.2 (pinned by hiredis-client). Upgrading +# hiredis-client + redis-client to >= 0.26.3 would resolve this; until then +# keep connection_pool on 2.x. gem 'connection_pool', '~> 2.2', '< 3' gem 'dalli' gem 'faraday', '~> 2.0', '>= 2.14.3' diff --git a/Gemfile.lock b/Gemfile.lock index da28e442df..5d2ed937d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -976,7 +976,7 @@ GEM rack unicorn uniform_notifier (1.17.0) - uri (1.1.1) + uri (0.13.3) version_gem (1.1.3) webmock (3.24.0) addressable (>= 2.8.0) @@ -1167,6 +1167,7 @@ DEPENDENCIES uglifier unicorn unicorn-rails + uri (< 1.0.0) webmock (~> 3.24.0) webrick (~> 1.8.2) will_paginate (~> 3.3) diff --git a/app/models/proxy.rb b/app/models/proxy.rb index cfd2b0c383..5253d5d0ae 100644 --- a/app/models/proxy.rb +++ b/app/models/proxy.rb @@ -29,7 +29,7 @@ class Proxy < ApplicationRecord # rubocop:disable Metrics/ClassLength validates :error_status_no_match, :error_status_auth_missing, :error_status_auth_failed, :error_status_limits_exceeded, presence: true - uri_pattern = URI::RFC2396_Parser.new.pattern + uri_pattern = URI::DEFAULT_PARSER.pattern URI_OR_LOCALHOST = /\A(https?:\/\/([a-zA-Z0-9._:\/?-])+|.*localhost.*)\Z/ OPTIONAL_QUERY_FORMAT = "(?:\\?(#{uri_pattern.fetch(:QUERY)}))?"