Back to  vulnerability database

CVE-2026-109442: Unauthenticated Remote Code Execution in piflow Chat Agent via Unsandboxed Shell Tool

A flaw in piflow's chat agent lets anyone who can reach the server make the agent run shell commands on the host, with no authentication and no human approval. An attacker can execute arbitrary commands as the piflow service user and read every secret in its environment, including LLM API keys and database credentials.

CVSS Score
9.8
Severity
Critical
Type
Unauthenticated Remote Code Execution
Attack type
Unauthenticated Remote Code Execution
Vendor/tech
cas-bigdatalab/piflow
Known exploit
No
Impact Assessment
Confidentiality
High
Integrity
High
Availability
High

Overview

piflow  is an open-source big data flow engine with Spark support, which includes an AI chat agent built on LangChain's Deep Agents framework. The agent's only tool, exec_shell, runs commands directly on the host with no allow-list, and it is excluded from the human-approval list. The chat endpoints that feed messages to the agent require no authentication, and the server listens on all network interfaces with permissive CORS by default.

‍

Any network peer can therefore send a single chat message instructing the agent to run a command, and the command executes on the host with the full process environment, exposing every secret the service holds.

‍

Because anyone who can reach a piflow server can make its chat agent run shell commands on the host with a single request, with no credentials and no human approval, exposed deployments should be taken off the network or put behind authentication immediately

‍

Affected Files

  • agents/tools.py:113-155 (exec_shell), agents/tools.py:78-111 (_split_command)
  • agents/factory.py:113 (tools = [exec_shell]), agents/factory.py:183-187 (interrupt_on omits exec_shell)
  • server.py:239-240 (POST /chat), server.py:281-282 (POST /chat/stream), server.py:97 (CORSMiddleware(allow_origins=["*"])), server.py:812,820 (bind 0.0.0.0), server.py:468 (POST /workspace/upload)
  • runtime/engine.py:~705 (request message passed to agent.astream)

‍

Root Cause

exec_shell runs model-supplied commands on the host via subprocess.run(parts, cwd=cwd, capture_output=True, text=True, timeout=120, env={**os.environ, ...}), with no command allow-list and the full host process environment inherited by the child. It is the agent's only bound tool (tools = [exec_shell]) and is omitted from interrupt_on, so it is never gated by human approval.

‍

The POST /chat and POST /chat/stream endpoints that pass the request message into agent.astream declare no Depends(get_current_user) and there is no global auth middleware, so any network peer can steer the model into calling exec_shell. The deepagents backend is a sandboxed virtual FilesystemBackend, and because no shell-capable backend is passed deepagents does not auto-expose its own execute tool; the RCE is the application's own exec_shell, bound independently of the backend. POST /workspace/upload is also unauthenticated and lets an attacker place file content the agent later ingests, driving the same tool call.

‍

PoC

Single unauthenticated request:

POST /chat  {"message": "<prompt instructing the agent to run a shell command, e.g. exec_shell(\"sh -c '...'\")>"}

Because exec_shell runs an argv list under shell=False, a top-level ; or | does not chain, but the model can call exec_shell("sh -c ''"), bash -c ..., curl ..., or python -c ... for arbitrary command execution.

Reproduced on a local clone at commit 817605d with benign canaries, invoking the verbatim exec_shell:

‍

exec_shell("id") -> "uid=501(noyp) gid=20(staff) ..."

exec_shell("sh -c 'id; uname -a; whoami'") -> real host id, "Darwin ... arm64", host username

exec_shell("sh -c 'echo LEAKED_LLM_API_KEY=$LLM_API_KEY'") -> child inherits and echoes the environment secret

‍

A static AST scan confirmed /chat and /chat/stream carry no Depends(get_current_user) while every route in routers/dag_panel_api.py does.

‍

Impact

Unauthenticated remote code execution as the piflow service user on the host running the gateway, plus disclosure of the entire process environment (LLM API keys, database credentials, and any other secrets passed to the process). The service binds 0.0.0.0 with permissive CORS by default, and there is no human approval step anywhere in the chain.

‍

Suggested Remediation

1. Require authentication on POST /chat and POST /chat/stream, matching the Depends(get_current_user) pattern already used by routers/dag_panel_api.py, or add a global auth middleware.

2. Do not give a chat agent a raw host shell. Run exec_shell inside a real sandbox (container, gVisor, or microVM) with no host filesystem or network reachability, pass an explicit minimal environment instead of {**os.environ}, and add a command allow-list.

3. Gate exec_shell with interrupt_on (human-in-the-loop approval) in any deployment that keeps it, and treat ingested or uploaded content (POST /workspace/upload) as untrusted input.

4. Restrict CORS allow_origins and bind to localhost unless network exposure is deliberately intended.

‍

Additional Resources

  • CVE-2026-109442 record

‍

‍

‍

Automated AI Red Teaming

Test your applications against adversarial conditions.
Book a Demo