Patch Forecast
Amber warning
Patch this week.

GHSA-v2f8-6655-7grj

Vibe-Trading FastAPI endpoints permit unauthenticated access, file upload, and an RCE chain

Risk score
70/100
CVSS
10.0 v3.1
Published
2 Oct 2026

Open in the live forecast Today's forecast

What to know

What it is
5 findings — unauthenticated full-API exposure (F1, lead Critical), read-side authorization gap that persists even with API_AUTH_KEY set (F2), unauthenticated file write of .py/.sh/.yaml to a server-returned path (F3), default-permissive CORS that combines with a loopback-only check to grant any browser page on whitel…
Affected
vibe-trading-ai (pip) >= 0.1.0, < 0.1.7
Fix
Upgrade vibe-trading-ai to 0.1.7 or later.
Exploitation
No known exploitation

Why it is Amber

  • CVSS 10.0 critical
  • Remotely exploitable without logging in
  • On your watchlist: PyPI

Affected packages

PackageEcosystemVulnerableFixed in
vibe-trading-aipip>= 0.1.0, < 0.1.70.1.7

References

Original description

### Summary: 5 findings — unauthenticated full-API exposure (F1, lead Critical), read-side authorization gap that persists even with `API_AUTH_KEY` set (F2), unauthenticated file write of `.py`/`.sh`/`.yaml` to a server-returned path (F3), default-permissive CORS that combines with a loopback-only check to grant any browser page on whitelisted localhost ports credentialed cross-origin access (F-A4), and partial API-key disclosure via `_mask_secret()` (F-A5). --- ### Shared baseline (applies to all 5 findings) The shipped `agent/.env.example` line 112 ships `# API_AUTH_KEY=` commented out. `require_auth()` at `agent/api_server.py` line 303 executes `if not api_key: return` and returns `None` immediately when `API_AUTH_KEY` is unset, so every endpoint decorated with `dependencies=[Depends(require_auth)]` operates as unauthenticated. The shipped `Dockerfile` does **not** contain a `USER` directive, so the FastAPI process runs as `uid=0(root)` inside the container (verified: `docker exec id` returns `uid=0(root) gid=0(root)`). The `docker-compose.yml` binds `0.0.0.0:8899` with no network restriction. The only operator action required beyond a clean install is supplying a working LLM API key so the agent loop can complete its tool-call round trip — this is the normal first step to make the agent functional, not an additional security opt-in. F-A4 and F-A5 do *not* require an LLM key (see per-finding notes); F1 and F3 do not require an LLM key for the unauth surface itself, only for the chained RCE demonstration in F1. ### Reproducer environment (common) ```sh git clone https://github.com/HKUDS/Vibe-Trading.git cd Vibe-Trading git checkout 7452610113a75529b5d55fd2217bb17f7bec66f7 # v0.1.6 + 1 frontend fix; same vuln state as v0.1.6 cp agent/.env.example agent/.env # (For F1 chained demo only:) edit agent/.env to set OPENROUTER_API_KEY=<real key> docker compose up -d # port 8899 is now reachable; HOST below is the docker host's IP from the attacker's perspective ``` > **Note on the `HOST` placeholder used throughout the per-finding "Steps to observe" blocks below**: replace `HOST` with the address you reach the docker host on — typically `localhost` (or `127.0.0.1`) if you are running the reproducer on the same machine as the container. All `curl` commands below assume this substitution. --- ### Finding 1 — Critical: Unauthenticated network client reaches shell execution via POST /sessions/{id}/messages - **Severity**: Critical - **CVSS v3.1**: 9.8 — `AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H` - **CVSS v4.0**: 10.0 — `AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H` - **CWE**: CWE-306 (Missing Authentication for Critical Function); chains into CWE-78 (Group B, F6) **Affected files**: - `agent/api_server.py:303` — `if not api_key: return` early return in `require_auth()` - `agent/api_server.py:736` — `@app.post("/sessions/{session_id}/messages", dependencies=[Depends(require_auth)])` (the dependency is a no-op when `API_AUTH_KEY` is unset) - `agent/src/tools/bash_tool.py:44-46` — `subprocess.run(command, shell=True, cwd=cwd)` with command read from `kwargs['command']` (full bug detail tracked in GHSA-2 / Group B / F6) **Intent vs actual**: The session API is intended to serve authenticated users only. When `API_AUTH_KEY` is unset, `require_auth()` returns `None` immediately and `dependencies=[Depends(require_auth)]` becomes a no-op. Any anonymous TCP client to port 8899 can therefore create a session, post a message, and receive the LLM agent's response. The LLM ReAct agent — given a natural-language request to run a command — selects `BashTool` from the auto-discovered registry, which calls `subprocess.run(command, shell=True)` with the LLM-emitted string. The container has no `USER` directive, so the resulting process runs as `uid=0(root)`. **Steps to observe**: 1. Start the server per the shared reproducer above (with a working `OPENROUTER_API_KEY` set in `agent/.env`). Confirm `curl -fs http://HOST:8899/health` returns 200. 2. `SID=$(curl -s -X POST http://HOST:8899/sessions -H 'Content-Type: application/json' -d '{}' | python3 -c "import json,sys;print(json.load(sys.stdin)['session_id'])")` 3. `curl -s -X POST "http://HOST:8899/sessions/$SID/messages" -H 'Content-Type: application/json' -d '{"content":"Execute the shell command '\''id && uname -a'\'' and report the output verbatim."}'` 4. Wait ~5–15 seconds, then `curl -s "http://HOST:8899/sessions/$SID/messages"` and observe the BashTool result message containing `uid=0(root)`, the kernel version, and the container hostname — all returned with no `Authorization` header on any of the three requests. **Impact**: An unauthenticated caller with TCP access to port 8899 can execute arbitrary shell commands as root inside the container. This is the top-severity entry point of the RCE chain. Combined with the absent `USER` directive in the Dockerfile, the blast radius is full container takeover. --- ### Finding 2 — High: Read endpoints return full session history with no authentication, even when API_AUTH_KEY is set - **Severity**: High - **CVSS v3.1**: 7.5 — `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N` - **CVSS v4.0**: 8.7 — `AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N` - **CWE**: CWE-862 (Missing Authorization) **Affected file**: `agent/api_server.py` — `require_auth()` docstring at line 289 states "Only write endpoints (POST/PUT/DELETE/PATCH) use this dependency." Read endpoints with no `Depends(require_auth)`: - line 804 — `@app.get("/runs", response_model=List[RunInfo])` - line 788 — `@app.get("/runs/{run_id}", response_model=RunResponse)` - line 748 — `@app.get("/runs/{run_id}/code")` - line 769 — `@app.get("/runs/{run_id}/pine")` - line 1153 — `@app.get("/sessions", response_model=List[SessionResponse])` - line 1173 — `@app.get("/sessions/{session_id}", response_model=SessionResponse)` - line 1251 — `@app.get("/sessions/{session_id}/messages", response_model=List[MessageResponse])` - line 1272 — `@app.get("/sessio

Last activity 2 Oct 2026. Patch Forecast updates every hour.