fix(panw_prisma_airs): warn at init when latest-only scanning is enabled

Addresses the Veria finding on caller-supplied history. Latest-only scanning
does not rescan earlier turns, so an operator who enables it on a shape where
history is not server-controlled is accepting that trade-off.

This matches how the guardrail already treats fallback_on_error='allow': a
security-reducing opt-in gets an explicit startup warning rather than only a
config-field description. The warning fires only on an explicit true; unset
and false are silent, so no existing deployment gains log noise.

Semantics are unchanged - this makes the trade-off visible at runtime instead
of relying on the operator having read the field docs.
This commit is contained in:
scthornton 2026-09-08 17:14:50 -04:00
parent 0f28bce05c
commit 3c4b0f8591
2 changed files with 31 additions and 0 deletions

View file

@ -205,6 +205,12 @@ class PanwPrismaAirsHandler(CustomGuardrail):
guardrail_name,
)
if self.experimental_use_latest_role_message_only is True:
verbose_proxy_logger.warning(
"PANW Prisma AIRS Guardrail '%s': experimental_use_latest_role_message_only=true - only the latest user/developer message is scanned on the request side. Earlier turns in caller-supplied history are not rescanned before being forwarded to the model. Enable only where every turn is scanned while it is the latest message, or where conversation history is server-controlled.",
guardrail_name,
)
verbose_proxy_logger.info(
"Initialized PANW Prisma AIRS Guardrail: %s (profile=%s, mask_request=%s, mask_response=%s, fallback_on_error=%s, timeout=%s)",
guardrail_name,

View file

@ -5243,6 +5243,31 @@ class TestPanwAirsLatestRoleMessageOnlyOpenAIShape:
assert mock_api.call_count == 2
class TestPanwAirsLatestRoleMessageOnlyStartupWarning:
"""Enabling latest-only scanning trusts caller-supplied history, so the operator
gets an explicit startup warning - same treatment as fallback_on_error='allow'."""
def test_warns_when_explicitly_enabled(self):
with patch(
"litellm.proxy.guardrails.guardrail_hooks.panw_prisma_airs.panw_prisma_airs.verbose_proxy_logger"
) as mock_logger:
make_handler(experimental_use_latest_role_message_only=True)
warned = " ".join(str(c) for c in mock_logger.warning.call_args_list)
assert "experimental_use_latest_role_message_only=true" in warned
assert "not rescanned" in warned
@pytest.mark.parametrize("flag", [None, False])
def test_no_warning_when_unset_or_disabled(self, flag):
with patch(
"litellm.proxy.guardrails.guardrail_hooks.panw_prisma_airs.panw_prisma_airs.verbose_proxy_logger"
) as mock_logger:
make_handler(experimental_use_latest_role_message_only=flag)
warned = " ".join(str(c) for c in mock_logger.warning.call_args_list)
assert "experimental_use_latest_role_message_only" not in warned
class TestPanwAirsMcpToolCallWithoutCallId:
"""Tests for MCP tool invocations flowing through apply_guardrail without
litellm_call_id — the bug fix for _convert_mcp_to_llm_format synthetic data."""