{"record":{"id":"c0c4c3ef05fbb97f","repo":"zeroclaw-labs/zeroclaw","slug":"expected-type-webauthn-create-got-cd-type","errorCode":null,"errorMessage":"Expected type 'webauthn.create', got '{cd_type}'","messagePattern":"Expected type 'webauthn\\.create', got '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":400,"severity":"error","filePath":"crates/zeroclaw-runtime/src/security/webauthn.rs","lineNumber":276,"sourceCode":"\n    /// Complete a WebAuthn registration ceremony.\n    /// Validates the client response against the registration state,\n    /// extracts the public key, and stores the credential.\n    pub fn finish_registration(\n        &self,\n        reg_state: &RegistrationState,\n        response: &RegisterCredentialResponse,\n    ) -> Result<WebAuthnCredential> {\n        // 1. 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        // Verify type\n        let cd_type = client_data[\"type\"].as_str().unwrap_or_default();\n        anyhow::ensure!(\n            cd_type == \"webauthn.create\",\n            \"Expected type 'webauthn.create', got '{cd_type}'\"\n        );\n\n        // Verify challenge matches\n        let cd_challenge = client_data[\"challenge\"].as_str().unwrap_or_default();\n        anyhow::ensure!(\n            cd_challenge == reg_state.challenge,\n            \"Challenge mismatch in registration response\"\n        );\n\n        // Verify origin\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        );","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/security/webauthn.rs#L258-L294","documentation":"In finish_registration, the client_data_json of a registration response must carry type == \"webauthn.create\" per the WebAuthn registration ceremony. Any other value — most commonly \"webauthn.get\" — means the payload belongs to a different ceremony or was constructed incorrectly, and registration is rejected.","triggerScenarios":"Sending an authentication assertion (navigator.credentials.get result) to the registration finish endpoint; hand-built or test client_data_json using the wrong type string; a proxy or client library rewriting the payload.","commonSituations":"Frontend code mixing up the create/get response handlers and posting to the wrong endpoint; e2e tests synthesizing client data with copy-pasted fields; replayed or cached responses from a previous ceremony.","solutions":["Make the client call navigator.credentials.create() and send that exact response to the register-finish endpoint","Keep registration and authentication response handling in separate code paths; never reuse payloads between them","In tests, build client_data_json with type 'webauthn.create' and the challenge returned by register start"],"exampleFix":"// before: posting an authentication response to register finish\nconst resp = await navigator.credentials.get({ publicKey: authOptions });\nawait fetch('/register/finish', { method: 'POST', body: serialize(resp) });\n// after\nconst resp = await navigator.credentials.create({ publicKey: regOptions });\nawait fetch('/register/finish', { method: 'POST', body: serialize(resp) });","handlingStrategy":"try-catch","validationCode":"// optional client-side pre-check to give a clearer error than the server\nconst cd = JSON.parse(Array.from(atob(resp.response.clientDataJSON.replace(/-/g,'+').replace(/_/g,'/')), c => String.fromCharCode(c.charCodeAt(0))).split('').join(''));\nif (cd.type !== 'webauthn.create') throw new Error('wrong ceremony: ensure navigator.credentials.create() response is sent to register finish');","typeGuard":null,"tryCatchPattern":"in the HTTP handler wrapping finish_registration, catch the anyhow error and map ceremony-validation failures (type/challenge/origin messages) to HTTP 400 with the error text; do not retry the same payload","preventionTips":["Keep register and login fetch handlers in distinct functions with distinct response types","Add an e2e test that drives the real browser ceremony so payload mixups surface immediately","Log client_data_json type on failure for fast diagnosis"],"tags":["webauthn","authentication","registration","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"}