{"record":{"id":"fc1e0df451fa6500","repo":"zeroclaw-labs/zeroclaw","slug":"expected-type-webauthn-get-got-cd-type","errorCode":null,"errorMessage":"Expected type 'webauthn.get', got '{cd_type}'","messagePattern":"Expected type 'webauthn\\.get', got '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":401,"severity":"error","filePath":"crates/zeroclaw-runtime/src/security/webauthn.rs","lineNumber":425,"sourceCode":"                ::zeroclaw_log::record!(\n                    WARN,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                        .with_attrs(::serde_json::json!({\"credential_id\": response.id})),\n                    \"webauthn verify refused: credential id not in store\"\n                );\n                anyhow::Error::msg(format!(\"Credential not found: {}\", response.id))\n            })?;\n\n        // 3. Validate client data JSON\n        let client_data_bytes = URL_SAFE_NO_PAD\n            .decode(&response.client_data_json)\n            .context(\"Invalid base64url in client_data_json\")?;\n        let client_data: serde_json::Value =\n            serde_json::from_slice(&client_data_bytes).context(\"Invalid client data JSON\")?;\n\n        let cd_type = client_data[\"type\"].as_str().unwrap_or_default();\n        anyhow::ensure!(\n            cd_type == \"webauthn.get\",\n            \"Expected type 'webauthn.get', got '{cd_type}'\"\n        );\n\n        let cd_challenge = client_data[\"challenge\"].as_str().unwrap_or_default();\n        anyhow::ensure!(\n            cd_challenge == auth_state.challenge,\n            \"Challenge mismatch in authentication response\"\n        );\n\n        let cd_origin = client_data[\"origin\"].as_str().unwrap_or_default();\n        anyhow::ensure!(\n            cd_origin == self.config.rp_origin,\n            \"Origin mismatch: expected '{}', got '{cd_origin}'\",\n            self.config.rp_origin\n        );\n\n        // 4. Verify signature","sourceCodeStart":407,"sourceCodeEnd":443,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/security/webauthn.rs#L407-L443","documentation":"finish_authentication requires the client_data_json of an authentication response to carry type == \"webauthn.get\", the WebAuthn authentication ceremony type. A different value — commonly \"webauthn.create\" — means the payload belongs to the registration ceremony or was malformed, and authentication is rejected.","triggerScenarios":"Posting a navigator.credentials.create (registration) response to the auth-finish endpoint; hand-rolled test clients writing the wrong type string; client libraries or proxies that rewrite client_data_json.","commonSituations":"Frontend handlers wiring the create response into the login flow; e2e fixtures reusing registration client data for login; copy-paste errors in custom FIDO clients.","solutions":["Send only navigator.credentials.get() results to the auth-finish endpoint","Keep create/get response serializers separate; validate ceremony type client-side before posting","In tests, generate client_data_json with type 'webauthn.get' and the challenge from auth start"],"exampleFix":"// before: registration response posted to login finish\nconst resp = await navigator.credentials.create({ publicKey: regOptions });\nawait fetch('/auth/finish', { method: 'POST', body: serialize(resp) });\n// after\nconst resp = await navigator.credentials.get({ publicKey: authOptions });\nawait fetch('/auth/finish', { method: 'POST', body: serialize(resp) });","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"in the auth-finish HTTP handler, catch ceremony-validation errors (type/challenge/origin) and map to 400 with the message; do not retry the payload; client should call auth start again","preventionTips":["Type the client responses (RegisterResponse vs AuthenticateResponse) so they cannot be cross-posted","Drive real browser flows in e2e tests instead of hand-built JSON","Log the offending client_data type on failure"],"tags":["webauthn","authentication","ceremony","rust"],"backgroundTag":"webauthn-ceremony-type-mismatch","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}