vercel-labs/agent-browser · error · io::Error (InvalidInput)

NUL in Chrome argument

Error message

NUL in Chrome argument

What it means

The `wide` helper converts an OsStr (a Chrome argument such as an exe path, flag, or URL) to a NUL-terminated UTF-16 buffer for Windows APIs like CreateProcessW. Windows native string APIs cannot represent embedded NUL characters, so before appending the terminating NUL the function scans the encoded value for interior 0u16 code units and refuses the input with this error instead of silently truncating the argument. It is a fail-fast input validation guard for Windows process spawning.

Source

Thrown at cli/src/native/cdp/windows_process.rs:301

    // SAFETY: Duplicate into our process; ownership is transferred below.
    check(unsafe {
        DuplicateHandle(
            GetCurrentProcess(),
            handle,
            GetCurrentProcess(),
            &mut duplicate,
            0,
            1,
            DUPLICATE_SAME_ACCESS,
        )
    })?;
    owned(duplicate)
}

fn wide(value: &OsStr) -> io::Result<Vec<u16>> {
    let mut value: Vec<_> = value.encode_wide().collect();
    if value.contains(&0) {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "NUL in Chrome argument",
        ));
    }
    value.push(0);
    Ok(value)
}

/// Quote a single argument for Chrome's Windows C runtime. Backslashes are
/// doubled only before a quote or the closing quote, preserving paths/JSON.
fn quoted(value: &OsStr) -> io::Result<Vec<u16>> {
    let value = wide(value)?;
    let mut output = vec![b'"' as u16];
    let mut slashes = 0;
    for &ch in &value[..value.len() - 1] {
        if ch == b'\\' as u16 {
            slashes += 1;
            continue;

View on GitHub (pinned to 921a57b64d)

Solutions

  1. Trim or strip NUL bytes from the argument before passing it to the daemon/CLI, e.g. `s.trim_end_matches('\0')` or filter out 0 bytes from the source data.
  2. Validate every Chrome argument (path, flags, URL) for '\0' before invoking the library and reject or sanitize at your config-loading boundary.
  3. If the value comes from reading a file or byte buffer, decode it as UTF-8/UTF-16 properly instead of copying raw bytes that may include terminators.

Example fix

// before
let exe = std::fs::read_to_string("chrome_path.txt")?; // file saved as UTF-16, contains '\0'
daemon.launch(&exe, &["--headless"])?;

// after
let exe = std::fs::read_to_string("chrome_path.txt")?.trim_end_matches('\0').to_string();
assert!(!exe.contains('\0'), "chrome path must not contain NUL bytes");
daemon.launch(&exe, &["--headless"])?;
Defensive patterns

Strategy: validation

Validate before calling

fn assert_no_nul(arg: &std::ffi::OsStr) -> std::io::Result<()> {
    use std::os::windows::ffi::OsStrExt;
    if arg.encode_wide().any(|c| c == 0) {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            "NUL in Chrome argument",
        ));
    }
    Ok(())
}
// run for every arg before launching Chrome:
// assert_no_nul(exe.as_ref())?; for flag in flags { assert_no_nul(flag.as_ref())?; }

Type guard

fn is_nul_free(arg: &std::ffi::OsStr) -> bool {
    use std::os::windows::ffi::OsStrExt;
    !arg.encode_wide().any(|c| c == 0)
}

Prevention

When it happens

Trigger: Calling spawn/quoted paths that pass an OsStr containing interior NUL bytes to `wide(...)`; i.e. any Chrome argument, executable path, working directory, or environment value built from data that contains a 0 byte (e.g. bytes from a file, network input, or a String constructed with '\0' inside).

Common situations: Reading a Chrome path or argument list from a config file, database, or binary source that includes a trailing/interior NUL byte; interpolating untrusted user input into a Chrome flag; reconstructing a path from Windows wide-char data and accidentally keeping the terminator; concatenating byte buffers instead of strings.

Related errors


AI-assisted analysis of vercel-labs/agent-browser@921a57b64d (2026-09-10). Data as JSON: /api/errors/966b9191b5e6c803. Report an issue: GitHub.