denoland/deno · error

env contains NUL

Error message

env contains NUL

What it means

While building the envp array for posix_spawn, `flatten()` joins each inherited or overridden environment key and value into a single `KEY=value` C string. If either side contains a NUL byte, `CString::new` fails and the whole spawn is rejected with `io::ErrorKind::InvalidInput` ("env contains NUL").

Source

Thrown at cli/tools/desktop.rs:5764

      match v {
        Some(v) => {
          env_map.insert(k.to_os_string(), v.to_os_string());
        }
        None => {
          env_map.remove(k);
        }
      }
    }
    let envp: Vec<CString> = env_map
      .into_iter()
      .map(|(k, v)| {
        let mut s =
          Vec::with_capacity(k.as_bytes().len() + 1 + v.as_bytes().len());
        s.extend_from_slice(k.as_bytes());
        s.push(b'=');
        s.extend_from_slice(v.as_bytes());
        CString::new(s).map_err(|_| {
          std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            "env contains NUL",
          )
        })
      })
      .collect::<std::io::Result<_>>()?;
    let cwd = match cmd.get_current_dir() {
      Some(p) => {
        Some(CString::new(p.as_os_str().as_bytes()).map_err(|_| {
          std::io::Error::new(std::io::ErrorKind::InvalidInput, "cwd has NUL")
        })?)
      }
      None => None,
    };
    Ok((program, argv, envp, cwd))
  }

  pub fn spawn(cmd: &std::process::Command) -> std::io::Result<Child> {

View on GitHub (pinned to f7822238ca)

Solutions

  1. Validate both key and value with a NUL check in `Command::env` calls and when inheriting unusual env.
  2. Fix the producer writing 0x00 into the environ entry; encode binary values (hex/base64) instead of raw bytes.
  3. Clear or replace the offending variable before spawning if it is not needed by the child.

Example fix

// before
for (k, v) in &extra_env { cmd.env(k, v); }

// after
fn reject_nul(bytes: &[u8], what: &str) -> std::io::Result<()> {
  if bytes.contains(&0) {
    return Err(std::io::Error::new(
      std::io::ErrorKind::InvalidInput,
      format!("{what} contains NUL"),
    ));
  }
  Ok(())
}
for (k, v) in &extra_env {
  reject_nul(k.as_bytes(), "env key")?;
  reject_nul(v.as_bytes(), "env value")?;
  cmd.env(k, v);
}
Defensive patterns

Strategy: validation

Validate before calling

fn reject_nul(bytes: &[u8], what: &str) -> std::io::Result<()> {
  if bytes.contains(&0) {
    return Err(std::io::Error::new(
      std::io::ErrorKind::InvalidInput,
      format!("{what} contains NUL"),
    ));
  }
  Ok(())
}
for (k, v) in cmd.get_envs() {
  reject_nul(k.as_bytes(), "env key")?;
  if let Some(v) = v { reject_nul(v.as_bytes(), "env value")?; }
}

Type guard

fn is_nul_free(s: &std::ffi::OsStr) -> bool {
  !s.as_bytes().contains(&0)
}

Try / catch

match spawn(cmd) {
  Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {
    // scan env for the offending entry and drop/fix it before retrying
  }
  other => other,
}

Prevention

When it happens

Trigger: `Command::env(k, v)` (or the inherited process environment) carrying a key or value with an embedded 0x00 — e.g. tokens decoded from binary formats, base64-mangled secrets, or `env::vars_os` from a parent process with malformed environ entries.

Common situations: Passing machine-generated credentials/tokens as env vars; containers whose environ was written by a buggy injector; debug builds that stuff packed structs into env values.

Related errors


AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20). Data as JSON: /api/errors/afa95fabf02a822c. Report an issue: GitHub.