Reproduction steps:
Arm the gate for a module that declares subprocess:false: module_sandbox.install_restrictions(ns, {"subprocess": False}, slug). (repro: repro_module_sandbox_startfile_spawn.py)
Inspect the gated os proxy (ns["os"]): os.system -> _shim (BLOCKED) os.popen -> _shim (BLOCKED) os.execv -> _shim (BLOCKED) os.spawnv -> _shim (BLOCKED) os.startfile -> REAL (startfile) # not shimmed
Live, inside the gated namespace: os.system("echo x") -> SandboxViolation (blocked) os.startfile("calc.exe") -> spawns Calculator, NO SandboxViolation
Expected:
subprocess:false blocks every process-spawning primitive — the intent behind
refusing os.system/popen/exec/spawn/posix_spawn.
Actual:
_install_subprocess_gate (module_sandbox.py:485-494) refuses os.system,
os.popen, the eight os.exec, the eight os.spawn, and os.posix_spawn — but
never os.startfile. On Windows os.startfile ShellExecute-launches its argument,
i.e. it spawns a process (any .exe/.bat/.hta, or a protocol handler). So a
module that declares subprocess:false — and whose users therefore believe it
cannot shell out — can spawn arbitrary processes on Windows through
os.startfile, entirely outside the gate. Same class as the subprocess-namespace
hole; os.startfile is the missed primitive.
Scope (honest):
os.startfile is Windows-only (absent on POSIX), so this is the Windows
counterpart to the *nix spawn primitives already refused. Requires an opted-in
subprocess:false module (trust default "any" makes one trivial to load).
Reproduced live on Windows (Calculator launched); the proxy-identity check
above shows startfile is the real function, not a refuse shim.
Suggested fix:
Add "startfile" to the refuse list in _install_subprocess_gate (guard with
hasattr(_real_os, "startfile") since it's Windows-only), alongside
os.system/popen/exec/spawn/posix_spawn.