{"record":{"id":"6f0884c70a2f9644","repo":"Hmbown/CodeWhale","slug":"external-credential-path-contains-a-nul-byte","errorCode":null,"errorMessage":"external credential path contains a NUL byte","messagePattern":"external credential path contains a NUL byte","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/external_credentials.rs","lineNumber":185,"sourceCode":"    let mut normals = path\n        .components()\n        .filter_map(|component| match component {\n            Component::Normal(part) => Some(Ok(part)),\n            Component::RootDir => None,\n            Component::Prefix(_) | Component::CurDir | Component::ParentDir => {\n                Some(Err(io::Error::new(\n                    io::ErrorKind::InvalidInput,\n                    \"external credential path must be lexically normalized\",\n                )))\n            }\n        })\n        .peekable();\n\n    let mut opened_leaf = false;\n    while let Some(component) = normals.next() {\n        let component = component?;\n        let component = CString::new(component.as_bytes()).map_err(|_| {\n            io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"external credential path contains a NUL byte\",\n            )\n        })?;\n        let leaf = normals.peek().is_none();\n        #[cfg(test)]\n        if leaf {\n            BEFORE_LEAF_OPEN_HOOK.with(|hook| {\n                if let Some(hook) = hook.borrow_mut().take() {\n                    hook();\n                }\n            });\n        }\n        let flags = if leaf {\n            libc::O_RDONLY | libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_NONBLOCK\n        } else {\n            libc::O_RDONLY | libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_DIRECTORY\n        };","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/external_credentials.rs#L167-L203","documentation":"Path components passed to `open_secure_regular_file` are converted to `CString` for `openat` syscalls. Since NUL bytes cannot appear inside a real filesystem component name, any embedded NUL means the path string is malformed or hostile, and the opener rejects it with this `InvalidInput` error before making any syscall.","triggerScenarios":"`read_to_string`/`read_codewhale_owned_to_string` called with a path whose bytes contain a NUL character (e.g. a truncated/concatenated string, or untrusted input with `\\0`).","commonSituations":"Untrusted or interpolated paths containing raw NULs; buffers built from fixed-size C-style strings that kept a trailing NUL; injection attempts where validation is the intended defense.","solutions":["Sanitize/validate the path string before use: reject or strip any `\\0` bytes at the source of the value.","If the path came from a fixed-size buffer or C FFI, trim at the first NUL instead of treating the padding as part of the name.","Fix the code/config that produced the malformed string — a NUL in a path is never legitimate.","Log the offending value's bytes to identify where the NUL is being introduced."],"exampleFix":"// before\nlet path = String::from_utf8_lossy(&raw_buf).to_string(); // may contain \\0\nlet creds = read_to_string(path)?;\n// after\nlet path = String::from_utf8_lossy(&raw_buf).trim_end_matches('\\0').to_string();\nassert!(!path.contains('\\0'));\nlet creds = read_to_string(path)?;","handlingStrategy":"validation","validationCode":"if path.as_os_str().as_encoded_bytes().contains(&0) {\n    return Err(anyhow::anyhow!(\"credential path contains NUL byte\"));\n}","typeGuard":"fn is_nul_free(p: &Path) -> bool {\n    !p.as_os_str().as_encoded_bytes().contains(&0)\n}","tryCatchPattern":"match read_to_string(&cred_path) {\n    Err(e) if e.to_string().contains(\"NUL byte\") => {\n        eprintln!(\"Malformed credential path (embedded NUL) in {cred_path:?}; check input source.\");\n    }\n    other => other?,\n}","preventionTips":["Trim NUL terminators when importing paths from C buffers or fixed-size fields.","Validate untrusted strings for control characters before using them as paths.","Use Rust String/PathBuf throughout — don't round-trip paths through C-style arrays.","Log offending byte values to trace where the NUL enters."],"tags":["security","filesystem","input-validation","credentials"],"backgroundTag":"invalid-argument-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}