{"record":{"id":"f15517e582bb7ec8","repo":"zeroclaw-labs/zeroclaw","slug":"hex-string-has-odd-length","errorCode":null,"errorMessage":"Hex string has odd length","messagePattern":"Hex string has odd length","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/secrets.rs","lineNumber":971,"sourceCode":"    s\n}\n\n/// Build the `/grant` argument for `icacls` using a normalized username.\n/// Returns `None` when the username is empty or whitespace-only.\n#[cfg(any(windows, test))]\nfn build_windows_icacls_grant_arg(username: &str) -> Option<String> {\n    let normalized = username.trim();\n    if normalized.is_empty() {\n        return None;\n    }\n    Some(format!(\"{normalized}:F\"))\n}\n\n/// Hex-decode a hex string to bytes.\n#[allow(clippy::manual_is_multiple_of)]\nfn hex_decode(hex: &str) -> Result<Vec<u8>> {\n    if (hex.len() & 1) != 0 {\n        anyhow::bail!(\"Hex string has odd length\");\n    }\n    // Reject non-ASCII up front: valid hex is always ASCII, and this guarantees\n    // every byte is a char boundary so the byte-index slicing below cannot panic\n    // on a corrupt/tampered ciphertext (it returns the Err the signature promises).\n    if !hex.is_ascii() {\n        anyhow::bail!(\"Hex string contains non-ASCII characters\");\n    }\n    (0..hex.len())\n        .step_by(2)\n        .map(|i| {\n            u8::from_str_radix(&hex[i..i + 2], 16)\n                .map_err(|e| anyhow::Error::msg(format!(\"Invalid hex at position {i}: {e}\")))\n        })\n        .collect()\n}\n\nfn is_onepassword_ref(value: &str) -> bool {\n    value.starts_with(\"op://\")","sourceCodeStart":953,"sourceCodeEnd":989,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/secrets.rs#L953-L989","documentation":"hex_decode is the parser for hex-encoded material in zeroclaw-config's secret store — the master key file contents and `enc:`/`enc2:` ciphertext bodies. It bails when the input length is odd, because hex encodes one byte per two characters, so an odd length can never decode. In practice this means the stored material is corrupt or truncated: the key file was cut mid-write, edited by hand, or the config value was mangled. Callers wrap it with context like \"Failed to read secret key file\" or \"Secret key file created by concurrent process is corrupt\".","triggerScenarios":"load_or_create_key reads ~/.zeroclaw/.secret_key whose hex was truncated (disk full during an old write, manual edit, copy-paste losing a char); decrypt_chacha20/decrypt_legacy_xor parse an enc2:/enc: value from config.toml that was truncated or hand-edited; a secrets value migrated between systems lost characters.","commonSituations":"Key file corrupted by a crash during an old non-atomic write; user hand-copied the key between machines and dropped a character; config files trimmed/sanitized by tooling that cut long strings.","solutions":["Inspect the hex input: `wc -c` the key file — a 32-byte key must be exactly 64 hex chars; find where it got truncated","If the key file is corrupt and unrecoverable, regenerate it (delete ~/.zeroclaw/.secret_key and re-run `zeroclaw quickstart`) — note values encrypted with the old key become undecryptable","For a corrupt config value, restore config.toml from the .bak file or version control and re-save secrets","Always copy key material programmatically, never by selecting text"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn hex_shape_ok(s: &str) -> bool {\n    s.len() % 2 == 0\n}","typeGuard":null,"tryCatchPattern":"match std::fs::read_to_string(&key_path) {\n    Ok(hex) if !hex_shape_ok(hex.trim()) => {\n        eprintln!(\"key file at {} has an odd hex length — it is truncated; restore from backup or regenerate\", key_path.display());\n    }\n    other => other?,\n}","preventionTips":["Copy key files only with cp/rsync — never by selecting text","Keep a durable backup of the master key (secret manager) so corruption means restore, not data loss","After any manual edit of key/ciphertext files, check `wc -c` matches 2x the byte length (64 chars for the 32-byte key)"],"tags":["secrets","hex","encoding","corruption","key-management"],"backgroundTag":"invalid-hex-string","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}