{"record":{"id":"787d4bd770afb9d8","repo":"denoland/deno","slug":"cwd-has-nul","errorCode":null,"errorMessage":"cwd has NUL","messagePattern":"cwd has NUL","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/tools/desktop.rs","lineNumber":5774,"sourceCode":"      .into_iter()\n      .map(|(k, v)| {\n        let mut s =\n          Vec::with_capacity(k.as_bytes().len() + 1 + v.as_bytes().len());\n        s.extend_from_slice(k.as_bytes());\n        s.push(b'=');\n        s.extend_from_slice(v.as_bytes());\n        CString::new(s).map_err(|_| {\n          std::io::Error::new(\n            std::io::ErrorKind::InvalidInput,\n            \"env contains NUL\",\n          )\n        })\n      })\n      .collect::<std::io::Result<_>>()?;\n    let cwd = match cmd.get_current_dir() {\n      Some(p) => {\n        Some(CString::new(p.as_os_str().as_bytes()).map_err(|_| {\n          std::io::Error::new(std::io::ErrorKind::InvalidInput, \"cwd has NUL\")\n        })?)\n      }\n      None => None,\n    };\n    Ok((program, argv, envp, cwd))\n  }\n\n  pub fn spawn(cmd: &std::process::Command) -> std::io::Result<Child> {\n    let (program, argv, envp, cwd) = flatten(cmd)?;\n    let mut argv_ptrs: Vec<*mut libc::c_char> =\n      argv.iter().map(|c| c.as_ptr() as *mut _).collect();\n    argv_ptrs.push(std::ptr::null_mut());\n    let mut envp_ptrs: Vec<*mut libc::c_char> =\n      envp.iter().map(|c| c.as_ptr() as *mut _).collect();\n    envp_ptrs.push(std::ptr::null_mut());\n\n    // SAFETY: posix_spawn FFI. We initialize attrs/actions before use,\n    // destroy them on every exit path, and keep argv/envp CString backing","sourceCodeStart":5756,"sourceCodeEnd":5792,"githubUrl":"https://github.com/denoland/deno/blob/f7822238cab635a3a19f99f493f675fa81a7f9d8/cli/tools/desktop.rs#L5756-L5792","documentation":"The last NUL check in `flatten()`: when `Command::get_current_dir()` is set, the cwd path must also convert to a C string for `posix_chdir` inside spawn. An embedded 0x00 makes `CString::new` fail and the spawn is rejected with `io::ErrorKind::InvalidInput` (\"cwd has NUL\").","triggerScenarios":"Calling `Command::current_dir(p)` where `p` contains a NUL byte — user-controlled directory names, decoded buffers, or path joins with corrupt components — before the posix_spawn call.","commonSituations":"Sandbox/workspace tools that chdir into user-named directories; zip/tar extraction tools where entry names feed cwd; string truncation bugs producing trailing NULs.","solutions":["NUL-check the cwd path before calling `current_dir`, same as for the program path.","Reject directory names with control bytes at creation time so they never become cwd candidates.","If the cwd comes from untrusted archive metadata, canonicalize and validate it is a real directory first."],"exampleFix":"// before\nlet mut cmd = std::process::Command::new(prog);\ncmd.current_dir(&user_dir); // user_dir contains 0x00\n\n// after\nif user_dir.as_os_str().as_bytes().contains(&0) {\n  return Err(std::io::Error::new(\n    std::io::ErrorKind::InvalidInput,\n    \"cwd has NUL\",\n  ));\n}\nlet mut cmd = std::process::Command::new(prog);\ncmd.current_dir(&user_dir);","handlingStrategy":"validation","validationCode":"if let Some(cwd) = cmd.get_current_dir() {\n  if cwd.as_os_str().as_bytes().contains(&0) {\n    return Err(std::io::Error::new(\n      std::io::ErrorKind::InvalidInput,\n      \"cwd has NUL\",\n    ));\n  }\n}","typeGuard":"fn is_nul_free(s: &std::ffi::OsStr) -> bool {\n  !s.as_bytes().contains(&0)\n}","tryCatchPattern":"match spawn(cmd) {\n  Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {\n    // cwd was invalid — fall back to spawning in the current directory\n  }\n  other => other,\n}","preventionTips":["Validate user-supplied directory names at creation time","Canonicalize cwd candidates with std::fs::canonicalize before use","Reject control bytes in extracted archive entry names used as directories"],"tags":["subprocess","posix-spawn","nul-byte","working-directory","invalid-input"],"backgroundTag":"nul-byte-in-path","analyzedSha":"f7822238cab635a3a19f99f493f675fa81a7f9d8","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}