{"record":{"id":"e3ec98e6bba67574","repo":"basecamp/fizzy","slug":"maximum-nesting-depth-exceeded","errorCode":null,"errorMessage":"Maximum nesting depth exceeded","messagePattern":"Maximum nesting depth exceeded","errorType":"exception","errorClass":"ActionPack::WebAuthn::InvalidCborError","httpStatus":null,"severity":"error","filePath":"lib/action_pack/web_authn/cbor_decoder.rb","lineNumber":113,"sourceCode":"    def decode(bytes, **args)\n      bytes = bytes.bytes if bytes.respond_to?(:bytes)\n      new(bytes, **args).decode\n    end\n  end\n\n  def initialize(bytes, max_depth: MAX_DEPTH, max_size: MAX_SIZE) # :nodoc:\n    raise ActionPack::WebAuthn::InvalidCborError, \"Input exceeds maximum size\" if bytes.length > max_size\n\n    @bytes = bytes\n    @max_depth = max_depth\n    @position = 0\n    @depth = 0\n  end\n\n  # Decodes the next CBOR data item from the byte sequence.\n  def decode\n    raise ActionPack::WebAuthn::InvalidCborError, \"Unexpected end of input\" if @position >= @bytes.length\n    raise ActionPack::WebAuthn::InvalidCborError, \"Maximum nesting depth exceeded\" if @depth >= @max_depth\n\n    @depth += 1\n\n    result = case major_type\n    when UNSIGNED_INTEGER_TYPE then decode_unsigned_integer\n    when NEGATIVE_INTEGER_TYPE then decode_negative_integer\n    when BYTE_STRING_TYPE then decode_byte_string\n    when TEXT_STRING_TYPE then decode_text_string\n    when ARRAY_TYPE then decode_array\n    when MAP_TYPE then decode_map\n    when TAG_TYPE then decode_tag\n    when FLOAT_OR_SIMPLE_TYPE then decode_float_or_simple\n    end\n\n    @depth -= 1\n    result\n  end\n","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/basecamp/fizzy/blob/7aabe7458060d8a1759a53b7ede39e74e6c0b20d/lib/action_pack/web_authn/cbor_decoder.rb#L95-L131","documentation":"The decoder tracks nesting (@depth) and refuses to recurse past MAX_DEPTH (16 by default), raising InvalidCborError. This protects the Ruby VM from stack exhaustion via deeply nested arrays/maps — a classic CBOR compression-bomb shape. Legitimate WebAuthn structures are 3–4 levels deep, so the limit only trips on malformed or hostile input unless you deliberately decode deep custom structures.","triggerScenarios":"Decoding CBOR with more than 16 nested arrays/maps, e.g. \\x82\\x82\\x82... repeated; hostile input crafted as a billion-laughs-style nesting bomb; custom non-WebAuthn payloads that embed deep document trees.","commonSituations":"Exposing CborDecoder on a public endpoint without depth caps; fuzzing runs; migrating from another CBOR gem with no/looser depth limits; legitimate deep structures in domain-specific CBOR.","solutions":["Inspect the payload's nesting before decoding in tests; anything near 16 levels in WebAuthn data is corrupt.","If your own schema legitimately nests deeper, raise the cap explicitly: CborDecoder.decode(bytes, max_depth: 64).","Keep the decoder off any unauthenticated endpoint; if exposed, keep default limits and fail closed.","Rescue InvalidCborError and log bytesize + first bytes to identify the offending producer."],"exampleFix":"# before\nActionPack::WebAuthn::CborDecoder.decode(user_supplied_bytes)\n\n# after — explicit limits for a trusted, deeply-nested schema\nActionPack::WebAuthn::CborDecoder.decode(user_supplied_bytes, max_depth: 64, max_size: 1.megabyte)","handlingStrategy":"validation","validationCode":"# for trusted schemas that legitimately nest deeper than 16\nActionPack::WebAuthn::CborDecoder.decode(bytes, max_depth: 64)\n# for untrusted input, keep the default and pre-check nothing deeper than expected","typeGuard":"def within_cbor_depth_limit?(bytes, max_depth = ActionPack::WebAuthn::CborDecoder::MAX_DEPTH)\n  # cheap structural check is not possible; rely on decode with explicit limit and rescue\n  ActionPack::WebAuthn::CborDecoder.decode(bytes, max_depth: max_depth)\n  true\nrescue ActionPack::WebAuthn::InvalidCborError\n  false\nend","tryCatchPattern":"begin\n  value = ActionPack::WebAuthn::CborDecoder.decode(bytes)\nrescue ActionPack::WebAuthn::InvalidCborError => e\n  # depth bombs and malformed data are both permanent rejections\n  render json: { error: e.message }, status: :bad_request\nend","preventionTips":["Keep the decoder off unauthenticated endpoints; keep default limits on public ones.","WebAuthn structures nest 3–4 levels — deeper input is corrupt or hostile.","Set explicit max_depth matching your schema when you control both ends."],"tags":["cbor","nesting-depth","denial-of-service","input-validation"],"backgroundTag":"deserialization-depth-limit-exceeded","analyzedSha":"7aabe7458060d8a1759a53b7ede39e74e6c0b20d","analyzedAt":"2026-08-21T18:33:25.349Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}