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

  1. Log the decoded authenticatorData length right before finish_authentication; anything under 37 bytes means the client payload is wrong, not the server check.
  2. Verify the frontend sends response.authenticatorData from navigator.credentials.get() exactly, base64url-encoded, and that the server decodes base64url (with or without padding) symmetrically.
  3. Confirm the challenge and credential being asserted came from begin_authentication; a stale or replayed challenge often correlates with mangled payloads.
  4. 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

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

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/2791b0e6c6472483. Report an issue: GitHub.