mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-07 12:47:55 -10:00
fix: fail-closed on non-object JSON and surface HOL Guard denial in result
- Return False for non-dict verdicts (e.g. [], "allow", null) to avoid AttributeError and preserve fail-closed behavior (CodeRabbit) - Set ToolResponse.result to the denial message since the agent tool executor returns result and discards error, so denials reach the model instead of a null result (Greptile) - Add non-object JSON regression test; update block test assertion Co-authored-by: Mervin Praison <[email protected]>
This commit is contained in:
1 parent
96f6d0d93b
commit
37a80c3133
2 files changed
+34
-3
No files matched your search
@@ -65,6 +65,9 @@ def _inspect_with_hol_guard(command: str) -> bool:
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
return False
|
||||
|
||||
if not isinstance(verdict, dict):
|
||||
return False
|
||||
|
||||
decision = str(verdict.get("decision", "")).lower()
|
||||
return decision in ("allow", "benign")
|
||||
|
||||
@@ -80,10 +83,15 @@ def hol_guard_tool_policy(request: ToolRequest, call_next):
|
||||
if _inspect_with_hol_guard(command):
|
||||
return call_next(request)
|
||||
|
||||
blocked_message = "Blocked by HOL Guard: command was not explicitly allowed."
|
||||
# Surface the denial in `result` too: the agent tool executor returns
|
||||
# `ToolResponse.result` to the model and discards `error`, so a denial with
|
||||
# only `error` set would reach the model as a null result. Keeping `error`
|
||||
# populated preserves the signal for after_tool hooks and logging.
|
||||
return ToolResponse(
|
||||
tool_name=request.tool_name,
|
||||
result=None,
|
||||
error="Blocked by HOL Guard: command was not explicitly allowed.",
|
||||
result=blocked_message,
|
||||
error=blocked_message,
|
||||
context=request.context,
|
||||
)
|
||||
|
||||
|
||||
@@ -84,7 +84,9 @@ def test_block_verdict_does_not_execute():
|
||||
|
||||
assert executed == []
|
||||
assert result.error is not None
|
||||
assert result.result is None
|
||||
# The denial message is surfaced in `result` too, because the agent tool
|
||||
# executor returns `ToolResponse.result` to the model and discards `error`.
|
||||
assert result.result == result.error
|
||||
|
||||
|
||||
def test_missing_cli_fails_closed():
|
||||
@@ -122,6 +124,27 @@ def test_malformed_json_fails_closed():
|
||||
assert result.error is not None
|
||||
|
||||
|
||||
def test_non_object_json_fails_closed():
|
||||
module = _load_example()
|
||||
executed = []
|
||||
|
||||
def call_next(req):
|
||||
executed.append(req)
|
||||
return "ran"
|
||||
|
||||
for payload in ("[]", '"allow"', "null"):
|
||||
with patch.object(module.shutil, "which", return_value="/usr/bin/hol-guard"), \
|
||||
patch.object(
|
||||
module.subprocess,
|
||||
"run",
|
||||
return_value=_FakeCompleted(0, payload),
|
||||
):
|
||||
result = module.hol_guard_tool_policy(_make_request(), call_next)
|
||||
|
||||
assert executed == []
|
||||
assert result.error is not None
|
||||
|
||||
|
||||
def test_nonzero_exit_fails_closed():
|
||||
module = _load_example()
|
||||
executed = []
|
||||
|
||||
Reference in new issue
Block a user