Repository navigation
Proxy forwards auth headers to arbitrary upstream URLs, enabling SSRF and credential theft via malicious config routes #22
Description
Summary
subprocess.Popen(["Codex.exe"], shell=True) is used as a fallback when LOCALAPPDATA resolution fails on Windows. Using shell=True with a bare executable name performs a shell-expanded PATH search, which is exploitable via PATH hijacking.
Evidence
codex_shim/server.py, _restart_codex_app() / _do_restart():
local_appdata = _os.environ.get("LOCALAPPDATA", "")
codex_exe = Path(local_appdata) / "Programs" / "Codex" / "Codex.exe"
if codex_exe.exists():
_subprocess.Popen([str(codex_exe)])
else:
_subprocess.Popen(["Codex.exe"], shell=True)When LOCALAPPDATA is empty (e.g., unset by a process manager, a container, or a deliberately crafted environment), codex_exe.exists() returns False, and the fallback Popen(["Codex.exe"], shell=True) resolves Codex.exe via the PATH. On Windows, shell=True invokes cmd.exe /C Codex.exe, which searches PATH in order — any directory earlier in PATH that contains a file named Codex.exe will be executed instead of the real Codex.
Why this matters
The /api/switch endpoint (authenticated with a picker token) accepts a "restart_codex": true field and calls _restart_codex_app(). If an attacker has already compromised PATH ordering (common in shared user environments, or via a malicious npm package), they can get the shim to execute an arbitrary binary named Codex.exe the next time any authenticated picker user switches models with restart enabled. The triggered binary runs in the context of the user who started the shim.
Attack or failure scenario
- Attacker plants a trojanized
Codex.exein a directory that appears early in the victim's%PATH%(e.g., a writablenode_modules/.binor a localscripts/folder). - Victim switches model via the picker UI with "Auto-restart Codex" checked.
- Shim calls
_restart_codex_app(). IfLOCALAPPDATAis empty or the resolved path does not exist, the fallbackPopen(["Codex.exe"], shell=True)executes the malicious binary.
This is a persistence/escalation step in a broader attack; it requires a separate PATH-hijack primitive but is entirely realistic in developer environments with many npm/pip tools on PATH.
Root cause
shell=True with an unqualified executable name is a well-known Windows PATH hijacking vector. The intent was a graceful fallback, but it trades correctness for safety. If the known path doesn't exist, the right failure mode is logging an error and returning, not executing an unresolved binary.
Recommended fix
Remove the shell=True fallback entirely and fail explicitly:
if codex_exe.exists():
_subprocess.Popen([str(codex_exe)])
else:
# Cannot locate Codex.exe; skip restart silently rather than PATH-search
import sys as _sys
print("[restart] LOCALAPPDATA not set or Codex.exe not found; skipping restart", file=_sys.stderr)
returnIf LOCALAPPDATA is unset, log the condition rather than falling back to a PATH search.
Acceptance criteria
- No
shell=Trueremains in_restart_codex_appor_do_restart. - If
codex_exedoes not exist, the function logs a warning and returns without spawning a subprocess. - A test verifies that when the resolved path does not exist, no subprocess is spawned.
Suggested labels
security, bug
Priority
P2
Severity
Medium — requires a prior PATH-hijack primitive; not directly exploitable remotely, but the fallback is unambiguously unsafe and should not exist.
Confidence
Confirmed — shell=True with unqualified name is present in the source.
Activity
Merge pull request #28 from OnlyTerp/devin/1781763007-fix-open-issues
devin-ai-integration commentedon Jun 18, 2026
Fixed in #28 (merged): inbound Anthropic credentials (authorization, x-api-key) are now stripped before dispatch to a non-default upstream unless the route explicitly opts in with auth:"passthrough". Also fixed a latent bug where slot construction dropped the passthrough intent, so the opt-in could never re-enable forwarding.