diff --git a/litellm/proxy/guardrails/guardrail_hooks/akto/akto.py b/litellm/proxy/guardrails/guardrail_hooks/akto/akto.py index 34afcd23582..8f8aa57fc3f 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/akto/akto.py +++ b/litellm/proxy/guardrails/guardrail_hooks/akto/akto.py @@ -4,7 +4,6 @@ Mode: - pre_call: Validates request against Akto guardrails, blocks if flagged. """ -import asyncio import json import os from datetime import datetime @@ -65,8 +64,6 @@ class AktoGuardrail(CustomGuardrail): self.async_handler = get_async_httpx_client( llm_provider=httpxSpecialProvider.GuardrailCallback, ) - self.background_tasks: set = set() - self.akto_base_url = ( akto_base_url or os.environ.get("AKTO_GUARDRAIL_API_BASE", "") ).rstrip("/") @@ -93,31 +90,6 @@ class AktoGuardrail(CustomGuardrail): ] super().__init__(**kwargs) - # ── Helpers ── - - def schedule(self, coro) -> None: - """Schedule a fire-and-forget background task""" - task = asyncio.create_task(coro) - self.background_tasks.add(task) - task.add_done_callback(self.background_tasks.discard) - - async def ingest_blocked_request(self, payload: dict) -> None: - """Fire-and-forget: ingest a blocked request to Akto for audit.""" - try: - await self.async_handler.post( - url=f"{self.akto_base_url}{HTTP_PROXY_PATH}", - data=json.dumps(payload), - params={"akto_connector": AKTO_CONNECTOR_NAME, "ingest_data": "true"}, - headers={ - "content-type": "application/json", - "Authorization": self.akto_api_key, - }, - timeout=self.guardrail_timeout, - ) - except (httpx.RequestError, httpx.HTTPStatusError) as e: - # Fire-and-forget: log but never crash the request path - verbose_proxy_logger.error("Akto blocked-request ingest error: %s", e) - # ── Payload builders ── @staticmethod @@ -365,16 +337,6 @@ class AktoGuardrail(CustomGuardrail): ) if not allowed: - blocked_payload = self.build_akto_payload( - inputs, request_data, status_code=403 - ) - blocked_payload["responsePayload"] = json.dumps( - {"x-blocked-by": "Akto Proxy", "reason": reason} - ) - blocked_payload["responseHeaders"] = json.dumps( - {"content-type": "application/json"} - ) - self.schedule(self.ingest_blocked_request(blocked_payload)) detail = ( f"Blocked by Akto Guardrails: {reason}" if reason diff --git a/tests/guardrails_tests/test_akto_guardrails.py b/tests/guardrails_tests/test_akto_guardrails.py index 2bb8801e36d..9ebd9dce565 100644 --- a/tests/guardrails_tests/test_akto_guardrails.py +++ b/tests/guardrails_tests/test_akto_guardrails.py @@ -1,4 +1,3 @@ -import asyncio import json import os from unittest.mock import AsyncMock, MagicMock, patch @@ -306,7 +305,7 @@ async def test_pre_call_allowed(akto_validate, sample_inputs, sample_request_dat @pytest.mark.asyncio async def test_pre_call_blocked(akto_validate, sample_inputs, sample_request_data): akto_validate.async_handler.post = AsyncMock( - side_effect=[_mock_blocked_response("PII"), MagicMock(status_code=200)] + return_value=_mock_blocked_response("PII") ) with pytest.raises(HTTPException) as exc_info: @@ -316,19 +315,10 @@ async def test_pre_call_blocked(akto_validate, sample_inputs, sample_request_dat input_type="request", ) - await asyncio.gather(*akto_validate.background_tasks) - assert exc_info.value.status_code == 403 assert "PII" in exc_info.value.detail - assert akto_validate.async_handler.post.call_count == 2 - - # Second call is the blocked-request ingestion - ingest_call = akto_validate.async_handler.post.call_args_list[1].kwargs - assert ingest_call["params"].get("ingest_data") == "true" - assert "guardrails" not in ingest_call["params"] - ingest_payload = json.loads(ingest_call["data"]) - assert ingest_payload["statusCode"] == "403" - assert json.loads(ingest_payload["responsePayload"])["x-blocked-by"] == "Akto Proxy" + # Only the guardrail validation call; blocked-request ingestion is handled by AktoLogger via failure_callback. + akto_validate.async_handler.post.assert_called_once() # ---------------------------------------------------------------------------