{"record":{"id":"624d94bd309ba8e6","repo":"jdx/mise","slug":"history-directory-contains-a-nul-character","errorCode":null,"errorMessage":"history directory contains a NUL character","messagePattern":"history directory contains a NUL character","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/store.rs","lineNumber":135,"sourceCode":"    let sddl: Vec<u16> = \"D:P(A;OICI;FA;;;OW)\\0\".encode_utf16().collect();\n    let mut descriptor = std::ptr::null_mut();\n    // SAFETY: both pointers are valid for the call; Windows allocates the\n    // descriptor, released with LocalFree below on every path.\n    if unsafe {\n        ConvertStringSecurityDescriptorToSecurityDescriptorW(\n            sddl.as_ptr(),\n            1,\n            &mut descriptor,\n            std::ptr::null_mut(),\n        )\n    } == 0\n    {\n        return Err(std::io::Error::last_os_error().into());\n    }\n    let result = (|| -> Result<()> {\n        let mut path: Vec<u16> = dir.as_os_str().encode_wide().collect();\n        if path.contains(&0) {\n            eyre::bail!(\"history directory contains a NUL character\");\n        }\n        path.push(0);\n        let attributes = SECURITY_ATTRIBUTES {\n            nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,\n            lpSecurityDescriptor: descriptor,\n            bInheritHandle: 0,\n        };\n        if !dir.is_dir()\n            // SAFETY: path is NUL-terminated and attributes/descriptor remain live.\n            && unsafe { CreateDirectoryW(path.as_ptr(), &attributes) } == 0\n            && !dir.is_dir()\n        {\n            return Err(std::io::Error::last_os_error().into());\n        }\n        // Also tighten an existing directory instead of trusting inherited ACLs.\n        // SAFETY: path and descriptor are valid until the call returns.\n        if unsafe {\n            SetFileSecurityW(","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/store.rs#L117-L153","documentation":"On Windows, `create_private_dir` encodes the history directory path as UTF-16 for a Win32 API call. Interior NUL characters cannot appear in a valid Win32 path string, so if the encoded wide path contains a NUL, the call is refused up front with this error. This is a defensive check that a sanitized path never silently truncates at the Win32 boundary.","triggerScenarios":"Constructing a history directory path that contains an embedded NUL character — typically from a corrupted environment variable, a bad config value, or a string built from binary data — then attempting to create the private directory on Windows.","commonSituations":"MISE_DATA_DIR or similar env var set from a mis-decoded value; a config file with a literal `\\0` escape expanded into the path; a script interpolating binary output into the data-dir setting.","solutions":["Inspect the history/data directory configuration (`MISE_DATA_DIR`, settings) for stray NUL or `\\0` sequences and fix the value.","Re-set the env var from a clean shell: `export MISE_DATA_DIR=$(printf '%s' \"$value\" | tr -d '\\0')`.","Point the data dir at a simple ASCII path (e.g. `%USERPROFILE%\\.local\\share\\mise`) and retry.","If the value comes from a config file, re-save that file as clean UTF-8 without embedded NULs."],"exampleFix":"// before: path built from raw bytes\nlet dir = PathBuf::from(OsString::from_vec(bytes));\n// after: strip NULs before use\nlet dir = PathBuf::from(String::from_utf8_lossy(&bytes).replace('\\0', \"\"));","handlingStrategy":"validation","validationCode":"let dir = std::env::var(\"MISE_DATA_DIR\").unwrap_or_default();\nif dir.contains('\\0') {\n    eprintln!(\"MISE_DATA_DIR contains a NUL character; fix before running\");\n}","typeGuard":"fn is_valid_dir_name(p: &std::path::Path) -> bool {\n    p.as_os_str().to_str().map(|s| !s.contains('\\0')).unwrap_or(false)\n}","tryCatchPattern":"match init_history_store() {\n    Ok(store) => {},\n    Err(e) if e.to_string().contains(\"NUL character\") => {\n        eprintln!(\"data-dir path is corrupted; reset MISE_DATA_DIR\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Set MISE_DATA_DIR from trusted, text-only sources","Sanitize interpolated config values with `tr -d '\\0'` in scripts","Re-save config files as clean UTF-8 if they were edited with binary tools"],"tags":["windows","path","nul-character","directory"],"backgroundTag":"invalid-argument-value","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}