lostisland/faraday · error · Faraday::Error
exceeded nested parameter depth limit of #{@param_depth_limi
Error message
exceeded nested parameter depth limit of #{@param_depth_limit} What it means
NestedParamsEncoder#decode guards its recursion with a depth counter: validate_params_depth! raises Faraday::Error once nesting exceeds @param_depth_limit (default 100). This is a stack-exhaustion/DoS protection for adversarial query strings like a[b][c][d]... with hundreds of levels, mirroring similar limits in Rack's query parser.
Source
Thrown at lib/faraday/encoders/nested_params_encoder.rb:153
"(got #{context[subkey].class.name}) for param `#{subkey}'"
end
context[subkey] ||= value_type.new
end
def match_context(context, subkey)
context << {} if !context.last.is_a?(Hash) || context.last.key?(subkey)
context.last
end
def add_to_context(is_array, context, value, subkey)
is_array ? context << value : context[subkey] = value
end
def validate_params_depth!(depth)
return unless @param_depth_limit && depth > @param_depth_limit
raise Faraday::Error, "exceeded nested parameter depth limit of #{@param_depth_limit}"
end
# Internal: convert a nested hash with purely numeric keys into an array.
# FIXME: this is not compatible with Rack::Utils.parse_nested_query
# @!visibility private
def dehash(hash, depth)
hash.each do |key, value|
hash[key] = dehash(value, depth + 1) if value.is_a?(Hash)
end
if depth.positive? && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
hash.sort.map(&:last)
else
hash
end
end
end
View on GitHub (pinned to b25b1b26cc)
Solutions
- Rescue Faraday::Error at the parse boundary and reject the request (400) — for untrusted input the limit doing its job is the correct outcome.
- If your data legitimately nests deeper, raise the ceiling at boot: Faraday::NestedParamsEncoder.param_depth_limit = 250 (it is an attr_accessor on the encoder module).
- Prefer sending deep structures as a JSON request body rather than nested query params.
- For untrusted query strings that need no nesting, switch to Faraday::FlatParamsEncoder, which never recurses and cannot hit the limit.
Example fix
# before
Faraday::Utils.parse_query('a' + '[b]' * 150 + '=1')
# => Faraday::Error: exceeded nested parameter depth limit of 100
# after (untrusted input: reject instead of parse)
begin
params = Faraday::Utils.parse_query(raw_query)
rescue Faraday::Error
return [400, {}, ['query too deeply nested']]
end
# after (trusted deep data: raise the ceiling once, at boot)
Faraday::NestedParamsEncoder.param_depth_limit = 250 Defensive patterns
Strategy: try-catch
Validate before calling
return [400, {}, ['query too deep']] if raw_query.count('[') >= Faraday::NestedParamsEncoder.param_depth_limit
params = Faraday::NestedParamsEncoder.decode(raw_query) Type guard
def within_depth_limit?(raw_query)
raw_query.count('[') < (Faraday::NestedParamsEncoder.param_depth_limit || Float::INFINITY)
end Try / catch
begin
params = Faraday::NestedParamsEncoder.decode(raw_query)
rescue Faraday::Error => e
raise unless e.message.include?('depth limit')
params = Faraday::FlatParamsEncoder.decode(raw_query) # or reject: [400, {}, []]
end Prevention
- Wrap all decoding of untrusted query strings in a rescue Faraday::Error and reject the request — the limit is a DoS guard doing its job.
- Send deep structures as JSON bodies, not nested query params.
- If raising the ceiling for trusted data, set Faraday::NestedParamsEncoder.param_depth_limit once at boot, never per request.
When it happens
Trigger: Decoding a query string whose bracket nesting exceeds 100 levels, e.g. 'a' + '[b]' * 101 + '=1'; legitimately deep structures only if your own encoder emitted them (hashes nested >100 deep serialized to a query string). Triggered wherever Faraday parses untrusted URLs: env[:params] decoding in adapters, Faraday::Utils.parse_query with the nested decoder.
Common situations: A public endpoint or middleware that parses attacker-controlled URLs receiving scanner traffic with deeply nested keys (classic DoS probe); replaying captured logs; rare apps that serialize genuinely deep object trees into query strings instead of a JSON body.
Related errors
- expected #{value_type.name} (got #{context[subkey].class.nam
- Can't convert #{params.class} into Hash.
- Expected :read, :write, :open. Got #{type.inspect} :(
- no stubbed request for #{env[:method]} #{env[:url]} #{env[:b
- unknown http method: #{method}
AI-assisted analysis of lostisland/faraday@b25b1b26cc (2026-08-21).
Data as JSON: /api/errors/cfa6696b16333daa.
Report an issue: GitHub.