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
- Re-copy the full secret from the enrollment source (QR code / otpauth:// URL)
- Validate the secret decodes to at least 20 bytes (160 bits per RFC 4226) before enabling OTP
- 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
- Validate secret length (>= 32 base32 chars / 160 bits) at config load
- Copy secrets with a password manager or file transfer, not by eye
- Round-trip check: decode then re-encode to confirm the secret survived storage
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
- OTP secret is empty
- matrix: configure either `access_token` or `password`
- Nextcloud Talk: no bot secret configured (set bot_token or w
- model_provider `{family}` has multiple configured aliases; u
- security.otp.cache_valid_secs must be greater than or equal
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/5e6b05303bfbf33f.
Report an issue: GitHub.