企业级 Agent Runtime 的第一道防线:安全沙箱
当我们谈论企业级 AI Agent Runtime 时,第一个需要解决的问题不是”模型有多聪明”,而是”Agent 执行的代码有多安全”。一个能读写文件、执行命令、访问网络的 Agent,如果没有安全边界,就是一颗不知道什么时候会爆炸的定时炸弹。
企业级的 Agent Runtime 首先需要一个安全沙箱:文件系统隔离 + 网络访问隔离。
为什么 Agent Runtime 必须有沙箱?
传统软件运行在用户的权限下,执行的是人类编写的、经过代码审查的确定性代码。AI Agent 完全不同——它执行的代码来自大模型的实时推理,具有不确定性。更危险的是,Agent 可能被**提示注入(Prompt Injection)**攻击劫持,执行攻击者想要的操作。
没有沙箱的 Agent Runtime,面临的风险是真实且严重的:
风险一:凭证窃取
1 | # Agent 被提示注入后可能执行的命令 |
用户的 SSH 私钥、云服务凭证、包管理器的认证令牌——所有敏感文件对无沙箱的 Agent 一览无余。
风险二:数据外泄
1 | # 读取敏感文件并发送到攻击者服务器 |
即使 Agent 读到了敏感数据,如果网络被隔离,数据也送不出去。但没有网络隔离,一切都是透明的。
风险三:持久化后门
1 | # 在用户的 shell 配置中注入恶意代码 |
Agent 有写文件的能力,就有能力在用户不知情的情况下植入持久化后门。
风险四:供应链投毒
1 | { |
Agent 执行 npm install 时,恶意包的 postinstall 脚本就会运行。没有沙箱,这些脚本和 Agent 拥有完全相同的权限。
这些不是理论攻击——每一种都有真实的 CVE 和安全事件。 这就是为什么企业级 Agent Runtime 的安全沙箱不是可选项,而是必选项。
安全沙箱的两大支柱
一个完整的 Agent 安全沙箱需要两种隔离能力协同工作:
1 | ┌──────────────────────────────────────────────────┐ |
单独的文件隔离不够——Agent 可以读到敏感文件后,通过网络发送出去。
单独的网络隔离也不够——Agent 可以把数据写入文件,通过 Git push 等合法渠道间接外泄。
两者必须同时存在,形成纵深防御(Defense in Depth)。
macOS 的 Seatbelt(沙箱)机制
macOS 的沙箱机制内部代号 Seatbelt,是苹果从 macOS 10.5 (2007) 开始引入的操作系统级安全能力。它的架构分三层:
1 | 用户空间: sandbox-exec / App Sandbox 权限声明 |
核心原理:Deny Default
Seatbelt 的沙箱配置文件使用类 Scheme 语法编写,最关键的设计是 deny default——默认拒绝一切,只放行明确允许的操作:
1 | (version 1) |
当一个进程运行在这个沙箱中时,内核的 TrustedBSD MAC 框架会在每一个安全相关的系统调用上设置拦截点。不是在系统调用之后检查,而是在操作执行之前判定是否放行。被拒绝的操作直接返回 EPERM(Operation not permitted),进程无法绕过。
文件系统隔离实战
Seatbelt 控制的文件操作粒度非常细:
操作
含义
file-read-data
读取文件内容
file-read-metadata
读取文件元信息 (stat)
file-write-data
写入文件内容
file-write-create
创建新文件
file-write-unlink
删除文件
file-mount
挂载文件系统
路径过滤支持三种模式:
1 | ;; literal: 精确匹配 |
实际效果演示:
1 | # 在沙箱中尝试读取 SSH 密钥 |
操作系统内核直接拒绝了这些操作。不是 Agent Runtime 在应用层做检查,而是内核本身在强制执行。即使 Agent 的代码绕过了 Runtime 的所有检查,操作系统也不会放行。
网络访问隔离实战
Seatbelt 同样可以精确控制网络行为:
1 | ;; 完全禁止网络访问 |
实际效果演示:
1 | # 使用 no-internet 配置文件运行命令 |
不可逆性:安全的根本保证
Seatbelt 最重要的安全特性之一:沙箱一旦应用到进程,就不能被移除或放宽。子进程会继承父进程的沙箱,并且只能添加更多限制,不能减少。
1 | // 沙箱只能变紧,不能变松 |
这意味着即使攻击者在沙箱内获得了代码执行能力,也不能通过任何编程手段关闭沙箱——必须找到一个操作系统内核漏洞才能逃逸。这将攻击的成本提升了一个数量级。
真实案例:Safari 沙箱阻止攻击链
2016-2019 年间,多个针对 Safari 浏览器的零日攻击被发现。攻击者利用 WebKit 渲染引擎的漏洞获得了代码执行权限,但因为 Safari 的渲染进程运行在 Seatbelt 沙箱中:
- 无法读取
~/.ssh/或其他敏感文件 - 无法向攻击者服务器发送数据
- 无法获取 Mach 端口进行提权
攻击者必须额外链接一个沙箱逃逸漏洞 + 一个内核提权漏洞,总共三个零日漏洞才能完成完整攻击。 这就是沙箱的价值——不是让攻击变得不可能,而是让攻击的成本变得极高。
Windows 的 AppContainer 隔离机制
Windows 从 Windows 8 开始引入了 AppContainer 隔离机制,这是所有 UWP 应用和现代浏览器(Edge、Chrome、Firefox)沙箱的基础。
核心原理:基于安全令牌的隔离
AppContainer 的设计与 macOS Seatbelt 截然不同。它不使用配置文件,而是通过修改进程的**安全访问令牌(Security Token)**实现隔离:
1 | 普通进程的安全令牌: |
关键差异在访问检查算法上:
1 | 普通进程的访问检查: |
这意味着即使一个文件对 Everyone 授予了完全访问权限,AppContainer 进程仍然无法访问它——因为第一轮 AppContainer 检查就会失败。AppContainer 进程从零权限开始,必须被逐一授予每种访问能力。
文件系统隔离
每个 AppContainer 应用获得自己的隔离存储空间:
1 | C:\Users\{用户名}\AppData\Local\Packages\{应用包名}\ |
实际效果:
- AppContainer 应用无法读取
%APPDATA%、%PROGRAMFILES%或其他应用的数据 - 无法访问桌面、文档等用户目录(除非声明了对应的能力)
- 无法枚举其他已安装的应用或它们的数据
- 无法访问浏览器的 Cookie、密码数据库
- 即使应用被卸载,其隔离存储空间也会被清理干净
以代码方式创建 AppContainer 并启动隔离进程:
1 |
|
网络访问隔离
AppContainer 实现了默认拒绝的网络模型。一个 AppContainer 进程完全没有网络能力,除非显式授予:
能力
含义
internetClient
允许出站互联网连接
internetClientServer
允许入站 + 出站互联网连接
privateNetworkClientServer
允许访问内网(家庭/企业网络)
网络限制由 Windows Filtering Platform (WFP) 在内核态网络栈中执行:
1 | 应用层 (Winsock / WinHTTP) |
WFP 的内核态执行意味着:即使攻击者绕过了用户态 API 直接发起内核调用,网络限制仍然有效。
更严格的是,AppContainer 进程默认无法访问 localhost。这阻止了沙箱内的进程与本机上运行的其他服务通信,防止了通过本地服务中转的攻击路径。
真实案例:浏览器渲染进程隔离
Microsoft Edge 和 Google Chrome 都使用 AppContainer 来隔离渲染进程:
1 | Edge 浏览器的进程架构: |
2020 年,一个 Chromium V8 引擎的漏洞被利用来在渲染进程中执行任意代码。但因为渲染进程运行在 AppContainer 中:
- 无法读取用户文件(DACL 中没有该 AppContainer SID 的 ACE)
- 无法发起网络连接(没有网络能力 SID)
- 无法与其他进程通信(localhost 被阻止)
- 攻击者需要再找一个沙箱逃逸漏洞才能扩大战果
Claude Code 的沙箱实践:sandbox-runtime
Anthropic 为 Claude Code 开发了 @anthropic-ai/sandbox-runtime(简称 srt),这是目前 AI Agent 领域最成熟的客户端沙箱实现之一。它的设计思路完全符合我们上面讨论的两大支柱。
跨平台架构
srt 在不同操作系统上使用不同的原生沙箱机制:
1 | macOS: sandbox-exec + 动态生成的 Seatbelt 配置文件 |
文件系统隔离配置
1 | { |
规则语义:
- 读取采用 deny-then-allow 模式:默认允许读取,通过
denyRead排除敏感路径 - 写入采用 allow-only 模式:默认拒绝所有写入,只开放
allowWrite指定的路径 denyWrite优先级高于allowWrite,即使在允许写入的目录中也能排除特定文件
网络隔离配置
1 | { |
在 macOS 上,srt 生成的 Seatbelt 配置文件只允许连接到本地代理端口,所有流量必须经过代理服务器进行域名过滤。在 Linux 上,更激进地使用 --unshare-net 直接移除网络命名空间,所有流量通过 Unix 域套接字转发到宿主机的代理。
实际效果
1 | # 正常操作:可以读取项目文件 |
为什么权限提示(Permission Prompt)不够?
有人可能会问:让用户在 Agent 执行每个命令前确认,不就够了吗?
答案是:不够,而且差得远。
问题一:审批疲劳
当 Agent 每执行一个命令都要弹出确认框时,用户很快就会形成肌肉记忆,不假思索地点击”允许”。这就像 Windows Vista 时代的 UAC 弹窗——用户被训练成了自动点击”是”的机器。
问题二:隐蔽的攻击
并非所有攻击命令都一眼能看出危险。一个看似正常的 npm install 命令,背后可能触发恶意包的 postinstall 脚本。一个 git clone 可能拉取一个包含恶意 Git hooks 的仓库。即使是安全专家也不一定能在几秒钟的确认窗口中识别出所有风险。
问题三:没有纵深防御
权限提示是单点防御——用户一旦点击允许,就完全没有后续保护了。沙箱提供的是纵深防御——即使用户批准了命令执行,操作系统仍然在强制限制命令能做什么。
1 | 权限提示模式: |
Claude Code 的做法是两者结合:用权限提示做第一层过滤,用沙箱做第二层兜底。这才是企业级的安全态度。
企业级 Agent Runtime 的沙箱设计原则
基于对 macOS Seatbelt 和 Windows AppContainer 的分析,以及 Claude Code 沙箱实践的经验,我认为企业级 Agent Runtime 的沙箱应该遵循以下设计原则:
原则一:Deny Default(默认拒绝)
Seatbelt 的 (deny default) 和 AppContainer 的”零权限起步”是同一个思想:进程启动时什么都不能做,只被授予它真正需要的能力。 这比”先给所有权限再逐个收回”安全得多,因为你不可能枚举出所有需要收回的权限,但你可以精确列出需要授予的权限。
原则二:内核级执行
安全检查必须在操作系统内核中执行,而不是在应用层。Seatbelt 通过 TrustedBSD MAC 框架在内核中拦截系统调用,AppContainer 通过安全引用监视器(SRM)和 WFP 在内核中执行访问检查。应用层的安全检查可以被绕过,但内核级的强制执行不行——除非攻击者有内核漏洞。
原则三:不可逆性
沙箱一旦应用就不能被进程自身移除或放宽。macOS Seatbelt 的 sandbox_init() 调用是不可逆的,子进程只能继承或收紧沙箱。Windows AppContainer 的安全令牌同样无法在运行时被修改。这确保了即使攻击者在沙箱内获得了代码执行能力,也无法自行脱离沙箱。
原则四:文件 + 网络双重隔离
必须同时实现文件系统隔离和网络访问隔离。单独的任一种都不足以构成完整的安全边界。
原则五:最小权限 + 能力声明
借鉴 AppContainer 的能力模型,Agent Runtime 应该要求每个 Agent/Skill 明确声明它需要的能力:
1 | { |
一个代码审查技能不需要写入权限,也不需要网络访问。通过能力声明,Runtime 可以为每个技能配置最小权限的沙箱。
结论
企业级 AI Agent Runtime 的安全沙箱不是锦上添花,而是地基。
macOS 的 Seatbelt 用 deny-default 的沙箱配置文件和 TrustedBSD MAC 内核框架,证明了操作系统级文件/网络隔离的可行性和可靠性。Windows 的 AppContainer 用安全令牌和能力模型,证明了 deny-by-default 的进程隔离可以做到既安全又实用。Claude Code 的 sandbox-runtime 证明了这些 OS 原语可以被成功应用到 AI Agent 场景。
没有沙箱的 Agent Runtime,就像没有安全带的汽车——在路况好的时候看不出区别,出事故的时候就是生与死的区别。
当你评估一个 Agent Runtime 是否足够”企业级”时,第一个要问的问题应该是:它有沙箱吗?文件系统隔离和网络隔离分别是怎么实现的?是在应用层做的检查,还是操作系统内核在强制执行?
如果答案是”没有沙箱”或”应用层检查”,那无论这个 Runtime 的模型有多强、功能有多丰富——它都不是企业级的。
AI AI-agents security sandbox macOS Windows architecture best-practices
