// risk.go - 命令风险评估(黑名单 + 动态分级) // Logan.Li // 移植自 opsxcli internal/agent/safety/risk.go,置于 tools 包避免与 safety 循环依赖。 // 供 ssh_execute 的 DynamicRiskTool.RiskLevelForArgs 调用,决定是否触发用户确认。 package tools import "strings" // dangerousCommands 危险命令黑名单(系统损坏/数据丢失/服务中断) // Logan.Li var dangerousCommands = []string{ "rm -rf /", "rm -rf /*", "rm -rf /bin", "rm -rf /boot", "rm -rf /etc", "rm -rf /home", "rm -rf /lib", "rm -rf /lib64", "rm -rf /proc", "rm -rf /root", "rm -rf /sbin", "rm -rf /sys", "rm -rf /usr", "rm -rf /var", "mkfs", "mkfs.ext", "mkfs.xfs", "mkfs.btrfs", "mkfs.vfat", "dd if=/dev/zero", "dd if=/dev/random", "dd if=/dev/urandom", ":(){ :|:& };:", ":() { : | : & }; :", "> /dev/sda", "> /dev/sdb", "> /dev/sdc", "> /dev/hda", "mv / /dev/null", "mv /* /dev/null", "chmod -R 000 /", "chmod -R 777 /", "systemctl stop ssh", "systemctl disable ssh", "kill -9 1", "iptables -F", "iptables --flush", } // readOnlyCommands 只读命令(安全) // Logan.Li var readOnlyCommands = []string{ "cat", "grep", "egrep", "fgrep", "awk", "sed", "head", "tail", "less", "more", "sort", "uniq", "wc", "ps", "top", "df", "du", "free", "uptime", "uname", "hostname", "ifconfig", "ip addr", "ip link", "ip route", "netstat", "ss", "ls", "ll", "pwd", "who", "w", "last", "vmstat", "iostat", "mpstat", "sar", "lsof", "find", "which", "whereis", "file", "stat", "date", "echo", "id", "whoami", "groups", "getent", "dmesg", "systemctl status", "systemctl list-units", "lsblk", "blkid", "mount", "journalctl", } // modificationCommands 文件修改命令(低-中风险) // Logan.Li var modificationCommands = []string{ "cp", "mv", "mkdir", "rmdir", "touch", "chmod", "chown", "chgrp", "ln", "tar", "gzip", "gunzip", "zip", "unzip", "rsync", "tee", } // highRiskCommands 高风险命令(服务控制/删除/重启) // Logan.Li var highRiskCommands = []string{ "rm", "dd", "shutdown", "reboot", "halt", "poweroff", "init", "systemctl restart", "systemctl stop", "systemctl start", "kill", "killall", "pkill", "userdel", "groupdel", "fdisk", "parted", "mkfs", } // IsDangerousCommand 检查命令是否命中黑名单 // Logan.Li // 返回 (是否危险, 命中的模式)。 func IsDangerousCommand(command string) (bool, string) { trimmed := strings.TrimSpace(command) if trimmed == "" { return false, "" } lower := strings.ToLower(trimmed) for _, d := range dangerousCommands { if strings.Contains(lower, strings.ToLower(d)) { return true, d } } // rm -rf + 根目录路径特殊检测 if strings.HasPrefix(lower, "rm") && strings.Contains(lower, "-rf") && containsRootPath(lower) { return true, "rm -rf 包含根目录路径" } return false, "" } // containsRootPath 检测是否含系统根目录路径 // Logan.Li func containsRootPath(cmd string) bool { paths := []string{" /boot", " /etc", " /usr", " /var", " /lib", " /lib64", " /sbin", " /bin", " /opt", " /home", " /root", " /tmp", " /var/log"} for _, p := range paths { if strings.Contains(cmd, p) { return true } } return false } // AssessBashRisk 动态评估命令风险等级 // Logan.Li // 黑名单→Critical;纯只读→Safe;组合命令取最高;sudo 提升。 func AssessBashRisk(command string) RiskLevel { trimmed := strings.TrimSpace(command) if trimmed == "" { return RiskSafe } lower := strings.ToLower(trimmed) if isDangerous, _ := IsDangerousCommand(command); isDangerous { return RiskCritical } if isReadOnlyCommand(lower) { return RiskSafe } if strings.Contains(lower, "&&") || strings.Contains(lower, "||") || strings.Contains(lower, "|") || strings.Contains(lower, ";") { return assessCompoundCommand(lower) } isSudo := strings.HasPrefix(lower, "sudo ") return assessSingleCommand(strings.TrimPrefix(lower, "sudo "), isSudo) } // isReadOnlyCommand 是否纯只读命令 // Logan.Li func isReadOnlyCommand(cmd string) bool { cmd = strings.TrimPrefix(cmd, "sudo ") cmd = strings.TrimSpace(cmd) for _, ro := range readOnlyCommands { if strings.HasPrefix(cmd, strings.ToLower(ro)) { return true } } return false } // assessCompoundCommand 组合命令取子命令最高风险 // Logan.Li func assessCompoundCommand(cmd string) RiskLevel { maxRisk := RiskSafe parts := []string{cmd} for _, sep := range []string{"&&", "||", "|", ";"} { var next []string for _, p := range parts { for _, sp := range strings.Split(p, sep) { sp = strings.TrimSpace(sp) if sp != "" { next = append(next, sp) } } } parts = next } for _, p := range parts { if r := assessSingleCommand(strings.TrimPrefix(p, "sudo "), false); r > maxRisk { maxRisk = r } } if strings.HasPrefix(cmd, "sudo ") && maxRisk < RiskCritical { maxRisk++ } return maxRisk } // assessSingleCommand 单命令风险(按前缀分类) // Logan.Li func assessSingleCommand(cmd string, isSudo bool) RiskLevel { cmd = strings.TrimSpace(cmd) fields := strings.Fields(cmd) if len(fields) == 0 { return RiskSafe } main := strings.ToLower(fields[0]) for _, h := range highRiskCommands { if main == strings.ToLower(h) || strings.HasPrefix(main, strings.ToLower(h)) { if isSudo { return RiskHigh } return RiskMedium } } for _, m := range modificationCommands { if main == strings.ToLower(m) { if isSudo { return RiskMedium } return RiskLow } } if isSudo { return RiskMedium } return RiskLow }