basecamp/fizzy · error · ActionPack::WebAuthn::InvalidResponseError
Authenticator data is too short for attested credential data
Error message
Authenticator data is too short for attested credential data
What it means
During registration the flags byte carries the attested-credential-data bit (0x40). When that bit is set the parser needs at least 18 more bytes — a 16-byte AAGUID plus a 2-byte credential ID length field (AAGUID_LENGTH + CREDENTIAL_ID_LENGTH_BYTES). If the buffer ends first, the attested credential block is truncated and InvalidResponseError is raised.
Source
Thrown at lib/action_pack/web_authn/authenticator/data.rb:103
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
if bytes.length < position + AAGUID_LENGTH + CREDENTIAL_ID_LENGTH_BYTES
raise ActionPack::WebAuthn::InvalidResponseError, "Authenticator data is too short for attested credential data"
end
aaguid_bytes = bytes[position, AAGUID_LENGTH].pack("C*")
aaguid = aaguid_bytes.unpack("H8H4H4H4H12").join("-")
position += AAGUID_LENGTH
credential_id_length = bytes[position, CREDENTIAL_ID_LENGTH_BYTES].pack("C*").unpack1("n")
position += CREDENTIAL_ID_LENGTH_BYTES
if bytes.length < position + credential_id_length + 1
raise ActionPack::WebAuthn::InvalidResponseError, "Authenticator data is too short for credential ID and public key"
end
credential_id = Base64.urlsafe_encode64(bytes[position, credential_id_length].pack("C*"), padding: false)
position += credential_id_length
public_key_bytes = bytes[position..].pack("C*")
endView on GitHub (pinned to 7aabe74580)
Solutions
- Confirm the total decoded length: registration data must exceed 37 + 18 + credential_id_length + COSE key bytes; anything close to 55 bytes with flag 0x40 set is truncated.
- Send the authenticatorData buffer from the browser untouched (base64url of the whole ArrayBuffer), never a reassembled copy.
- Rebuild fixtures from a real navigator.credentials.create() ceremony.
- Rescue InvalidResponseError and surface a clear 'registration payload incomplete' message rather than a stack trace.
Example fix
# before
# fixture with attested flag but no attested block
flags = 0x45 # UP | UV | AT (0x40)
data = ActionPack::WebAuthn::Authenticator::Data.decode(header_37_bytes_with(flags)) # raises
# after — include a minimal well-formed attested block (16-byte AAGUID + 2-byte length + id + key)
buf = rp_id_hash_32 + [0x45].pack('C') + [0].pack('N') + aaguid_16 + [credential_id.bytesize].pack('n') + credential_id + cose_key
data = ActionPack::WebAuthn::Authenticator::Data.decode(buf) Defensive patterns
Strategy: try-catch
Validate before calling
raw = Base64.urlsafe_decode64(params[:authenticator_data].to_s)
flags = raw.getbyte(32)
# registration needs 37 + 18 bytes minimum when the attested flag (0x40) is set
min = (flags & 0x40).zero? ? 37 : 55
return render(json: { error: 'attestation data truncated' }, status: :bad_request) if raw.bytesize < min Type guard
def plausible_attestation?(raw) flags = raw.getbyte(32) raw.bytesize >= ((flags & 0x40).zero? ? 37 : 55) end
Try / catch
begin
attestation = ActionPack::WebAuthn::Authenticator::AttestationResponse.new(**webauthn_params)
attestation.validate!
rescue ActionPack::WebAuthn::InvalidResponseError => e
render json: { error: "registration failed: #{e.message}" }, status: :bad_request
end Prevention
- Forward the browser's authenticatorData buffer untouched; never reassemble it field-by-field.
- Build fixtures with a real navigator.credentials.create() capture.
- Rescue InvalidResponseError at the controller boundary so malformed registrations return 400, not 500.
When it happens
Trigger: A registration (attestation) response whose authenticator data is exactly the 37-byte header plus a flags byte claiming attested data, but with the AAGUID/credential-ID block cut off; fabricated fixtures that set flag 0x40 without appending an attested credential payload.
Common situations: Test fixtures built by hand that copy flags from a real registration but strip the credential body; bugs in custom client code that reconstructs authenticatorData from parts; transport truncation of large registration payloads (credential IDs can be 100+ bytes).
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Authenticator data is too short for credential ID and public
- Authenticator data is too short
- Unexpected end of input
- Invalid base64 encoding in authenticator data
- Client data is not valid JSON
AI-assisted analysis of basecamp/fizzy@7aabe74580 (2026-08-21).
Data as JSON: /api/errors/4decd3126c93cd70.
Report an issue: GitHub.