PT-2026-111186

Publicado

2026-10-02

·

Atualizado

2026-10-02

Descrição por

CVSS 3.1
7.5
Alta
MétricasAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

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.

Shared baseline (applies to both findings)

The container has no USER directive (Dockerfile:15 — FROM python:3.11-slim AS runtime, no subsequent USER), so the FastAPI process runs as uid=0(root).
The two file-read tools described here are members of the auto-discovered LLM tool registry. Combined with GHSA-1 / F1, they are reachable from any anonymous TCP client to port 8899, but the same defects also apply to authenticated sessions and to prompt-injection in any document the agent processes. See GHSA-1's shared reproducer block for the install steps; the same docker compose up -d setup applies here.
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 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 rsa and any other SSH key files
  • /root/.aws/credentials, /root/.kube/config, /root/.docker/config.json
  • /app/agent/.env (the file containing the operator's real OPENROUTER 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:
  1. Per GHSA-1 shared reproducer, start the server with a working LLM API key and create an unauthenticated session.
  2. (Setup for safe demo: inside the container, docker exec a planted file: docker exec <container> sh -c 'mkdir -p /root/secrets && printf "broker id,api key,api secret DEMO,FAKE KEY,FAKE SECRET " > /root/secrets/aws.csv'.)
  3. 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."}'
  4. Poll curl -s "http://HOST:8899/sessions/$SID/messages". Observe the agent invoke ExtractShadowStrategyTool with journal path="/root/secrets/aws.csv", which passes safe user path() and attempts to parse the file as a trade journal CSV.
  5. 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.
  6. Repeat with journal path="/app/agent/.env" to confirm the .env file 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.

Finding 10 — High: read document() opens any server-readable file with no sandbox enforcement, returning full content of /etc/shadow and /proc/self/environ

  • 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); CWE-200 (Information Exposure)
Affected file: agent/src/tools/doc reader tool.py
  • line 259 — def read document(file path: str, pages: str = "") -> str:
  • line 270 — path = Path(file path) — followed only by path.exists() and path.is file() checks before dispatching to format-specific readers
  • No call to safe path, safe user path, or any other sandbox enforcement appears anywhere in the function
Intent vs actual: DocReaderTool is intended to allow the LLM agent to read documents and data files provided for analysis. Like other file-reading tools in the project, it should apply a sandbox check before opening the file. The actual implementation takes the LLM-emitted file path string, runs only path.exists() and path.is file(), and dispatches to the appropriate reader. No call to safe path or safe user path exists in the function. A runtime probe confirmed:
  • read document('/etc/passwd') returned HTTP 200 with 839 characters of content
  • read document('/etc/shadow') returned the full shadow password file
  • read document('/proc/self/environ') returned the full process environment, including OPENROUTER API KEY and TUSHARE TOKEN in plaintext
This is strictly wider than F9: F9 is bounded to /root + /app via the (overly-broad) envelope; F10 has no envelope at all and reaches /etc, /proc, /var, and any other path the FastAPI process can read.
Steps to observe:
  1. Per GHSA-1 shared reproducer, start the server with a working LLM API key and create an unauthenticated session.
  2. curl -s -X POST "http://HOST:8899/sessions/$SID/messages" -H 'Content-Type: application/json' -d '{"content":"Please read and summarize the document at /proc/self/environ"}'
  3. Poll curl -s "http://HOST:8899/sessions/$SID/messages". Observe the agent invoke read document with file path="/proc/self/environ" and return the full process environment in the message stream.
  4. Observe OPENROUTER API KEY, TUSHARE TOKEN, and any other variables in agent/.env appearing in plaintext.
  5. Repeat with file path="/etc/shadow" to confirm shadow password file access.
Impact: An unauthenticated caller can retrieve any file the server process can read. Running as root, that includes /etc/shadow, /etc/passwd, /proc/self/environ (full plaintext API keys), /root/.ssh/id rsa, and any secret files mounted into the container. This is the broadest file-read primitive in the codebase and provides a credential-extraction path that does not require shell execution — endpoint monitoring tuned to BashTool / shell signatures will miss it entirely.

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:
  1. Different fix scope — F10's fix is a single missing call (safe path(file path) in read document before line 270). F9's fix is in safe user path() itself: the envelope must be replaced with a strict allowlist of operator-configured directories, not Path.home() ∪ Path.cwd(). A fix that adds the missing safe user path call to read document is insufficient because safe user path itself accepts /root and /app/agent/.env. Both surfaces need work.
  2. Different reachability classes — F9 is reachable through tools that already gate on safe user path (ExtractShadowStrategyTool and several journal tools), so even a hypothetical F10 fix that switched read document to use safe user path would still leak /root/* because the envelope is broken. F9 is the structural defect; F10 is the missed call.

Suggested remediation

  1. F9 — In safe user path() at path utils.py:52-77, replace the Path.home() ∪ Path.cwd() envelope with a strict allowlist of operator-configured directories (e.g. an explicit BROKER EXPORTS DIR env var defaulting to /app/data/broker exports/). Reject /root, /app/agent/.env, and /app/agent/uploads/ (the latter to prevent F3-uploaded files from being subsequently parsed as a credential-leak vector via the parse-error channel).'
  2. F10 — Add a safe path() (or safe user path()) call at doc reader tool.py:270 before the existing path.exists() / path.is file() checks. Once F9 is patched, the same allowlist will apply uniformly to both read document and the safe user path-gated tools.
  3. Defense-in-depth — Drop the FastAPI process to a non-root user. Add a RUN useradd -m vibe && chown -R vibe /app step to the Dockerfile and USER vibe before CMD. This does not fix the Path Traversal but materially reduces the credential-extraction blast radius of any successful exploit (and benefits every other finding in GHSA-1 and GHSA-2). See GHSA-1 / shared baseline for the matching USER recommendation.

Explorabilidade
Correção
Impactos
Fornecedores
Produtos afetados
Identificadores relacionados
GHSA-5RMQ-CHC7-M22F
Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