Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 19 additions & 68 deletions assets/settings/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -962,75 +962,26 @@
// Per-tool permission rules for granular control over tool actions.
// This setting only applies to the native Zed agent.
"tool_permissions": {
// Here are some examples of tool-specific permissions.
"tools": {
"terminal": {
"default_mode": "confirm",
"always_deny": [
// Dangerous rm commands
{ "pattern": "rm\\s+-rf\\s+(/|\\.\\.|\"|~|\\*)" },
{ "pattern": "rm\\s+-rf\\s*$" },
// Disk destruction
{ "pattern": "> /dev/sd" },
{ "pattern": "mkfs\\." },
{ "pattern": "dd\\s+if=/dev/(zero|random)" },
// Fork bomb
{ "pattern": ":\\(\\)\\{\\s*:\\|:&\\s*\\};:" },
// System files
{ "pattern": "/etc/passwd" },
{ "pattern": "/etc/shadow" },
// Windows destructive commands
{ "pattern": "del /f /s /q c:\\\\" },
{ "pattern": "format c:" },
{ "pattern": "rd /s /q" },
],
"always_confirm": [
// File deletion
{ "pattern": "rm\\s" },
// Destructive git operations
{ "pattern": "git\\s+(reset|clean)\\s+--hard" },
{ "pattern": "git\\s+push\\s+(-f|--force)" },
// Database operations
{ "pattern": "DROP\\s+TABLE", "case_sensitive": true },
{ "pattern": "DELETE\\s+FROM", "case_sensitive": true },
// Privileged commands
{ "pattern": "sudo\\s" },
],
},
"edit_file": {
"default_mode": "confirm",
"always_deny": [
// Secrets and credentials
{ "pattern": "\\.env($|\\.)" },
{ "pattern": "secrets?/" },
{ "pattern": "\\.pem$" },
{ "pattern": "\\.key$" },
],
},
"delete_path": {
"default_mode": "confirm",
"always_deny": [
// System directories
{ "pattern": "^/etc" },
{ "pattern": "^/usr" },
{ "pattern": "^/bin" },
{ "pattern": "^/sbin" },
{ "pattern": "^/var" },
{ "pattern": "^/boot" },
{ "pattern": "^/root" },
// Home directory root
{ "pattern": "^~$" },
{ "pattern": "^/Users/[^/]+$" },
{ "pattern": "^/home/[^/]+$" },
// Git directory
{ "pattern": "\\.git/?$" },
// Windows system directories
{ "pattern": "^C:\\\\Windows" },
{ "pattern": "^C:\\\\Program Files" },
],
},
"fetch": {
"default_mode": "confirm",
},
// "terminal": {
// "default_mode": "confirm",
// "always_confirm": [
// // Destructive git operations
// { "pattern": "git\\s+(reset|clean)\\s+--hard" },
// { "pattern": "git\\s+push\\s+(-f|--force)" },
// ],
// },
// "edit_file": {
// "default_mode": "confirm",
// "always_deny": [
// // Secrets and credentials
// { "pattern": "\\.env($|\\.)" },
// { "pattern": "secrets?/" },
// { "pattern": "\\.pem$" },
// { "pattern": "\\.key$" },
// ],
// },
},
},
// When enabled, agent edits will be displayed in single-file editors for review
Expand Down
111 changes: 0 additions & 111 deletions crates/agent_settings/src/agent_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,98 +542,6 @@ mod tests {
assert_eq!(permissions.invalid_patterns().len(), 2);
}

#[test]
fn test_default_json_tool_permissions_parse() {
let default_json = include_str!("../../../assets/settings/default.json");

let value: serde_json::Value = serde_json_lenient::from_str(default_json)
.expect("default.json should be valid JSON with comments");

let agent = value
.get("agent")
.expect("default.json should have 'agent' key");
let tool_permissions = agent
.get("tool_permissions")
.expect("agent should have 'tool_permissions' key");

let content: ToolPermissionsContent = serde_json::from_value(tool_permissions.clone())
.expect("tool_permissions should parse into ToolPermissionsContent");

let permissions = compile_tool_permissions(Some(content));

let terminal = permissions
.tools
.get("terminal")
.expect("terminal tool should be configured");
assert!(
!terminal.always_deny.is_empty(),
"terminal should have deny rules"
);
assert!(
!terminal.always_confirm.is_empty(),
"terminal should have confirm rules"
);
let edit_file = permissions
.tools
.get("edit_file")
.expect("edit_file tool should be configured");
assert!(
!edit_file.always_deny.is_empty(),
"edit_file should have deny rules"
);

let delete_path = permissions
.tools
.get("delete_path")
.expect("delete_path tool should be configured");
assert!(
!delete_path.always_deny.is_empty(),
"delete_path should have deny rules"
);

let fetch = permissions
.tools
.get("fetch")
.expect("fetch tool should be configured");
assert_eq!(
fetch.default_mode,
settings::ToolPermissionMode::Confirm,
"fetch should have confirm as default mode"
);
}

#[test]
fn test_default_deny_rules_match_dangerous_commands() {
let default_json = include_str!("../../../assets/settings/default.json");
let value: serde_json::Value = serde_json_lenient::from_str(default_json).unwrap();
let tool_permissions = value["agent"]["tool_permissions"].clone();
let content: ToolPermissionsContent = serde_json::from_value(tool_permissions).unwrap();
let permissions = compile_tool_permissions(Some(content));

let terminal = permissions.tools.get("terminal").unwrap();

let dangerous_commands = [
"rm -rf /",
"rm -rf ~",
"rm -rf ..",
"mkfs.ext4 /dev/sda",
"dd if=/dev/zero of=/dev/sda",
"cat /etc/passwd",
"cat /etc/shadow",
"del /f /s /q c:\\",
"format c:",
"rd /s /q c:\\windows",
];

for cmd in &dangerous_commands {
assert!(
terminal.always_deny.iter().any(|r| r.is_match(cmd)),
"Command '{}' should be blocked by deny rules",
cmd
);
}
}

#[test]
fn test_deny_takes_precedence_over_allow_and_confirm() {
let json = json!({
Expand Down Expand Up @@ -739,25 +647,6 @@ mod tests {
);
}

#[test]
fn test_default_json_fork_bomb_pattern_matches() {
let default_json = include_str!("../../../assets/settings/default.json");
let value: serde_json::Value = serde_json_lenient::from_str(default_json).unwrap();
let tool_permissions = value["agent"]["tool_permissions"].clone();
let content: ToolPermissionsContent = serde_json::from_value(tool_permissions).unwrap();
let permissions = compile_tool_permissions(Some(content));

let terminal = permissions.tools.get("terminal").unwrap();

assert!(
terminal
.always_deny
.iter()
.any(|r| r.is_match(":(){ :|:& };:")),
"Default deny rules should block the classic fork bomb"
);
}

#[test]
fn test_compiled_regex_stores_case_sensitivity() {
let case_sensitive = CompiledRegex::new("test", true).unwrap();
Expand Down