use echo_core::tools::ToolParameters;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ToolRiskCategory {
ReadOnly,
FileWrite,
ShellExec,
GitWrite,
DatabaseWrite,
NetworkCall,
Destructive,
}
impl ToolRiskCategory {
pub fn description(&self) -> &str {
match self {
Self::ReadOnly => "Read-only — no risk",
Self::FileWrite => "File will be modified or overwritten",
Self::ShellExec => "Arbitrary command execution",
Self::GitWrite => "Repository state will be changed",
Self::DatabaseWrite => "Database will be modified",
Self::NetworkCall => "Data may be sent over the network",
Self::Destructive => "Irreversible operation — data loss possible",
}
}
pub fn level(&self) -> u8 {
match self {
Self::ReadOnly => 0,
Self::NetworkCall => 1,
Self::FileWrite => 2,
Self::GitWrite => 2,
Self::DatabaseWrite => 2,
Self::ShellExec => 3,
Self::Destructive => 3,
}
}
pub fn permission_label(&self) -> &str {
match self {
Self::ReadOnly => "Read",
Self::FileWrite => "Edit",
Self::ShellExec => "Execute",
Self::GitWrite => "Git",
Self::DatabaseWrite => "Database",
Self::NetworkCall => "Network",
Self::Destructive => "Delete",
}
}
}
pub struct ToolRiskClassifier;
impl ToolRiskClassifier {
pub fn classify(tool_name: &str) -> ToolRiskCategory {
match tool_name {
"read_file" | "read_text" | "search" | "grep" | "list_files" | "git_log"
| "git_status" | "git_diff" | "git_blame" | "git_branch" => {
ToolRiskCategory::ReadOnly
}
"edit_file" | "write_file" | "append_file" | "create_file" | "update_file"
| "move_file" => {
ToolRiskCategory::FileWrite
}
"shell" | "execute" => {
ToolRiskCategory::ShellExec
}
"git_commit" | "git_add" | "git_push" | "git_tag" => {
ToolRiskCategory::GitWrite
}
"db_query" | "db_execute" | "sql" => {
ToolRiskCategory::DatabaseWrite
}
"web_fetch" | "web_search" | "http_request" | "api_call" => {
ToolRiskCategory::NetworkCall
}
"delete_file" | "rm" | "drop_table" | "truncate" => {
ToolRiskCategory::Destructive
}
_ => ToolRiskCategory::ReadOnly,
}
}
pub fn safety_notice(tool_name: &str, params: &ToolParameters) -> String {
let category = Self::classify(tool_name);
let path = params
.get("path")
.or_else(|| params.get("file_path"))
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let command = params
.get("command")
.and_then(|v| v.as_str())
.unwrap_or("");
match category {
ToolRiskCategory::ReadOnly => String::new(),
ToolRiskCategory::FileWrite => {
format!("Editing {path} — risk: file will be modified")
}
ToolRiskCategory::ShellExec => {
format!("Running: {command} — risk: arbitrary command execution")
}
ToolRiskCategory::GitWrite => {
format!("Git write to repository — risk: repository state change")
}
ToolRiskCategory::DatabaseWrite => {
format!("Database modification — risk: data corruption")
}
ToolRiskCategory::NetworkCall => {
format!("Network request — risk: data may leave this machine")
}
ToolRiskCategory::Destructive => {
format!("DELETING {path} — risk: irreversible data loss")
}
}
}
}