zeroclaw-labs/zeroclaw · critical
Key file must contain exactly 32 bytes (got {})
Error message
Key file must contain exactly 32 bytes (got {}) What it means
load_or_create_key validates that an existing master key file contains exactly 32 bytes, the ChaCha20 key size used by the secrets module. Wrong-length files are rejected rather than padded or truncated, to avoid silently deriving a weak or garbage key. Note the reported length tells you how big the file actually is.
Source
Thrown at crates/zeroclaw-config/src/secrets.rs:574
/// Opening with `open_no_follow` binds the "not a symlink" check to the same
/// object we read from — there is no check-then-follow window.
fn read_key_file_no_follow(key_path: &Path) -> std::io::Result<String> {
use std::io::Read; // function-scoped — avoids redundant module import
let mut file = open_no_follow(key_path)?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
Ok(buf)
}
/// Load the key from `key_path`, creating it if absent.
///
/// Reads go through a no-follow / reparse-point-verified handle. Creation
/// uses atomic no-replace publication (write-to-temp then `hard_link` on Unix
/// / `MoveFileExW` on Windows) so concurrent readers never observe empty or
/// partial key material.
fn load_or_create_key(key_path: &Path) -> Result<Vec<u8>> {
let validate_key = |bytes: Vec<u8>| {
anyhow::ensure!(
bytes.len() == 32,
"Key file must contain exactly 32 bytes (got {})",
bytes.len()
);
Ok(bytes)
};
match read_key_file_no_follow(key_path) {
Ok(hex) => validate_key(hex_decode(hex.trim()).context("Secret key file is corrupt")?),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
let key = generate_random_key();
match write_key_file_atomic_publish(key_path, &key) {
Ok(()) => Ok(key),
Err(write_err) => {
// Only recover if another process won the race.
// All other failures must propagate so we don't
// silently accept a bad key.
if !is_already_exists_error(&write_err) {View on GitHub (pinned to 88bb9c8533)
Solutions
- If no secrets were encrypted yet, remove the invalid key file and let the loader atomically create a correct 32-byte one
- Regenerate a proper key: head -c 32 /dev/urandom > keyfile (chmod 600)
- If secrets already exist, restore the original 32-byte key from backup instead of regenerating — a new key makes existing secrets undecryptable
Example fix
# before printf 'my-password' > ~/.zeroclaw/key # 11 bytes, rejected # after head -c 32 /dev/urandom > ~/.zeroclaw/key && chmod 600 ~/.zeroclaw/key
Defensive patterns
Strategy: validation
Validate before calling
let meta = std::fs::metadata(&key_path)?;
if meta.len() != 32 {
anyhow::bail!(
"key file {} is {} bytes; expected 32 — restore the original key or remove it to regenerate (existing secrets would be lost)",
key_path.display(),
meta.len()
);
}
// safe to call Secrets::with_key(...) now Try / catch
on "Key file must contain exactly 32 bytes" — stop startup; if no secrets exist yet, delete the file and retry once; otherwise restore the correct 32-byte key from backup
Prevention
- Generate keys only with head -c 32 /dev/urandom > key (raw bytes, no newline)
- Back up the 32-byte key alongside encrypted secrets; losing it loses the secrets
- chmod 600 the key file and never edit it with text editors that add trailing newlines
When it happens
Trigger: Hand-creating a key file containing a passphrase, hex text, or base64 instead of 32 raw bytes; a key file with a trailing newline; truncation or corruption of the key file; pointing the key path at the wrong file; writing a 64-character hex string as text (64 bytes).
Common situations: Operators generating keys with echo secret > keyfile instead of raw random bytes; editors appending newlines; copying keys between machines in a lossy way; leftover files from experiments at the configured key path.
Related errors
- Encrypted value too short (missing nonce)
- 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
- microsoft365.client_secret must not be empty when auth_flow
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c0404b531c38f550.
Report an issue: GitHub.