diff --git a/backend/src/v1/agent/tool_registry.py b/backend/src/v1/agent/tool_registry.py index 31c5557..0f63641 100644 --- a/backend/src/v1/agent/tool_registry.py +++ b/backend/src/v1/agent/tool_registry.py @@ -6,6 +6,10 @@ from typing import Any def validate_tool_spec(spec: dict[str, Any]) -> None: name = spec["name"] target = spec["execution_target"] + + if not (name.startswith("ui.") or name.startswith("srv.")): + raise ValueError("Tool name must be in ui.* or srv.* namespace") + if name.startswith("ui.") and target != "frontend": raise ValueError("ui.* must use frontend target") if name.startswith("srv.") and target != "backend": diff --git a/backend/tests/unit/v1/agent/test_agent_security_rules.py b/backend/tests/unit/v1/agent/test_agent_security_rules.py new file mode 100644 index 0000000..766ac42 --- /dev/null +++ b/backend/tests/unit/v1/agent/test_agent_security_rules.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from v1.agent.tool_registry import validate_tool_spec + + +class TestAgentSecurityRules: + def test_tool_name_must_be_allowlisted(self): + validate_tool_spec({"name": "ui.navigate_to", "execution_target": "frontend"}) + validate_tool_spec({"name": "srv.search_docs", "execution_target": "backend"}) + + def test_tool_name_rejected_if_not_in_namespace(self): + try: + validate_tool_spec( + {"name": "malicious.tool", "execution_target": "frontend"} + ) + except ValueError: + pass + else: + raise AssertionError("Should have raised ValueError for unknown namespace") + + def test_frontend_result_fails_when_interrupt_mismatch(self): + pass