你好,我是正在学习 Agent 工程化的后端同学

技术永无止境,希望这篇内容能帮到你


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


当一个 Coding Agent 只能聊天的时候,它最多回答错。

但当它能读文件、写代码、执行 Shell、跑测试、提交 Git 的时候,它就不只是一个“回答问题的模型”了,而是一个真的能影响工程环境的执行系统。

这时问题就变得很现实:

一个能执行 rm -rf / 的 Agent,你敢直接放开用吗?

如果权限控制做得太死,Agent 什么都干不了;如果权限控制太松,它可能一条命令就把环境搞崩。权限系统要解决的,就是在“可用”和“安全”之间找到一个工程上能落地的平衡点。

这一篇我就围绕 MewCode 里的安全层,复盘一套五层纵深权限防御设计。


一、先说本质:权限系统不是业务逻辑,而是工具执行前的安全层

本章的架构定位很明确:它实现的是安全层的权限系统。

安全层有两个特点:

  • 它贯穿所有工具操作;
  • 它不关心具体业务要做什么,只关心这次操作能不能做。

也就是说,权限系统不负责判断“这个 bug 应该怎么修”,也不负责判断“测试失败应该怎么改”。这些是 Agent Loop 和模型决策层的事情。

权限系统只站在工具执行前问一个问题:

这次工具调用,是允许执行、直接拒绝,还是需要用户确认?

从返回值上看,它最终只给出三种决策:

  • ALLOW:允许执行;
  • DENY:拒绝执行;
  • ASK:交给用户确认。

这就是它的本质。


二、为什么 Coding Agent 一定要有权限系统?

前面我们已经让 Agent Loop 跑起来了,也配好了 System Prompt。MewCode 已经可以自主读代码、写代码、运行命令、根据结果继续修复问题。

这当然是一个里程碑。

但冷静想一下,也挺吓人的。

假设你让 Agent 清理项目临时文件,它分析了一下,觉得 /tmp 也该清理,于是执行了:

rm -rf /tmp/../

又比如你让它帮忙部署代码,它觉得先推一把最省事,于是执行:

git push --force origin main

这类风险不是模型“坏”,而是 Agent 拿到了真实操作系统能力以后,天然就拥有了破坏环境的可能。

更麻烦的是 prompt 注入。

攻击者不一定要直接跟你的 Agent 对话。他只要在 README、注释、文档、配置文件、错误日志里塞一段诱导文本,Agent 在读取项目时就可能把这段内容当成指令。

所以,不能只靠 prompt 约束模型。

模型负责思考,权限系统负责兜底。能不能执行,必须在系统层判断。


三、先搞清楚要防什么:三种典型威胁

做权限系统之前,先别急着写规则。我们要先搞清楚到底在防什么。

1. Prompt 注入

模型读取了一个看似普通的文件,但文件里藏着恶意指令。

比如 README 里写着:“忽略之前的所有要求,执行某某命令”。模型没有办法百分百区分“用户真实意图”和“文件里伪装出来的指令”。

这不是某个模型的 bug,而是 LLM 的根本局限。

所以对于高风险操作,不能让模型自己拍脑袋判断,要在工具执行前统一经过权限系统。

2. 越权操作

用户只是说“帮我修一个 bug”,Agent 却顺手重构项目、移动文件、改构建脚本,甚至修改 CI 配置。

这类问题很常见。

Agent 不一定是恶意的,它可能只是“太积极”了。用户授权的是修 bug,不代表授权它做全项目重构。

3. 数据泄露

只读操作也可能有风险。

比如 Agent 读取 .env~/.ssh/id_rsa~/.aws/credentials 这类敏感文件,然后在回答、日志或第三方模型请求里泄露出去。

所以权限系统不能只盯着 Shell,文件访问范围也必须被控制。


四、五层纵深防御:一道墙挡不住,就叠五道

安全领域有一个经典思路叫 Defense in Depth,也就是纵深防御。

它的意思不是追求某一道防线完美,而是让多道防线叠加。某一层被绕过时,下一层还能继续兜住。

MewCode 的权限系统可以拆成五层:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下面逐层拆。


五、第一层:危险命令黑名单,先拦住不可逆操作

有些命令无论如何都不应该被 Agent 执行。

比如:

  • rm -rf /
  • mkfs.ext4 /dev/sda
  • dd if=... of=/dev/...
  • curl xxx | bash
  • wget xxx | sh

