Agent 开发进阶(七):给 AI 加上安全锁,权限系统的设计与实现
本文是「从零构建 Coding Agent」系列的第七篇,适合想让 Agent 安全可靠运行的开发者。
先问一个问题
当你的 Agent 越来越强大时,你是否担心过:
- 它可能会写错重要文件
- 它可能会执行危险的系统命令
- 它可能会在不该动手的时候动手
如果没有安全措施,这些担忧很可能变成现实。
Agent 的「安全隐患」问题
到了这一阶段,你的 Agent 已经具备了多种能力:
- 读写文件
- 执行系统命令
- 制定任务计划
- 压缩上下文
- 加载技能
但能力越大,风险也越大。如果让模型的「意图」直接变成「执行」,后果不堪设想。
这就是权限系统要解决的核心问题:在模型提议动作和系统执行动作之间,建立一道安全屏障。
权限系统的核心设计:四步管道
用一个图来表示权限系统的工作流程:
tool_call
|
v
1. deny rules -> 命中了就拒绝
|
v
2. mode check -> 根据当前模式决定
|
v
3. allow rules -> 命中了就放行
|
v
4. ask user -> 剩下的交给用户确认
关键点只有两个:
- 安全优先:危险操作先被挡住
- 分级处理:不同场景有不同的权限策略
几个必须搞懂的概念
权限系统
不是「有没有权限」这样一个简单的布尔值,而是一条管道,用来回答:
- 这次调用要不要直接拒绝?
- 能不能自动放行?
- 剩下的要不要问用户?
权限模式
系统当前的总体风格,例如:
- default:未命中规则时问用户
- plan:只允许读,不允许写
- auto:简单安全的操作自动放行
权限规则
「遇到某种工具调用时,该怎么处理」的小条款,例如:
{
"tool": "bash",
"content": "sudo *",
"behavior": "deny",
}
最小实现
1. 定义权限规则
# 拒绝规则
DENY_RULES = [
# 危险的 bash 命令
{"tool": "bash", "content": "sudo *", "behavior": "deny"},
{"tool": "bash", "content": "rm -rf *", "behavior": "deny"},
{"tool": "bash", "content": "shutdown *", "behavior": "deny"},
# 越界路径
{"tool": "write_file", "path": "/etc/*", "behavior": "deny"},
]
# 允许规则
ALLOW_RULES = [
# 安全的读取操作
{"tool": "read_file", "behavior": "allow"},
{"tool": "bash", "content": "ls *", "behavior": "allow"},
{"tool": "bash", "content": "git status", "behavior": "allow"},
]
# 工具分类
READ_ONLY_TOOLS = ["read_file", "search_code"]
WRITE_TOOLS = ["write_file", "edit_file", "bash"]
2. 权限检查函数
def check_permission(tool_name: str, tool_input: dict) -> dict:
"""检查工具调用的权限"""
# 1. 检查拒绝规则
for rule in DENY_RULES:
if matches_rule(rule, tool_name, tool_input):
return {"behavior": "deny", "reason": "匹配到拒绝规则"}
# 2. 检查权限模式
if current_mode == "plan" and tool_name in WRITE_TOOLS:
return {"behavior": "deny", "reason": "plan 模式禁止写操作"}
if current_mode == "auto" and tool_name in READ_ONLY_TOOLS:
return {"behavior": "allow", "reason": "auto 模式允许读操作"}
# 3. 检查允许规则
for rule in ALLOW_RULES:
if matches_rule(rule, tool_name, tool_input):
return {"behavior": "allow", "reason": "匹配到允许规则"}
# 4. 默认行为:询问用户
return {"behavior": "ask", "reason": "需要用户确认"}
def matches_rule(rule: dict, tool_name: str, tool_input: dict) -> bool:
"""检查工具调用是否匹配规则"""
if rule.get("tool") != tool_name:
return False
# 检查路径
if "path" in rule and "path" in tool_input:
import fnmatch
if not fnmatch.fnmatch(tool_input["path"], rule["path"]):
return False
# 检查内容
if "content" in rule and "content" in tool_input:
import fnmatch
if not fnmatch.fnmatch(tool_input["content"], rule["content"]):
return False
return True
3. 集成到工具执行流程
def execute_tool(tool_name: str, tool_input: dict) -> str:
"""执行工具,带权限检查"""
# 检查权限
decision = check_permission(tool_name, tool_input)
if decision["behavior"] == "deny":
return f"权限拒绝: {decision['reason']}"
if decision["behavior"] == "ask":
ok = ask_user_confirmation(tool_name, tool_input)
if not ok:
return "用户拒绝执行"
# 执行工具
handler = TOOL_HANDLERS.get(tool_name)
if not handler:
return f"未知工具: {tool_name}"
try:
result = handler(**tool_input)
return str(result)
except Exception as e:
return f"执行错误: {e}"
4. 权限模式管理
def set_mode(mode: str) -> str:
"""设置权限模式"""
global current_mode
if mode not in ["default", "plan", "auto"]:
return f"无效模式: {mode},可选值: default, plan, auto"
current_mode = mode
return f"权限模式已设置为: {mode}"
# 初始模式
current_mode = "default"
为什么顺序是这样
- 先检查 deny rules:危险操作应该优先被挡住,不应该交给模式去决定
- 再检查 mode:模式决定当前会话的整体策略方向
- 再检查 allow rules:安全的操作可以直接放行,提高效率
- 最后 ask user:前面都没覆盖的灰区,交给用户做最终决定
Bash 工具的特殊处理
Bash 工具是最危险的,因为它几乎能做任何事。即使是简单的实现,也应该挡住这些明显的危险点:
def check_bash_safety(command: str) -> bool:
"""检查 bash 命令的安全性"""
dangerous_patterns = [
"sudo", # 提权
"rm -rf", # 强制递归删除
"shutdown", # 关机
"reboot", # 重启
"> /dev/null", # 丢弃输出
"| bash", # 管道到 bash
"$(", # 命令替换
"`", # 反引号命令替换
]
for pattern in dangerous_patterns:
if pattern in command:
return False
return True
# 在 matches_rule 中添加 bash 安全检查
def matches_rule(rule: dict, tool_name: str, tool_input: dict) -> bool:
# ... 现有代码 ...
# 额外检查 bash 安全性
if tool_name == "bash" and "content" in tool_input:
if not check_bash_safety(tool_input["content"]):
return True # 匹配到危险模式
return False
新手最容易犯的 5 个错
1. 把权限系统做成简单开关
# ❌ 错误
if is_allowed:
execute_tool()
else:
return "权限拒绝"
# ✅ 正确
# 实现完整的四步权限管道
权限系统不是简单的开关,而是分级处理的管道。
2. 把 allow 规则放在 deny 规则前面
# ❌ 错误
if matches_allow_rule():
return "allow"
if matches_deny_rule():
return "deny"
# ✅ 正确
if matches_deny_rule():
return "deny"
if matches_allow_rule():
return "allow"
安全优先,危险操作应该先被挡住。
3. 不特殊处理 bash 工具
Bash 工具几乎能做任何事,应该被当作特殊情况处理。
4. 一开始就做复杂的权限模式
# ❌ 错误
modes = ["default", "plan", "auto", "admin", "readonly", "maintenance"]
# ✅ 正确
# 先实现三种基础模式:default, plan, auto
先把基础模式做稳,再考虑扩展。
5. 直接执行工具调用,跳过权限检查
# ❌ 错误
result = handler(**tool_input) # 直接执行
# ✅ 正确
decision = check_permission(tool_name, tool_input) # 先检查权限
if decision["behavior"] == "allow":
result = handler(**tool_input)
任何工具调用都应该经过权限检查。
为什么这很重要
因为 Agent 之所以「可靠」,不是因为它永远不会犯错。
而是因为系统为它设置了一道安全防线,确保它的行为在可控范围内。
就像人类社会一样:自由不是无边界的,而是在规则约束下的安全自由。
推荐的实现步骤
- 第一步:先实现三种基础模式(default、plan、auto)
- 第二步:实现 deny 和 allow 两类规则
- 第三步:为 bash 工具添加最小安全检查
- 第四步:添加拒绝计数,防止 Agent 卡住
权限系统与后续章节的关系
- s07 权限系统:决定「能不能执行」
- s08 执行钩子:决定「执行前后还能不能插入额外逻辑」
- s10 Prompt 管道:会把当前模式和权限说明放进 prompt 组装里
所以权限系统是后面很多机制的安全前提。
下一章预告
有了权限系统,你的 Agent 已经具备了基本的安全保障。下一章我们将探讨执行钩子机制,让你能够在工具执行前后插入自定义逻辑,进一步增强系统的可控性。
一句话总结:权限系统不是为了让 Agent 更笨,而是为了让 Agent 的行动先经过一道可靠的安全判断。
如果觉得有帮助,欢迎关注,我会持续更新「从零构建 Coding Agent」系列文章。