{"record":{"id":"3424d36bd4b96c9c","repo":"zeroclaw-labs/zeroclaw","slug":"origin-mismatch-expected-got-cd-origin","errorCode":null,"errorMessage":"Origin mismatch: expected '{}', got '{cd_origin}'","messagePattern":"Origin mismatch: expected '(.+?)', got '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":400,"severity":"error","filePath":"crates/zeroclaw-runtime/src/security/webauthn.rs","lineNumber":290,"sourceCode":"            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        );\n\n        // 2. Parse attestation object to extract public key and auth data\n        let attestation_bytes = URL_SAFE_NO_PAD\n            .decode(&response.attestation_object)\n            .context(\"Invalid base64url in attestation_object\")?;\n\n        // For \"none\" attestation, we extract the authData which contains the\n        // credential public key. The attestation object is CBOR-encoded but\n        // for our minimal implementation we accept a simplified JSON format\n        // from our enrollment UI, or parse the raw CBOR authData.\n        let (public_key_bytes, sign_count) =\n            extract_public_key_from_attestation(&attestation_bytes)?;\n\n        // 3. Validate credential ID length","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/security/webauthn.rs#L272-L308","documentation":"finish_registration verifies client_data_json origin equals the configured rp_origin for the relying party. The client's actual origin (scheme + host + port) differs from the configured value, so the response is rejected as coming from an unexpected origin.","triggerScenarios":"rp_origin configured as https://api.example.com while the frontend runs on https://app.example.com or http://localhost:3000; http vs https, trailing port (:443 explicit vs implicit), or a proxy/TLS terminator changing the scheme; frontend moved to a new domain without updating config.","commonSituations":"Local development against a production-configured server; deployments behind reverse proxies; migration to a new domain or port; forgetting the origin includes scheme and port.","solutions":["Set rp_origin in the WebAuthn config to the exact origin the frontend is served from, including scheme and port","Redeploy/restart the runtime so the new config is loaded","If you must serve multiple frontends, align on one domain or run separate relying-party configurations"],"exampleFix":"# before\nrp_origin = \"http://localhost:3000\"   # prod frontend is https://app.example.com\n# after\nrp_origin = \"https://app.example.com\"","handlingStrategy":"validation","validationCode":"// fail fast before the ceremony if origins can never match\nconst rpOrigin = serverConfig.rp_origin; // e.g. 'https://app.example.com'\nif (window.location.origin !== rpOrigin) {\n  throw new Error(`page origin ${window.location.origin} != configured rp_origin ${rpOrigin}`);\n}","typeGuard":null,"tryCatchPattern":"map the origin-mismatch message to HTTP 400 and include both expected and received origins in the response so misconfiguration is obvious in one round trip","preventionTips":["Derive rp_origin from the same environment variable the frontend build uses","Use environment-specific configs for dev/staging/prod","Remember origin includes scheme and explicit ports (localhost:3000 differs from localhost)"],"tags":["webauthn","origin","cors-like","configuration","registration","rust"],"backgroundTag":"webauthn-origin-mismatch","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}