fix(guardrails): stop forwarding platform to the WonderFence V2 client constructor

The V2 SDK client signature is (api_key, base_url, *, api_timeout,
connection_pool_limit) and has no platform parameter, but client_cache
forwarded platform whenever it was configured. The shipped example_config sets
platform: "aws", so every scanning request raised TypeError
("unexpected keyword argument 'platform'"), surfaced as HTTP 500
guardrail_failed_to_respond with fail_open off. platform is a per-request
analysis attribute that already reaches the service via AnalysisContext
(build_analysis_context), so it is dropped from ClientBuildSpec and the client
kwargs; nothing is lost.

The existing test masked this because the SDK stub is a Mock that accepts any
kwargs, so it never exercised the real constructor signature. The client-cache
test now asserts platform is NOT forwarded to the client, paired with a new
processing test asserting build_analysis_context sets platform on the context.

Verified live against the shipped platform: "aws" config: a malicious prompt now
returns HTTP 400 BLOCK with real WonderFence detections instead of HTTP 500.
This commit is contained in:
lior-k 2026-07-28 13:58:02 +03:00
parent 90ea9a7160
commit 1abc8e0720
No known key found for this signature in database
4 changed files with 35 additions and 6 deletions

View file

@ -188,7 +188,6 @@ class WonderFenceGuardrail(CustomGuardrail):
client_class=self._WonderFenceV2Client,
api_timeout=self.api_timeout,
api_base=self.api_base,
platform=self.platform,
connection_pool_limit=self._connection_pool_limit,
),
)

View file

@ -12,12 +12,19 @@ if TYPE_CHECKING:
@dataclass(frozen=True)
class ClientBuildSpec:
"""How to construct a WonderFenceV2Client on a cache miss."""
"""How to construct a WonderFenceV2Client on a cache miss.
``platform`` is deliberately absent: it is a per-request analysis attribute
that belongs on ``AnalysisContext`` (set by ``build_analysis_context``), not
on the client constructor. The V2 client signature is
``(api_key, base_url, *, api_timeout, connection_pool_limit)`` with no
``platform`` parameter, so forwarding it here raised ``TypeError`` on every
scan.
"""
client_class: Callable[..., object]
api_timeout: float
api_base: str | None
platform: str | None
connection_pool_limit: int | None
@ -58,8 +65,6 @@ def get_or_create_client(
}
if spec.api_base:
client_kwargs["base_url"] = spec.api_base
if spec.platform:
client_kwargs["platform"] = spec.platform
if spec.connection_pool_limit is not None:
client_kwargs["connection_pool_limit"] = spec.connection_pool_limit

View file

@ -101,8 +101,13 @@ async def test_get_client_forwards_config_to_v2_client(install_sdk_stub):
assert captured[0]["api_key"] == "resolved-key"
assert captured[0]["base_url"] == "https://wf.example.com"
assert captured[0]["api_timeout"] == 15 # rounded to int
assert captured[0]["platform"] == "aws"
assert captured[0]["connection_pool_limit"] == 42
# ``platform`` must NOT be forwarded to the V2 client: its constructor is
# (api_key, base_url, *, api_timeout, connection_pool_limit) and has no such
# parameter, so forwarding it raised TypeError on every scan. platform is a
# per-request analysis attribute that belongs on AnalysisContext instead
# (see test_build_analysis_context_sets_platform_on_context).
assert "platform" not in captured[0]
# ----------------------------- initialization -----------------------------

View file

@ -13,6 +13,7 @@ from litellm.proxy.guardrails.guardrail_hooks.alice_wonderfence.processing impor
JOINER,
RECONSTRUCT_MAX_CHARS,
apply_response_verdicts,
build_analysis_context,
check_scan_budget,
function_definition_segments,
reconstruct,
@ -20,6 +21,25 @@ from litellm.proxy.guardrails.guardrail_hooks.alice_wonderfence.processing impor
)
# --------------- build_analysis_context: platform belongs on the context ---------------
def test_build_analysis_context_sets_platform_on_context():
"""platform is a per-request analysis attribute and must be set on the
AnalysisContext, NOT forwarded to the SDK client constructor (whose
signature has no platform param). Pairs with
test_get_client_forwards_config_to_v2_client asserting platform is not
passed to the client."""
captured: dict = {}
def context_class(**kwargs):
captured.update(kwargs)
return object()
build_analysis_context({"model": "gpt-4"}, "aws", context_class)
assert captured["platform"] == "aws"
def _block(detections=None, correlation_ids=None):
return SegmentVerdict("BLOCK", None, detections or [], correlation_ids or [])