fix(agent_365): treat a verdict without a boolean allowed field as unavailable

A 200 body that is JSON but lacks a boolean allowed was recorded as a Defender
Block and rejected with 400. It is malformed, so it now routes through the
same unreachable_fallback handling as non-JSON and non-object bodies

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-10 07:26:52 +00:00
parent a1ab5d42cc
commit aee5b7a0d8
2 changed files with 38 additions and 1 deletions

View file

@ -304,11 +304,17 @@ class Agent365Guardrail(CustomGuardrail):
reason="the Agent 365 endpoint returned a non-object JSON body",
)
verdict: Final[_EvaluateResponse] = parsed_verdict
allowed: Final = verdict.get("allowed")
if not isinstance(allowed, bool):
return self._handle_unavailable(
data=data,
tool_name=tool_name,
reason="the Agent 365 endpoint returned a verdict without a boolean 'allowed' field",
)
raw_defender: Final = verdict.get("defender")
defender: Final = raw_defender if isinstance(raw_defender, dict) else _DefenderResult()
raw_correlation_id: Final = verdict.get("correlationId")
correlation_id: Final = raw_correlation_id if isinstance(raw_correlation_id, str) else None
allowed: Final = verdict.get("allowed") is True
self._record_verdict(
data=data,
verdict="Allow" if allowed else "Block",

View file

@ -592,6 +592,37 @@ class TestMalformedResponses:
await _run(guardrail, _mcp_data())
assert exc_info.value.status_code == 503
@pytest.mark.asyncio
@pytest.mark.parametrize(
"verdict",
[{}, {"allowed": None}, {"allowed": "true"}, {"allowed": 1}, {"allowed": "false"}],
ids=["missing", "null", "string-true", "int-one", "string-false"],
)
async def test_evaluate_non_boolean_allowed_fail_closed(self, verdict: dict):
handler: Final = FakeHandler([_token_response(), _response(200, verdict)])
guardrail: Final = _make_guardrail(handler)
data: Final = _mcp_data()
with pytest.raises(HTTPException) as exc_info:
await _run(guardrail, data)
assert exc_info.value.status_code == 503
assert "boolean 'allowed'" in exc_info.value.detail["message"]
assert _guardrail_info(data)["guardrail_response"]["verdict"] == "Unavailable"
@pytest.mark.asyncio
@pytest.mark.parametrize(
"verdict",
[{}, {"allowed": None}, {"allowed": "true"}, {"allowed": 1}, {"allowed": "false"}],
ids=["missing", "null", "string-true", "int-one", "string-false"],
)
async def test_evaluate_non_boolean_allowed_fail_open(self, verdict: dict):
handler: Final = FakeHandler([_token_response(), _response(200, verdict)])
guardrail: Final = _make_guardrail(handler, unreachable_fallback="fail_open")
data: Final = _mcp_data()
result: Final = await _run(guardrail, data)
assert result is data
assert _guardrail_info(data)["guardrail_response"]["verdict"] == "Unscanned"
assert _guardrail_info(data)["guardrail_status"] == "guardrail_failed_to_respond"
@pytest.mark.asyncio
async def test_bad_expires_in_still_allows(self):
handler: Final = FakeHandler(