Skip to content

CVE-2026-58166: Verified Reproduction

CVE-2026-58166: OpenBMB ChatDev unauthenticated path traversal file upload

CVE-2026-58166 is verified against OpenBMB/ChatDev · github. Affected versions: ChatDev <= 2.2.0 (v2.2.0 is latest release dated Mar 23, 2026). Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00238.

REPRO-2026-00238 OpenBMB/ChatDev · github RCE Jul 6, 2026 CVE entry .txt
Severity
CRITICAL
CVSS
9.1
Confidence
HIGH
Reproduced in
28m 27s
Tool calls
275
Spend
$9.38
01 · Overview

What Is CVE-2026-58166?

CVE-2026-58166 is a critical, unauthenticated path traversal (CWE-22) file-upload vulnerability in OpenBMB ChatDev's workflow server that Pruva chained into remote code execution (reproduction REPRO-2026-00238).

02 · Severity & CVSS

CVE-2026-58166 Severity & CVSS Score

CVE-2026-58166 is rated critical severity, with a CVSS base score of 9.1 out of 10.

CRITICAL threat level
9.1 / 10 CVSS base
03 · Affected Versions

Affected OpenBMB/ChatDev Versions

OpenBMB/ChatDev · github versions ChatDev <= 2.2.0 (v2.2.0 is latest release dated Mar 23, 2026) are affected.

How to Reproduce CVE-2026-58166

$ pruva-verify REPRO-2026-00238
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00238/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-58166

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

unauthenticated multipart upload filename with ../ traversal and uploaded workflow YAML/task prompt Python code

Attack chain
  1. /ws
  2. POST /api/uploads/{session_id}
  3. POST /api/workflow/run
Runnable proof: reproduction_steps.sh
Captured evidence: server fixed 1driver fixed 1server fixed 2driver fixed 2
How the agent worked 566 events · 275 tool calls · 28 min
28 minDuration
275Tool calls
118Reasoning steps
566Events
1Dead-ends
Agent activity over 28 min
Support
13
Repro
377
Judge
54
Variant
117
0:0028:27

Root Cause and Exploit Chain for CVE-2026-58166

Versions: ChatDev <= 2.2.0, represented by vulnerable checkout 4fd4da603801766b14ad8788649cfc1ad21f99a6^ = a6a5cda5560053136897aa44301eacc6c48d8168.Fixed: version/commit: 4fd4da603801766b14ad8788649cfc1ad21f99a6, which adds upload filename basename sanitization via _safe_upload_filename().

OpenBMB ChatDev through 2.2.0 exposes an unauthenticated file-upload path traversal in the product workflow server. The original POST /api/uploads/{session_id} route passes the raw multipart filename into AttachmentService.save_upload_file(), which joins it under a temporary upload directory without sanitization at the vulnerable commit. A remote unauthenticated attacker can first mint a session through the product /ws WebSocket, then upload a filename containing ../ path traversal segments. In this run, that primitive was chained on the original ChatDev product surface into code execution: the upload wrote a malicious workflow YAML outside the intended upload directory, and the product POST /api/workflow/run endpoint then executed attacker-controlled Python through ChatDev's real workflow python node runtime.

  • Package/component affected: OpenBMB/ChatDev workflow server, specifically server/routes/uploads.py and server/services/attachment_service.py (AttachmentService.save_upload_file). The demonstrated execution sink is the real workflow runtime reached by server/routes/execute_sync.py (POST /api/workflow/run), runtime.sdk.run_workflow, and runtime/node/executor/python_executor.py.
  • Affected versions: ChatDev <= 2.2.0, represented by vulnerable checkout 4fd4da603801766b14ad8788649cfc1ad21f99a6^ = a6a5cda5560053136897aa44301eacc6c48d8168.
  • Fixed version/commit: 4fd4da603801766b14ad8788649cfc1ad21f99a6, which adds upload filename basename sanitization via _safe_upload_filename().
  • Risk level and consequences: Critical. A remote unauthenticated attacker can write/delete files reachable by the server process via traversal. This run shows that the write primitive can be chained into product-level code execution by writing a workflow YAML to a traversed path and invoking ChatDev's workflow execution API to run attacker-controlled Python.

Impact Parity

  • Disclosed/claimed maximum impact: Remote code execution (code_execution) after unauthenticated path traversal upload.
  • Reproduced impact from this run: Full code execution on the original product API surface. Two vulnerable attempts created attacker-controlled marker files (bundle/repro/rce_marker_vuln_1.txt and bundle/repro/rce_marker_vuln_2.txt) from Python code executed by ChatDev's real workflow runtime after the malicious YAML was written via the unauthenticated upload path. Two fixed-commit attempts sanitized the filename, did not create the traversed YAML target, and returned 404 from /api/workflow/run because the target YAML file was not written.
  • Parity: full.
  • Not demonstrated: No privilege escalation or sandbox escape beyond the privileges of the ChatDev server process. The RCE demonstrated is arbitrary Python execution within the server's normal workflow execution environment.

