Langflow has Unauthenticated Remote Code Execution via Public Flow Build Endpoint
Description
Langflow is a tool for building and deploying AI-powered agents and workflows. In versions prior to 1.9.0, the POST /api/v1/build_public_tmp/{flow_id}/flow endpoint allows building public flows without requiring authentication. When the optional data parameter is supplied, the endpoint uses attacker-controlled flow data (containing arbitrary Python code in node definitions) instead of the stored flow data from the database. This code is passed to exec() with zero sandboxing, resulting in unauthenticated remote code execution. This is distinct from CVE-2025-3248, which fixed /api/v1/validate/code by adding authentication. The build_public_tmp endpoint is designed to be unauthenticated (for public flows) but incorrectly accepts attacker-supplied flow data containing arbitrary executable code. This issue has been fixed in version 1.9.0.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
langflowPyPI | <= 1.8.2 | — |
Affected products
1- Range: < 1.9.0
Patches
173b6612e3ef2fix: prevent RCE via data parameter in build_public_tmp endpoint (#12160)
2 files changed · +84 −3
src/backend/base/langflow/api/v1/chat.py+8 −3 modified@@ -583,7 +583,6 @@ async def build_public_tmp( background_tasks: LimitVertexBuildBackgroundTasks, flow_id: uuid.UUID, inputs: Annotated[InputValueRequest | None, Body(embed=True)] = None, - data: Annotated[FlowDataRequest | None, Body(embed=True)] = None, files: list[str] | None = None, stop_component_id: str | None = None, start_component_id: str | None = None, @@ -598,10 +597,16 @@ async def build_public_tmp( This endpoint is specifically for public flows that don't require authentication. It uses a client_id cookie to create a deterministic flow ID for tracking purposes. + Security Note: + - The 'data' parameter is NOT accepted to prevent flow definition tampering + - Public flows must execute the stored flow definition only + - The flow definition is always loaded from the database + The endpoint: 1. Verifies the requested flow is marked as public in the database 2. Creates a deterministic UUID based on client_id and flow_id 3. Uses the flow owner's permissions to build the flow + 4. Always loads the flow definition from the database Requirements: - The flow must be marked as PUBLIC in the database @@ -611,7 +616,6 @@ async def build_public_tmp( flow_id: UUID of the public flow to build background_tasks: Background tasks manager inputs: Optional input values for the flow - data: Optional flow data files: Optional files to include stop_component_id: Optional ID of component to stop at start_component_id: Optional ID of component to start from @@ -630,11 +634,12 @@ async def build_public_tmp( owner_user, new_flow_id = await verify_public_flow_and_get_user(flow_id=flow_id, client_id=client_id) # Start the flow build using the new flow ID + # data is always None for public flows - they load from database only job_id = await start_flow_build( flow_id=new_flow_id, background_tasks=background_tasks, inputs=inputs, - data=data, + data=None, # Always None - public flows load from database only files=files, stop_component_id=stop_component_id, start_component_id=start_component_id,
src/backend/tests/unit/test_chat_endpoint.py+76 −0 modified@@ -432,3 +432,79 @@ async def mock_cancel_flow_build_with_cancelled_error(*_args, **_kwargs): finally: # Restore the original function to avoid affecting other tests monkeypatch.setattr(langflow.api.v1.chat, "cancel_flow_build", original_cancel_flow_build) + + +@pytest.mark.benchmark +async def test_build_public_tmp_ignores_data_parameter(client, json_memory_chatbot_no_llm, logged_in_headers): + """Test that build_public_tmp endpoint silently ignores data parameter for security. + + Security Test: Verifies that when a user attempts to provide custom flow data + to the public flow endpoint, FastAPI silently ignores the extra parameter and + the endpoint functions normally using the stored flow data from the database. + """ + # Create a flow + flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers) + + # Make the flow public + response = await client.patch( + f"api/v1/flows/{flow_id}", + json={"access_type": "PUBLIC"}, + headers=logged_in_headers, + ) + assert response.status_code == codes.OK + + # Create malicious flow data with different structure + malicious_data = {"nodes": [{"id": "malicious", "data": {"type": "CustomComponent"}}], "edges": []} + + # Set a client_id cookie + client.cookies.set("client_id", "test-security-client-123") + + # Attempt to build with malicious data - FastAPI will silently ignore it + response = await client.post( + f"api/v1/build_public_tmp/{flow_id}/flow", + json={ + "inputs": {"session": "test_session"}, + "data": malicious_data, # This will be silently ignored by FastAPI + }, + headers={"Content-Type": "application/json"}, + ) + + # Verify the request succeeded - the data parameter is simply ignored + assert response.status_code == codes.OK + response_data = response.json() + assert "job_id" in response_data + + +@pytest.mark.benchmark +async def test_build_public_tmp_without_data_parameter(client, json_memory_chatbot_no_llm, logged_in_headers): + """Test that build_public_tmp endpoint works without data parameter. + + Security Test: Verifies that when no data parameter is provided, the endpoint + works normally and returns a job_id. This proves the data parameter is optional + and the stored flow definition is always used. + """ + # Create a flow + flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers) + + # Make the flow public + response = await client.patch( + f"api/v1/flows/{flow_id}", + json={"access_type": "PUBLIC"}, + headers=logged_in_headers, + ) + assert response.status_code == codes.OK + + # Set a client_id cookie + client.cookies.set("client_id", "test-no-data-client") + + # Build without providing data parameter + response = await client.post( + f"api/v1/build_public_tmp/{flow_id}/flow", + json={"inputs": {"session": "test_session"}}, + headers={"Content-Type": "application/json"}, + ) + + # Verify the request succeeded + assert response.status_code == codes.OK + response_data = response.json() + assert "job_id" in response_data
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
12- github.com/advisories/GHSA-rvqx-wpfh-mfx7ghsax_refsource_MISCADVISORY
- github.com/advisories/GHSA-vwmf-pq79-vjvxghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-33017ghsaADVISORY
- github.com/langflow-ai/langflow/commit/73b6612e3ef25fdae0a752d75b0fabd47328d4f0ghsax_refsource_MISCWEB
- github.com/langflow-ai/langflow/issues/12345ghsaWEB
- github.com/langflow-ai/langflow/pull/12160ghsaWEB
- github.com/langflow-ai/langflow/releases/tag/1.8.2ghsaWEB
- github.com/langflow-ai/langflow/security/advisories/GHSA-vwmf-pq79-vjvxghsax_refsource_CONFIRMWEB
- medium.com/@aviral23/cve-2026-33017-how-i-found-an-unauthenticated-rce-in-langflow-by-reading-the-code-they-already-dc96cdce5896ghsaWEB
- www.cisa.gov/known-exploited-vulnerabilities-catalogghsaWEB
- www.cisa.gov/known-exploited-vulnerabilities-catalogghsaWEB
- www.sysdig.com/blog/cve-2026-33017-how-attackers-compromised-langflow-ai-pipelines-in-20-hoursghsaWEB
News mentions
0No linked articles in our index yet.