You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
child_process via require(): null bytes in file/args are not validated — 'spawn /bin/true UNKNOWN' instead of ERR_INVALID_ARG_VALUE; the named-import path validates with a non-node message #9537
Found through the MCP debug logs that #9491 unlocked (#9500 / #9531): the harness's null-byte case (E62_mcp_add_json_unicode, a server arg containing \x00) logs
node : Connection failed after 0ms: The argument 'args[1]' must be a string without null bytes. Received 'a\x00b'
perry: Connection failed after 1ms: spawn /bin/true UNKNOWN
Two paths, two behaviours (the #9485 family again)
import{createRequire}from"node:module";constcp: any=createRequire(import.meta.url)("child_process");// cross-spawn's / the MCP SDK's shapecp.spawn("/bin/true",["a\x00b"]);// perry: returns a ChildProcess, then 'error' UNKNOWN "spawn /bin/true UNKNOWN"cp.spawn("/bin/tr\x00ue",[]);// perry: 'error' UNKNOWN "spawn /bin/tr ue UNKNOWN"cp.spawnSync("/bin/true",["a\x00b"]);// perry: returns a result objectcp.execFile("/bin/true",["a\x00b"],cb);// perry: cb(err) with code UNKNOWN
Node throws synchronously from all four, ERR_INVALID_ARG_VALUE, message The argument 'args[0]' must be a string without null bytes. Received 'a\x00b' (or 'file' for the command). Perry's CJS-default-namespace path performs no validation, hands the string to the OS, and reports the CString failure as UNKNOWN.
The named-import path (import { spawn, spawnSync } from "node:child_process") does validate — it throws ERR_INVALID_ARG_VALUE synchronously — but with a different message:
node : "The argument 'args[0]' must be a string without null bytes. Received 'a\\x00b'"
perry: "The argument must not contain null bytes"
So: (1) the validation lives on one lowering path and not the other — the dispatch-router / CJS-namespace path (#9498 made these calls reachable) skips it; the check belongs in the runtime entry points (js_child_process_spawn_streams, js_child_process_spawn_sync, js_child_process_exec_file, and exec's command) so every spelling shares it; (2) the message should name the argument ('file', 'args[i]', 'options.cwd', 'options.env'...) and echo the received value with \x00 escaping, as node's validateArgumentNullCheck does.
Byte-compare bar: both scripts above print node's lines under node --experimental-strip-types.
Activity
proggeramlug commentedon Sep 2, 2026
Closing: fixed in #9568 (child-process null-byte validation on the require() path), landed via merge train #9572 on 2026-09-02.