zeroclaw-labs/zeroclaw · error

OTP secret did not decode to any bytes

Error message

OTP secret did not decode to any bytes

What it means

The cleaned secret is non-empty but decodes to zero bytes (otp.rs:254-256). Base32 packs 5 bits per character and bytes are emitted only at 8-bit boundaries, so a 1-character secret produces no bytes. Real TOTP secrets are 32 chars (160 bits), so this almost always means a truncated paste.

Source

Thrown at crates/zeroclaw-runtime/src/security/otp.rs:255

    let mut output = Vec::new();
    let mut buffer = 0u32;
    let mut bits_left = 0u8;

    for ch in cleaned.chars() {
        let value = decode_char(ch)
            .with_context(|| format!("OTP secret contains invalid base32 character '{ch}'"))?;
        buffer = (buffer << 5) | u32::from(value);
        bits_left += 5;

        if bits_left >= 8 {
            let byte = ((buffer >> (bits_left - 8)) & 0xff) as u8;
            output.push(byte);
            bits_left -= 8;
        }
    }

    if output.is_empty() {
        anyhow::bail!("OTP secret did not decode to any bytes");
    }
    Ok(output)
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;

    fn test_config() -> OtpConfig {
        OtpConfig {
            enabled: true,
            token_ttl_secs: 30,
            cache_valid_secs: 120,
            ..OtpConfig::default()
        }
    }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Re-copy the full secret from the enrollment source (QR code / otpauth:// URL)
  2. Validate the secret decodes to at least 20 bytes (160 bits per RFC 4226) before enabling OTP
  3. Store secrets verbatim — avoid transforms that can truncate them
Defensive patterns

Strategy: validation

Validate before calling

fn otp_secret_decodes(raw: &str) -> bool {
    let cleaned: String = raw.chars().filter(|c| c.is_ascii_alphanumeric()).collect();
    // 2 base32 chars -> 10 bits -> at least 1 byte; real secrets are 32+ chars
    cleaned.len() >= 32 && cleaned.chars().all(|c| c.is_ascii_alphanumeric())
}

Try / catch

Catch from_config errors and report a config-invalid message with the secret's length (never its value) so the operator sees the truncation immediately.

Prevention

When it happens

Trigger: OtpConfig secret with exactly one base32 character after cleaning (e.g. "a" or "7="); the secret was cut off during copy-paste or templating.

Common situations: Secret truncated by a length limit or a stray newline; manual retyping of a QR-derived secret dropped characters; a templating system mangled the value.

Related errors


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