Critical severityCISA KEVNVD Advisory· Published Apr 7, 2025· Updated Nov 29, 2025
Langflow < 1.3.0 Unauthenticated RCE via /api/v1/validate/code
CVE-2025-3248
Description
Langflow versions prior to 1.3.0 are susceptible to code injection in the /api/v1/validate/code endpoint. A remote and unauthenticated attacker can send crafted HTTP requests to execute arbitrary code.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
langflowPyPI | < 1.3.0 | 1.3.0 |
langflow-basePyPI | < 0.3.0 | 0.3.0 |
Affected products
1- Range: 0
Patches
1faac4db133defix: auth current user on code validation (#6911)
3 files changed · +36 −12
src/backend/base/langflow/api/v1/validate.py+2 −1 modified@@ -1,6 +1,7 @@ from fastapi import APIRouter, HTTPException from loguru import logger +from langflow.api.utils import CurrentActiveUser from langflow.api.v1.base import Code, CodeValidationResponse, PromptValidationResponse, ValidatePromptRequest from langflow.base.prompts.api_utils import process_prompt_template from langflow.utils.validate import validate_code @@ -10,7 +11,7 @@ @router.post("/code", status_code=200) -async def post_validate_code(code: Code) -> CodeValidationResponse: +async def post_validate_code(code: Code, _current_user: CurrentActiveUser) -> CodeValidationResponse: try: errors = validate_code(code.code) return CodeValidationResponse(
src/backend/tests/unit/api/v1/test_validate.py+26 −4 modified@@ -1,14 +1,16 @@ +import pytest from fastapi import status from httpx import AsyncClient -async def test_post_validate_code(client: AsyncClient): +@pytest.mark.usefixtures("active_user") +async def test_post_validate_code(client: AsyncClient, logged_in_headers): good_code = """ from pprint import pprint var = {"a": 1, "b": 2} pprint(var) """ - response = await client.post("api/v1/validate/code", json={"code": good_code}) + response = await client.post("api/v1/validate/code", json={"code": good_code}, headers=logged_in_headers) result = response.json() assert response.status_code == status.HTTP_200_OK @@ -17,7 +19,8 @@ async def test_post_validate_code(client: AsyncClient): assert "function" in result, "The result must have a 'function' key" -async def test_post_validate_prompt(client: AsyncClient): +@pytest.mark.usefixtures("active_user") +async def test_post_validate_prompt(client: AsyncClient, logged_in_headers): basic_case = { "name": "string", "template": "string", @@ -48,10 +51,29 @@ async def test_post_validate_prompt(client: AsyncClient): "metadata": {}, }, } - response = await client.post("api/v1/validate/prompt", json=basic_case) + response = await client.post("api/v1/validate/prompt", json=basic_case, headers=logged_in_headers) result = response.json() assert response.status_code == status.HTTP_200_OK assert isinstance(result, dict), "The result must be a dictionary" assert "frontend_node" in result, "The result must have a 'frontend_node' key" assert "input_variables" in result, "The result must have an 'input_variables' key" + + +@pytest.mark.usefixtures("active_user") +async def test_post_validate_prompt_with_invalid_data(client: AsyncClient, logged_in_headers): + invalid_case = { + "name": "string", + # Missing required fields + "frontend_node": {"template": {}, "is_input": True}, + } + response = await client.post("api/v1/validate/prompt", json=invalid_case, headers=logged_in_headers) + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + + +async def test_post_validate_code_with_unauthenticated_user(client: AsyncClient): + code = """ + print("Hello World") + """ + response = await client.post("api/v1/validate/code", json={"code": code}, headers={"Authorization": "Bearer fake"}) + assert response.status_code == status.HTTP_401_UNAUTHORIZED
src/backend/tests/unit/test_endpoints.py+8 −7 modified@@ -127,15 +127,16 @@ async def test_get_all(client: AsyncClient, logged_in_headers): assert "ChatOutput" in json_response["outputs"] -async def test_post_validate_code(client: AsyncClient): +@pytest.mark.usefixtures("active_user") +async def test_post_validate_code(client: AsyncClient, logged_in_headers): # Test case with a valid import and function code1 = """ import math def square(x): return x ** 2 """ - response1 = await client.post("api/v1/validate/code", json={"code": code1}) + response1 = await client.post("api/v1/validate/code", json={"code": code1}, headers=logged_in_headers) assert response1.status_code == 200 assert response1.json() == {"imports": {"errors": []}, "function": {"errors": []}} @@ -146,7 +147,7 @@ def square(x): def square(x): return x ** 2 """ - response2 = await client.post("api/v1/validate/code", json={"code": code2}) + response2 = await client.post("api/v1/validate/code", json={"code": code2}, headers=logged_in_headers) assert response2.status_code == 200 assert response2.json() == { "imports": {"errors": ["No module named 'non_existent_module'"]}, @@ -160,19 +161,19 @@ def square(x): def square(x) return x ** 2 """ - response3 = await client.post("api/v1/validate/code", json={"code": code3}) + response3 = await client.post("api/v1/validate/code", json={"code": code3}, headers=logged_in_headers) assert response3.status_code == 200 assert response3.json() == { "imports": {"errors": []}, "function": {"errors": ["expected ':' (<unknown>, line 4)"]}, } # Test case with invalid JSON payload - response4 = await client.post("api/v1/validate/code", json={"invalid_key": code1}) + response4 = await client.post("api/v1/validate/code", json={"invalid_key": code1}, headers=logged_in_headers) assert response4.status_code == 422 # Test case with an empty code string - response5 = await client.post("api/v1/validate/code", json={"code": ""}) + response5 = await client.post("api/v1/validate/code", json={"code": ""}, headers=logged_in_headers) assert response5.status_code == 200 assert response5.json() == {"imports": {"errors": []}, "function": {"errors": []}} @@ -183,7 +184,7 @@ def square(x) def square(x) return x ** 2 """ - response6 = await client.post("api/v1/validate/code", json={"code": code6}) + response6 = await client.post("api/v1/validate/code", json={"code": code6}, headers=logged_in_headers) assert response6.status_code == 200 assert response6.json() == { "imports": {"errors": []},
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
10- github.com/langflow-ai/langflow/pull/6911ghsapatchWEB
- www.horizon3.ai/attack-research/disclosures/unsafe-at-any-speed-abusing-python-exec-for-unauth-rce-in-langflow-ai/mitreexploit
- github.com/advisories/GHSA-rvqx-wpfh-mfx7ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-3248ghsaADVISORY
- www.vulncheck.com/advisories/langflow-unauthenticated-rceghsathird-party-advisoryWEB
- github.com/langflow-ai/langflow/commit/faac4db133de32fcb6d483fa9ff52f40ce42bdc0ghsaWEB
- github.com/langflow-ai/langflow/releases/tag/1.3.0ghsarelease-notesWEB
- github.com/langflow-ai/langflow/security/advisories/GHSA-rvqx-wpfh-mfx7ghsaWEB
- www.cisa.gov/known-exploited-vulnerabilities-catalogghsaWEB
- www.horizon3.ai/attack-research/disclosures/unsafe-at-any-speed-abusing-python-exec-for-unauth-rce-in-langflow-aighsaWEB
News mentions
0No linked articles in our index yet.