# REPRO-2026-00254: Vibe-Trading DNS rebinding authentication bypass leading to RCE ## Summary Status: published Severity: high Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00254 CVE: CVE-2026-58169 ## Package Name: Unknown Ecosystem: Unknown Affected: Unknown Fixed: Unknown ## Root Cause # RCA Report — CVE-2026-58169 ## Summary Vibe-Trading before v0.1.10 contains a DNS rebinding authentication bypass vulnerability (CWE-346 Origin Validation Error). The FastAPI API server (`agent/api_server.py`) binds to `0.0.0.0` by default and treats any TCP connection from a loopback peer address (`127.0.0.1`, `::1`, `localhost`) as a trusted local/dev-mode caller, bypassing `API_AUTH_KEY` bearer-token authentication entirely. Because the server performs **no Host header validation** on loopback-trusted requests, an attacker can use DNS rebinding to make a victim's browser issue same-origin JSON requests to the local API with an attacker-controlled `Host` header and no bearer token. The loopback peer IP causes `_is_local_client()` to return `True`, so `_validate_api_auth()` skips authentication. Furthermore, `_shell_tools_enabled_for_request()` returns `True` for loopback clients, enabling the `bash` tool in the agent's tool registry. The attacker can then send a message to a session that triggers the `BashTool`, which executes `subprocess.run(command, shell=True)` — achieving remote code execution as the API process user. The attacker can also overwrite LLM and data-source settings to exfiltrate credentials. ## Impact - **Package/component affected:** `HKUDS/Vibe-Trading` — `agent/api_server.py` (loopback trust/auth boundary, Host-header validation), `agent/src/tools/bash_tool.py` (BashTool RCE primitive), `agent/src/tools/__init__.py` (shell tool gating) - **Affected versions:** Vibe-Trading < 0.1.10 (confirmed on v0.1.9, commit `8cebccb9b301f962d11bf0fa8c6be9bf7d7e12f2`) - **Fixed version:** v0.1.10 (commit `54f944743efee7b9cbaaeafb5966c583d0d4ac66`), via PR #242 (`6e06017`) — adds `_reject_untrusted_loopback_host` middleware - **Risk level:** High (CVSS 7.7 / 8.1) - **Consequences:** Unauthenticated remote code execution as the API process user; unauthorized access to privileged control-plane endpoints (`/live/runner/start`, `/swarm/runs`, `/sessions/{id}/messages`); credential exfiltration via settings overwrite (`/settings/llm`, `/settings/data-sources`) ## Impact Parity - **Disclosed/claimed maximum impact:** DNS rebinding authentication bypass leading to remote code execution (code_execution) and credential exfiltration - **Reproduced impact from this run:** Full chain demonstrated — DNS rebinding → auth bypass → shell tools enabled → BashTool executes arbitrary shell command as API process user (`uid=1000(vscode)`). The proof file `/tmp/vibe_trading_rce_proof_*.txt` was created with `RCE_CONFIRMED`. The agent trace shows `tool_call: bash` with `exit_code: 0` and `stdout: "uid=1000(vscode)..."`. - **Parity:** `full` - **Not demonstrated:** The reproduction used a mock LLM server (simulating an attacker-controlled LLM endpoint) to instruct the agent to invoke the bash tool. In a real attack, the attacker would either poison the LLM settings (via the auth-bypassed `/settings/llm` PUT) to point the agent at an attacker-controlled LLM, or craft a user message that persuades the agent to run a shell command. The code-execution primitive (BashTool with `subprocess.run(shell=True)`) is the same either way. ## Root Cause The root cause is a **loopback peer IP trust without Host header validation**: 1. `_is_local_client(request)` (line 668) checks only `request.client.host` (the TCP peer address). If it is `127.0.0.1`, `::1`, `localhost`, or a trusted Docker gateway, it returns `True`. 2. `_validate_api_auth()` (line 645): when `API_AUTH_KEY` is not configured (dev mode), a loopback client is allowed without any token. When `API_AUTH_KEY` IS configured, the loopback shortcut is not used — but in dev mode (the default), the bypass is complete. 3. `_shell_tools_enabled_for_request(request)` (line 727): returns `_is_local_client(request) or _env_shell_tools_enabled()`. For loopback clients, this is `True`, enabling the `bash` and `background_run` tools in the agent registry. 4. **No Host header validation existed** — a loopback request carrying `Host: attacker.example:8899` was trusted as local. DNS rebinding makes a browser send exactly such a request: the attacker's domain resolves to `127.0.0.1`, the browser connects from loopback, and sends `Host: attacker.example:8899` (the original hostname) with no bearer token. 5. The `BashTool.execute()` (line 38 of `bash_tool.py`) calls `subprocess.run(command, shell=True, cwd=cwd, ...)` — arbitrary command execution. **Fix (PR #242, commit `6e06017`):** Added `_reject_untrusted_loopback_host` middleware that rejects loopback requests carrying an untrusted `Host` header with HTTP 403 ("Untrusted local API host") before route handlers are reached. Accepted loopback Host values: `localhost`, `127.0.0.1`, `::1`, `[::1]`, `testserver`, plus any configured via `API_ALLOWED_HOSTS`. Remote clients remain governed by bearer-token auth. Normal loopback dev-mode requests with `Host: 127.0.0.1:port` continue to work (no regression). Related fixes in the same v0.1.10 cycle: - PR #243 (`7eb8dea`): require explicit opt-in (`VIBE_TRADING_ENABLE_SHELL_TOOLS`) for agent shell tools — closes the BashTool exposure even if the Host gate is bypassed - PR #245 (`b608fc5`): require bearer token for settings WRITE endpoints — closes the credential exfiltration path ## Reproduction Steps 1. **Script:** `bundle/repro/reproduction_steps.sh` — self-contained, run with `PRUVA_ROOT= bash bundle/repro/reproduction_steps.sh` 2. **What the script does:** - Installs Python dependencies (fastapi, uvicorn, langchain, dnslib, etc.) - Clones/reuses the Vibe-Trading repo from the project cache - Starts a DNS rebinding server (`dns_rebind_server.py`) that resolves `rebind.attacker.test` to `127.0.0.1` - Proves the DNS rebinding with a real DNS A-record query → `127.0.0.1` - Starts a mock OpenAI-compatible LLM server (`mock_llm_server.py`) that returns a `bash` tool-call instructing the agent to execute `id; whoami; echo RCE_CONFIRMED > /tmp/...` - **Phase 1 (vulnerable v0.1.9):** Starts the real Vibe-Trading API server (bound to `0.0.0.0:8899`, dev mode, mock LLM). Uses `curl --resolve rebind.attacker.test:8899:127.0.0.1` to send DNS-rebound HTTP requests with no bearer token: - `POST /sessions` → HTTP 201 (auth bypassed, session created) - `POST /sessions/{id}/messages` → triggers the agent loop → BashTool executes the attacker's command → RCE proof file created - `PUT /settings/llm` → auth-bypassed settings write (credential exfiltration path) - **Phase 2 (fixed v0.1.10):** Same DNS-rebound requests → HTTP 403 ("Untrusted local API host"). Normal loopback with `Host: 127.0.0.1:8899` → HTTP 201 (no regression). - Writes `validation_verdict.json` and `runtime_manifest.json` 3. **Expected evidence:** - `logs/dns_rebind_proof.txt`: `DNS_A:rebind.attacker.test:127.0.0.1` - `logs/vuln_create_session.txt`: HTTP 201 with session_id (no auth) - `logs/vuln_rce_proof.txt`: `RCE_CONFIRMED` - `logs/vuln_trace.jsonl`: `tool_call: bash` with `exit_code: 0`, `stdout: "uid=1000(vscode)..."` - `logs/fixed_create_session.txt`: HTTP 403 `{"detail":"Untrusted local API host"}` - `logs/fixed_normal_loopback.txt`: HTTP 201 (no regression) ## Evidence All artifacts are under `bundle/logs/` and `bundle/repro/`: | Artifact | Description | |---|---| | `logs/reproduction_steps.log` | Full script execution log | | `logs/dns_rebind_proof.txt` | DNS A-record query proving `rebind.attacker.test → 127.0.0.1` | | `logs/vuln_create_session.txt` | Vulnerable: POST /sessions → HTTP 201 (auth bypass) | | `logs/vuln_send_message.txt` | Vulnerable: POST /sessions/{id}/messages → HTTP 200 | | `logs/vuln_rce_proof.txt` | RCE proof file: `RCE_CONFIRMED` | | `logs/vuln_trace.jsonl` | Agent trace showing `tool_call: bash`, `tool_result: exit_code 0, uid=1000(vscode)` | | `logs/vuln_session_messages.json` | Session message history (user + assistant reply) | | `logs/vuln_settings_write.txt` | Vulnerable: PUT /settings/llm → HTTP 422 (auth bypassed, validation error) | | `logs/fixed_create_session.txt` | Fixed: POST /sessions → HTTP 403 "Untrusted local API host" | | `logs/fixed_normal_loopback.txt` | Fixed: normal loopback → HTTP 201 (no regression) | | `logs/fixed_settings_write.txt` | Fixed: PUT /settings/llm → HTTP 403 | | `logs/api_server_vulnerable.log` | Vulnerable API server log | | `logs/api_server_fixed.log` | Fixed API server log | | `logs/mock_llm_server.log` | Mock LLM server log | | `logs/dns_rebind_server.log` | DNS rebinding server log | **Key trace excerpt (vuln_trace.jsonl):** ```json {"type": "tool_call", "iter": 1, "tool": "bash", "args": {"command": "id; whoami; echo RCE_CONFIRMED > /tmp/vibe_trading_rce_proof_49561.txt", "run_dir": "..."}} {"type": "tool_result", "iter": 1, "tool": "bash", "status": "ok", "elapsed_ms": 4, "preview": "{\"status\": \"ok\", \"exit_code\": 0, \"stdout\": \"uid=1000(vscode) gid=1000(vscode) groups=1000(vscode),969(969)\\nvscode\\n\", \"stderr\": \"id: cannot find name for group ID 969\\n\"}"} ``` **Vulnerable vs fixed comparison:** | Request | v0.1.9 (vulnerable) | v0.1.10 (fixed) | |---|---|---| | `POST /sessions` (DNS-rebound, no auth) | **201** (auth bypassed) | **403** ("Untrusted local API host") | | `POST /sessions/{id}/messages` (DNS-rebound) | **200** → BashTool RCE | **403** | | `PUT /settings/llm` (DNS-rebound) | **422** (auth bypassed) | **403** | | `POST /sessions` (normal loopback `Host: 127.0.0.1`) | 201 | **201** (no regression) | **Environment:** - Python 3.14.4, FastAPI + uvicorn, langchain-openai (ChatOpenAI) - Vibe-Trading v0.1.9 (vulnerable) / v0.1.10 (fixed) - API server bound to `0.0.0.0:8899` (default), dev mode (no `API_AUTH_KEY`) - DNS rebinding server: `dnslib` UDP server on `127.0.0.1:5353` - Mock LLM: `http.server` on `127.0.0.1:9099` returning OpenAI-compatible tool-call responses ## Recommendations / Next Steps 1. **Upgrade to v0.1.10 or later** — the `_reject_untrusted_loopback_host` middleware (PR #242) blocks DNS-rebinding Host headers on loopback peers with HTTP 403. 2. **Set `API_AUTH_KEY`** in any deployment exposed beyond a single-user localhost — this forces bearer-token auth even for loopback clients when configured, removing the dev-mode bypass. 3. **Set `VIBE_TRADING_ENABLE_SHELL_TOOLS=1` only when explicitly needed** — PR #243 requires explicit opt-in for the BashTool; without it, the RCE primitive is unavailable even if auth is bypassed. 4. **Bind to `127.0.0.1` instead of `0.0.0.0`** when the API is not meant to be network-accessible. 5. **Configure `API_ALLOWED_HOSTS`** if fronting the local API with a specific loopback hostname. 6. **Testing:** run the security regression tests: `PYTHONPATH=agent python -m pytest agent/tests/test_security_auth_api.py -q` ## Additional Notes - **Idempotency:** The script was run twice consecutively; both runs produced identical results (HTTP 201 + RCE on vulnerable, HTTP 403 on fixed). The script cleans up all background processes on exit and uses a PID-suffixed proof file to avoid collisions. - **Mock LLM justification:** The reproduction uses a mock OpenAI-compatible LLM server that returns a `bash` tool-call. This simulates the attack scenario where the attacker either (a) poisons the LLM settings via the auth-bypassed `/settings/llm` PUT to point at an attacker-controlled LLM, or (b) sends a user message that the agent's LLM responds to with a bash tool-call. The BashTool's `subprocess.run(shell=True)` is the actual RCE primitive in both cases — the mock LLM merely drives the agent loop to reach it. - **The `PUT /settings/llm` returned 422** (not 200) on the vulnerable version because the request body did not match the full Pydantic model schema, but the important point is that the request **passed authentication** (no 401/403) — the auth bypass is confirmed. On the fixed version, the same request returns 403 at the Host-gate middleware before reaching the route handler. - **CORS does not defend against DNS rebinding** — after rebinding, the attacker's hostname becomes the browser's same origin, so credentialed CORS requests are sent without cross-origin restrictions. ## Reproduction Details Reproduced: 2026-07-06T09:04:26.589Z Duration: 1088 seconds Tool calls: 239 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00254 pruva-verify CVE-2026-58169 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00254&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00254/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-58169 - Source: HKUDS/Vibe-Trading ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 16048 bytes) - bundle/repro/rca_report.md (analysis, 12258 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 14744 bytes) - bundle/vuln_variant/rca_report.md (analysis, 14671 bytes) - bundle/artifact_promotion_manifest.json (other, 18334 bytes) - bundle/vuln_variant/source_identity.json (other, 1581 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 3045 bytes) - bundle/logs/vuln_rce_proof.txt (other, 14 bytes) - bundle/logs/vuln_trace.jsonl (other, 1015 bytes) - bundle/logs/dns_rebind_proof.txt (other, 37 bytes) - bundle/repro/validation_verdict.json (other, 1022 bytes) - bundle/repro/runtime_manifest.json (other, 1229 bytes) - bundle/logs/fixed_create_session.txt (other, 47 bytes) - bundle/logs/vuln_create_session.txt (other, 175 bytes) - bundle/logs/reproduction_steps.log (log, 3158 bytes) - bundle/logs/vuln_send_message.txt (other, 67 bytes) - bundle/logs/vuln_session_messages.json (other, 500 bytes) - bundle/logs/vuln_settings_write.txt (other, 159 bytes) - bundle/logs/fixed_normal_loopback.txt (other, 175 bytes) - bundle/logs/fixed_settings_write.txt (other, 47 bytes) - bundle/logs/api_server_vulnerable.log (log, 1121 bytes) - bundle/logs/api_server_fixed.log (log, 803 bytes) - bundle/logs/mock_llm_server.log (log, 119 bytes) - bundle/logs/dns_rebind_server.log (log, 83 bytes) - bundle/logs/vuln_variant/fixed_optedin_ts_rce_proof.txt (other, 14 bytes) - bundle/logs/vuln_variant/fixed_default_host_testserver.txt (other, 191 bytes) - bundle/logs/vuln_variant/fixed_default_host_rebind.attacker.test.txt (other, 73 bytes) - bundle/logs/vuln_variant/variant_steps.log (log, 2962 bytes) - bundle/vuln_variant/variant_manifest.json (other, 4548 bytes) - bundle/vuln_variant/validation_verdict.json (other, 3676 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 11475 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 3724 bytes) - bundle/logs/vuln_variant/vuln_host_rebind.attacker.test.txt (other, 201 bytes) - bundle/logs/vuln_variant/vuln_host_testserver.txt (other, 191 bytes) - bundle/logs/vuln_variant/vuln_host_localhost.txt (other, 190 bytes) - bundle/logs/vuln_variant/vuln_rce_proof.txt (other, 14 bytes) - bundle/logs/vuln_variant/fixed_default_host_localhost.txt (other, 190 bytes) - bundle/logs/vuln_variant/fixed_default_ts_msg.txt (other, 74 bytes) - bundle/logs/vuln_variant/fixed_optedin_host_rebind.attacker.test.txt (other, 73 bytes) - bundle/logs/vuln_variant/fixed_optedin_host_testserver.txt (other, 191 bytes) - bundle/logs/vuln_variant/fixed_optedin_ts_msg.txt (other, 74 bytes) - bundle/logs/vuln_variant/fixed_optedin_ts_state.json (other, 25 bytes) - bundle/logs/vuln_variant/fixed_optedin_ts_req.json (other, 88 bytes) - bundle/logs/vuln_variant/api_server_vuln.log (log, 1121 bytes) - bundle/logs/vuln_variant/api_server_fixed-default.log (log, 2532 bytes) - bundle/logs/vuln_variant/api_server_fixed-optedin.log (log, 2532 bytes) - bundle/logs/vuln_variant/mock_llm_server.log (log, 0 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00254 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00254/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00254 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev