Skip to content

znaide v1.0.3

Choose a tag to compare

@twowb twowb released this 10 Sep 04:04

znaide v1.0.3(2026-09-10)

危险命令判定重写。核心变化:不再对命令原文做子串匹配,改为把命令解析成 argv + 目标路径再判定。原先"多打一个空格就绕过"和"具体路径被误伤"这两类问题同时消失。

旧实现的问题

is_dangerous_command() 对整条命令字符串做 contains:

  • 漏网:"rm -rf /" 是字面量,rm -rf /(多一个空格)、rm -r -f /、rm --recursive --force /、rm -rf "/"、sudo rm -rf /、mkfs -t ext4 /dev/sdb1(mkfs. 的写法才拦)全部漏过;rm -rf ..(删父目录)不在表里。
  • 误伤:rm -rf /tmp/123/ 命中 "rm -rf /" 被硬拒;fdisk -l(只读列举)、dd if=x of=./img、git push --force-with-lease、echo "rm -rf /" 同理。
  • 不可覆盖:交互模式下命中即在弹确认框之前返回 Denied,人坐在旁边也无法放行(只有 yolo 能过)。

根因是拿文本当语义:要判的是"目标路径规范化后是否落在高危集",这是路径运算,不是字符串匹配。

新判定流水线

命令字符串
  │
  ├─ 1. 词法切分  split_segments()
  │      按 ; && || | & 换行 切成"简单命令段";处理 '…' / "…" / 反斜杠转义;
  │      > < 作为独立 token 保留(重定向目标单独判定);不做变量展开、不解析 $( )
  │
  ├─ 2. 变量替换  collect_assignments() + expand_word()
  │      收集同一条命令串内的"字面赋值"(值里不含 $ 或反引号),预置 HOME / PWD;
  │      展开 $VAR 与 ${VAR};未知变量原样保留 → 后续走 Uncertain
  │
  ├─ 3. 逐段取 argv,剥离前缀  strip_prefixes()
  │      跳过前置赋值 FOO=1 与 sudo / doas / env / command / builtin / exec /
  │      nohup / setsid / time / nice / ionice / stdbuf / timeout(含 -u root 这类带值选项)
  │      bash\|sh\|zsh… -c '…' 递归判定脚本文本(深度上限 3)
  │
  ├─ 4. 按命令分派规则  judge_segment()
  │      flag 集合(argv 层判定,空格数量、flag 顺序、合并写法都不影响)
  │      + 目标路径(非选项参数)
  │
  └─ 5. 目标路径规范化  classify_target() / resolve_path()
         展开 ~ → 相对路径按 cwd 绝对化 → 纯词法折叠 . 与 ..(不碰文件系统)
         → 与高危集比较 → Safe / Dangerous / Uncertain

多段命令取最严重的一段(Blocked > Uncertain > Safe);> >> 2> &> 后的目标若为块设备直接判高危。xargs rm … 的目标来自标准输入、判定时看不到 → Uncertain。

判定模型:三态

结果 含义 交互模式 无头模式
Blocked 目标解析出来确实高危 弹红色确认框,显示解析出的目标,只给单次放行(不提供"本会话都允许") 直接拒绝(bypassPermissions 也不放行)
Uncertain 目标无法判定(未知变量 / 通配符与 .. 混用 / xargs rm 等间接调用),或属于需点头的中风险(shutdown、reboot) 同样要人工点头,且不吃"本会话都允许" 按档位:全自动放行,其余拒绝
Safe 没解析出危险点 按权限档位处理 按权限档位处理

超级 yolo 跳过以上全部判定。

高危目标集(dangerous_why):根目录 /、家目录 ~、当前工作目录或其上级(cwd.starts_with(p),所以 rm -rf .、rm -rf ..、rm -rf .* 都命中)、块设备(前缀表:/dev/sd* /dev/hd* /dev/vd* /dev/xvd* /dev/nvme* /dev/mmcblk* /dev/loop* /dev/mapper/ /dev/dm- /dev/disk* /dev/rdisk* /dev/fd0 /dev/ram* \\.\PhysicalDrive*)。

各命令规则

命令 判高危的条件
rm 带递归 flag(-r / -R / --recursive,任意顺序与合并写法)且目标落在高危集
find 带 -delete 或 -exec/-execdir rm…,且起始目录落在高危集
dd of= 的值是块设备(或无法判定)
mkfs* 任意参数是块设备 / 高危路径(不要求 mkfs. 点号写法)
fdisk sfdisk parted sgdisk wipefs 带 -l / --list → 放行(只读列举);否则目标含块设备 → 高危
chmod chown chgrp setfacl 带 -R / --recursive 且目标落在高危集
truncate shred 目标是块设备
git push 带 --force / -f 且不是 --force-with-lease
shutdown reboot poweroff halt init 一律 Uncertain(降级为普通确认,不再当作"磁盘级破坏")

边界情况处理

  • 通配符:取通配符之前的片段所在目录做判定。通配符不会匹配 . / ..,安全目录内的通配符爬不出去 → rm -rf /tmp/* 放行、rm -rf /* 与 rm -rf .* 拦;通配符与 .. 混用(/tmp/*/../..)无法确定 → Uncertain。
  • 简单变量:T=/; rm -rf $T 能解析出 / → 直接高危(比"未知变量一律 Uncertain"更强);D=./build; rm -rf $D 解析成安全路径 → 放行。
  • 引号:echo "rm -rf /"、grep -rn 'rm -rf /' docs/ 现在按 argv 判定 → 放行(旧实现全部误伤)。

接口与接线变更

  • permissions::is_dangerous_command() 删除,替换为 permissions::inspect_command(cmd, cwd) -> CommandRisk。
  • Permission::check_command() 退化为纯档位判定(这个档位是否需要确认),不再内含风险判定——风险判定收敛到 session 层一处,否则交互模式下"用户已确认"的命令会在工具内被二次拒绝。
  • session::check_permission() 成为唯一风险闸门:判定 → 高危/无法判定时发 PermissionRequest;SessionEvent::PermissionRequest 新增 warning: Option<String>,带 warning 时不设置 command_always。
  • tools/shell.rs 的高危兜底改为只在 ctx.events.is_none()(无头 / 宿主直调)时生效。
  • TUI:带 warning 的确认框用红色边框 + "⚠ 高危命令确认"标题、弹窗加高、a(本会话都允许)键失效,并把解析出的目标/原因显示出来。

判定示例

命令 结果
rm -rf / / rm -r -f / / rm --recursive --force / / rm -rfv / Blocked(flag 与 argv 层判定,空格与顺序无关)
rm -rf "/" / sudo -u root rm -rf / / FOO=1 rm -rf / / bash -c 'rm -rf /' Blocked
rm -rf .. / rm -rf . / rm -rf .* / rm -rf /tmp/../.. Blocked(规范化后落在 cwd 上级 / 根)
T=/; rm -rf $T Blocked(字面赋值可解析)
rm -rf /tmp/123/ / rm -rf ~/x / rm -rf ./build Safe(具体路径)
rm -rf /tmp/* Safe(通配符出不了 /tmp);rm -rf /* 为 Blocked
rm -rf $UNKNOWN_DIR / xargs rm -rf / shutdown -h now Uncertain
dd if=/dev/zero of=/dev/sda Blocked;dd if=x of=./img、of=/dev/null 放行
mkfs -t ext4 /dev/sdb1 / chmod -R 777 / / find / -delete / echo x > /dev/sda Blocked
fdisk -l / fdisk -l /dev/sda / chmod -R 755 ./dir Safe
git push --force origin main、git push -f Blocked;git push --force-with-lease、git push origin main 放行

已知边界(不宣称安全)

这是启发式减速带,不是安全边界。文本层看不穿:变量间接赋值链、命令替换 $(…)、xargs / make / python -c / npx rimraf 之类的间接调用、以及执行时才产生的值。undo 仍只管 write_file/edit真要限制进程能碰什么,需要内核级隔离(Landlock / 命名空间 / seccomp,见 docs/plan-sandbox.md,这是下一步计划~~~我太忙了)。

验证

  • cargo test --workspace:core 94 / mock-e2e 13 / tui 77,全绿(其中 permissions 12 个、tools::shell 新增 2 个直接覆盖"无头高危拒绝"与"具体路径不误伤")。
  • cargo build --release 通过,--version 输出 1.0.3。
  • 未做:undo 快照仍只覆盖 write_file / edit(设计边界,使用说明 5.3 已如实描述)。