{"record":{"id":"c0404b531c38f550","repo":"zeroclaw-labs/zeroclaw","slug":"key-file-must-contain-exactly-32-bytes-got","errorCode":null,"errorMessage":"Key file must contain exactly 32 bytes (got {})","messagePattern":"Key file must contain exactly 32 bytes \\(got (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/zeroclaw-config/src/secrets.rs","lineNumber":574,"sourceCode":"/// Opening with `open_no_follow` binds the \"not a symlink\" check to the same\n/// object we read from — there is no check-then-follow window.\nfn read_key_file_no_follow(key_path: &Path) -> std::io::Result<String> {\n    use std::io::Read; // function-scoped — avoids redundant module import\n    let mut file = open_no_follow(key_path)?;\n    let mut buf = String::new();\n    file.read_to_string(&mut buf)?;\n    Ok(buf)\n}\n\n/// Load the key from `key_path`, creating it if absent.\n///\n/// Reads go through a no-follow / reparse-point-verified handle.  Creation\n/// uses atomic no-replace publication (write-to-temp then `hard_link` on Unix\n/// / `MoveFileExW` on Windows) so concurrent readers never observe empty or\n/// partial key material.\nfn load_or_create_key(key_path: &Path) -> Result<Vec<u8>> {\n    let validate_key = |bytes: Vec<u8>| {\n        anyhow::ensure!(\n            bytes.len() == 32,\n            \"Key file must contain exactly 32 bytes (got {})\",\n            bytes.len()\n        );\n        Ok(bytes)\n    };\n\n    match read_key_file_no_follow(key_path) {\n        Ok(hex) => validate_key(hex_decode(hex.trim()).context(\"Secret key file is corrupt\")?),\n        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {\n            let key = generate_random_key();\n            match write_key_file_atomic_publish(key_path, &key) {\n                Ok(()) => Ok(key),\n                Err(write_err) => {\n                    // Only recover if another process won the race.\n                    // All other failures must propagate so we don't\n                    // silently accept a bad key.\n                    if !is_already_exists_error(&write_err) {","sourceCodeStart":556,"sourceCodeEnd":592,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/secrets.rs#L556-L592","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before\nprintf 'my-password' > ~/.zeroclaw/key   # 11 bytes, rejected\n# after\nhead -c 32 /dev/urandom > ~/.zeroclaw/key && chmod 600 ~/.zeroclaw/key","handlingStrategy":"validation","validationCode":"let meta = std::fs::metadata(&key_path)?;\nif meta.len() != 32 {\n    anyhow::bail!(\n        \"key file {} is {} bytes; expected 32 — restore the original key or remove it to regenerate (existing secrets would be lost)\",\n        key_path.display(),\n        meta.len()\n    );\n}\n// safe to call Secrets::with_key(...) now","typeGuard":null,"tryCatchPattern":"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","preventionTips":["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"],"tags":["crypto","secrets","key-management","config","rust"],"backgroundTag":"invalid-key-file","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}