From e598dc954c4bdcaa64123814f0b567cd893a89f0 Mon Sep 17 00:00:00 2001 From: Hamza Rashid Date: Thu, 2 Apr 2026 21:50:26 +0000 Subject: [PATCH] fix(api): rename verify endpoint and redact 422 request input Rename the deployment provider credential verification route to /deployments/providers/verify and sanitize RequestValidationError responses so raw request payloads are not echoed back in 422 errors. Update route/tests accordingly, including regression coverage for input redaction. --- src/backend/base/langflow/main.py | 15 ++++++++++++++- src/backend/tests/unit/api/v1/test_projects.py | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/main.py b/src/backend/base/langflow/main.py index fd1d3db7dc..c13e4f4d22 100644 --- a/src/backend/base/langflow/main.py +++ b/src/backend/base/langflow/main.py @@ -8,13 +8,14 @@ import warnings from contextlib import asynccontextmanager, suppress from http import HTTPStatus from pathlib import Path -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Any, cast from urllib.parse import urlencode import anyio import httpx import sqlalchemy from fastapi import FastAPI, HTTPException, Request, Response, status +from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles @@ -78,6 +79,11 @@ async def log_exception_to_telemetry(exc: Exception, context: str) -> None: await logger.awarning(f"Failed to log {context} exception to telemetry") +def _sanitize_validation_errors(errors: list[dict[str, Any]]) -> list[dict[str, Any]]: + """Strip request payload echoes from validation errors to avoid leaking submitted data.""" + return [{key: value for key, value in error.items() if key != "input"} for error in errors] + + class RequestCancelledMiddleware(BaseHTTPMiddleware): def __init__(self, app) -> None: super().__init__(app) @@ -560,6 +566,13 @@ def create_app(): # Discover and register additional routers from plugins (langflow.plugins entry-point) load_plugin_routes(app) + @app.exception_handler(RequestValidationError) + async def request_validation_exception_handler(_request: Request, exc: RequestValidationError): + return JSONResponse( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + content={"detail": _sanitize_validation_errors(exc.errors())}, + ) + @app.exception_handler(Exception) async def exception_handler(_request: Request, exc: Exception): if isinstance(exc, HTTPException): diff --git a/src/backend/tests/unit/api/v1/test_projects.py b/src/backend/tests/unit/api/v1/test_projects.py index c48c06baa0..8ddaf9678f 100644 --- a/src/backend/tests/unit/api/v1/test_projects.py +++ b/src/backend/tests/unit/api/v1/test_projects.py @@ -134,6 +134,24 @@ async def test_create_project_validation_error(client: AsyncClient, logged_in_he assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY +async def test_create_project_validation_error_does_not_echo_request_input(client: AsyncClient, logged_in_headers): + """Regression test: request-validation 422 responses must not echo submitted payloads.""" + sensitive_marker = "secret-api-key-should-not-echo" + response = await client.post( + "api/v1/projects/", + # Send a JSON string (instead of object) to trigger RequestValidationError consistently. + content=json.dumps(sensitive_marker), + headers={**logged_in_headers, "Content-Type": "application/json"}, + ) + + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + detail = response.json()["detail"] + assert isinstance(detail, list) + assert detail + assert all("input" not in error for error in detail) + assert sensitive_marker not in response.text + + async def test_delete_project_then_404(client: AsyncClient, logged_in_headers, basic_case): create_resp = await client.post("api/v1/projects/", json=basic_case, headers=logged_in_headers) proj_id = create_resp.json()["id"]