diff --git a/src/octopal/cli/main.py b/src/octopal/cli/main.py index b67c648..793b8e7 100644 --- a/src/octopal/cli/main.py +++ b/src/octopal/cli/main.py @@ -2350,7 +2350,7 @@ def _octo_tool_policy_steps() -> list[ToolPolicyPipelineStep]: ), ToolPolicyPipelineStep( label="octo.direct_exec_denylist", - policy=ToolPolicy(deny=["exec_run"]), + policy=ToolPolicy(deny=["exec_run", "test_run"]), ), ] diff --git a/src/octopal/runtime/octo/router.py b/src/octopal/runtime/octo/router.py index c831204..20163f0 100644 --- a/src/octopal/runtime/octo/router.py +++ b/src/octopal/runtime/octo/router.py @@ -1718,7 +1718,7 @@ def _get_octo_tools(octo: Any, chat_id: int) -> tuple[list[ToolSpec], dict[str, ), ToolPolicyPipelineStep( label="octo.direct_exec_denylist", - policy=ToolPolicy(deny=["exec_run"]), + policy=ToolPolicy(deny=["exec_run", "test_run"]), ), ] all_tools = get_tools(mcp_manager=mcp_manager) diff --git a/tests/test_cli_tools_resolve.py b/tests/test_cli_tools_resolve.py index 1e2145e..88b344d 100644 --- a/tests/test_cli_tools_resolve.py +++ b/tests/test_cli_tools_resolve.py @@ -31,6 +31,7 @@ def test_build_tool_resolution_snapshot_for_octo_applies_policy_and_profile() -> assert "web_fetch" in blocked_rows assert blocked_rows["web_fetch"]["reason"] == "blocked_by_deny:octo.raw_fetch_denylist" assert "exec_run" in blocked_rows + assert "test_run" in blocked_rows def test_build_tool_resolution_snapshot_for_octo_blocks_direct_exec_without_profile() -> None: @@ -45,7 +46,9 @@ def test_build_tool_resolution_snapshot_for_octo_blocks_direct_exec_without_prof blocked_rows = {row["name"]: row for row in snapshot["blocked"]} assert "exec_run" not in available_names + assert "test_run" not in available_names assert blocked_rows["exec_run"]["reason"] == "blocked_by_deny:octo.direct_exec_denylist" + assert blocked_rows["test_run"]["reason"] == "blocked_by_deny:octo.direct_exec_denylist" def test_tools_resolve_json_outputs_snapshot() -> None: diff --git a/tests/test_octo_tool_loop.py b/tests/test_octo_tool_loop.py index ca33832..e7309e8 100644 --- a/tests/test_octo_tool_loop.py +++ b/tests/test_octo_tool_loop.py @@ -48,6 +48,7 @@ class DummyOcto: tool_specs, ctx = _get_octo_tools(DummyOcto(), 0) assert "exec_run" not in {tool.name for tool in tool_specs} + assert "test_run" not in {tool.name for tool in tool_specs} async def scenario() -> None: result, meta = await _handle_octo_tool_call( @@ -64,6 +65,29 @@ async def scenario() -> None: asyncio.run(scenario()) +def test_default_octo_tool_policy_blocks_test_run() -> None: + class DummyOcto: + mcp_manager = None + + tool_specs, ctx = _get_octo_tools(DummyOcto(), 0) + + assert "test_run" not in {tool.name for tool in tool_specs} + + async def scenario() -> None: + result, meta = await _handle_octo_tool_call( + {"function": {"name": "test_run", "arguments": '{"command":"pytest -q"}'}}, + tool_specs, + ctx, + ) + + assert result["type"] == "policy_block" + assert result["tool"] == "test_run" + assert result["reason"] == "blocked_by_deny:octo.direct_exec_denylist" + assert meta["error_type"] == "policy_block" + + asyncio.run(scenario()) + + @pytest.mark.asyncio async def test_handle_octo_tool_call_captures_tool_exceptions() -> None: def _boom(_args, _ctx): diff --git a/tests/test_router_tool_budget.py b/tests/test_router_tool_budget.py index 24101ca..062e618 100644 --- a/tests/test_router_tool_budget.py +++ b/tests/test_router_tool_budget.py @@ -98,7 +98,9 @@ def fake_get_tools(mcp_manager=None): assert "octo_check_update" in names assert "octo_update_self" in names assert "exec_run" not in names + assert "test_run" not in names assert "exec_run" not in {spec.name for spec in ctx["tool_resolution_report"].available_tools} + assert "test_run" not in {spec.name for spec in ctx["tool_resolution_report"].available_tools} assert "mcp_demo_tool_0" not in names assert "mcp_demo_tool_0" in all_names assert len(tool_specs) < len(all_names)