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.
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).
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 — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
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 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 Proof of Reproduction for CVE-2026-58166
- 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
unauthenticated multipart upload filename with ../ traversal and uploaded workflow YAML/task prompt Python code
- /ws
- POST /api/uploads/{session_id}
- POST /api/workflow/run
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-58166
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.pyandserver/services/attachment_service.py(AttachmentService.save_upload_file). The demonstrated execution sink is the real workflow runtime reached byserver/routes/execute_sync.py(POST /api/workflow/run),runtime.sdk.run_workflow, andruntime/node/executor/python_executor.py. - Affected versions: ChatDev
<= 2.2.0, represented by vulnerable checkout4fd4da603801766b14ad8788649cfc1ad21f99a6^=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.txtandbundle/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 returned404from/api/workflow/runbecause 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
- Run
bundle/repro/reproduction_steps.shwithbash. - The script:
- Reuses the prepared project cache at
<project_cache_dir>/repoor cloneshttps://github.com/OpenBMB/ChatDev.gitif absent. - Resolves the fixed commit and checks out
4fd4da603801766b14ad8788649cfc1ad21f99a6^for vulnerable runs and4fd4da603801766b14ad8788649cfc1ad21f99a6for 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
/wsWebSocket 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/runendpoint to execute attacker-controlled Python that writes a marker file underbundle/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.
- Reuses the prepared project cache at
- Expected evidence:
bundle/repro/runtime_manifest.jsonwithentrypoint_kind=api_remote,service_started=true,healthcheck_passed=true, andtarget_path_reached=true.bundle/repro/eval_summary.jsonwith 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, andresult_fixed_2.json.bundle/repro/rce_marker_vuln_1.txtandbundle/repro/rce_marker_vuln_2.txtcontainingRCE_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/runfrom 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
4fd4da603801766b14ad8788649cfc1ad21f99a6or 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.pyshim is used only to avoid a Python import-cycle in the current environment when importingruntimesubmodules; it does not modifyAttachmentService, 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.
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/nulltotal 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 .
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{"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]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{"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]Artifacts and Evidence for CVE-2026-58166
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-58166
FAQ: CVE-2026-58166
How does the ChatDev path-traversal-to-RCE chain work?
Which ChatDev versions are affected by CVE-2026-58166?
How severe is CVE-2026-58166?
How can I reproduce CVE-2026-58166?
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.