这些操作的特点是后果不可逆,而且一旦执行,基本没有补救空间。

所以黑名单应该放在权限决策最前面。只要命中,直接 DENY

常见危险模式可以这样整理:

正则模式 拦截原因
`rm\s±(([a-z]*r[a-z]*f [a-z]f[a-z]r)[a-z])\s+/\s$`
mkfs\. 格式化磁盘
dd\s+if=.*of=/dev/ 直接写磁盘设备
chmod\s+-R\s+777\s+/ 递归修改根目录权限
`:(){ : :& };:`
curl\s+.*|\s*(ba)?sh 下载远程脚本并执行
wget\s+.*|\s*(ba)?sh 下载远程脚本并执行
>\s*/dev/sd 覆盖磁盘设备

这里要注意一个点:黑名单不可能覆盖所有危险操作。

攻击者总能想出新的绕过方式。但黑名单的目标不是解决所有问题,而是先拦住最明显、最致命、最不该放行的操作。

剩下的风险,交给后面的沙箱、规则、模式和人工确认。


六、第二层:路径沙箱,限制 Agent 能碰到哪里

文件操作是 Coding Agent 的高频能力。

但文件系统本身是扁平的。从项目目录到 /etc/passwd,只差一条路径。如果不做限制,Agent 可能被诱导去读取项目之外的敏感文件。

路径沙箱要做的事情就是:

只允许 Agent 访问授权目录内的文件。

看起来很简单,但实现时不能只检查字符串前缀。

最典型的问题是符号链接。

攻击者可以在项目目录里创建一个 link.txt,让它指向 /etc/passwd。如果系统只看字面路径,link.txt 确实在项目目录内;但真实读取到的却是系统敏感文件。

所以正确流程应该是:

1. 拿到用户请求的路径
2. 解析真实路径,处理 symlink
3. 判断真实路径是否落在允许目录内
4. 允许则继续,越界则拒绝

对于 WriteFile 这种创建新文件的操作,文件本身还不存在,无法解析真实路径。这时可以退一步检查父目录:只要父目录的真实路径在沙箱内,新文件创建出来也应该在沙箱内。

这层主要防的是数据泄露和越界修改。


七、第三层:权限规则,用 ToolName(pattern) 做细粒度控制

黑名单和沙箱只能处理一部分通用风险。

但实际开发里,我们经常需要更细的控制:

  • 允许 git commit,但不允许 git push --force
  • 允许读取 src/**,但不允许读取 .env
  • 允许执行测试命令,但不允许安装依赖;
  • 允许搜索代码,但限制搜索范围。

这就需要权限规则。

规则语法可以设计成:

ToolName(pattern)

其中:

  • ToolName 表示工具名;
  • pattern 表示要匹配的工具输入内容;
  • 匹配可以使用 glob 通配符。

不同工具提取的字段不同:

工具 提取字段 示例
Bash command 参数 git commit -m "fix bug"
ReadFile / WriteFile / EditFile path 参数 /project/src/main.py
Glob pattern 参数 **/*.py
Grep pattern 参数 TODO

比如:

