package permission // 权限请求人类可读说明生成器. // // 当权限系统需要向用户询问时,生成清晰,有帮助的说明文本, // 让用户理解为什么需要授权以及操作的风险等级. // // 设计思路: // - 每种工具都有针对性的说明模板 // - 风险评估帮助用户快速判断是否安全 // - 建议规则减少重复授权的烦扰 import ( "fmt" "path/filepath" "strings" ) // RiskLevel 表示操作的风险等级. // 帮助用户快速判断是否需要仔细审查. type RiskLevel string const ( RiskLow RiskLevel = "low" // 只读操作,不会修改任何内容 RiskMedium RiskLevel = "medium" // 文件修改或包管理操作 RiskHigh RiskLevel = "high" // 系统命令,网络访问,危险操作 ) // SuggestedRule 是一条建议的永久权限规则. // 当用户反复批准同类操作时,可以建议添加永久规则以减少打扰. type SuggestedRule struct { // RuleString 是规则字符串,如 "Bash(prefix:npm)" RuleString string // Description 是规则的人类可读描述 Description string } // ExplainPermissionRequest 为权限请求生成人类可读的说明. // // 根据工具类型和输入参数,生成清晰的操作描述,帮助用户理解: // - 将要执行什么操作 // - 操作的具体内容(命令,文件路径,URL 等) // - 为什么需要授权 func ExplainPermissionRequest(toolName string, input map[string]any) string { switch toolName { case "Bash": return explainBash(input) case "Edit", "FileEdit": return explainEdit(input) case "Write", "FileWrite": return explainWrite(input) case "WebFetch": return explainWebFetch(input) case "Agent": return explainAgent(input) case "NotebookEdit": return explainNotebookEdit(input) default: return explainGeneric(toolName, input) } } // explainBash 生成 Bash 命令的权限说明. func explainBash(input map[string]any) string { command, _ := input["command"].(string) if command == "" { return "运行一个 Shell 命令" } // 提取主命令名用于说明 cmdName, subCmd := ExtractCommandName(command) cmdLower := strings.ToLower(cmdName) var detail string switch cmdLower { case "npm", "npx": detail = describePackageManager("npm", subCmd, command) case "yarn": detail = describePackageManager("yarn", subCmd, command) case "pnpm": detail = describePackageManager("pnpm", subCmd, command) case "pip", "pip3": detail = describePackageManager("pip", subCmd, command) case "go": detail = describeGoCommand(subCmd, command) case "git": detail = describeGitCommand(subCmd, command) case "rm", "rmdir": detail = "这将删除文件或目录,操作不可逆。" case "chmod": detail = "这将修改文件权限。" case "chown": detail = "这将修改文件所有者。" case "sudo", "su": detail = "这将以提升的权限执行命令,请仔细检查。" case "curl", "wget": detail = "这将从网络下载内容。" case "docker": detail = describeDockerCommand(subCmd) case "make": detail = "这将执行 Makefile 中定义的构建任务。" case "cargo": detail = describePackageManager("cargo", subCmd, command) default: // 短命令直接显示,长命令截断 if len(command) <= 80 { detail = "" } else { detail = "" } } // 构建说明文本 var sb strings.Builder // 显示完整命令,超长时截断 displayCmd := command if len(displayCmd) > 200 { displayCmd = displayCmd[:197] + "..." } sb.WriteString(fmt.Sprintf("运行命令: `%s`", displayCmd)) if detail != "" { sb.WriteString("\n") sb.WriteString(detail) } return sb.String() } // describePackageManager 描述包管理器命令. func describePackageManager(manager, subCmd, fullCmd string) string { switch strings.ToLower(subCmd) { case "install", "i", "add": return fmt.Sprintf("这将使用 %s 安装依赖包。", manager) case "uninstall", "remove", "rm": return fmt.Sprintf("这将使用 %s 移除依赖包。", manager) case "run": return fmt.Sprintf("这将执行 %s 脚本。", manager) case "test": return fmt.Sprintf("这将运行 %s 测试。", manager) case "build": return fmt.Sprintf("这将执行 %s 构建。", manager) case "init": return fmt.Sprintf("这将初始化新的 %s 项目。", manager) case "publish": return fmt.Sprintf("这将发布包到 %s 仓库,请确认是否需要。", manager) default: return fmt.Sprintf("这将执行 %s 命令。", manager) } } // describeGoCommand 描述 Go 工具链命令. func describeGoCommand(subCmd, fullCmd string) string { switch strings.ToLower(subCmd) { case "build": return "这将编译 Go 项目。" case "test": return "这将运行 Go 测试。" case "run": return "这将编译并运行 Go 程序。" case "get": return "这将下载并安装 Go 依赖。" case "mod": return "这将管理 Go 模块依赖。" case "install": return "这将编译并安装 Go 工具。" case "generate": return "这将执行 go generate 代码生成。" case "vet": return "这将对 Go 代码执行静态分析检查。" default: return "这将执行 Go 工具链命令。" } } // describeGitCommand 描述 Git 命令. func describeGitCommand(subCmd, fullCmd string) string { switch strings.ToLower(subCmd) { case "push": if strings.Contains(fullCmd, "--force") || strings.Contains(fullCmd, " -f") { return "这将强制推送到远程仓库,可能覆盖远程历史,请谨慎确认。" } return "这将推送提交到远程仓库。" case "pull": return "这将从远程仓库拉取并合并更新。" case "commit": return "这将创建一个新的 Git 提交。" case "reset": if strings.Contains(fullCmd, "--hard") { return "这将硬重置 Git 状态,未提交的更改将丢失。" } return "这将重置 Git 状态。" case "checkout": return "这将切换 Git 分支或恢复文件。" case "branch": return "这将管理 Git 分支。" case "merge": return "这将合并 Git 分支。" case "rebase": return "这将执行 Git 变基操作。" case "stash": return "这将暂存当前的工作更改。" case "clean": return "这将清理未跟踪的文件,操作不可逆。" case "clone": return "这将克隆远程仓库到本地。" default: return "这将执行 Git 命令。" } } // describeDockerCommand 描述 Docker 命令. func describeDockerCommand(subCmd string) string { switch strings.ToLower(subCmd) { case "build": return "这将构建 Docker 镜像。" case "run": return "这将运行 Docker 容器。" case "push": return "这将推送 Docker 镜像到仓库。" case "pull": return "这将拉取 Docker 镜像。" case "rm", "rmi": return "这将删除 Docker 容器或镜像。" default: return "这将执行 Docker 命令。" } } // explainEdit 生成文件编辑的权限说明. func explainEdit(input map[string]any) string { path := ExtractFilePath(input) if path == "" { return "修改一个文件" } oldStr, _ := input["old_string"].(string) newStr, _ := input["new_string"].(string) var sb strings.Builder sb.WriteString(fmt.Sprintf("修改文件: %s", path)) // 显示修改内容的摘要 if oldStr != "" { oldDisplay := truncateString(oldStr, 60) newDisplay := truncateString(newStr, 60) sb.WriteString(fmt.Sprintf("\n将 `%s` 替换为 `%s`", oldDisplay, newDisplay)) } return sb.String() } // explainWrite 生成文件写入的权限说明. func explainWrite(input map[string]any) string { path := ExtractFilePath(input) if path == "" { return "创建或覆写一个文件" } content, _ := input["content"].(string) size := len(content) var sb strings.Builder sb.WriteString(fmt.Sprintf("写入文件: %s", path)) if size > 0 { sb.WriteString(fmt.Sprintf(" (%s)", formatSize(size))) } // 判断是新建还是覆写 ext := strings.ToLower(filepath.Ext(path)) if ext != "" { sb.WriteString(fmt.Sprintf("\n文件类型: %s", ext)) } return sb.String() } // explainWebFetch 生成网页访问的权限说明. func explainWebFetch(input map[string]any) string { url := ExtractURL(input) if url == "" { return "访问一个网页" } domain := ExtractDomainFromURL(url) displayURL := url if len(displayURL) > 100 { displayURL = displayURL[:97] + "..." } return fmt.Sprintf("访问网页: %s\n域名: %s", displayURL, domain) } // explainAgent 生成子代理的权限说明. func explainAgent(input map[string]any) string { prompt, _ := input["prompt"].(string) if prompt == "" { prompt, _ = input["description"].(string) } if prompt == "" { return "启动一个子代理执行任务" } displayPrompt := truncateString(prompt, 120) return fmt.Sprintf("启动子代理: %s", displayPrompt) } // explainNotebookEdit 生成 Notebook 编辑的权限说明. func explainNotebookEdit(input map[string]any) string { path := ExtractFilePath(input) if path == "" { return "编辑一个 Jupyter Notebook" } return fmt.Sprintf("编辑 Notebook: %s", path) } // explainGeneric 生成通用工具的权限说明. func explainGeneric(toolName string, input map[string]any) string { return fmt.Sprintf("使用工具: %s", toolName) } // AssessRisk 评估工具操作的风险等级. // // 风险等级分类: // - Low: 只读操作,不会修改系统状态 // - Medium: 文件修改,包安装等可逆操作 // - High: 系统命令,网络访问,不可逆操作 func AssessRisk(toolName string, input map[string]any) RiskLevel { switch toolName { case "Bash": return assessBashRisk(input) case "Edit", "FileEdit": return assessFileRisk(input) case "Write", "FileWrite": return assessFileRisk(input) case "WebFetch": return RiskMedium case "Agent": return RiskMedium case "NotebookEdit": return RiskMedium case "Glob", "Grep", "Read", "FileRead", "WebSearch", "TaskList": return RiskLow default: return RiskMedium } } // assessBashRisk 评估 Bash 命令的风险等级. func assessBashRisk(input map[string]any) RiskLevel { command, _ := input["command"].(string) if command == "" { return RiskHigh // 空命令视为高风险 } cmdName, _ := ExtractCommandName(command) cmdLower := strings.ToLower(cmdName) // 高风险命令 highRiskCmds := map[string]bool{ "rm": true, "rmdir": true, "sudo": true, "su": true, "dd": true, "mkfs": true, "shutdown": true, "reboot": true, "kill": true, "killall": true, "pkill": true, "chmod": true, "chown": true, "chgrp": true, "curl": true, "wget": true, // 网络下载 } if highRiskCmds[cmdLower] { return RiskHigh } // 特定子命令高风险 if cmdLower == "git" { if strings.Contains(command, "push") || strings.Contains(command, "reset --hard") || strings.Contains(command, "clean -f") { return RiskHigh } } // 检查危险命令模式 if IsDangerousCommand(command) { return RiskHigh } // 低风险:只读命令 lowRiskCmds := map[string]bool{ "ls": true, "cat": true, "head": true, "tail": true, "grep": true, "find": true, "which": true, "whereis": true, "echo": true, "printf": true, "wc": true, "sort": true, "diff": true, "file": true, "stat": true, "type": true, "pwd": true, "date": true, "uname": true, "whoami": true, "env": true, "printenv": true, "tree": true, "du": true, "df": true, "free": true, "top": true, "ps": true, } if lowRiskCmds[cmdLower] { return RiskLow } // 中风险:包管理,构建工具等 mediumRiskCmds := map[string]bool{ "npm": true, "npx": true, "yarn": true, "pnpm": true, "pip": true, "pip3": true, "go": true, "cargo": true, "make": true, "cmake": true, "gcc": true, "g++": true, "python": true, "python3": true, "node": true, "deno": true, "rustc": true, "javac": true, "java": true, "docker": true, "git": true, "mkdir": true, "touch": true, "cp": true, "mv": true, "sed": true, "awk": true, "tee": true, } if mediumRiskCmds[cmdLower] { return RiskMedium } // 默认为中风险 return RiskMedium } // assessFileRisk 评估文件操作的风险等级. func assessFileRisk(input map[string]any) RiskLevel { path := ExtractFilePath(input) if path == "" { return RiskMedium } // 检查是否为敏感配置文件 if IsDangerousPath(path) { return RiskHigh } // 检查文件扩展名 ext := strings.ToLower(filepath.Ext(path)) highRiskExts := map[string]bool{ ".env": true, ".pem": true, ".key": true, ".crt": true, ".p12": true, ".pfx": true, ".jks": true, ".sh": true, ".bash": true, ".zsh": true, } if highRiskExts[ext] { return RiskHigh } // 检查文件名 base := strings.ToLower(filepath.Base(path)) highRiskNames := map[string]bool{ ".env": true, ".env.local": true, ".env.production": true, ".gitignore": true, ".gitconfig": true, ".npmrc": true, "dockerfile": true, "docker-compose.yml": true, "makefile": true, "cmakelists.txt": true, "package.json": true, "go.mod": true, "cargo.toml": true, } if highRiskNames[base] { return RiskHigh } return RiskMedium } // SuggestRules 为权限请求建议可添加的永久规则. // // 返回的建议规则可以告知用户:"如果你信任这类操作,可以添加规则以自动放行." func SuggestRules(toolName string, input map[string]any) []SuggestedRule { var suggestions []SuggestedRule switch toolName { case "Bash": suggestions = suggestBashRules(input) case "Edit", "FileEdit", "Write", "FileWrite": suggestions = suggestFileRules(toolName, input) case "WebFetch": suggestions = suggestWebFetchRules(input) } return suggestions } // suggestBashRules 为 Bash 命令建议规则. func suggestBashRules(input map[string]any) []SuggestedRule { command, _ := input["command"].(string) if command == "" { return nil } cmdName, subCmd := ExtractCommandName(command) if cmdName == "" { return nil } var rules []SuggestedRule // 建议基于主命令的规则 ruleStr := fmt.Sprintf("Bash(prefix:%s)", cmdName) desc := fmt.Sprintf("允许所有 %s 命令", cmdName) rules = append(rules, SuggestedRule{RuleString: ruleStr, Description: desc}) // 如果有子命令,建议更具体的规则 if subCmd != "" { specificRule := fmt.Sprintf("Bash(prefix:%s %s)", cmdName, subCmd) specificDesc := fmt.Sprintf("允许 %s %s 命令", cmdName, subCmd) rules = append(rules, SuggestedRule{RuleString: specificRule, Description: specificDesc}) } return rules } // suggestFileRules 为文件操作建议规则. func suggestFileRules(toolName string, input map[string]any) []SuggestedRule { path := ExtractFilePath(input) if path == "" { return nil } var rules []SuggestedRule // 建议基于目录的规则 dir := filepath.Dir(path) if dir != "" && dir != "." { dirRule := fmt.Sprintf("%s(%s/**)", toolName, dir) dirDesc := fmt.Sprintf("允许 %s 操作 %s/ 目录下的所有文件", toolName, dir) rules = append(rules, SuggestedRule{RuleString: dirRule, Description: dirDesc}) } // 建议基于文件扩展名的规则 ext := filepath.Ext(path) if ext != "" { extRule := fmt.Sprintf("%s(**/*%s)", toolName, ext) extDesc := fmt.Sprintf("允许 %s 操作所有 %s 文件", toolName, ext) rules = append(rules, SuggestedRule{RuleString: extRule, Description: extDesc}) } return rules } // suggestWebFetchRules 为网页访问建议规则. func suggestWebFetchRules(input map[string]any) []SuggestedRule { url := ExtractURL(input) if url == "" { return nil } domain := ExtractDomainFromURL(url) if domain == "" { return nil } ruleStr := fmt.Sprintf("WebFetch(domain:%s)", domain) desc := fmt.Sprintf("允许访问 %s 域名", domain) return []SuggestedRule{{RuleString: ruleStr, Description: desc}} } // truncateString 截断字符串到指定长度,超出部分用 ... 替代. func truncateString(s string, maxLen int) string { if len(s) <= maxLen { return s } if maxLen < 4 { return s[:maxLen] } return s[:maxLen-3] + "..." } // formatSize 将字节数格式化为人类可读的大小. func formatSize(bytes int) string { if bytes < 1024 { return fmt.Sprintf("%d 字节", bytes) } if bytes < 1024*1024 { return fmt.Sprintf("%.1f KB", float64(bytes)/1024) } return fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024)) }