Automated daily security review (fan-out audit + adversarial verification). Coverage: command/template/path injection, unsafe exec/eval/deserialization, authN/access-control, CORS/Origin, SSRF, hardcoded secrets, XSS, shell scripts, and dependency risk.
Overall posture is strong. No Critical/High issues. No hardcoded secrets, no command injection (all subprocess calls are argv-based / shlex.quote'd, no shell=True/os.system/eval/pickle), no XSS sinks (no dangerouslySetInnerHTML), SSRF mint host is pinned, and all dependencies are exact-pinned with no known critical CVEs. The 3 verified findings below are Medium/Low.
Medium
M1 — No Host-header validation → DNS-rebinding can reach the command-executing API in the default tokenless mode
- Files:
backend/main.py:199-214 (_access_ok / require_auth), frontend/lib/proxyAuth.ts:49-69 (isCrossSiteRequest), backend/config.py:230-237 (broad default Origin regex). No TrustedHostMiddleware / Host allowlist exists anywhere in the backend.
- Description: In the default
yapcode up mode there is no token; the only protections on /api/tools/execute (which the backend turns into real command execution) are (a) the proxy's same-origin/CSRF check and (b) the backend trusting loopback clients. Neither pins the expected Host. A DNS-rebinding attacker lures the victim to http://evil.com, then rebinds evil.com → 127.0.0.1. The follow-up fetch("http://evil.com:3000/api/tools/execute") is now same-origin to the browser → Sec-Fetch-Site: same-origin and Origin.host === Host both pass isCrossSiteRequest. The Next proxy strips Origin (documented at proxyAuth.ts:11-16), so the backend sees a loopback client with no Origin and trusts it (_access_ok, main.py:199-201; the Origin check at main.py:212-214 only fires when an Origin is present). Full exploit chain → command execution. The broad default Origin regex (config.py:230-237, any localhost/private-LAN host on any port) similarly lets any co-resident localhost web app drive the proxy in tokenless mode.
- Severity: Medium (requires victim to open an attacker page + a rebinding setup; only affects the tokenless default. Token/network mode is unaffected since the token gates it).
- Fix (manual / needs design care): Add a Host-header allowlist — Starlette
TrustedHostMiddleware on the backend (default localhost, 127.0.0.1, [::1], plus any configured LAN host) and/or a Host check inside blockCrossSite(). This is the standard DNS-rebinding mitigation and complements the existing CSRF logic. Care needed so legitimate LAN access in network mode (192.168.x.x / hostname) is still permitted — recommend a VC_ALLOWED_HOSTS env knob.
Low
L1 — tool_use_id used as a filesystem path component without validation (defense-in-depth gap)
- Files:
backend/tmux_hooks/_common.py:40-41 (decision_path), backend/tmux_hooks/hook_pretool.py:86,96 (read + os.remove), backend/tmux_runner.py:674-679 (os.replace write side).
- Description:
tool_use_id is interpolated directly into decisions/<id>.json and reaches open(), os.remove(), and os.replace() with no sanitization — unlike every other externally-derived path component in the codebase (session_id/handle are validated via validate_session_id/_SESSION_ID_RE). A traversal value (e.g. ../../...) would let the hook delete/write files outside decisions/. The value is currently CLI-minted (toolu_*), not directly attacker- or model-controlled, so practical exploitability is low — but it is the one path segment that skips the validation applied consistently elsewhere.
- Severity: Low.
- Fix: Validate
tool_use_id as a safe single path component (reuse the existing regex, or os.path.basename + reject if it changes) in both decision_path() and the runner's write side.
L2 — --reload enabled in the network-exposed run script
- File:
backend/run-network.sh:47-52.
- Description:
run-network.sh binds 0.0.0.0 and runs uvicorn with --reload --reload-dir .. Running the autoreloader on a network-exposed service enlarges the attack surface (file-watching + child-process machinery; any writable-code scenario becomes RCE-on-write). Access is still gated by the mandatory VC_AUTH_TOKEN, so this is hardening, not an exploitable hole.
- Severity: Low.
- Fix: Drop
--reload/--reload-dir from run-network.sh; keep them only in the localhost run.sh.
Generated by the automated daily security review. Verified findings only; speculative/low-signal items were discarded during the verification pass.
Automated daily security review (fan-out audit + adversarial verification). Coverage: command/template/path injection, unsafe exec/eval/deserialization, authN/access-control, CORS/Origin, SSRF, hardcoded secrets, XSS, shell scripts, and dependency risk.
Overall posture is strong. No Critical/High issues. No hardcoded secrets, no command injection (all subprocess calls are argv-based /
shlex.quote'd, noshell=True/os.system/eval/pickle), no XSS sinks (nodangerouslySetInnerHTML), SSRF mint host is pinned, and all dependencies are exact-pinned with no known critical CVEs. The 3 verified findings below are Medium/Low.Medium
M1 — No Host-header validation → DNS-rebinding can reach the command-executing API in the default tokenless mode
backend/main.py:199-214(_access_ok/require_auth),frontend/lib/proxyAuth.ts:49-69(isCrossSiteRequest),backend/config.py:230-237(broad default Origin regex). NoTrustedHostMiddleware/ Host allowlist exists anywhere in the backend.yapcode upmode there is no token; the only protections on/api/tools/execute(which the backend turns into real command execution) are (a) the proxy's same-origin/CSRF check and (b) the backend trusting loopback clients. Neither pins the expectedHost. A DNS-rebinding attacker lures the victim tohttp://evil.com, then rebindsevil.com → 127.0.0.1. The follow-upfetch("http://evil.com:3000/api/tools/execute")is now same-origin to the browser →Sec-Fetch-Site: same-originandOrigin.host === Hostboth passisCrossSiteRequest. The Next proxy stripsOrigin(documented atproxyAuth.ts:11-16), so the backend sees a loopback client with no Origin and trusts it (_access_ok,main.py:199-201; the Origin check atmain.py:212-214only fires when an Origin is present). Full exploit chain → command execution. The broad default Origin regex (config.py:230-237, anylocalhost/private-LAN host on any port) similarly lets any co-resident localhost web app drive the proxy in tokenless mode.TrustedHostMiddlewareon the backend (defaultlocalhost,127.0.0.1,[::1], plus any configured LAN host) and/or aHostcheck insideblockCrossSite(). This is the standard DNS-rebinding mitigation and complements the existing CSRF logic. Care needed so legitimate LAN access in network mode (192.168.x.x/ hostname) is still permitted — recommend aVC_ALLOWED_HOSTSenv knob.Low
L1 —
tool_use_idused as a filesystem path component without validation (defense-in-depth gap)backend/tmux_hooks/_common.py:40-41(decision_path),backend/tmux_hooks/hook_pretool.py:86,96(read +os.remove),backend/tmux_runner.py:674-679(os.replacewrite side).tool_use_idis interpolated directly intodecisions/<id>.jsonand reachesopen(),os.remove(), andos.replace()with no sanitization — unlike every other externally-derived path component in the codebase (session_id/handleare validated viavalidate_session_id/_SESSION_ID_RE). A traversal value (e.g.../../...) would let the hook delete/write files outsidedecisions/. The value is currently CLI-minted (toolu_*), not directly attacker- or model-controlled, so practical exploitability is low — but it is the one path segment that skips the validation applied consistently elsewhere.tool_use_idas a safe single path component (reuse the existing regex, oros.path.basename+ reject if it changes) in bothdecision_path()and the runner's write side.L2 —
--reloadenabled in the network-exposed run scriptbackend/run-network.sh:47-52.run-network.shbinds0.0.0.0and runs uvicorn with--reload --reload-dir .. Running the autoreloader on a network-exposed service enlarges the attack surface (file-watching + child-process machinery; any writable-code scenario becomes RCE-on-write). Access is still gated by the mandatoryVC_AUTH_TOKEN, so this is hardening, not an exploitable hole.--reload/--reload-dirfromrun-network.sh; keep them only in the localhostrun.sh.Generated by the automated daily security review. Verified findings only; speculative/low-signal items were discarded during the verification pass.