← Back to Attack Research

Validation that runs your code: CVE-2025-3248 and unauthenticated RCE in Langflow

Langflow shipped an endpoint whose whole job was to syntax-check user component code. It ran without authentication, and it handed the submitted source straight to Python's compiler and interpreter. The allowlist meant to keep the snippet harmless missed a language rule that turns validation into execution. Here is the mechanism behind CVE-2025-3248, why it landed on CISA's Known Exploited list, and how to recognize the exposure.

The endpoint was supposed to be the safe one. Langflow, a popular open-source builder for LLM and agent workflows, exposed /api/v1/validate/code so the visual editor could tell a user whether the Python they typed into a custom component was syntactically valid. Checking syntax feels harmless. But the endpoint answered without any authentication, and to check the code it compiled and executed it. CVE-2025-3248, rated CVSS 9.8 and added to CISA's Known Exploited Vulnerabilities catalog on 5 May 2025, is the story of how a syntax check on any Langflow instance before version 1.3.0 became unauthenticated remote code execution, and how attackers used it to plant the Flodrix botnet.

Langflow sits in a category of software that has grown fast in the last two years: the AI orchestration platform. You wire together prompts, models, retrievers, and tools on a canvas, and part of the appeal is that you can drop in a custom component, a short block of Python that becomes a node in the graph. To make that editing experience pleasant, the backend offers a validation endpoint. Type some code, the frontend posts it to /api/v1/validate/code, and the server tells you whether it parses. This is a convenience feature. It is also, in every version before 1.3.0, a pre-authentication path from the public internet to arbitrary command execution on the host. This post walks the mechanism, explains the one language rule that defeats the endpoint's guardrail, and lays out how to determine whether an instance is exposed without ever sending a malicious request. Verifiable security.

Two weaknesses stacked on one endpoint

CVE-2025-3248 is not one bug, it is two weaknesses that compound. The advisory tracks it as CWE-94, improper control of generation of code, better known as code injection, layered on top of CWE-306, missing authentication for a critical function. Either one alone would be a serious finding. Together they are the reason this reached a 9.8 and the exploited-in-the-wild list within weeks.

The first weakness is the missing authentication. The validation endpoint was reachable without a session, a token, or an API key. Langflow's management API answered /api/v1/validate/code to anyone who could reach the port. In a lot of real deployments that port is on the public internet, because Langflow is frequently stood up as a shared internal tool that someone exposed for remote access, or run in a cloud instance with a permissive security group. The precondition for exploitation is therefore nothing more than network reachability to the API. No credential is needed because the endpoint never asks for one.

The second weakness is what the endpoint does with the input. To decide whether a snippet of Python is valid, the handler does not merely parse it into an abstract syntax tree and inspect the shape. It compiles the source and executes it. In Python terms, the code path reaches compile() and then evaluates the resulting object, with no sandbox, no restricted builtins, and no separate low-privilege interpreter. The validator does try to be careful: it walks the submitted code and attempts to restrict it to import statements and function definitions, on the theory that a bare set of imports and def blocks cannot do anything dangerous just by being defined. That theory is where the bug lives.

The rule the allowlist forgot: definition time is run time

Restricting a snippet to imports and function definitions sounds like it should be safe. A function definition, after all, does not run the function. Writing def handler(): os.system("...") defines a function whose body only executes when something calls handler(), and the validator never calls it. If the body of the function were the only place code lived, the allowlist would hold.

But a Python function definition is not inert. Two parts of a def statement are evaluated immediately, at the moment the function is defined, before it is ever called. The first is the default values of the parameters. When you write def handler(x=expr), the expression expr is evaluated once, at definition time, so that its result can be stored as the default. The second is the decorator expression. When you write @expr above a function, expr is evaluated at definition time to produce the decorator that will wrap the function. Neither of these waits for the function to be called. They run as soon as the def is processed, which is exactly what happens when the validator compiles and executes the snippet to check it.

That is the whole trick. The attacker never needs the validator to call the function. They place their payload in a position that Python evaluates during the definition itself. A default argument value such as def _(a=SOMETHING()) causes SOMETHING() to run the instant the definition is processed. A decorator such as @SOMETHING() above a trivial function does the same. Both positions survive an allowlist that only checks that the top-level statements are imports and function definitions, because they are, technically, part of a function definition. The allowlist sees a def and waves it through. Python, doing what Python always does, evaluates the default and the decorator right then, and the attacker's expression runs inside the Langflow server process.

The validator's allowlist was answering the wrong question. It asked "is this only functions and imports," when the question that mattered was "does anything execute while I define these."

Combine that with the missing authentication and the picture is complete. An unauthenticated attacker sends a POST to /api/v1/validate/code whose body contains a function definition with a payload in a default argument or a decorator. The server, believing it is only syntax-checking, compiles and evaluates the snippet. The default or decorator expression executes immediately. Because the expression can reach into modules such as os or subprocess, the result is command execution in the Langflow service process, with no credentials, no token, and no user interaction anywhere in the flow.

CVE-2025-3248: A SYNTAX CHECK THAT EXECUTES unauthenticated POST --> /api/v1/validate/code (CWE-306: no auth) | v submitted Python source | v allowlist: "only imports + function defs?" --> PASS | (the def looks harmless) v compile() + exec() the snippet (CWE-94: code injection) | v Python evaluates, AT DEFINITION TIME: - default argument values def _(a = PAYLOAD) - decorator expressions @PAYLOAD | v PAYLOAD runs in the Langflow process --> unauthenticated RCE precondition: network reachability only | fixed in Langflow 1.3.0

