Skip to content

Commit 920a36d

Browse files
authored
adds builtin tool namespace for tool permission (#3205)
1 parent b407929 commit 920a36d

File tree

4 files changed

+61
-10
lines changed

4 files changed

+61
-10
lines changed

crates/chat-cli/src/cli/chat/cli/hooks.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ fn hook_matches_tool(hook: &Hook, tool_name: &str) -> bool {
7676

7777
// Use matches_any_pattern for both MCP and built-in tools
7878
let mut patterns = std::collections::HashSet::new();
79-
patterns.insert(pattern.clone());
79+
patterns.insert(pattern.as_str());
8080
matches_any_pattern(&patterns, tool_name)
8181
},
8282
}

crates/chat-cli/src/util/pattern_matching.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ use std::collections::HashSet;
33
use globset::Glob;
44

55
/// Check if a string matches any pattern in a set of patterns
6-
pub fn matches_any_pattern(patterns: &HashSet<String>, text: &str) -> bool {
6+
pub fn matches_any_pattern(patterns: &HashSet<&str>, text: &str) -> bool {
77
patterns.iter().any(|pattern| {
88
// Exact match first
9-
if pattern == text {
9+
if *pattern == text {
1010
return true;
1111
}
1212

@@ -30,7 +30,7 @@ mod tests {
3030
#[test]
3131
fn test_exact_match() {
3232
let mut patterns = HashSet::new();
33-
patterns.insert("fs_read".to_string());
33+
patterns.insert("fs_read");
3434

3535
assert!(matches_any_pattern(&patterns, "fs_read"));
3636
assert!(!matches_any_pattern(&patterns, "fs_write"));
@@ -39,7 +39,7 @@ mod tests {
3939
#[test]
4040
fn test_wildcard_patterns() {
4141
let mut patterns = HashSet::new();
42-
patterns.insert("fs_*".to_string());
42+
patterns.insert("fs_*");
4343

4444
assert!(matches_any_pattern(&patterns, "fs_read"));
4545
assert!(matches_any_pattern(&patterns, "fs_write"));
@@ -49,7 +49,7 @@ mod tests {
4949
#[test]
5050
fn test_mcp_patterns() {
5151
let mut patterns = HashSet::new();
52-
patterns.insert("@mcp-server/*".to_string());
52+
patterns.insert("@mcp-server/*");
5353

5454
assert!(matches_any_pattern(&patterns, "@mcp-server/tool1"));
5555
assert!(matches_any_pattern(&patterns, "@mcp-server/tool2"));
@@ -59,7 +59,7 @@ mod tests {
5959
#[test]
6060
fn test_question_mark_wildcard() {
6161
let mut patterns = HashSet::new();
62-
patterns.insert("fs_?ead".to_string());
62+
patterns.insert("fs_?ead");
6363

6464
assert!(matches_any_pattern(&patterns, "fs_read"));
6565
assert!(!matches_any_pattern(&patterns, "fs_write"));

crates/chat-cli/src/util/tool_permission_checker.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,43 @@ use tracing::debug;
55
use crate::util::MCP_SERVER_TOOL_DELIMITER;
66
use crate::util::pattern_matching::matches_any_pattern;
77

8+
const BUILT_IN_PREFIX: &str = "@builtin";
9+
const BUILT_IN_PREFIX_WITH_SLASH: &str = "@builtin/";
10+
811
/// Checks if a tool is allowed based on the agent's allowed_tools configuration.
912
/// This function handles both native tools and MCP tools with wildcard pattern support.
1013
pub fn is_tool_in_allowlist(allowed_tools: &HashSet<String>, tool_name: &str, server_name: Option<&str>) -> bool {
11-
let filter_patterns = |predicate: fn(&str) -> bool| -> HashSet<String> {
14+
let filter_patterns = |predicate: fn(&str) -> bool| -> HashSet<&str> {
1215
allowed_tools
1316
.iter()
1417
.filter(|pattern| predicate(pattern))
15-
.cloned()
18+
.map(String::as_str)
1619
.collect()
1720
};
1821

1922
match server_name {
2023
// Native tool
2124
None => {
22-
let patterns = filter_patterns(|p| !p.starts_with('@'));
25+
for name in allowed_tools {
26+
if name
27+
.strip_prefix(BUILT_IN_PREFIX)
28+
.is_some_and(|n| n.is_empty() || n == "/" || n == "/*")
29+
{
30+
return true;
31+
}
32+
}
33+
34+
let patterns = allowed_tools
35+
.iter()
36+
.filter_map(|p| {
37+
if !p.starts_with('@') {
38+
Some(p.as_str())
39+
} else {
40+
p.strip_prefix(BUILT_IN_PREFIX_WITH_SLASH)
41+
}
42+
})
43+
.collect::<HashSet<_>>();
44+
2345
debug!("Native patterns: {:?}", patterns);
2446
let result = matches_any_pattern(&patterns, tool_name);
2547
debug!("Native tool '{}' permission check result: {}", tool_name, result);
@@ -79,4 +101,30 @@ mod tests {
79101
assert!(is_tool_in_allowlist(&allowed, "read_file", Some("git")));
80102
assert!(!is_tool_in_allowlist(&allowed, "write_file", Some("git")));
81103
}
104+
105+
#[test]
106+
fn test_builtin_namespace() {
107+
let mut allowed = HashSet::new();
108+
allowed.insert("@builtin".to_string());
109+
allowed.insert("@builtin/".to_string());
110+
allowed.insert("@builtin/*".to_string());
111+
112+
// @builtin should allow all native tools
113+
assert!(is_tool_in_allowlist(&allowed, "fs_read", None));
114+
115+
// But should not allow MCP tools
116+
assert!(!is_tool_in_allowlist(&allowed, "tool", Some("server")));
117+
118+
allowed.clear();
119+
allowed.insert("@builtin/fs_read".to_string());
120+
121+
assert!(is_tool_in_allowlist(&allowed, "fs_read", None));
122+
assert!(!is_tool_in_allowlist(&allowed, "fs_write", None));
123+
124+
allowed.clear();
125+
allowed.insert("@builtin/fs_*".to_string());
126+
127+
assert!(is_tool_in_allowlist(&allowed, "fs_read", None));
128+
assert!(is_tool_in_allowlist(&allowed, "fs_write", None));
129+
}
82130
}

docs/agent-format.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ The `allowedTools` field supports glob-style wildcard patterns using `*` and `?`
212212
- **Server pattern**: `"@*-mcp/read_*"` → matches `@git-mcp/read_file`, `@db-mcp/read_data`
213213
- **Any tool from pattern servers**: `"@git-*/*"` → matches any tool from servers matching `git-*`
214214

215+
Optionally, you can also prefix native tools with the namespace `@builtin`.
216+
215217
### Examples
216218

217219
```json
@@ -226,6 +228,7 @@ The `allowedTools` field supports glob-style wildcard patterns using `*` and `?`
226228
"fs_*", // All filesystem tools
227229
"execute_*", // All execute tools
228230
"*_test", // Any tool ending in _test
231+
@builtin, // All native tools
229232

230233
// MCP tool wildcards
231234
"@server/api_*", // All API tools from server

0 commit comments

Comments
 (0)