{"record":{"id":"2791b0e6c6472483","repo":"zeroclaw-labs/zeroclaw","slug":"authenticator-data-is-shorter-than-the-required-fi","errorCode":null,"errorMessage":"Authenticator data is shorter than the required fixed fields","messagePattern":"Authenticator data is shorter than the required fixed fields","errorType":"exception","errorClass":null,"httpStatus":401,"severity":"error","filePath":"crates/zeroclaw-runtime/src/security/webauthn.rs","lineNumber":589,"sourceCode":"            .context(\"Failed to write WebAuthn credentials file\")?;\n\n        // Set restrictive permissions on the credentials file\n        #[cfg(unix)]\n        {\n            use std::os::unix::fs::PermissionsExt;\n            std::fs::set_permissions(\n                &self.credentials_path,\n                std::fs::Permissions::from_mode(0o600),\n            )\n            .context(\"Failed to set credentials file permissions\")?;\n        }\n\n        Ok(())\n    }\n}\n\nfn validate_assertion_authenticator_data(auth_data: &[u8], rp_id: &str) -> Result<u32> {\n    anyhow::ensure!(\n        auth_data.len() >= AUTHENTICATOR_DATA_FIXED_LEN,\n        \"Authenticator data is shorter than the required fixed fields\"\n    );\n\n    let expected_rp_id_hash = ring::digest::digest(&ring::digest::SHA256, rp_id.as_bytes());\n    anyhow::ensure!(\n        &auth_data[..32] == expected_rp_id_hash.as_ref(),\n        \"Authenticator data relying party ID hash mismatch\"\n    );\n    anyhow::ensure!(\n        auth_data[32] & AUTHENTICATOR_FLAG_UP != 0,\n        \"Authenticator data does not assert user presence\"\n    );\n\n    Ok(u32::from_be_bytes([\n        auth_data[33],\n        auth_data[34],\n        auth_data[35],","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/security/webauthn.rs#L571-L607","documentation":"During a WebAuthn assertion (finish_authentication), the authenticator data blob must contain at least AUTHENTICATOR_DATA_FIXED_LEN fixed bytes (32-byte RP ID hash + 1 flags byte + 4-byte sign count) before the optional attestedCredentialData/extensions. validate_assertion_authenticator_data rejects anything shorter because the fixed fields cannot even be read, so the assertion is malformed or truncated.","triggerScenarios":"Calling finish_authentication with a PublicKeyCredential whose response.authenticatorData base64url-decodes to fewer than 37 bytes: client sending the wrong field (e.g. rawId or clientDataJSON), a base64/base64url decoding mismatch that drops padding bytes, hand-crafted test fixtures with dummy bytes, or a tampered/forged assertion.","commonSituations":"Mismatched serialization between the browser WebAuthn API response and the server's decoder (standard vs URL-safe alphabet, padding stripping), unit tests with synthetic short byte arrays, JSON proxies or middleware truncating long fields, copying an example payload from another RP implementation.","solutions":["Log the decoded authenticatorData length right before finish_authentication; anything under 37 bytes means the client payload is wrong, not the server check.","Verify the frontend sends response.authenticatorData from navigator.credentials.get() exactly, base64url-encoded, and that the server decodes base64url (with or without padding) symmetrically.","Confirm the challenge and credential being asserted came from begin_authentication; a stale or replayed challenge often correlates with mangled payloads.","Treat the attempt as malformed input: deny authentication and do not retry the same payload."],"exampleFix":"// before: passing the raw JSON string straight through\nlet auth_data = b64::decode(&assertion.response.authenticator_data)?;\nlet res = webauthn::finish_authentication(&cred, &assertion, rp_id).await;\n\n// after: validate the decoded buffer shape before entering the API\nlet auth_data = b64_url::decode(&assertion.response.authenticator_data)?;\nif auth_data.len() < 37 {\n    return Err(anyhow!(\"client sent malformed authenticatorData ({} bytes)\", auth_data.len()));\n}\nlet res = webauthn::finish_authentication(&cred, &assertion, rp_id).await;","handlingStrategy":"validation","validationCode":"fn authenticator_data_shape_ok(encoded: &str) -> bool {\n    match base64url::decode(encoded) {\n        Ok(bytes) => bytes.len() >= 37, // 32 rpIdHash + 1 flags + 4 signCount\n        Err(_) => false,\n    }\n}\n\n// before finish_authentication:\nassert!(authenticator_data_shape_ok(&assertion.authenticator_data), \"client sent malformed authenticatorData\");","typeGuard":null,"tryCatchPattern":"match webauthn::finish_authentication(&cred, &assertion, rp_id).await {\n    Ok(res) => Ok(res),\n    Err(e) if e.to_string().contains(\"shorter than the required fixed fields\") => {\n        deny_login(\"malformed authenticator data\"); // do not retry the same payload\n        Err(e)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Use one shared base64url codec (same padding policy) on client and server for all WebAuthn buffers.","Add contract tests that round-trip real browser credentials through your JSON layer before shipping serialization changes.","Log decoded byte lengths of authenticatorData on every failure; length anomalies catch client bugs immediately."],"tags":["webauthn","authentication","input-validation","security"],"backgroundTag":"webauthn-malformed-authenticator-data","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}