{"record":{"id":"e902bbfc398b56dc","repo":"zeroclaw-labs/zeroclaw","slug":"hex-string-contains-non-ascii-characters","errorCode":null,"errorMessage":"Hex string contains non-ASCII characters","messagePattern":"Hex string contains non-ASCII characters","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/secrets.rs","lineNumber":977,"sourceCode":"fn 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://\")\n}\n\nfn validate_onepassword_ref(reference: &str) -> Result<()> {\n    let path = reference.strip_prefix(\"op://\").unwrap_or(\"\");\n    let mut segments = path.split('/');\n    let has_required_segments = (0..3).all(|_| segments.next().is_some_and(|s| !s.is_empty()));","sourceCodeStart":959,"sourceCodeEnd":995,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/secrets.rs#L959-L995","documentation":"Second guard in hex_decode: after the even-length check, the input must be pure ASCII, because valid hex is always ASCII and this also guarantees every byte is a UTF-8 char boundary so the byte-slicing loop cannot panic on corrupt material — it returns this Err instead. It fires on the same surfaces as the odd-length error (key file, enc/enc2 ciphertext) when the string contains multi-byte characters, smart quotes, whitespace-like Unicode, or binary noise pasted into the hex field.","triggerScenarios":"A key file or encrypted config value containing non-ASCII: pasted from a chat/browser that converted characters (smart quotes around the hex), UTF-8 BOM prefix, mojibake after an encoding-changing transfer (Windows-1252 ↔ UTF-8), or tampered/corrupt ciphertext containing arbitrary bytes in a non-hex range.","commonSituations":"Copying keys through rich-text mediums that mangle characters; files saved with BOM by Windows editors; tampered secret values (the guard also serves tamper-evidence by refusing cleanly); double-encoding mishaps where raw bytes instead of hex were stored.","solutions":["Find the offending bytes: `grep -P '[^\\x00-\\x7F]' keyfile` or open in a hex editor and remove BOM/smart quotes","Restore the material from a clean copy (backup, .bak config, password manager) rather than hand-repairing characters","If unrecoverable, regenerate the key (quickstart) and re-encrypt secrets — old ciphertexts will not decrypt","Write keys/ciphertexts only via the tool itself; avoid round-tripping through editors/chat"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn hex_ascii_ok(s: &str) -> bool {\n    s.is_ascii()\n}","typeGuard":null,"tryCatchPattern":"let hex = std::fs::read_to_string(&key_path)?;\nif !hex_ascii_ok(hex.trim()) {\n    eprintln!(\"key file contains non-ASCII characters (BOM/smart quotes?) — restore a clean copy\");\n    return;\n}","preventionTips":["Save secret material as plain UTF-8/ASCII without BOM; disable 'smart quotes' in editors used for config","Transfer keys via binary-safe channels (scp, secret manager), not chat/email/clipboard round-trips"],"tags":["secrets","hex","encoding","ascii","corruption"],"backgroundTag":"invalid-hex-string","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}