Root Cause

At the vulnerable checkout, AttachmentService.save_upload_file() trusts UploadFile.filename and joins it directly onto a temporary directory:

filename = upload.filename or "upload.bin"
temp_dir = Path(tempfile.mkdtemp(prefix="mac_upload_"))
temp_path = temp_dir / filename
with temp_path.open("wb") as buffer:
    ...

Because Python path joining preserves ../ segments, a multipart filename such as ../../../../tmp/chatdev_cve58166_vuln_1_link.yaml escapes the temporary upload directory. The product upload route is reachable remotely at POST /api/uploads/{session_id} and only requires a known session id. The reproduction obtains that session without authentication by opening the product /ws WebSocket; the server returns a fresh UUID session and records it in active_connections, after which ensure_known_session(..., require_connection=False) accepts uploads for that session.

The vulnerable function also removes temp_path in a finally block. A direct traversal write is therefore deleted after registration, but the reproduction uses a symlink at the traversed path. open("wb") follows the symlink and writes the malicious YAML to the symlink target, while unlink() removes only the symlink. The resulting YAML file persists outside the upload directory. The product /api/workflow/run endpoint accepts an absolute yaml_file path and runs the referenced workflow; the uploaded YAML contains a single ChatDev python node, causing PythonNodeExecutor to execute attacker-supplied Python from task_prompt with subprocess.run().

The fix commit 4fd4da603801766b14ad8788649cfc1ad21f99a6 adds AttachmentService._safe_upload_filename(), normalizing path separators and reducing the multipart filename to a safe basename before joining it with the temporary directory. In the fixed negative controls, the same traversal filename is returned as chatdev_cve58166_fixed_*_link.yaml, the /tmp/...fixed_*.yaml target is not created, and the follow-on workflow execution cannot find the malicious YAML.

Fix commit: https://github.com/OpenBMB/ChatDev/commit/4fd4da603801766b14ad8788649cfc1ad21f99a6

Reproduction Steps

  1. Run bundle/repro/reproduction_steps.sh with bash.
  2. The script:
    • Reuses the prepared project cache at <project_cache_dir>/repo or clones https://github.com/OpenBMB/ChatDev.git if absent.
    • Resolves the fixed commit and checks out 4fd4da603801766b14ad8788649cfc1ad21f99a6^ for vulnerable runs and 4fd4da603801766b14ad8788649cfc1ad21f99a6 for fixed runs.
    • Starts the original product server via server_main.py --host 127.0.0.1 --port <port> and waits for /health.
    • Opens the real /ws WebSocket to mint an unauthenticated session.
    • Sends a real multipart POST /api/uploads/{session_id} request with a traversal filename targeting a symlink under /tmp.
    • In vulnerable runs, persists a malicious workflow YAML outside the upload directory and then calls the real POST /api/workflow/run endpoint to execute attacker-controlled Python that writes a marker file under bundle/repro/.
    • In fixed runs, verifies the same request is sanitized, the traversed YAML target is not created, and the workflow run fails closed with no marker file.
  3. Expected evidence:
    • bundle/repro/runtime_manifest.json with entrypoint_kind=api_remote, service_started=true, healthcheck_passed=true, and target_path_reached=true.
    • bundle/repro/eval_summary.json with both vulnerable code-execution attempts true and both fixed-blocked attempts true.
    • bundle/repro/result_vuln_1.json, result_vuln_2.json, result_fixed_1.json, and result_fixed_2.json.
    • bundle/repro/rce_marker_vuln_1.txt and bundle/repro/rce_marker_vuln_2.txt containing RCE_OK_FROM_CHATDEV_WORKFLOW ....
    • Runtime logs in bundle/logs/.

Evidence

The script was run twice consecutively and succeeded both times. The final run produced:

{
  "vuln_attempts_code_execution": [true, true],
  "vuln_attempts_persistent_yaml_write": [true, true],
  "vuln_response_names": [
    "../../../../../../../../../../../../../../../../tmp/chatdev_cve58166_vuln_1_link.yaml",
    "../../../../../../../../../../../../../../../../tmp/chatdev_cve58166_vuln_2_link.yaml"
  ],
  "vuln_marker_contents": [
    "RCE_OK_FROM_CHATDEV_WORKFLOW role=vuln attempt=1\n",
    "RCE_OK_FROM_CHATDEV_WORKFLOW role=vuln attempt=2\n"
  ],
  "fixed_attempts_blocked_chain": [true, true],
  "fixed_response_names": [
    "chatdev_cve58166_fixed_1_link.yaml",
    "chatdev_cve58166_fixed_2_link.yaml"
  ],
  "fixed_target_created": [false, false],
  "confirmed": true
}