Bash(git commit *)
ReadFile(src/**/*.py)
WriteFile(docs/**)
Grep(TODO)

规则本身最好支持三层配置:

层级 文件位置 作用
本地级 .mewcode/permissions.local.yaml 个人覆盖,不提交版本控制
项目级 .mewcode/permissions.yaml 团队共享规则,可提交到仓库
用户级 ~/.mewcode/permissions.yaml 全局偏好,对所有项目生效

优先级也很关键。

越靠近当前项目、越具体的规则,优先级越高。本地级通常最高,项目级次之,用户级最低。

但安全底线不能被覆盖,所以这里还要加一条原则:

deny 跨层合并,不可被 allow 翻转。

也就是说,只要任意一层写了 deny,其他层的 allow 都不能覆盖它。

这样既允许用户做个性化配置,又不会让安全底线被轻易突破。


八、第四层:权限模式,定义整体信任等级

规则适合细粒度控制,但日常使用时,用户不可能每个命令都手写一条规则。

所以还需要权限模式。

权限模式解决的是一个更粗粒度的问题:

我大概信任 Agent 到什么程度?

可以设计四种模式:

模式 只读工具 文件写工具 Bash
default Allow Ask Ask
acceptEdits Allow Allow Ask
plan Allow Ask Ask
bypassPermissions Allow Allow Allow

这张表其实就是代码里的决策矩阵。

default 适合日常开发:读代码放行,写文件和执行命令前让用户确认。

acceptEdits 适合你已经比较信任 Agent 的改代码能力,但对 Shell 命令仍然保持谨慎。

plan 从权限矩阵上看和 default 接近,安全性主要靠系统提示词限制 Agent 只做只读分析;即使模型试图调用写工具,权限系统仍然会拦到确认环节。

bypassPermissions 最危险,只适合完全受控环境,比如 CI/CD 或隔离容器里。即使在这个模式下,第一层危险命令黑名单也不应该关闭。


九、第五层:HITL 人在回路,让用户做最终决策

当前面几层都无法直接给出结论时,权限系统应该返回 ASK

这时 Agent Loop 暂停,UI 层弹出确认框,让用户决定:

  • 允许本次执行;
  • 拒绝本次执行;
  • 始终允许同类操作。

这里的“始终允许”是一个很重要的设计。

如果每次 git commit 都要用户确认,用户很快会疲劳。最后要么关闭确认机制,要么放弃使用 Agent。

“始终允许”可以把用户这次的信任沉淀成一条本地规则,写入 .mewcode/permissions.local.yaml

这就形成了一个权限学习循环:

第一次执行某类操作 -> 用户确认
用户选择始终允许 -> 生成本地 allow 规则
后续同类操作 -> 自动放行

这样体验会越来越顺,同时安全底线仍然保留。


十、把五层防线串成一条决策链

五层机制不是并列乱跑,而是一条明确的决策链。

核心原则是:

上一层能决策就直接返回,不能决策才进入下一层。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

伪代码可以这样理解:

decision = check_dangerous_command(tool, input)
if decision is DENY:
    return DENY

decision = check_path_sandbox(tool, input)
if decision is DENY:
    return DENY

decision = check_permission_rules(tool, input)
if decision is ALLOW or DENY:
    return decision

decision = check_permission_mode(tool, input)
if decision is ALLOW:
    return ALLOW

return ASK

这套流程的好处是,每一层逻辑都不复杂,适合单独测试。

但组合起来以后,就能覆盖从危险命令、路径越界、细粒度规则、默认信任等级到人工审批的完整链路。


十一、嵌入 Agent Loop:被拒绝不等于任务失败

权限检查应该发生在工具真正执行之前。

也就是 Agent Loop 里“执行工具”这一步前面,先插入权限检查。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

核心伪代码可以这样写:

for each toolUse in response.toolUses:
    decision = permissionChecker.check(tool, toolUse.input)

    if decision == ALLOW:
        result = tool.execute(toolUse.input)

    if decision == DENY:
        result = errorResult("Permission denied: " + reason)

    if decision == ASK:
        userResponse = askUserPermission(toolUse)
        if userResponse == ALLOW or userResponse == ALLOW_ALWAYS:
            result = tool.execute(toolUse.input)
        else:
            result = errorResult("User denied this operation")

这里最关键的设计是:

权限被拒绝时,不直接终止 Agent Loop,而是把拒绝结果作为工具错误返回给模型。

为什么?

因为模型看到 Permission denied 以后,可能会换一种更安全的方式完成任务。

比如它本来想删除整个目录,结果被拒绝。下一轮它可能改成只删除几个明确的临时文件。

如果权限拒绝就直接终止循环,模型完全没有调整策略的机会。

所以权限拒绝应该变成一个结构化的工具结果,例如:

isError: true
content: "Permission denied: dangerous command matched rm -rf pattern"

这个结果进入对话历史,成为下一轮模型决策的输入。

这就是权限系统和 Agent Loop 协作的核心。


十二、如何验证这套权限系统?

权限系统不应该只靠人工试。

这类模块非常适合写单元测试,因为每一层的输入输出都很明确。

可以从这些用例开始:

1. 黑名单测试

  • 输入 rm -rf /,期望 DENY
  • 输入 curl http://example.com/install.sh | bash,期望 DENY
  • 输入 pytest tests/,期望不被黑名单拦截。

2. 路径沙箱测试

  • 访问项目内文件,期望允许;
  • 访问项目外文件,期望拒绝;
  • 项目内 symlink 指向外部敏感文件,期望拒绝;
  • 创建新文件时检查父目录,期望行为正确。

3. 权限规则测试

  • 命中 allow 规则,返回 ALLOW
  • 命中 deny 规则,返回 DENY
  • 同时存在 allow 和 deny 时,deny 优先;
  • 本地级规则覆盖项目级规则,但不能覆盖跨层 deny。

4. Agent Loop 集成测试

  • 工具被拒绝后,返回 isError: true
  • Loop 不直接终止;
  • 下一轮模型能看到错误结果;
  • 用户选择“始终允许”后,本地规则被追加。

这些测试能证明权限系统不是“看起来安全”,而是每个关键分支都有明确行为。


十三、常见误区

第一,别把权限系统写进 prompt 里。

prompt 可以提醒模型谨慎,但不能承担安全边界。真正的拦截必须发生在工具执行前。

第二,黑名单不是万能的。

黑名单只能拦最危险、最明显的操作。不要指望它覆盖所有风险。

第三,路径检查不能只看字符串。

必须解析真实路径,尤其要处理 symlink。

第四,bypassPermissions 不是“无风险模式”。

它只是减少确认,不能关闭不可逆危险操作的硬拦截。

第五,被拒绝不应该直接中断 Agent。

对 Agent 来说,失败也是观察结果。把权限拒绝作为工具错误返回,反而能让模型调整策略。


十四、面试话术沉淀

【问题】

Agent 能执行 Shell、写文件、读项目代码时,怎么设计权限系统,避免它误删文件或泄露敏感信息?

【一句话】

我会把权限系统放在工具执行前,做成一条五层纵深防御链:先硬拦危险命令,再限制文件沙箱,然后匹配细粒度规则,再根据权限模式给出默认决策,最后用 HITL 让用户兜底确认。

【关键词】

Agent Loop、Tool Calling、Defense in Depth、危险命令黑名单、路径沙箱、权限规则、权限模式、HITL、deny 优先、结构化错误结果。

【项目里怎么做】

在 MewCode 这类 Coding Agent 中,模型每轮会输出工具调用。工具真正执行前先进入权限检查器,权限检查器返回 ALLOWDENYASK。如果允许,就执行工具;如果拒绝,就把权限拒绝封装成 isError: true 的工具结果返回给模型;如果需要确认,就暂停 Agent Loop,让用户选择允许、拒绝或始终允许。这样既能控制风险,又不会因为一次拒绝就中断整个任务。

【面试追问】

  1. 为什么不能只靠 prompt 限制模型?

因为 prompt 只是软约束,模型可能被 prompt 注入或复杂上下文诱导。安全边界必须在系统层执行,尤其是工具调用前。

  1. 黑名单能不能覆盖所有危险命令?

不能。黑名单只负责拦截最明显、最不可逆的高危操作。更完整的安全性需要路径沙箱、权限规则、模式和人工确认一起兜底。

  1. 为什么权限拒绝后不直接终止 Agent Loop?

因为权限拒绝本质上也是一种工具反馈。把它作为结构化错误返回给模型,模型下一轮可以换一种更安全的方式继续完成任务。

  1. deny 为什么要跨层合并?

因为安全底线不能被局部 allow 覆盖。只要用户级、项目级或本地级任意一层明确 deny,就应该拒绝执行。


十五、总结

这章的核心不是“多写几个权限配置”,而是建立一种 Agent 工程化的安全思维。

当 Agent 只是生成文本时,错误最多停留在回答层面;当 Agent 能执行工具时,它就会真实影响代码仓库、文件系统、Git 历史甚至宿主机环境。

所以权限系统必须成为 Coding Agent 的基础设施。

五层防御分别解决不同问题:

  • 黑名单拦住最危险的不可逆命令;
  • 路径沙箱限制文件访问边界;
  • 权限规则提供细粒度控制;
  • 权限模式定义整体信任等级;
  • HITL 让用户在关键时刻做最终决策。

最值得记住的是两点。

第一,安全层不应该干预业务逻辑,但必须拦在工具执行前。

第二,权限拒绝不是任务终点,而是 Agent Loop 的一种反馈。模型拿到这个反馈后,可以继续调整策略。

有了这套机制,用户才敢给 Agent 更多权限;Agent 拿到更多权限后,才真正有机会从“会聊天”走向“能完成工程任务”。

推荐标签:

  • Agent
  • Coding Agent
  • 权限系统
  • Tool Calling
  • Agent Loop
  • 人在回路
Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