Files
odysseus/src/agent_tools/__init__.py
T
botinateandClaude Fable 5 544a664766 fix(security): block all bare email tool names for non-admins; harden fence-tag regex
Review follow-up on #3681 (thanks @vgalin):

1. Routing bare email names made 10 of the 14 email tools executable by
   non-admin owners — is_public_blocked_tool() runs on the bare name
   before dispatch, and NON_ADMIN_BLOCKED_TOOLS only listed 4. Define the
   full email tool set once (BUILTIN_EMAIL_TOOLS in tool_security.py) and
   derive the blocklist, the fence tags (TOOL_TAGS), the bare-name
   dispatch, and the native-call mapping from it so they can't drift.
   This also fixes 4 tools (search_emails, draft_email, draft_email_reply,
   ai_draft_email_reply) that were missing from the old tool_schemas copy
   and therefore unreachable even for native function-calling models.

2. The relaxed fence regex from the previous commit could prefix-match
   longer fence tags: ```python3 parsed as tool "python" with content
   "3\nprint(...)" and executed as code. Add a (?![\w-]) boundary after
   the tag.

Tests: test_public_agent_policy_blocks_sensitive_tools now covers all 14
bare email names + the mcp__email__ form; new tests/test_fenced_inline_args.py
pins inline-args parsing, the python3/hyphenated-tag non-matches, and
strip/parse display mirroring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 01:27:55 -07:00

138 lines
5.1 KiB
Python

"""
agent_tools.py — Facade module.
Re-exports tool parsing, schemas, execution, and implementations
for backward compatibility. All importers continue to work unchanged.
Sub-modules:
- tool_parsing.py: regex patterns, parse/strip functions
- tool_schemas.py: FUNCTION_TOOL_SCHEMAS, function_call_to_tool_block
- tool_execution.py: execute_tool_block, format_tool_result, MCP helpers
- tool_implementations.py: all do_* tool functions
"""
import logging
from collections import namedtuple
from src.tool_security import BUILTIN_EMAIL_TOOLS
from src.tool_utils import _truncate, get_mcp_manager, set_mcp_manager
logger = logging.getLogger(__name__)
from .subprocess_tools import BashTool, PythonTool
from .web_tools import WebSearchTool, WebFetchTool
from .filesystem_tools import ReadFileTool, WriteFileTool, EditFileTool, LsTool, GlobTool, GrepTool
from .document_tools import CreateDocumentTool, UpdateDocumentTool, EditDocumentTool, SuggestDocumentTool, ManageDocumentTool
TOOL_HANDLERS = {
"bash": BashTool().execute,
"python": PythonTool().execute,
"web_search": WebSearchTool().execute,
"web_fetch": WebFetchTool().execute,
"read_file": ReadFileTool().execute,
"write_file": WriteFileTool().execute,
"edit_file": EditFileTool().execute,
"ls": LsTool().execute,
"glob": GlobTool().execute,
"grep": GrepTool().execute,
"create_document": CreateDocumentTool().execute,
"update_document": UpdateDocumentTool().execute,
"edit_document": EditDocumentTool().execute,
"suggest_document": SuggestDocumentTool().execute,
"manage_documents": ManageDocumentTool().execute,
}
# ---------------------------------------------------------------------------
# Constants (re-exported for backward compatibility — single source of truth
# is src.constants; always prefer importing from there for new code)
# ---------------------------------------------------------------------------
MAX_AGENT_ROUNDS = 50
SHELL_TIMEOUT = 60
PYTHON_TIMEOUT = 30
# Tool types that trigger execution
TOOL_TAGS = {"bash", "python", "web_search", "web_fetch", "read_file", "write_file", "edit_file",
"grep", "glob", "ls",
"create_document", "update_document", "edit_document",
"search_chats",
"chat_with_model", "create_session", "list_sessions",
"send_to_session",
"pipeline",
"manage_session", "manage_memory", "list_models",
"ui_control", "generate_image", "ask_user", "update_plan",
"manage_tasks", "api_call", "ask_teacher", "manage_skills",
"suggest_document",
"manage_endpoints", "manage_mcp", "manage_webhooks",
"manage_tokens", "manage_documents", "manage_settings",
"manage_notes", "manage_calendar",
"resolve_contact", "manage_contact",
# Email tool names come from BUILTIN_EMAIL_TOOLS (unioned below)
# so the fence regex, dispatch, and non-admin blocklist all cover
# the same set.
# Cookbook tools (LLM serving + downloads). Without these
# entries, native function calls to e.g. list_served_models
# are rejected as "Unknown function call" before reaching
# the dispatcher — silent failure for the whole cookbook
# surface.
"download_model", "serve_model",
"list_served_models", "stop_served_model",
"list_downloads", "cancel_download",
"search_hf_models", "list_cached_models",
"list_serve_presets", "serve_preset", "adopt_served_model",
"list_cookbook_servers",
# Other tools the agent reaches for that were also missing.
"edit_image", "trigger_research", "manage_research",
# Generic loopback to any UI-button endpoint (cookbook,
# gallery, email folders, etc.) — agent uses this when
# there's no named tool wrapper for the action.
"app_api"} | BUILTIN_EMAIL_TOOLS
ToolBlock = namedtuple("ToolBlock", ["tool_type", "content"])
# ---------------------------------------------------------------------------
# Re-exports from sub-modules
# ---------------------------------------------------------------------------
# Parsing
from src.tool_parsing import ( # noqa: E402, F401
parse_tool_blocks,
strip_tool_blocks,
_TOOL_NAME_MAP,
_TOOL_BLOCK_RE,
_TOOL_CALL_RE,
_XML_TOOL_CALL_RE,
_XML_INVOKE_RE,
_XML_PARAM_RE,
)
# Schemas
from src.tool_schemas import ( # noqa: E402, F401
FUNCTION_TOOL_SCHEMAS,
function_call_to_tool_block,
)
# Execution
from src.tool_execution import ( # noqa: E402, F401
execute_tool_block,
format_tool_result,
)
# Document functions
from .document_tools import (
set_active_document,
set_active_model
)
# Implementations
from src.tool_implementations import ( # noqa: E402, F401
do_search_chats,
do_manage_skills,
do_manage_tasks,
do_manage_endpoints,
do_manage_mcp,
do_manage_webhooks,
do_manage_tokens,
do_manage_settings,
do_api_call,
)