Vulnerability GHSA-5rmq-chc7-m22f
Summary
Vibe-Trading file-read tools expose arbitrary server-readable files
Details
Summary:
2 findings — safe_user_path() accepts any path under Path.home() or Path.cwd(), which inside the shipped root container resolves to /root and /app (so all of root's home, including /root/.ssh/id_rsa, /root/.aws/credentials, /root/.kube/config, and /app/agent/.env, passes the check) (F9). read_document() has no sandbox call at all and returns the full content of any path the FastAPI process can read, including /etc/shadow, /etc/passwd, /proc/self/environ, and any secret file mounted into the container (F10). F10 is strictly broader than F9 but they have different fix scopes (F10 = a missing safe_path() call in one function; F9 = the envelope definition in path_utils.py), so both must be patched.
Finding 9 — High: safe_user_path() accepts the entire user home directory and process CWD, allowing LLM tool calls to read /root credentials
- 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-22 (Path Traversal); CWE-552 (Files Accessible to External Parties)
Affected file: agent/src/tools/path_utils.py
- line 52 —
def safe_user_path(p: str) -> Path: - line 73-77 —
if resolved.is_relative_to(home) or resolved.is_relative_to(cwd): return resolved—home = Path.home(),cwd = Path.cwd()
Intent vs actual: safe_user_path() is intended to permit journal and shadow-account tools to open broker export files the operator may have placed anywhere under their home directory or the project folder. The intended invariant is that only user-owned broker data files are accessible — not system credential files or SSH keys. The actual envelope check accepts any path whose resolved form is inside Path.home() or Path.cwd(). Inside the shipped Docker container, Path.home() resolves to /root and Path.cwd() resolves to /app. Every file under either subtree passes the check, including:
/root/.ssh/id_rsaand any other SSH key files/root/.aws/credentials,/root/.kube/config,/root/.docker/config.json/app/agent/.env(the file containing the operator's realOPENROUTER_API_KEY,TUSHARE_TOKEN, and any other secrets)
A runtime probe inside the container confirmed that safe_user_path('/root/.aws/credentials') returned the path without raising ValueError. ExtractShadowStrategyTool was then invoked against /root/secrets/aws.csv (a planted credential file) and returned an error message containing the first line of the file via the parse-error channel.
Steps to observe:
- Per GHSA-1 shared reproducer, start the server with a working LLM API key and create an unauthenticated session.
- (Setup for safe demo: inside the container,
docker execa planted file:docker exec <container> sh -c 'mkdir -p /root/secrets && printf "broker_id,api_key,api_secret\nDEMO,FAKE_KEY,FAKE_SECRET\n" > /root/secrets/aws.csv'.) curl -s -X POST "http://HOST:8899/sessions/$SID/messages" -H 'Content-Type: application/json' -d '{"content":"Analyze the trade journal at the path /root/secrets/aws.csv and tell me what you find."}'- Poll
curl -s "http://HOST:8899/sessions/$SID/messages". Observe the agent invokeExtractShadowStrategyToolwithjournal_path="/root/secrets/aws.csv", which passessafe_user_path()and attempts to parse the file as a trade journal CSV. - Observe the error response — when the file's structure does not match the expected journal schema, the parse error often includes the first line (column names) verbatim, leaking the file's first line.
- Repeat with
journal_path="/app/agent/.env"to confirm the.envfile is within the accepted envelope.
Impact: Any unauthenticated caller can instruct the LLM to attempt to parse any file under /root or /app as a trade journal, extracting the file's first line via the parse-error message channel. Files with valid CSV-like first lines may leak multiple bytes. In the shipped root container, /root encompasses all credentials a careless operator may have mounted into the home directory; /app includes the agent's own secrets and any operator-staged data files.
Why F9 and F10 are listed separately
A maintainer might be tempted to fix only one, on the theory that F10 dominates F9. Two reasons to fix both:
-
Different fix scope — F10's fix is a single missing call (
safe_path(file_path)inread_documentbefore line 270). F9's fix is insafe_user_path()itself: the envelope must be replaced with a strict allowlist of operator-configured directories, notPath.home() ∪ Path.cwd(). A fix that adds the missingsafe_user_pathcall toread_documentis insufficient becausesafe_user_pathitself accepts/rootand/app/agent/.env. Both surfaces need work. -
Different reachability classes — F9 is reachable through tools that already gate on
safe_user_path(ExtractShadowStrategyTooland several journal tools), so even a hypothetical F10 fix that switchedread_documentto usesafe_user_pathwould still leak/root/*because the envelope is broken. F9 is the structural defect; F10 is the missed call.
Related Vulnerabilities
Other vulnerabilities affecting the same packages