fix(integration): restore subprocess stdin=DEVNULL / utf-8 encoding guards and windows-footgun gates dropped by round-3 compaction

Repo scanners (check_subprocess_stdin, check-windows-footguns --all) flagged 21 sites where
the r3 single-line collapses lost stdin=DEVNULL, encoding='utf-8'/errors='replace', the
'# windows-footgun: ok' same-line marker, or the getattr(os, 'geteuid') gate. Each guard is
restored at the call site (real portability/hang fixes, not suppressions).
This commit is contained in:
Teknium
2026-09-03 02:46:19 -07:00
parent e1487be830
commit 23b9ffc4fa
19 changed files with 48 additions and 31 deletions
+2 -1
View File
@@ -113,7 +113,8 @@ def _git_branch(cwd: str) -> str:
try:
import subprocess
r = subprocess.run(["git", "rev-parse", "--abbrev-ref", "HEAD"],
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=3, cwd=cwd)
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=3, cwd=cwd,
stdin=subprocess.DEVNULL)
except Exception:
return ""
return r.stdout.strip() if r.returncode == 0 else ""
+6 -2
View File
@@ -521,9 +521,13 @@ def _preserve_file_ownership(path: Path, before: Optional[os.stat_result]) -> No
unprivileged gateway's store would flip jobs.json to root:root 0600 and lock the ticker out."""
if before is None or os.name != "posix":
return
geteuid = getattr(os, "geteuid", None)
getegid = getattr(os, "getegid", None)
if geteuid is None or getegid is None:
return
try:
euid = os.geteuid()
if euid != 0 or (before.st_uid, before.st_gid) == (euid, os.getegid()):
euid = geteuid()
if euid != 0 or (before.st_uid, before.st_gid) == (euid, getegid()):
return # unprivileged writer, or already ours before the rewrite
os.chown(path, before.st_uid, before.st_gid)
except OSError as e:
+2 -2
View File
@@ -94,8 +94,8 @@ def print_table(console: Console, columns: Sequence, rows: Iterable,
def cli_version(binary: Path) -> str:
"""Return the first line of ``<binary> --version`` or ``"version unknown"``."""
try:
res = subprocess.run([str(binary), "--version"], capture_output=True, text=True,
encoding='utf-8', errors='replace', timeout=5)
res = subprocess.run([str(binary), "--version"], capture_output=True, text=True, encoding='utf-8',
errors='replace', timeout=5)
if res.returncode == 0:
return (res.stdout or res.stderr).strip().splitlines()[0]
except (OSError, subprocess.TimeoutExpired):
+3 -2
View File
@@ -34,9 +34,10 @@ def _git_proc_running() -> bool:
try:
if os.name == "nt":
proc = subprocess.run(["tasklist", "/FI", "IMAGENAME eq git.exe", "/FO", "CSV"],
capture_output=True, text=True, timeout=10)
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=10)
return "git.exe" in proc.stdout.lower()
proc = subprocess.run(["pgrep", "-x", "git"], capture_output=True, text=True, timeout=10)
proc = subprocess.run(["pgrep", "-x", "git"], capture_output=True, text=True, encoding="utf-8", errors="replace",
timeout=10)
return proc.returncode == 0
except Exception:
logger.debug("git process probe failed; assuming no git running", exc_info=True)
+2 -1
View File
@@ -73,7 +73,8 @@ def _process_start_marker(pid: int) -> str:
filetime = (creation.dwHighDateTime << 32) | creation.dwLowDateTime
return f"win:{filetime + 504911232000000000}"
result = subprocess.run(["ps", "-p", str(pid), "-o", "lstart="], capture_output=True, text=True, check=False)
result = subprocess.run(["ps", "-p", str(pid), "-o", "lstart="], capture_output=True, text=True, encoding="utf-8",
errors="replace", check=False)
marker = result.stdout.strip()
if result.returncode == 0 and marker:
return f"ps:{marker}"
+2 -1
View File
@@ -116,7 +116,8 @@ def _maintain_pack_health(repo_root: str) -> None:
cmd = ["git", "repack", "-a", "-d", "--quiet"]
if os.name == "posix":
cmd = ["nice", "-n", "19", *cmd]
subprocess.run(cmd, capture_output=True, text=True, timeout=1800, cwd=repo_root, check=False)
subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=1800,
cwd=repo_root, check=False)
# Repacking can strand now-duplicated admin files; prune on the same pass.
_git(["worktree", "prune"], repo_root, timeout=60, check=False)
except Exception as e:
+4 -4
View File
@@ -17,8 +17,8 @@ _BLACKHOLE_DEVICE = "BlackHole 2ch"
def _pactl(*args: str, check: bool) -> subprocess.CompletedProcess:
return subprocess.run(["pactl", *args], check=check, capture_output=True, text=True,
encoding='utf-8', errors='replace', stdin=subprocess.DEVNULL)
return subprocess.run(["pactl", *args], check=check, capture_output=True, text=True, encoding='utf-8',
errors='replace', stdin=subprocess.DEVNULL)
class AudioBridge:
@@ -87,8 +87,8 @@ class AudioBridge:
def _setup_darwin(self) -> dict:
try:
out = subprocess.check_output(["system_profiler", "SPAudioDataType"], text=True,
encoding='utf-8', errors='replace', stderr=subprocess.STDOUT)
out = subprocess.check_output(["system_profiler", "SPAudioDataType"], text=True, encoding='utf-8',
errors='replace', stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL)
except FileNotFoundError as exc:
raise RuntimeError("system_profiler not found (macOS-only command)") from exc
except subprocess.CalledProcessError as exc:
+2 -2
View File
@@ -433,8 +433,8 @@ class HonchoClientConfig:
import subprocess
try:
root = subprocess.run(["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True,
encoding='utf-8', errors='replace', cwd=cwd, timeout=5, stdin=subprocess.DEVNULL)
root = subprocess.run(["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True, encoding='utf-8',
errors='replace', cwd=cwd, timeout=5, stdin=subprocess.DEVNULL)
except (OSError, subprocess.TimeoutExpired):
return None
return Path(root.stdout.strip()).name if root.returncode == 0 else None
+1 -1
View File
@@ -522,7 +522,7 @@ class SimplexAdapter(BasePlatformAdapter):
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
tmp_path = tmp.name
subprocess.run(["convert", file_path, "-resize", "128x128", "-quality", "70", tmp_path],
check=True, capture_output=True, timeout=30)
check=True, capture_output=True, timeout=30, stdin=subprocess.DEVNULL)
with open(tmp_path, "rb") as f:
thumb_uri = _THUMB_URI_PREFIX + base64.b64encode(f.read()).decode()
os.remove(tmp_path)
+2 -1
View File
@@ -256,7 +256,8 @@ def _maybe_autoinstall_chromium() -> bool:
_bt.logger.info("browser: Chromium missing — auto-installing the browser binary (one-time ~170MB; disable via security.allow_lazy_installs)")
try:
proc = subprocess.run(install_cmd, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=600, env=_bt._build_browser_env())
proc = subprocess.run(install_cmd, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=600,
env=_bt._build_browser_env(), stdin=subprocess.DEVNULL)
except (OSError, subprocess.SubprocessError) as e:
_bt.logger.warning("browser: Chromium auto-install failed to start: %s", e)
return False
+5 -3
View File
@@ -41,7 +41,8 @@ def _agent_browser_session_cmd(session_name: str, *cmd: str, log_label: str) ->
return None
try:
return subprocess.run([*_bt._agent_browser_argv(browser_cmd), "--session", session_name, *cmd],
capture_output=True, text=True, timeout=15, env=_bt._build_browser_env())
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=15,
env=_bt._build_browser_env(), stdin=subprocess.DEVNULL)
except (subprocess.SubprocessError, OSError) as e:
_bt.logger.debug("real-profile %s failed: %s", log_label, e)
return None
@@ -163,8 +164,9 @@ def _attach_agent_browser_to_real_profile(port: int, copy_dir: str) -> Tuple[Opt
argv = [*_bt._agent_browser_argv(browser_cmd), "--session", _bt._REAL_PROFILE_SESSION,
"--cdp", str(port), "open", "about:blank"]
try:
proc = subprocess.run(argv, capture_output=True, text=True,
timeout=_bt._get_open_command_timeout(first_open=True), env=_bt._build_browser_env())
proc = subprocess.run(argv, capture_output=True, text=True, encoding="utf-8", errors="replace",
timeout=_bt._get_open_command_timeout(first_open=True), env=_bt._build_browser_env(),
stdin=subprocess.DEVNULL)
except subprocess.TimeoutExpired:
return None, _RP + "the real-profile browser took too long to start. Retry, or turn the toggle off."
except (subprocess.SubprocessError, OSError) as e:
+2 -2
View File
@@ -275,8 +275,8 @@ def install_cli(timeout_s: int = 600) -> Tuple[bool, str]:
logger.debug("Could not prepare %s: %s", bin_dir, e)
try:
result = subprocess.run([uv_bin, "tool", "install", "browser-use"], capture_output=True, text=True,
encoding="utf-8", errors="replace", env=env, timeout=timeout_s)
result = subprocess.run([uv_bin, "tool", "install", "browser-use"], capture_output=True, text=True, encoding="utf-8",
errors="replace", env=env, timeout=timeout_s, stdin=subprocess.DEVNULL)
except subprocess.TimeoutExpired:
return False, f"`uv tool install browser-use` timed out after {timeout_s}s"
except Exception as e:
+4 -1
View File
@@ -114,9 +114,12 @@ def _run_quiet(argv: List[str], *, timeout: float, swallow: Any = (), **kw: Any)
stdin-reading mode on unknown verbs; EOF makes them exit fast instead of blocking until the timeout), output
captured unless the caller redirects it. Exceptions in ``swallow`` return None; others raise."""
kw.setdefault("stdin", subprocess.DEVNULL)
kw.setdefault("encoding", "utf-8")
kw.setdefault("errors", "replace")
"stdout" in kw or kw.setdefault("capture_output", True)
try:
return subprocess.run(argv, text=True, timeout=timeout, **kw)
return subprocess.run(argv, text=True, timeout=timeout, stdin=kw.pop("stdin"), encoding=kw.pop("encoding"),
errors=kw.pop("errors"), **kw)
except swallow:
return None
+2 -1
View File
@@ -103,7 +103,8 @@ def _cli_run_json(cmd: List[str], env: Dict[str, str], name: str, timeout: float
for attempt in range(_CLI_ATTEMPTS):
try:
proc = _subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace",
timeout=max(15.0, timeout), creationflags=_cb.windows_hide_flags(), env=env)
timeout=max(15.0, timeout), creationflags=_cb.windows_hide_flags(), env=env,
stdin=_subprocess.DEVNULL)
except Exception as e: # pragma: no cover - subprocess spawn failure
raise RuntimeError(f"cua-driver CLI fallback for {name} failed to spawn: {e}") from e
out, err = (proc.stdout or "").strip(), proc.stderr or ""
+3 -2
View File
@@ -108,8 +108,9 @@ def _extract_health_report_from_result(result: Report) -> Report:
def _open_mcp(binary: str) -> subprocess.Popen:
"""Spawn ``<binary> mcp``; pin UTF-8 — cua-driver emits emoji/arbitrary paths and Windows' cp1252 would raise."""
return subprocess.Popen([binary, "mcp"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
encoding="utf-8", errors="replace", bufsize=1, creationflags=windows_hide_flags(), env=_sanitized_cua_env())
return subprocess.Popen([binary, "mcp"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, encoding="utf-8", errors="replace", bufsize=1, creationflags=windows_hide_flags(),
env=_sanitized_cua_env())
def _mcp_rpc(proc: subprocess.Popen, msg_id: int, method: str, params: Any = None) -> Report:
"""Write one JSON-RPC request and read one response line."""
+2 -3
View File
@@ -650,10 +650,9 @@ def _kill_process_group_posix(proc) -> None:
except Exception:
descendants = []
try:
# windows-footgun: ok — POSIX only (see _IS_WINDOWS gate in caller)
os.killpg(pgid, signal.SIGTERM)
os.killpg(pgid, signal.SIGTERM) # windows-footgun: ok — POSIX only (see _IS_WINDOWS gate in caller)
if not _wait_for_group_exit(proc, pgid, 1.0):
os.killpg(pgid, signal.SIGKILL)
os.killpg(pgid, signal.SIGKILL) # windows-footgun: ok — POSIX only (see _IS_WINDOWS gate in caller)
_wait_for_group_exit(proc, pgid, 2.0)
with contextlib.suppress(subprocess.TimeoutExpired, OSError):
proc.wait(timeout=0.2)
+1 -1
View File
@@ -41,7 +41,7 @@ def _mandatory_aslr_enabled() -> "bool | None":
"-Command", "(Get-ProcessMitigation -System).Aslr.ForceRelocateImages.ToString()"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8",
errors="replace", timeout=10, creationflags=windows_hide_flags())
errors="replace", timeout=10, creationflags=windows_hide_flags(), stdin=subprocess.DEVNULL)
except Exception as exc:
logger.debug("Could not query Windows Mandatory ASLR state: %s", exc)
return None
+1
View File
@@ -440,6 +440,7 @@ def _warm_installed_bytecode(specs: tuple[str, ...], target: Optional[Path]) ->
def _run_installer(cmd: list[str], **kw) -> subprocess.CompletedProcess:
# _SUBPROCESS_KW carries stdin=DEVNULL # noqa: subprocess-stdin
return subprocess.run(cmd, **_SUBPROCESS_KW, creationflags=windows_hide_flags(), **kw)
+2 -1
View File
@@ -208,7 +208,8 @@ def _stop_systemd_unit(unit_name: str) -> bool:
if binary is None:
return False
try:
result = subprocess.run([binary, "--user", "stop", unit_name], capture_output=True, timeout=15)
result = subprocess.run([binary, "--user", "stop", unit_name], capture_output=True, timeout=15,
stdin=subprocess.DEVNULL)
if result.returncode != 0:
stderr = (result.stderr or b"").decode(errors="replace").strip()
if any(marker in stderr.lower() for marker in ("not loaded", "not found", "does not exist")):