@@ -265,6 +265,60 @@ def test_copilot_cli_payload_skipped(
265265 assert json .loads (capsys .readouterr ().out ) == {}
266266
267267
268+ class TestCodexMcpResponses :
269+ """Codex PreToolUse responses for MCP calls through scan_command."""
270+
271+ _PAYLOAD = { # noqa: RUF012
272+ 'hook_event_name' : 'PreToolUse' ,
273+ 'session_id' : 'session-123' ,
274+ 'turn_id' : 'turn-1' ,
275+ 'tool_name' : 'mcp__grafana__query_loki_logs' ,
276+ 'tool_input' : {'logql' : '{app="api"}' },
277+ }
278+
279+ def test_permitted_mcp_call_returns_empty_response (
280+ self ,
281+ mock_ctx : MagicMock ,
282+ mocker : MockerFixture ,
283+ capsys : pytest .CaptureFixture [str ],
284+ mock_scan_command_deps : dict [str , MagicMock ],
285+ ) -> None :
286+ """Older Codex runtimes reject permissionDecision "allow" as unsupported
287+ and report a failed hook on every MCP call - allow must be empty."""
288+ mocker .patch ('sys.stdin' , StringIO (json .dumps (self ._PAYLOAD )))
289+ mock_scan_command_deps ['load_policy' ].return_value = {'fail_open' : True }
290+ mock_scan_command_deps ['get_handler' ].return_value = MagicMock (
291+ return_value = HookDecision .allow (AiHookEventType .MCP_EXECUTION )
292+ )
293+
294+ scan_command (mock_ctx , ide = 'codex' )
295+
296+ assert json .loads (capsys .readouterr ().out ) == {}
297+
298+ def test_blocked_mcp_call_returns_deny_with_reason (
299+ self ,
300+ mock_ctx : MagicMock ,
301+ mocker : MockerFixture ,
302+ capsys : pytest .CaptureFixture [str ],
303+ mock_scan_command_deps : dict [str , MagicMock ],
304+ ) -> None :
305+ mocker .patch ('sys.stdin' , StringIO (json .dumps (self ._PAYLOAD )))
306+ mock_scan_command_deps ['load_policy' ].return_value = {'fail_open' : True }
307+ mock_scan_command_deps ['get_handler' ].return_value = MagicMock (
308+ return_value = HookDecision .deny (AiHookEventType .MCP_EXECUTION , 'Secret detected in MCP arguments' )
309+ )
310+
311+ scan_command (mock_ctx , ide = 'codex' )
312+
313+ assert json .loads (capsys .readouterr ().out ) == {
314+ 'hookSpecificOutput' : {
315+ 'hookEventName' : 'PreToolUse' ,
316+ 'permissionDecision' : 'deny' ,
317+ 'permissionDecisionReason' : 'Secret detected in MCP arguments' ,
318+ }
319+ }
320+
321+
268322class TestSelfDetach :
269323 """Report-mode scans respawn detached and release the IDE immediately."""
270324
0 commit comments