Important artifact locations:

  • Main proof log: bundle/logs/reproduction_steps.log.
  • Vulnerable server logs: bundle/logs/server_vuln_1.log, bundle/logs/server_vuln_2.log.
  • Vulnerable driver logs: bundle/logs/driver_vuln_1.log, bundle/logs/driver_vuln_2.log.
  • Fixed server logs: bundle/logs/server_fixed_1.log, bundle/logs/server_fixed_2.log.
  • Fixed driver logs: bundle/logs/driver_fixed_1.log, bundle/logs/driver_fixed_2.log.
  • Structured runtime evidence: bundle/repro/runtime_manifest.json.
  • Marker files proving attacker-controlled Python execution: bundle/repro/rce_marker_vuln_1.txt, bundle/repro/rce_marker_vuln_2.txt.

Example vulnerable result (bundle/repro/result_vuln_1.json): the upload response echoed the raw traversal filename, the traversed YAML target existed after upload, the symlink was removed, /api/workflow/run returned HTTP 200 with final_message RCE_MARKER_WRITTEN role=vuln attempt=1, and the marker contained RCE_OK_FROM_CHATDEV_WORKFLOW role=vuln attempt=1.

Example fixed result (bundle/repro/result_fixed_1.json): the upload response name was sanitized to chatdev_cve58166_fixed_1_link.yaml, the traversed YAML target did not exist, /api/workflow/run returned 404 (YAML file not found), and no marker file was created.

Recommendations / Next Steps

  • Keep the fixed _safe_upload_filename() behavior: normalize both POSIX and Windows separators, strip directory components, and reject or replace empty/special names such as ., .., and blank filenames.
  • Consider adding defense in depth to save_upload_file() by resolving the final path and verifying it remains under the temporary directory before opening it.
  • Avoid following symlinks for temporary upload writes where possible, or create temp files with safe APIs that prevent symlink traversal.
  • Restrict /api/workflow/run from accepting arbitrary absolute YAML paths unless this is an explicit trusted-admin feature. Prefer resolving workflow files only under the configured workflow directory.
  • Add end-to-end regression tests that send multipart filenames with ../, absolute paths, Windows separators, symlink targets, and the RCE chain pattern demonstrated here.
  • Upgrade users to a version containing commit 4fd4da603801766b14ad8788649cfc1ad21f99a6 or later.

Additional Notes

  • The reproduction is idempotent: the script removes old /tmp/chatdev_cve58166_* files and marker files before each attempt, chooses free local ports, and runs two vulnerable and two fixed attempts per invocation.
  • The script uses the original product server entrypoint (server_main.py) and real product routes. It does not reimplement the vulnerable upload handler or workflow execution sink.
  • A small sitecustomize.py shim is used only to avoid a Python import-cycle in the current environment when importing runtime submodules; it does not modify AttachmentService, routes, workflow execution logic, or the vulnerable/fixed behavior under test.

