|
18 | 18 | from ai_guardian.guard import Guard |
19 | 19 |
|
20 | 20 |
|
21 | | -# Tools that affect control flow must never be driven by untrusted data |
22 | | -_CONTROL_FLOW_RESOURCES = frozenset({"shell:exec", "agent:spawn", "code:eval"}) |
23 | | - |
24 | | -# Mapping from tool names to (resource_type, target_key) pairs |
| 21 | +# Tools that affect control flow must never be driven by untrusted data. |
| 22 | +# MCP tool calls are included because MCP tools can execute arbitrary |
| 23 | +# actions on remote servers (file I/O, network, code execution). |
| 24 | +_CONTROL_FLOW_RESOURCES = frozenset({ |
| 25 | + "shell:exec", |
| 26 | + "agent:spawn", |
| 27 | + "code:eval", |
| 28 | + "mcp:tool_call", |
| 29 | +}) |
| 30 | + |
| 31 | +# Mapping from tool names (lowercase) to (resource_type, target_key) pairs. |
| 32 | +# Lookup is case-insensitive; see _map_tool(). |
25 | 33 | _TOOL_RESOURCE_MAP: dict[str, tuple[str, str]] = { |
| 34 | + # Generic / SDK names |
26 | 35 | "read_file": ("file:read", "path"), |
27 | 36 | "write_file": ("file:write", "path"), |
28 | 37 | "edit_file": ("file:write", "file_path"), |
|
33 | 42 | "http_request": ("network:fetch", "url"), |
34 | 43 | "fetch": ("network:fetch", "url"), |
35 | 44 | "mcp_call": ("mcp:tool_call", "tool_name"), |
| 45 | + # Claude Code tool names (PascalCase -> lowercase keys) |
| 46 | + "read": ("file:read", "file_path"), |
| 47 | + "write": ("file:write", "file_path"), |
| 48 | + "edit": ("file:write", "file_path"), |
| 49 | + "glob": ("file:search", "pattern"), |
| 50 | + "grep": ("file:search", "pattern"), |
| 51 | + "webfetch": ("network:fetch", "url"), |
| 52 | + "websearch": ("network:search", "query"), |
| 53 | + "agent": ("agent:spawn", "prompt"), |
| 54 | + "notebookedit": ("file:write", "file_path"), |
| 55 | + "skill": ("agent:spawn", "skill"), |
36 | 56 | } |
37 | 57 |
|
38 | 58 |
|
@@ -76,14 +96,25 @@ def __repr__(self) -> str: |
76 | 96 | def _map_tool(tool_name: str) -> tuple[str, str]: |
77 | 97 | """Map a tool name to its (resource_type, target_key) pair. |
78 | 98 |
|
79 | | - Falls back to a generic "tool:{name}" resource with "input" as the |
80 | | - target key for unknown tools. |
| 99 | + Lookup is **case-insensitive** so both ``"Bash"`` (Claude Code) and |
| 100 | + ``"bash"`` (generic) resolve to ``("shell:exec", "command")``. |
| 101 | +
|
| 102 | + MCP tools (``mcp__*``) are mapped to ``mcp:tool_call``. |
| 103 | +
|
| 104 | + Falls back to a generic ``"tool:{name}"`` resource with ``"input"`` |
| 105 | + as the target key for unknown tools. |
81 | 106 | """ |
82 | | - if tool_name in _TOOL_RESOURCE_MAP: |
83 | | - return _TOOL_RESOURCE_MAP[tool_name] |
| 107 | + lower = tool_name.lower() |
| 108 | + |
| 109 | + if lower in _TOOL_RESOURCE_MAP: |
| 110 | + return _TOOL_RESOURCE_MAP[lower] |
| 111 | + |
| 112 | + # MCP tools: mcp__server__tool_name -> mcp:tool_call |
| 113 | + if lower.startswith("mcp__") or lower.startswith("mcp_"): |
| 114 | + return "mcp:tool_call", "input" |
84 | 115 |
|
85 | 116 | for prefix, (resource, _) in _TOOL_RESOURCE_MAP.items(): |
86 | | - if tool_name.startswith(prefix): |
| 117 | + if lower.startswith(prefix): |
87 | 118 | return resource, "input" |
88 | 119 |
|
89 | 120 | return f"tool:{tool_name}", "input" |
|
0 commit comments