feat: rm/cp/mv/chmod/chown 等文件操作命令支持(需人工审批)

- SHELL_WRITE_COMMANDS 新增 rm cp mv chmod chown mkdir touch ln
  所有文件操作命令任意参数都需人工审批
- _PIPE_DANGEROUS_TARGETS 移除 rm mv chmod chown,它们现在在
  写白名单中审批执行,不再直接拒绝
- 管道分支增加写表子命令不匹配时的回退处理
This commit is contained in:
Your Name
2026-07-25 14:42:30 +08:00
parent 8852e6d0ca
commit 3c3b7abda5
2 changed files with 25 additions and 4 deletions
+19 -3
View File
@@ -33,9 +33,10 @@ GLOBAL_FLAGS_WITH_VALUE = {
}
FORBIDDEN_TOKENS = {";", "||", "&&", "<", "<<", "`"}
# 管道 |、重定向 >、>>、$ 变量引用 允许在诊断中使用
# 但含 | 的命令中若管道后出现 sh/bash/rm/mv/chmod/chown/dd/format/mkfs 则拒绝
_PIPE_DANGEROUS_TARGETS = {"sh", "bash", "dash", "zsh", "rm", "mv", "chmod", "chown",
"dd", "format", "mkfs", "fdisk", "parted", "wget", "curl"}
# 但含 | 的命令中若管道后出现 sh/bash/dd/format/mkfs/fdisk/parted 则直接拒绝(武器级危险)
# rm/mv/chmod/chown — 现已在 SHELL_WRITE_COMMANDS 中,管道后会触发审批而非直接拒绝
_PIPE_DANGEROUS_TARGETS = {"sh", "bash", "dash", "zsh",
"dd", "format", "mkfs", "fdisk", "parted"}
@dataclass(frozen=True)
@@ -258,6 +259,15 @@ _ETCDCTL_WRITE_SUBVERBS = {
# 系统修改命令白名单:需要人工审批
SHELL_WRITE_COMMANDS: dict[str, set[str] | None] = {
"systemctl": {"restart", "start", "stop", "reload", "enable", "disable", "daemon-reload"},
# 文件操作命令:任意参数都需审批
"rm": None,
"cp": None,
"mv": None,
"chmod": None,
"chown": None,
"mkdir": None,
"touch": None,
"ln": None,
}
# ip 命令的写操作动词 (出现即拒绝自动执行)
@@ -338,6 +348,12 @@ def classify_shell_command(command: str) -> CommandDecision:
verb = _first_non_flag(before_parts[1:])
if verb is not None and verb in allowed:
return CommandDecision("write", True, binary, [command], needs_shell=True)
# binary 在写表但子命令不匹配写表 — 走只读白名单检查
if binary in SHELL_READ_ONLY_COMMANDS:
ro_allowed = SHELL_READ_ONLY_COMMANDS[binary]
if ro_allowed is None or (verb is not None and verb in ro_allowed):
return CommandDecision("read", False, binary, [command], needs_shell=True)
raise ValueError(f"命令 {binary} 不支持该子命令: {verb}")
return CommandDecision("read", False, binary, [command], needs_shell=True)
+6 -1
View File
@@ -84,8 +84,13 @@ class ShellCommandPolicyTest(unittest.TestCase):
self.assertTrue(decision.requires_approval)
def test_rejects_unknown_binary(self):
# rm 现在在写白名单中(需审批),不会直接拒绝
decision = classify_shell_command("rm -rf /tmp/cache")
self.assertEqual(decision.mode, "write")
self.assertTrue(decision.requires_approval)
# 完全不在任何白名单中的命令才拒绝
with self.assertRaises(ValueError):
classify_shell_command("rm -rf /")
classify_shell_command("hacktool -x")
def test_rejects_shell_operators_in_shell_command(self):
# 分号 ; 仍被拒绝