CVE-2026-58166 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:59
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-58166 · REPRO-20
0:02
0:04
web search
0:05
web search
0:08
0:10
0:25
0:25
extract_facts
no facts extracted
0:26
0:27
0:27
supportrepro
0:28
0:28
0:28
0:29
0:29
0:29
0:31
0:31
$ls -la /data/pruva/project-cache/cb19670c-9322-4afb-8baf-ad6441138574/ 2>/dev/null; echo "---REPO---"; ls -la /data/pruva/project-cache/cb19670c-9322-4afb-8baf-ad6441138574/repo 2>/dev/null | head -30; echo "---MIRRORS---"; ls -la /data/pruva/project-cache/cb19670c-9322-4afb-8baf-ad6441138574/repo-mirrors 2>/dev/null
total 0
drwxr-xr-x 1 vscode vscode   24 Jul  5 14:44 .
drwxr-xr-x 1 vscode vscode 4558 Jul  5 14:44 ..
drwxr-xr-x 1 vscode vscode    0 Jul  5 14:44 repo-mirrors
---REPO---
---MIRRORS---
total 0
drwxr-xr-x 1 vscode vscode  0 Jul  5 14:44 .
0:33
web search
0:37
0:37
0:37
$curl -sL https://github.com/OpenBMB/ChatDev/commit/4fd4da603801766b14ad8788649cfc1ad21f99a6.patch -o /tmp/fix.patch && echo "=== PATCH SIZE ===" && wc -l /tmp/fix.patch && echo "=== PATCH ===" && cat /tmp/fix.patch
0.3s
{"cwd":"/data/pruva/runs/3283e9ad-873d-4160-b2a4-c262fdf0e854","duration_ms":266,"exit_code":0,"stderr":"","stdout":"=== PATCH SIZE ===\n141 /tmp/fix.patch\n=== PATCH ===\nFrom 0014dbba7c6c3278091285bd7ea0e96154ad3a15 Mon Sep 17 00:00:00 2001\nFrom: swaylq <5630262+swaylq@users.noreply.github.com>\nDate: Tue, 16 Jun 2026 13:22:34 +0800\nSubject: [PATCH] fix: sanitize upload filename to prevent path traversal\n (#638)\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\n`AttachmentService.save_upload_file` joined the client-supplied multipart\nfilename onto a fresh temp directory without sanitization. A filename with\n`../` segments escaped the temp dir, so the upload wrote attacker-controlled\nbytes to an arbitrary host path and the `finally` cleanup `unlink` then\ndeleted that same traversed path — arbitrary file write and delete behind an\nendpoint that only requires an unauthenticated session id.\n\nReduce the filename to its basename (normalising POSIX and Windows\nseparators, falling back to `upload.bin` for empty/`.`/`..`) before building\nthe temp path, so writes stay confined to the temp directory.\n\nAdds regression tests: a param… [truncated]
0:43
0:44
0:45
$echo "=== ISSUE 638 ==="; curl -sL "https://api.github.com/repos/OpenBMB/ChatDev/issues/638" 2>/dev/null | jq -r '{title, state, body}' 2>/dev/null | head -120; echo; echo "=== PR 641 ==="; curl -sL "https://api.github.com/repos/OpenBMB/ChatDev/pulls/641" 2>/dev/null | jq -r '{title, state, merged, body, base: .base.ref, merge_commit_sha}' 2>/dev/null | head -60
0.8s
{"cwd":"/data/pruva/runs/3283e9ad-873d-4160-b2a4-c262fdf0e854","duration_ms":825,"exit_code":0,"stderr":"","stdout":"=== ISSUE 638 ===\n{\n  \"title\": \"Arbitrary file write and delete via unsanitized upload filename in the ChatDev workflow server\",\n  \"state\": \"closed\",\n  \"body\": \"\\n\\nThe ChatDev workflow server saves uploaded files to a path built by joining a freshly created temporary directory with the client-supplied multipart filename, with no sanitization. A filename containing parent-directory segments escapes the temporary directory and writes attacker-controlled bytes to an arbitrary location on the host. The function's cleanup step then unlinks the same traversed path, so the upload also deletes an arbitrary file. The endpoint requires only a known session id, which is minted without authentication. Confirmed by calling the real `save_upload_file` with a traversal filename: the file was written outside the temp directory, and an existing victim file was deleted.\\n\\n### Details\\n\\n`server/services/attachment_service.py` (`save_upload_file`, around lines 46 to 80):\\n\\n```python\\nfilename = upload.filename or \\\"upload.bin\\\"\\ntemp_dir = Path(tempfile.… [truncated]
0:59
08 · How to Fix

How to Fix CVE-2026-58166

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-58166 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-58166

How does the ChatDev path-traversal-to-RCE chain work?

An unauthenticated attacker mints a session over ChatDev's /ws WebSocket, then uploads a filename containing ../ segments to write a malicious workflow YAML outside the intended upload directory; the product's POST /api/workflow/run endpoint then executes attacker-controlled Python through ChatDev's real workflow python node runtime (runtime/node/executor/python_executor.py).

Which ChatDev versions are affected by CVE-2026-58166?

ChatDev <= 2.2.0 is affected (v2.2.0 was the latest release, dated Mar 23, 2026); the reproduction verified the vulnerable checkout at commit 4fd4da603801766b14ad8788649cfc1ad21f99a6^.

How severe is CVE-2026-58166?

It is rated critical - an unauthenticated attacker can traverse outside the upload directory and reach code execution via the workflow runtime, with no authentication required.

How can I reproduce CVE-2026-58166?

Download the verified script from this page and run it in an isolated environment against ChatDev <=2.2.0; it establishes a session via /ws, uploads a filename with path-traversal sequences to plant a malicious workflow YAML outside the upload directory, then triggers POST /api/workflow/run to execute attacker-controlled Python.
11 · References

References for CVE-2026-58166

Authoritative sources for CVE-2026-58166 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.