The guardrail checks the shape of the statements, not what evaluates while they are defined. Illustrative flow, not a working payload.

Why a 9.8, and why it reached the exploited list

The CVSS 3.1 vector is AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, which reads as a checklist of the worst attributes a vulnerability can have. It is reachable over the network, the attack is low complexity, it requires no privileges, and it needs no user interaction, so every access metric points at maximum exposure. On the impact side, confidentiality, integrity, and availability are all high, because code execution in the service process gives an attacker whatever the process can read, change, and stop. The only reason the scope is unchanged rather than changed is a scoring technicality about crossing a security boundary, and it does not soften the outcome. The result is a 9.8, one of the highest ratings the scale produces.

Translate the score into consequences. Execution lands as the Langflow service account, so the blast radius is whatever that account holds. For an AI orchestration platform that is rarely trivial. These systems commonly store provider API keys for the models they call, credentials for the databases and vector stores they retrieve from, tokens for the tools they can invoke, and the prompts and data of whatever workflows the team has built. A single unauthenticated request that reaches code execution therefore does not just compromise a server, it can hand over the keys to every downstream service the platform was trusted to talk to. That is the definition of a trust anchor, and Langflow, sitting at the center of an automation graph, is one.

The exploited-in-the-wild status is not a projection. CISA added CVE-2025-3248 to the Known Exploited Vulnerabilities catalog on 5 May 2025, with a remediation deadline of 26 May 2025 for the agencies bound by the directive. Public reporting tied the vulnerability to the deployment of the Flodrix botnet: attackers scanned for internet-facing Langflow instances, sent a crafted request to the validation endpoint, and used the resulting code execution to pull down and run botnet malware. The path from a single unauthenticated POST to a botnet node is short precisely because there is no authentication step to slow it and no sandbox to contain it. When a bug this reachable is this powerful, mass exploitation follows the disclosure quickly, and here it did.

Be precise about scope, because precision is the point. The flaw is in Langflow's own code, in versions before 1.3.0, and it is fixed in 1.3.0, where the endpoint enforces authentication. An instance that is patched, or one whose management API is not reachable from an attacker's network, does not carry the same risk. The work is not to assume every Langflow install is owned, it is to determine which instances are exposed, on which version, and reachable from where.

Recognizing the exposure without firing a payload

You can assess this risk from readable facts, without sending a single malicious request. Three questions decide it. First, is a Langflow instance reachable from a network an attacker controls, and does it answer on its API port. A management interface that responds to unauthenticated callers is the precondition, so the first job is simply knowing where Langflow is exposed. Second, what version is it. Langflow reports its version, and any build before 1.3.0 is affected, so a version string below that line on an internet-facing host is an active, catalog-listed risk by definition. Third, what account does the service run as and what secrets does it hold. That answers how bad a hit would be before one happens, and it tells you which instances to fix first.

On the detection side, the signal is clean because the vulnerable behavior is narrow. Watch for POST requests to /api/v1/validate/code, particularly from outside your environment or in volumes that no legitimate editor session would produce. Watch for the Langflow server process spawning unexpected child processes, since a validation endpoint has no business launching a shell or a downloader. And watch for the published Flodrix indicators. Any of those three is a reason to treat the host as an incident rather than a maintenance item.

One clarification worth stating plainly, because the naming causes confusion. This post is strictly about the 2025 validation-endpoint bug, CVE-2025-3248 at /api/v1/validate/code, fixed in 1.3.0. It is not the newer Langflow remote code execution issue, CVE-2026-33017, which lives at a different endpoint under /api/v1/build_public_tmp/ and is patched in 1.9.0. They are separate vulnerabilities in the same product. If you are triaging Langflow, check your version against both, but do not treat a 1.3.0-or-later build as safe from the later issue, and do not treat this article's mechanism as describing that one.

What to do about it

Close CVE-2025-3248 on your Langflow footprint

How Celvex catches this

Find. Prove. Fix. Verify.

Find

A read-only sweep locates internet-facing Langflow instances, fingerprints each version, and flags any build before 1.3.0 against the known-exploited catalog, identifying CVE-2025-3248 exposure without sending a payload to the validation endpoint.

Prove

A confirmed exposed, vulnerable-version host becomes an Ed25519-signed Proof Capsule carrying the endpoint, the version evidence, and the matching CVE and catalog entry, reproducible offline by you or your auditor.

Fix

The capsule's remediation block names the steps: upgrade to 1.3.0 or later, remove the management API from the public internet, reduce the service privilege, and rotate any secrets the instance held.

Verify

A fresh sweep confirms the instance is patched or no longer exposed. The finding closes and the verified-fix event is recorded for the audit trail.

The lesson of CVE-2025-3248 generalizes past Langflow. Any feature that compiles or evaluates user-supplied code to inspect it is executing that code, and an allowlist that reasons about the shape of the code rather than what evaluates during its definition will miss the default-argument and decorator positions every time. The safe way to check whether Python parses is to build the abstract syntax tree and inspect it, never to compile and run it. Any endpoint that reaches an interpreter, validation or not, belongs behind authentication and off the open internet. AI orchestration platforms are full of convenience features that quietly cross that boundary, which is why they deserve the same input-validation scrutiny as any public web app.

Verifiable security. Find it. Prove it. Fix it. Verify the fix held. That is what we ship.

Sources

Is a vulnerable Langflow instance answering from your perimeter?

Free Exposure Check, no signup required. We map your internet-facing AI orchestration and management surfaces, fingerprint their versions against the known-exploited catalog, and ship a signed Proof Capsule for the highest-confidence finding.

Run a Free Scan →