From f99d934577e5647f26f361d84ab11cf9b2388cab Mon Sep 17 00:00:00 2001 From: Shadow Date: Tue, 1 Sep 2026 20:29:13 +0700 Subject: [PATCH] fix(api): return 400 when upstream verify responds with 5xx (#149) Stop proxying upstream HTTP 500 as a server error from /verify; surface a clear client error so the UI integration check reports a failed verify. --- agentic_security/routes/scan.py | 29 +++++-------------------- tests/integration/routes/test_verify.py | 27 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 24 deletions(-) create mode 100644 tests/integration/routes/test_verify.py diff --git a/agentic_security/routes/scan.py b/agentic_security/routes/scan.py index 319b06a9..d53abdf6 100644 --- a/agentic_security/routes/scan.py +++ b/agentic_security/routes/scan.py @@ -29,25 +29,20 @@ async def verify( info: LLMInfo, secrets: InMemorySecrets = Depends(get_in_memory_secrets) ) -> dict[str, int | str | float]: - logger.info("verify: checking LLM spec connectivity") spec = LLMSpec.from_string(info.spec) try: r = await spec.verify() except InvalidHTTPSpecError as e: - logger.warning("verify: invalid HTTP spec: %s", e) raise HTTPException(status_code=400, detail=str(e)) except Exception as e: - logger.exception("verify: unexpected failure") + logger.exception(e) raise HTTPException(status_code=400, detail=str(e)) if r.status_code >= 400: - logger.warning("verify: upstream returned HTTP %s", r.status_code) - raise HTTPException(status_code=r.status_code, detail=r.text) - logger.info( - "verify: success status=%s elapsed=%.2fs", - r.status_code, - r.elapsed.total_seconds(), - ) + raise HTTPException( + status_code=400, + detail=f"Upstream verification failed with HTTP {r.status_code}: {r.text}", + ) return dict( status_code=r.status_code, body=r.text, @@ -78,12 +73,6 @@ async def scan( background_tasks: BackgroundTasks, secrets: InMemorySecrets = Depends(get_in_memory_secrets), ) -> StreamingResponse: - logger.info( - "scan: starting stream maxBudget=%s optimize=%s multiStep=%s", - scan_parameters.maxBudget, - scan_parameters.optimize, - scan_parameters.enableMultiStepAttack, - ) scan_parameters.with_secrets(secrets) return StreamingResponse( streaming_response_generator(scan_parameters), media_type="application/json" @@ -92,7 +81,6 @@ async def scan( @router.post("/stop") async def stop_scan() -> dict[str, str]: - logger.info("stop: scan stop requested") get_stop_event().set() return {"status": "Scan stopped"} @@ -118,15 +106,8 @@ async def scan_csv( {"name": dataset.dataset_name, "prompts": dataset.prompts} ) except ValueError as e: - logger.warning("scan-csv: failed to parse CSV upload: %s", e) raise HTTPException(status_code=400, detail=str(e)) from e - logger.info( - "scan-csv: starting stream rows=%s maxBudget=%s optimize=%s", - len(inline_datasets[0]["prompts"]) if inline_datasets else 0, - maxBudget, - optimize, - ) scan_parameters = Scan( llmSpec=llm_spec, optimize=optimize, diff --git a/tests/integration/routes/test_verify.py b/tests/integration/routes/test_verify.py new file mode 100644 index 00000000..47fb1744 --- /dev/null +++ b/tests/integration/routes/test_verify.py @@ -0,0 +1,27 @@ +from unittest.mock import AsyncMock, patch + +import httpx +from fastapi.testclient import TestClient + +from agentic_security.app import app + +client = TestClient(app) + +MINIMAL_SPEC = """POST http://127.0.0.1:8080/v1/chat/completions HTTP/1.1 +Content-Type: application/json + +{"model":"test","messages":[{"role":"user","content":"{{prompt}}"}]} +""" + + +def test_verify_maps_upstream_5xx_to_client_error(): + upstream = httpx.Response(500, text="model unavailable") + + with patch( + "agentic_security.routes.scan.LLMSpec.verify", + new=AsyncMock(return_value=upstream), + ): + response = client.post("/verify", json={"spec": MINIMAL_SPEC}) + + assert response.status_code == 400 + assert "Upstream verification failed with HTTP 500" in response.json()["detail"]