从零构建基础AI代理:安全性第三部分
本文继续加强AI代理的安全性,实现三层策略门(路径范围、Shell黑名单、SSRF防护)、针对破坏性操作的始终确认模式、最小权限原则(通过工具白名单)以及资源/成本控制以防止失控循环。
在上一部分中,我们开始通过人类参与循环来填补安全漏洞:使用Docker沙箱来隔离失控命令、提示注入防御使模型不再信任工具输出作为指令,以及模式验证确保格式错误的工具调用不会被执行。在本部分中,我们完成剩余工作。我们将通过路径范围、Shell黑名单和SSRF防护来强化工具策略门,实施资源和成本限制以防止卡住的循环无限运行,从容器环境中清除秘密,并添加审计日志和紧急停止开关,以便记录每个决策并能够在飞行中终止任何会话。
工具策略强化
在“人类参与循环与安全性”部分中,check_permission是一个单一函数:读取工具和规划工具始终允许,写入工具在工作目录内允许(在acceptEdits模式下),其他所有操作需要询问。那是基于模式的决策。
现在我们添加一个策略门,它在模式决策之前运行。该门有三层:
路径范围: 每个路径参数都被解析(处理相对路径、符号链接和..遍历),如果逃逸项目工作目录则被拒绝。这阻止代理访问~/.ssh/id_rsa、/etc/passwd或项目树之外的任何内容。
Shell策略: 每个run_bash命令都通过危险模式的正则黑名单(fork炸弹、dd、mkfs、重定向到/etc/等)和代理绝不应调用的二进制文件令牌级黑名单(sudo、nc、curl、chmod、docker等)进行筛查。这捕捉到破坏性和数据外泄命令。
SSRF防护: 服务器端请求伪造是一种攻击,其中服务器端进程被诱骗向不应访问的内部资源发出请求。在代理上下文中,提示注入可能使webfetch访问内部服务或云元数据端点。防护解析URL的主机,检查结果IP是否属于回环、链路本地、多播以及RFC1918私有范围,并阻止它们。
def check_tool_policy(
tool_name: str,
args: dict[str, Any],
working_dir: Path,
) -> tuple[bool, str | None]:
"""运行所有策略层。返回 (allowed, reason)。
由 ``agent.check_permission`` 在基于模式的决策之前调用。
返回 False 表示硬阻止,任何模式都无法覆盖。
"""
# 第1层:路径范围。
ok, reason = check_path_scope(tool_name, args, working_dir)
if not ok:
return False, reason
# 第2层:Shell策略。
if tool_name == "run_bash":
ok, reason = check_shell_policy(args.get("command", ""))
if not ok:
return False, reason
# 第3层:Web/SSRF策略。
if tool_name == "webfetch":
ok, reason = check_web_policy(args.get("url", ""))
if not ok:
return False, reason
return True, None路径范围
旧的检查仅在写入工具上运行。现在,检查推广到每个使用路径的工具:read_file、glob_files、grep、write_file、edit_file。每个工具的路径参数被解析(处理相对路径、符号链接和..遍历),如果逃逸工作目录则被拒绝。
Shell策略
run_bash是最危险的工具,因此它有自己的筛查。每个命令运行两个检查。首先,危险模式的正则黑名单;然后,命令被词法解析,每个令牌与黑名单二进制文件比对。
Web策略(SSRF防护)
webfetch针对服务器端请求伪造进行筛查。云元数据端点、本地主机和私有IP范围均被阻止。
始终确认模式
在硬策略门之上,某些模式足够危险,无论权限模式如何,都需要明确确认。这些是不可逆/数据外泄类操作。check_permission函数现在具有三层结构:硬策略门、始终确认、模式决策。始终确认返回(bool, reason),以便此层携带其自己的特定原因。
最小权限原则
另一个控制适合此处:一个--tools CLI标志,接受逗号分隔的工具允许列表。检测器过滤进程内注册表和暴露给LLM的模式,因此模型甚至看不到它无法调用的工具。例如:python agent.py --tools read_file,glob_files,grep,run_bash 如果知道代理永远不需要写入文件,可以通过限制允许的工具来极大地缩小危险爆炸半径。
资源与成本控制
我们将向检测器添加一些资源和成本控制,以限制代理在我们不注意时失控。
硬迭代上限
IterationCaps跟踪两个计数器:turns(每次用户回合内的LLM响应次数,默认40,可通过--max-turns覆盖)和tool_calls(每次工具调用递增,默认200,可通过--max-tool-calls覆盖)。当任一计数器超过上限时,循环终止,并向用户返回一条消息,要求他们决定是否继续。对于总是启用此功能的用户,可以使用--iterations boundless标志。