{"record":{"id":"cfa6696b16333daa","repo":"lostisland/faraday","slug":"exceeded-nested-parameter-depth-limit-of-param","errorCode":null,"errorMessage":"exceeded nested parameter depth limit of #{@param_depth_limit}","messagePattern":"exceeded nested parameter depth limit of #(.+?)","errorType":"exception","errorClass":"Faraday::Error","httpStatus":null,"severity":"error","filePath":"lib/faraday/encoders/nested_params_encoder.rb","lineNumber":153,"sourceCode":"                         \"(got #{context[subkey].class.name}) for param `#{subkey}'\"\n      end\n\n      context[subkey] ||= value_type.new\n    end\n\n    def match_context(context, subkey)\n      context << {} if !context.last.is_a?(Hash) || context.last.key?(subkey)\n      context.last\n    end\n\n    def add_to_context(is_array, context, value, subkey)\n      is_array ? context << value : context[subkey] = value\n    end\n\n    def validate_params_depth!(depth)\n      return unless @param_depth_limit && depth > @param_depth_limit\n\n      raise Faraday::Error, \"exceeded nested parameter depth limit of #{@param_depth_limit}\"\n    end\n\n    # Internal: convert a nested hash with purely numeric keys into an array.\n    # FIXME: this is not compatible with Rack::Utils.parse_nested_query\n    # @!visibility private\n    def dehash(hash, depth)\n      hash.each do |key, value|\n        hash[key] = dehash(value, depth + 1) if value.is_a?(Hash)\n      end\n\n      if depth.positive? && !hash.empty? && hash.keys.all? { |k| k =~ /^\\d+$/ }\n        hash.sort.map(&:last)\n      else\n        hash\n      end\n    end\n  end\n","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/lostisland/faraday/blob/b25b1b26ccef34b1460b0267115be238ca758087/lib/faraday/encoders/nested_params_encoder.rb#L135-L171","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nFaraday::Utils.parse_query('a' + '[b]' * 150 + '=1')\n# => Faraday::Error: exceeded nested parameter depth limit of 100\n\n# after (untrusted input: reject instead of parse)\nbegin\n  params = Faraday::Utils.parse_query(raw_query)\nrescue Faraday::Error\n  return [400, {}, ['query too deeply nested']]\nend\n\n# after (trusted deep data: raise the ceiling once, at boot)\nFaraday::NestedParamsEncoder.param_depth_limit = 250","handlingStrategy":"try-catch","validationCode":"return [400, {}, ['query too deep']] if raw_query.count('[') >= Faraday::NestedParamsEncoder.param_depth_limit\nparams = Faraday::NestedParamsEncoder.decode(raw_query)","typeGuard":"def within_depth_limit?(raw_query)\n  raw_query.count('[') < (Faraday::NestedParamsEncoder.param_depth_limit || Float::INFINITY)\nend","tryCatchPattern":"begin\n  params = Faraday::NestedParamsEncoder.decode(raw_query)\nrescue Faraday::Error => e\n  raise unless e.message.include?('depth limit')\n  params = Faraday::FlatParamsEncoder.decode(raw_query) # or reject: [400, {}, []]\nend","preventionTips":["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."],"tags":["ruby","faraday","nested-params","depth-limit","dos-protection","untrusted-input"],"backgroundTag":"nesting-depth-limit-exceeded","analyzedSha":"b25b1b26ccef34b1460b0267115be238ca758087","analyzedAt":"2026-08-21T19:43:20.220Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}