Commit Graph
3 Commits
Author SHA1 Message Date
psighoandClaude Opus 4.8 bf50be9971 harden(local-agents): close cmd.exe prompt-injection + POSIX detect fallback
Addresses review feedback on the Windows detection PR.

1. Command injection (the important one). spawnAgent ran .cmd/.bat shims via a
   hand-rolled `spawn('cmd.exe', ['/d','/s','/c', shim, ...args])`. cmd.exe
   re-parses its tail with its own rules and ignores `\"`-escaping, so an
   adversarial prompt (claude `-p <prompt>`, codex `exec <prompt>`) containing
   `"` + `& | > %VAR%` broke out and executed — the BatBadBut / CVE-2024-1874
   class. Verified with a `x" & echo PWNED` payload: it ran.
   Fix: use Node's own safe `.cmd` handling — `spawn(shim, args, {shell:true})`.
   On Node >=18.20.2 / >=20.12.2 / >=21 (CVE-2024-27980) Node escapes each arg
   for cmd, so the whole prompt lands as ONE literal argument and cannot break
   out (verified on Node 24: the same payload arrived intact in %1). .exe/POSIX
   still spawn directly with shell:false.

2. POSIX detection regression. Detection gated on resolveBin() returning a
   value; on a bash-less/busybox image where both `command -v` and `which` are
   absent, an installed agent flipped to "not installed". Fix: on POSIX fall
   back to the bare name (`resolveBin(bin) || (isWindows ? undefined : bin)`) —
   the OS resolves PATH at exec time and the ENOENT branch still catches
   genuinely-absent binaries. Windows keeps resolveBin authoritative.

Typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 05:03:42 +05:00
psighoandClaude Opus 4.8 a836e61adb fix(local-agents): don't 500 /detect when a version-probe spawn is denied
detectOne() ran the `--version` probe via execFile INSIDE a `new Promise`
executor but only handled the async error path. execFile can throw
SYNCHRONOUSLY (e.g. `spawn EPERM` when child-process creation is restricted),
and a synchronous throw in a Promise executor REJECTS the promise. Because
detectLocalAgents() used Promise.all, one agent's spawn failure rejected the
whole batch → GET /api/agents/local/detect returned 500 → the Settings "Local
Agents" card silently rendered "0/0 installed" even though all CLIs were on PATH.

- detectOne: wrap execFile in try/catch. The binary was already found on PATH
  (resolveBin returned a path) so it IS installed; on a sync throw we resolve
  installed:true with version '?' and filesystem-based auth, never rejecting.
- detectLocalAgents: Promise.allSettled instead of Promise.all, so one agent's
  failure degrades to "not installed" for that agent only, never the batch.

Verified: /detect returns 200 with claude/codex/hermes installed+ready.
Typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 03:22:46 +05:00
psigho 6ba02ec50d fix(local-agents): Windows CLI detection + Hermes auth path
Detection reported installed npm-global agents (Claude Code, Codex) as
'binary not found on PATH' on Windows, and an authed Hermes desktop as
'NOT AUTHED'. Two root causes:

1. resolvePath() used POSIX-only 'command -v'/'which', and
   execFile/spawn('claude', ...) cannot resolve npm's .cmd shims via
   PATHEXT -> ENOENT -> installed:false. Add resolveBin() (where.exe on
   Windows, preferring .exe > .cmd > .bat) and route detection + both
   spawn call sites through the resolved path.

2. Hermes desktop on Windows stores its login under %LOCALAPPDATA%\hermes   not ~/.hermes/. Add localAppData() and check both (presence-only).

Windows .cmd shims are launched via a new spawnAgent() helper that invokes
cmd.exe with an argv ARRAY (shell:false) instead of shell:true, so the
untrusted prompt is never re-parsed by cmd.exe -> no command-injection surface.

No new deps; typecheck clean.
2026-07-10 03:22:46 +05:00