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

Authenticator data is too short

Error message

Authenticator data is too short

What it means

Authenticator::Data.decode requires at least 37 bytes: 32-byte RP ID hash + 1-byte flags + 4-byte sign count (RELYING_PARTY_ID_HASH_LENGTH + FLAGS_LENGTH + SIGN_COUNT_LENGTH). Input below that cannot be valid authenticator data, so the parser rejects it immediately with InvalidResponseError.

Source

Thrown at lib/action_pack/web_authn/authenticator/data.rb:83

    def wrap(data)
      if data.is_a?(self)
        data
      else
        data = Base64.urlsafe_decode64(data) unless data.encoding == Encoding::BINARY
        decode(data)
      end
    rescue ArgumentError
      raise ActionPack::WebAuthn::InvalidResponseError, "Invalid base64 encoding in authenticator data"
    end

    # Decodes raw authenticator data bytes into a Data instance, parsing the
    # RP ID hash, flags, sign count, and (if present) attested credential data.
    def decode(bytes)
      bytes = bytes.bytes if bytes.is_a?(String)

      minimum_length = RELYING_PARTY_ID_HASH_LENGTH + FLAGS_LENGTH + SIGN_COUNT_LENGTH
      if bytes.length < minimum_length
        raise ActionPack::WebAuthn::InvalidResponseError, "Authenticator data is too short"
      end

      position = 0

      relying_party_id_hash = bytes[position, RELYING_PARTY_ID_HASH_LENGTH].pack("C*")
      position += RELYING_PARTY_ID_HASH_LENGTH

      flags = bytes[position]
      position += FLAGS_LENGTH

      sign_count = bytes[position, SIGN_COUNT_LENGTH].pack("C*").unpack1("N")
      position += SIGN_COUNT_LENGTH

      aaguid = nil
      credential_id = nil
      public_key_bytes = nil

      if flags & ATTESTED_CREDENTIAL_DATA_FLAG != 0

View on GitHub (pinned to 7aabe74580)

Solutions

  1. Check the decoded byte length in console: Base64.urlsafe_decode64(param).bytesize must be >= 37 (assertion) or >= 37+18+credential-id+key bytes (registration).
  2. Log bytesize at the trust boundary to confirm where truncation happens (client vs transport vs server).
  3. Regenerate fixtures from a real browser passkey ceremony (Chrome DevTools or a WebAuthn test harness) instead of hand-writing strings.
  4. Verify you are passing the authenticatorData field itself, not challenge, userHandle, or clientDataJSON.

Example fix

# before
data = ActionPack::WebAuthn::Authenticator::Data.wrap(params[:authenticator_data])

# after — cheap pre-flight length check
raw = Base64.urlsafe_decode64(params[:authenticator_data].to_s)
if raw.bytesize < 37
  return render json: { error: "authenticator data truncated" }, status: :bad_request
end
data = ActionPack::WebAuthn::Authenticator::Data.wrap(raw)
Defensive patterns

Strategy: validation

Validate before calling

raw = Base64.urlsafe_decode64(params[:authenticator_data].to_s)
return render(json: { error: 'authenticator data too short' }, status: :bad_request) if raw.bytesize < 37

Type guard

def plausible_authenticator_data?(value)
  return false unless (decoded = Base64.urlsafe_decode64(value) rescue nil)
  decoded.bytesize >= 37
end

Try / catch

begin
  data = ActionPack::WebAuthn::Authenticator::Data.wrap(raw)
rescue ActionPack::WebAuthn::InvalidResponseError => e
  render json: { error: e.message }, status: :bad_request
end

Prevention

When it happens

Trigger: Passing a base64url string that decodes to fewer than 37 bytes — empty string, a truncated value cut by a client or proxy, a lone SHA-256 hash, or handing the wrong field (e.g. the challenge or the RP ID hash) where authenticator data is expected.

Common situations: Unit-test fixtures fabricated from short strings instead of a real WebAuthn ceremony; a frontend that slices/substring's the authenticatorData buffer; JSON payloads truncated by request-size limits; double-decoding (decoding base64 twice yields short garbage).

Understand the failure class

Related errors


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