zeroclaw-labs/zeroclaw · error
Authenticator data is shorter than the required fixed fields
Error message
Authenticator data is shorter than the required fixed fields
What it means
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.
Source
Thrown at crates/zeroclaw-runtime/src/security/webauthn.rs:589
.context("Failed to write WebAuthn credentials file")?;
// Set restrictive permissions on the credentials file
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(
&self.credentials_path,
std::fs::Permissions::from_mode(0o600),
)
.context("Failed to set credentials file permissions")?;
}
Ok(())
}
}
fn validate_assertion_authenticator_data(auth_data: &[u8], rp_id: &str) -> Result<u32> {
anyhow::ensure!(
auth_data.len() >= AUTHENTICATOR_DATA_FIXED_LEN,
"Authenticator data is shorter than the required fixed fields"
);
let expected_rp_id_hash = ring::digest::digest(&ring::digest::SHA256, rp_id.as_bytes());
anyhow::ensure!(
&auth_data[..32] == expected_rp_id_hash.as_ref(),
"Authenticator data relying party ID hash mismatch"
);
anyhow::ensure!(
auth_data[32] & AUTHENTICATOR_FLAG_UP != 0,
"Authenticator data does not assert user presence"
);
Ok(u32::from_be_bytes([
auth_data[33],
auth_data[34],
auth_data[35],View on GitHub (pinned to 88bb9c8533)
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.
Example fix
// before: passing the raw JSON string straight through
let auth_data = b64::decode(&assertion.response.authenticator_data)?;
let res = webauthn::finish_authentication(&cred, &assertion, rp_id).await;
// after: validate the decoded buffer shape before entering the API
let auth_data = b64_url::decode(&assertion.response.authenticator_data)?;
if auth_data.len() < 37 {
return Err(anyhow!("client sent malformed authenticatorData ({} bytes)", auth_data.len()));
}
let res = webauthn::finish_authentication(&cred, &assertion, rp_id).await; Defensive patterns
Strategy: validation
Validate before calling
fn authenticator_data_shape_ok(encoded: &str) -> bool {
match base64url::decode(encoded) {
Ok(bytes) => bytes.len() >= 37, // 32 rpIdHash + 1 flags + 4 signCount
Err(_) => false,
}
}
// before finish_authentication:
assert!(authenticator_data_shape_ok(&assertion.authenticator_data), "client sent malformed authenticatorData"); Try / catch
match webauthn::finish_authentication(&cred, &assertion, rp_id).await {
Ok(res) => Ok(res),
Err(e) if e.to_string().contains("shorter than the required fixed fields") => {
deny_login("malformed authenticator data"); // do not retry the same payload
Err(e)
}
Err(e) => Err(e),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Authenticator data relying party ID hash mismatch
- Authenticator data does not assert user presence
- Invalid OTP code; estop resume denied
- skill '{$skill}' in {$url} resolves outside the cloned catal
- Invalid skill name: {name}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2791b0e6c6472483.
Report an issue: GitHub.