{"record":{"id":"c5e3d4cc720a2b4f","repo":"basecamp/fizzy","slug":"authenticator-data-is-too-short-for-credential-id","errorCode":null,"errorMessage":"Authenticator data is too short for credential ID and public key","messagePattern":"Authenticator data is too short for credential ID and public key","errorType":"exception","errorClass":"ActionPack::WebAuthn::InvalidResponseError","httpStatus":null,"severity":"error","filePath":"lib/action_pack/web_authn/authenticator/data.rb","lineNumber":114,"sourceCode":"\n      aaguid = nil\n      credential_id = nil\n      public_key_bytes = nil\n\n      if flags & ATTESTED_CREDENTIAL_DATA_FLAG != 0\n        if bytes.length < position + AAGUID_LENGTH + CREDENTIAL_ID_LENGTH_BYTES\n          raise ActionPack::WebAuthn::InvalidResponseError, \"Authenticator data is too short for attested credential data\"\n        end\n\n        aaguid_bytes = bytes[position, AAGUID_LENGTH].pack(\"C*\")\n        aaguid = aaguid_bytes.unpack(\"H8H4H4H4H12\").join(\"-\")\n        position += AAGUID_LENGTH\n\n        credential_id_length = bytes[position, CREDENTIAL_ID_LENGTH_BYTES].pack(\"C*\").unpack1(\"n\")\n        position += CREDENTIAL_ID_LENGTH_BYTES\n\n        if bytes.length < position + credential_id_length + 1\n          raise ActionPack::WebAuthn::InvalidResponseError, \"Authenticator data is too short for credential ID and public key\"\n        end\n\n        credential_id = Base64.urlsafe_encode64(bytes[position, credential_id_length].pack(\"C*\"), padding: false)\n        position += credential_id_length\n\n        public_key_bytes = bytes[position..].pack(\"C*\")\n      end\n\n      new(\n        bytes: bytes,\n        relying_party_id_hash: relying_party_id_hash,\n        flags: flags,\n        sign_count: sign_count,\n        aaguid: aaguid,\n        credential_id: credential_id,\n        public_key_bytes: public_key_bytes\n      )\n    end","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/basecamp/fizzy/blob/7aabe7458060d8a1759a53b7ede39e74e6c0b20d/lib/action_pack/web_authn/authenticator/data.rb#L96-L132","documentation":"With attested credential data present, the parser reads a 2-byte credential ID length, then requires the remaining buffer to hold at least credential_id_length bytes plus 1 or more bytes of COSE public key (the +1 guarantees a non-empty key). If the declared ID length overruns the buffer or the public key is missing entirely, InvalidResponseError is raised.","triggerScenarios":"A registration response where the credential ID length field claims more bytes than remain; a truncated attestation that cut off everything after the credential ID; a client that appends the COSE key as text/JSON instead of raw CBOR bytes so the byte math no longer lines up.","commonSituations":"Buffers truncated at a fixed request-size limit; custom client-side assembly of authenticatorData from parts; byte-slice off-by-one bugs in bespoke parsers feeding this API; fixtures copied from a different authenticator with a different credential ID size.","solutions":["Verify in console: decode the data, check bytesize > 55 + declared credential ID length, and that the trailing bytes form valid CBOR (they decode via CborDecoder.decode).","Pass through the browser's response.getPublicKey()/attestation object bytes untouched instead of re-encoding.","Raise or remove request body-size limits that could clip large credential IDs (some are 255+ bytes, security keys even 1023).","Rescue ActionPack::WebAuthn::InvalidResponseError at the controller boundary and return 400 with a registration-failed message."],"exampleFix":"# before\n# reconstructing data field-by-field (breaks byte math)\nauth_data = rp_hash + flags + sign_count + aaguid + [id_len].pack('n') + id_bytes # key forgotten\n\n# after — forward the browser's buffer whole\nresponse = ActionPack::WebAuthn::Authenticator::AttestationResponse.new(\n  client_data_json: params[:client_data_json],\n  authenticator_data: params[:authenticator_data], # full base64url buffer, untouched\n  ...\n)","handlingStrategy":"try-catch","validationCode":"raw = Base64.urlsafe_decode64(params[:authenticator_data].to_s)\nif raw.bytesize > 55 && (raw.getbyte(32) & 0x40) != 0\n  id_len = raw[53, 2].unpack1('n')\n  return render(json: { error: 'credential id/key truncated' }, status: :bad_request) if raw.bytesize < 55 + id_len + 1\nend","typeGuard":"def plausible_attested_credential?(raw)\n  return true if (raw.getbyte(32) & 0x40).zero?\n  id_len = raw[53, 2].unpack1('n')\n  raw.bytesize >= 55 + id_len + 1\nend","tryCatchPattern":"begin\n  response.validate!\nrescue ActionPack::WebAuthn::InvalidResponseError => e\n  render json: { error: e.message }, status: :bad_request\nend","preventionTips":["Don't impose request-size caps that clip large credential IDs (up to 1023 bytes).","Pass COSE key bytes as raw binary; the parser expects CBOR bytes, not hex or JSON.","Treat any short-buffer error as permanent client corruption — reject, don't retry."],"tags":["webauthn","registration","credential-id","binary-parsing"],"backgroundTag":"webauthn-malformed-authenticator-data","analyzedSha":"7aabe7458060d8a1759a53b7ede39e74e6c0b20d","analyzedAt":"2026-08-21T18:33:25.349Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}