basecamp/fizzy · error · ActionPack::WebAuthn::InvalidCborError

Unexpected end of input

Error message

Unexpected end of input

What it means

CborDecoder#decode raises InvalidCborError when the read position is at or past the end of the buffer. At the top level this means empty input; inside nested structures it means a definite-length array or map declared more elements than the buffer actually contains.

Source

Thrown at lib/action_pack/web_authn/cbor_decoder.rb:112

    #   # => {"a" => 1, "b" => 2}
    def decode(bytes, **args)
      bytes = bytes.bytes if bytes.respond_to?(:bytes)
      new(bytes, **args).decode
    end
  end

  def initialize(bytes, max_depth: MAX_DEPTH, max_size: MAX_SIZE) # :nodoc:
    raise ActionPack::WebAuthn::InvalidCborError, "Input exceeds maximum size" if bytes.length > max_size

    @bytes = bytes
    @max_depth = max_depth
    @position = 0
    @depth = 0
  end

  # Decodes the next CBOR data item from the byte sequence.
  def decode
    raise ActionPack::WebAuthn::InvalidCborError, "Unexpected end of input" if @position >= @bytes.length
    raise ActionPack::WebAuthn::InvalidCborError, "Maximum nesting depth exceeded" if @depth >= @max_depth

    @depth += 1

    result = case major_type
    when UNSIGNED_INTEGER_TYPE then decode_unsigned_integer
    when NEGATIVE_INTEGER_TYPE then decode_negative_integer
    when BYTE_STRING_TYPE then decode_byte_string
    when TEXT_STRING_TYPE then decode_text_string
    when ARRAY_TYPE then decode_array
    when MAP_TYPE then decode_map
    when TAG_TYPE then decode_tag
    when FLOAT_OR_SIMPLE_TYPE then decode_float_or_simple
    end

    @depth -= 1
    result
  end

View on GitHub (pinned to 7aabe74580)

Solutions

  1. Check the input is non-empty before decoding: raise early if bytes.length.zero?.
  2. Validate the payload length against the declared structure (a quick manual parse of the first header byte) in tests.
  3. Regenerate the CBOR with a known encoder and compare byte-for-byte to find the truncation point.
  4. Rescue ActionPack::WebAuthn::InvalidCborError at the boundary and return 400 — truncated client input is not retryable.

Example fix

# before
value = ActionPack::WebAuthn::CborDecoder.decode(params[:attestation_object].to_s)

# after — reject empty/truncated input early with a clear message
raw = params[:attestation_object].to_s
return render(json: { error: 'attestation object missing' }, status: :bad_request) if raw.blank?
value = ActionPack::WebAuthn::CborDecoder.decode(raw)
Defensive patterns

Strategy: validation

Validate before calling

return render(json: { error: 'empty CBOR input' }, status: :bad_request) if bytes.nil? || bytes.length.zero?
value = ActionPack::WebAuthn::CborDecoder.decode(bytes)

Type guard

def decodable_cbor?(bytes)
  !bytes.nil? && bytes.length.positive? && bytes.bytesize <= ActionPack::WebAuthn::CborDecoder::MAX_SIZE
end

Try / catch

begin
  value = ActionPack::WebAuthn::CborDecoder.decode(bytes)
rescue ActionPack::WebAuthn::InvalidCborError => e
  render json: { error: "malformed CBOR: #{e.message}" }, status: :bad_request
end

Prevention

When it happens

Trigger: CborDecoder.decode('') or decode([]); an array header like \x83 (3 items) with only 2 items in the buffer; a map header claiming N pairs where the buffer ends early; trailing break-code confusion that leaves position at end when another item is requested.

Common situations: Truncated attestation objects fed to the decoder; fixtures built by concatenating fragments; a client that slices CBOR at the wrong offset (e.g. off-by-one after the flags byte); buffers built with String#slice on multibyte strings.

Understand the failure class

Related errors


AI-assisted analysis of basecamp/fizzy@7aabe74580 (2026-08-21). Data as JSON: /api/errors/576309adf517d7ab. Report an issue: GitHub.