From aeda146112c840372ae6f091c28d6379d8db6509 Mon Sep 17 00:00:00 2001 From: flamiinngo Date: Sun, 17 May 2026 03:31:08 +0100 Subject: [PATCH] fix(security): honor shell hook blocks even when message/reason is absent _parse_response in agent/shell_hooks.py only forwarded a pre_tool_call block directive if the hook also provided a non-empty message or reason. When either field was missing the function returned None, causing Hermes to treat the response as a no-op and execute the tool unconditionally. This means a hook that outputs {"action": "block"} or {"decision": "block"} without a reason string is silently ignored. The security boundary fails open: tools the user intended to gate are executed anyway. Fix: remove the message-presence guard. Honor the block unconditionally and fall back to a default message when none is provided. Existing hooks that already include a message or reason are unaffected. --- agent/shell_hooks.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/agent/shell_hooks.py b/agent/shell_hooks.py index bad5388f88b..687af5ec4ba 100644 --- a/agent/shell_hooks.py +++ b/agent/shell_hooks.py @@ -515,13 +515,11 @@ def _parse_response(event: str, stdout: str) -> Optional[Dict[str, Any]]: if event == "pre_tool_call": if data.get("action") == "block": - message = data.get("message") or data.get("reason") or "" - if isinstance(message, str) and message: - return {"action": "block", "message": message} + message = data.get("message") or data.get("reason") or "Blocked by shell hook." + return {"action": "block", "message": message} if data.get("decision") == "block": - message = data.get("reason") or data.get("message") or "" - if isinstance(message, str) and message: - return {"action": "block", "message": message} + message = data.get("reason") or data.get("message") or "Blocked by shell hook." + return {"action": "block", "message": message} return None context = data.get("context")