Skip to content

[PM-43133] Passkey creation options do not match the WebAuthn spec, breaking registration and dropping RP constraints #7359

Description

@psolvy

Origin

Native Application (non-browser app)

Web URL or App name

Any relying party that sends spec-compliant creation options. This is in the JSON parsing, so it is not specific to one site or app.

Passkey Action

  • Creating new passkey (Registration)
  • Signing in (Authentication)

Build Information

Password Manager 2026.8.0 (21819) on device. Also reproduced against main at b99ad92 with the unit test runner.

Additional Information

PasskeyAttestationOptions does not match PublicKeyCredentialCreationOptionsJSON from the WebAuthn spec in four places. One of them breaks registration outright, the other three drop data without any error.

1. authenticatorSelection is treated as required.

In the spec only rp, user, challenge and pubKeyCredParams are required. authenticatorSelection is optional. In PasskeyAttestationOptions.kt it is a non-nullable property with no default, so a request that leaves it out fails to deserialize:

kotlinx.serialization.MissingFieldException: Field 'authenticatorSelection' is required
for type with serial name 'PasskeyAttestationOptions', but it was missing at path: $

explicitNulls = false does not help here because it only relaxes nullable properties, and coerceInputValues = true only rewrites values that are present but invalid.

Once the parse fails, getPasskeyAttestationOptionsOrNull returns null and registration ends in Fido2RegisterCredentialResult.Error.MissingHostUrl for an unprivileged caller, or Error.InternalError for a privileged one. The add item screen also falls back to the package name and an empty username.

2. excludeCredentials is spelled excludedCredentials.

@SerialName("excludedCredentials") never matches, so the exclusion list from the relying party is silently replaced with an empty list. ignoreUnknownKeys = true is what hides it.

This is not only a parsing detail. registerFido2CredentialInternal re-serializes the parsed model and sends that JSON to the SDK, so the exclusion list never reaches the authenticator and a duplicate passkey is not prevented.

Worth noting that the fixtures in this repo already use the spec name with an empty array, which is exactly why the tests never caught it:

  • RelyingPartyParserTest.kt:105 and :139
  • BitwardenCredentialManagerTest.kt:1520

3. AuthenticatorAttachment.CROSS_PLATFORM is mapped to cross_platform.

The spec value has a hyphen. With coerceInputValues = true the unknown member is coerced to the property default, so "authenticatorAttachment": "cross-platform" decodes to null and then disappears from the JSON handed to the SDK.

4. ResidentKeyRequirement is missing discouraged.

The spec enum is discouraged, preferred, required. Only the last two are declared, so "residentKey": "discouraged" is coerced to null and dropped the same way.

I have a patch with unit tests ready and will open a PR against this issue.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions