VYPR
Critical severityCISA KEVNVD Advisory· Published Mar 20, 2026· Updated Mar 26, 2026

Langflow has Unauthenticated Remote Code Execution via Public Flow Build Endpoint

CVE-2026-33017

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.

PackageAffected versionsPatched versions
langflowPyPI
<= 1.8.2

Affected products

1

Patches

1
73b6612e3ef2

fix: prevent RCE via data parameter in build_public_tmp endpoint (#12160)

https://github.com/langflow-ai/langflowJanardan Singh KaviaMar 13, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.