hermes-agent/tests/tools/test_delegate_toolset_scope.py
alt-glitch 4b16341975 refactor(restructure): rewrite all imports for hermes_agent package
Rewrite all import statements, patch() targets, sys.modules keys,
importlib.import_module() strings, and subprocess -m references to use
hermes_agent.* paths.

Strip sys.path.insert hacks from production code (rely on editable install).
Update COMPONENT_PREFIXES for logger filtering.
Fix 3 hardcoded getLogger() calls to use __name__.
Update transport and tool registry discovery paths.
Update plugin module path strings.
Add legacy process-name patterns for gateway PID detection.
Add main() to skills_sync for console_script entry point.
Fix _get_bundled_dir() path traversal after move.

Part of #14182, #14183
2026-04-23 08:35:34 +05:30

66 lines
2.6 KiB
Python

"""Tests for delegate_tool toolset scoping.
Verifies that subagents cannot gain tools that the parent does not have.
The LLM controls the `toolsets` parameter — without intersection with the
parent's enabled_toolsets, it can escalate privileges by requesting
arbitrary toolsets.
"""
from unittest.mock import MagicMock, patch
from types import SimpleNamespace
from hermes_agent.tools.delegate import _strip_blocked_tools
class TestToolsetIntersection:
"""Subagent toolsets must be a subset of parent's enabled_toolsets."""
def test_requested_toolsets_intersected_with_parent(self):
"""LLM requests toolsets parent doesn't have — extras are dropped."""
parent = SimpleNamespace(enabled_toolsets=["terminal", "file"])
# Simulate the intersection logic from _build_child_agent
parent_toolsets = set(parent.enabled_toolsets)
requested = ["terminal", "file", "web", "browser", "rl"]
scoped = [t for t in requested if t in parent_toolsets]
assert sorted(scoped) == ["file", "terminal"]
assert "web" not in scoped
assert "browser" not in scoped
assert "rl" not in scoped
def test_all_requested_toolsets_available_on_parent(self):
"""LLM requests subset of parent tools — all pass through."""
parent = SimpleNamespace(enabled_toolsets=["terminal", "file", "web", "browser"])
parent_toolsets = set(parent.enabled_toolsets)
requested = ["terminal", "web"]
scoped = [t for t in requested if t in parent_toolsets]
assert sorted(scoped) == ["terminal", "web"]
def test_no_toolsets_requested_inherits_parent(self):
"""When toolsets is None/empty, child inherits parent's set."""
parent_toolsets = ["terminal", "file", "web"]
child = _strip_blocked_tools(parent_toolsets)
assert "terminal" in child
assert "file" in child
assert "web" in child
def test_strip_blocked_removes_delegation(self):
"""Blocked toolsets (delegation, clarify, etc.) are always removed."""
child = _strip_blocked_tools(["terminal", "delegation", "clarify", "memory"])
assert "delegation" not in child
assert "clarify" not in child
assert "memory" not in child
assert "terminal" in child
def test_empty_intersection_yields_empty_toolsets(self):
"""If parent has no overlap with requested, child gets nothing extra."""
parent = SimpleNamespace(enabled_toolsets=["terminal"])
parent_toolsets = set(parent.enabled_toolsets)
requested = ["web", "browser"]
scoped = [t for t in requested if t in parent_toolsets]
assert scoped == []