# REPRO-2026-00238: OpenBMB ChatDev unauthenticated path traversal file upload ## Summary Status: published Severity: critical Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00238 CVE: CVE-2026-58166 ## Package Name: OpenBMB/ChatDev Ecosystem: github Affected: ChatDev <= 2.2.0 (v2.2.0 is latest release dated Mar 23, 2026) Fixed: Unknown ## Root Cause # Root Cause Analysis — CVE-2026-58166 ## Summary 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. ## Impact - **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: ```python 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 `/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 ` 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: ```json { "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. ## Reproduction Details Reproduced: 2026-07-06T08:21:50.253Z Duration: 1707 seconds Tool calls: 275 Turns: Unknown Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00238 pruva-verify CVE-2026-58166 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00238&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00238/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-58166 - Source: https://github.com/OpenBMB/ChatDev ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 18770 bytes) - bundle/repro/rca_report.md (analysis, 10622 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 12766 bytes) - bundle/vuln_variant/rca_report.md (analysis, 11752 bytes) - bundle/artifact_promotion_manifest.json (other, 14220 bytes) - bundle/vuln_variant/source_identity.json (other, 1110 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1050 bytes) - bundle/repro/runtime_manifest.json (other, 1643 bytes) - bundle/repro/validation_verdict.json (other, 755 bytes) - bundle/repro/result_vuln.json (other, 1959 bytes) - bundle/logs/reproduction_steps.log (log, 3910 bytes) - bundle/repro/eval_summary.json (other, 745 bytes) - bundle/repro/result_vuln_1.json (other, 3057 bytes) - bundle/repro/result_fixed_1.json (other, 1799 bytes) - bundle/repro/real_product_rce_driver.py (script, 6083 bytes) - bundle/logs/server_vuln_1.log (log, 0 bytes) - bundle/logs/driver_vuln_1.log (log, 387 bytes) - bundle/logs/server_vuln_2.log (log, 0 bytes) - bundle/logs/driver_vuln_2.log (log, 387 bytes) - bundle/repro/result_vuln_2.json (other, 3057 bytes) - bundle/logs/server_fixed_1.log (log, 0 bytes) - bundle/logs/driver_fixed_1.log (log, 290 bytes) - bundle/logs/server_fixed_2.log (log, 0 bytes) - bundle/logs/driver_fixed_2.log (log, 290 bytes) - bundle/repro/result_fixed_2.json (other, 1799 bytes) - bundle/repro/rce_marker_vuln_1.txt (other, 49 bytes) - bundle/repro/rce_marker_vuln_2.txt (other, 49 bytes) - bundle/vuln_variant/validation_verdict.json (other, 3729 bytes) - bundle/vuln_variant/variant_manifest.json (other, 4309 bytes) - bundle/logs/vuln_variant_reproduction_steps.log (log, 13082 bytes) - bundle/vuln_variant/eval_summary.json (other, 2300 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 7081 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 1036 bytes) - bundle/logs/vuln_variant_driver_vulnerable.log (log, 5335 bytes) - bundle/logs/vuln_variant_driver_fixed.log (log, 4803 bytes) - bundle/vuln_variant/result_vulnerable.json (other, 5334 bytes) - bundle/vuln_variant/result_fixed.json (other, 4802 bytes) - bundle/vuln_variant/result_latest.json (other, 4802 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00238 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00238/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00238 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev