Agent Box 初探:从 OpenClaw 小龙虾安全问题谈 Agent Sandbox
Posted on 三 29 4月 2026 in Tech
| Abstract | Agent Box 初探:从 OpenClaw 小龙虾安全问题谈 Agent Sandbox |
|---|---|
| Authors | Walter Fan |
| Category | tech note |
| Status | v1.0 |
| Updated | 2026-04-29 |
| License | CC-BY-NC-ND 4.0 |
先说一个不太浪漫的判断
AI Agent 最迷人的地方,是它终于不只是“嘴上很会”。它可以写代码、跑命令、装依赖、打开浏览器、读文件、改配置,甚至自己失败了还能重试。听起来像一个很积极的实习生。
问题也在这里:一个积极但偶尔幻觉的实习生,如果直接坐在生产机器上敲命令,那就不是生产力工具,而是运维惊悚片。老程序员都懂,rm -rf 的杀伤力不取决于是谁敲的,取决于它敲在哪里。
最近 OpenClaw 小龙虾相关的安全讨论,就像给这个问题打了一束很刺眼的追光。公开 issue 和官方博客里反复出现几类风险:外部消息被拼进 agent 上下文导致 prompt injection;本地 Gateway / WebSocket 过度信任 localhost;sandboxed agent 仍然能从配置里读到解析后的 API key;工具执行缺少 allowlist、审批和参数校验。很多问题后来已经修,但它们共同说明一件事:
Agent 的风险,不在于它会不会犯错,而在于它犯错时手里拿着什么权限、站在什么地方。
所以我认为 Agent 时代真正缺的不是又一个更会聊天的窗口,而是一个有边界、有状态、有生命周期的工作间。你可以把它叫 Agent Box,也可以按 Kubernetes SIGs 项目的名字叫 Agent Sandbox。这篇文章基于我在 2026-04-29 查阅的官方文档,聊聊它的 why、what、how,再给一个小例子。
一句话结论:
Agent Sandbox 的价值,不是让 Agent 更聪明,而是让 Agent 更敢动手,同时让平台更敢放手。
Why:为什么 Agent 需要一个 Box
过去我们写服务,大多数工作负载可以粗略分两类:
- 无状态服务:开多个副本,用 Deployment 管,坏了重启,流量继续。
- 有状态服务:用 StatefulSet、PVC、Service,一套标准组合拳。
AI Agent 不完全属于这两类。它更像一个临时工位:今天要分析一个 CSV,明天要修一个 repo,后天要开一个浏览器查页面。它需要隔离,也需要保留现场;它应该可以快速创建,也应该能在不用时休眠;它要有自己的文件系统、进程和网络边界,还最好有一个稳定身份,方便后续连接。
这就尴尬了。
如果每次都临时起一个普通 Pod,状态容易丢,冷启动也慢。如果给每个 Agent 手工拼一个 StatefulSet、Service、PVC,平台同学会在 YAML 里提前退休。如果直接让 Agent 在共享环境里跑代码,那就更刺激了,刺激到安全团队半夜能梦见审计日志。
Agent Sandbox 要解决的,就是这个中间地带:
短命一次性执行 <----> 长期有状态服务
^
|
Agent 的工作间
官方文档里提到几个动机,我翻译成工程师能立刻感受到的语言:
| 痛点 | 没有 Sandbox 时 | 有 Sandbox 后 |
|---|---|---|
| 执行不可信代码 | 容易碰到宿主机、共享网络或敏感数据 | 在隔离 Pod 里执行,可叠加 gVisor 或 Kata |
| 多轮 Agent 工作 | 每轮重新安装依赖、重新拉代码 | 文件、依赖和中间结果可以留在同一个环境 |
| 启动延迟 | 冷启动等 Pod 调度和镜像拉取 | SandboxWarmPool 可预热环境 |
| 生命周期 | 谁创建、谁清理、谁恢复都要自己写 | Controller 负责创建、删除、休眠和恢复 |
| 平台治理 | 开发者各写各的 Kubernetes 资源 | 用 CRD 抽象成统一 API |
这背后的深层变化是:Agent 不是一个 API 调用,而是一段会持续活动的运行时。 既然它会活动,就应该被放进合适的容器里。
OpenClaw 小龙虾给 Agent Sandbox 上的一课
我不想把 OpenClaw 小龙虾写成“反面教材”。恰恰相反,一个真实流行的 Agent 框架越多人用,就越容易被安全研究者、攻击者和热心用户从各种角度拧螺丝。问题暴露出来并被修掉,是开源生态成熟的必经阶段。
但这些问题确实给 Agent Sandbox 提了一个醒:沙箱不是一个可选插件,而是 Agent runtime 的基本盘。
看公开资料里几类典型问题,背后的模式很清楚。
1. 外部输入不是“用户说的话”,而是潜在指令注入
OpenClaw 的一个公开 issue 提到,来自飞书、Telegram、Slack 等外部 channel 的消息如果被直接拼进 agent context,就可能出现 prompt injection:攻击者在公开群里发一段看起来像系统指令的文本,诱导主 agent 调用更高权限的 sub-agent 或执行工具。
这类问题的根不是“模型太笨”,而是可信上下文和不可信输入混在一起。
Agent Sandbox 给不了 prompt 注入的万能解药,但它能帮你把伤害关小:
- 外部 channel 输入必须标记为 untrusted content,不能和 system prompt 混写。
- 跨 agent 调用默认关闭,只有明确 allowlist 才能打开。
- 被外部输入触发的任务,应该进入低权限 Sandbox,而不是直接调用高权限工具。
- tool invocation 不能只靠模型“理解不要执行”,要靠平台策略硬拦。
一句话:prompt 边界和执行边界要同时存在。
2. 本地 Gateway 不是天然安全区
ClawJacked 这类问题提醒我们:localhost 不是护身符。浏览器页面、恶意广告、被污染的网页脚本,都可能尝试连接本地服务。如果本地 Gateway 对 localhost 连接过度信任,又缺少 origin 校验、速率限制和设备确认,Agent 就可能被“隔壁网页”接管。
这对 Agent Box 的启发很直接:
- Sandbox 控制面 API 不要裸露在用户浏览器可随便碰到的位置。
- WebSocket / local gateway 要校验 Origin、鉴权、限速和设备注册。
- “来自本机”不等于“来自可信用户”。
- Agent runtime 和控制面要分离,执行容器不能顺手拥有控制面管理权限。
很多传统桌面软件喜欢相信 localhost。Agent 不行。Agent 手里有 shell、文件系统、浏览器会话和各种 API,localhost 一旦失守,就像把门禁卡贴在门口写着“自取”。
3. 沙箱里不能放明文钥匙
另一个公开 issue 提到,sandboxed agent 可以通过配置命令读到解析后的 API secrets。表面上 agent 被关进 sandbox 了,实际上钥匙串也被放进房间了。
这说明一个常见误区:进程隔离不等于凭证隔离。
如果 Sandbox 里有长期 API key、云账号 token、GitHub PAT、Notion token,攻击者只要通过 prompt injection 让 agent 执行 cat config 或 config get,沙箱就变成自动提款机。
更靠谱的模式是:
Sandbox -> Gateway Tool / Credential Broker -> Target API
Agent 只看到工具能力和返回结果,不直接看到长期凭证。短期 token 也应该绑定 task_id、scope 和 ttl。任务结束,权限自然消失。
4. 工具权限不能靠“默认善良”
还有一类问题是工具治理:没有 allowlist / denylist,没有 per-agent permission,没有参数校验,没有 rate limit。任何进入 agent 的消息,都可能触发任意注册工具。
这和 Kubernetes 里把所有 Pod 都绑到 cluster-admin 差不多。平时很顺,出事很响。
Agent Sandbox 应该把工具权限变成平台对象,而不是 prompt 里的温馨提示:
- 模型看到哪些工具,先由 policy 过滤。
- 每次 tool call 进入
before_tool_call这类硬门禁。 - 高风险工具需要 approval gate。
- destructive operation 默认 deny,除非任务类型明确需要。
- 工具参数要校验,不允许模型随便拼 shell、URL、路径。
这也是为什么我前面强调 KSA/RBAC、NetworkPolicy、admission policy 和 credential broker。它们听起来不是“AI 功能”,但正是 Agent 能安全动手的前提。
OpenClaw 的这些讨论,本质上不是某个项目“写错几行代码”。它们暴露的是 Agent 工程的共同病灶:
Agent 把自然语言、外部输入、工具调用、凭证、文件系统和网络访问揉在一起。如果没有强边界,聪明会变成放大器,错误也会变成放大器。
What:Agent Sandbox 到底是什么
Agent Sandbox 是一个 Kubernetes-native 平台,核心是 Sandbox 这个 CRD。官方对它的定位很明确:管理隔离的、有状态的、单例的工作负载,特别适合 AI agent runtime、开发环境、Notebook、代码执行等场景。
别被 CRD 吓到。它的想法其实很朴素:
你别再手工拼 StatefulSet、Service、PVC 了。你告诉 Kubernetes“我想要一个 Sandbox”,剩下的交给 controller。
核心组件可以这样理解:
| 组件 | 作用 | 人话解释 |
|---|---|---|
Sandbox |
声明一个单 Pod、有状态、有稳定身份的环境 | 一个具体工位 |
SandboxTemplate |
复用运行时配置 | 工位装修模板 |
SandboxClaim |
面向用户的申请入口 | “给我来一个 Python 工位” |
SandboxWarmPool |
预热一批环境 | 提前把工位打开、电脑开机 |
| Python / Go SDK | 程序化创建、查询、操作 Sandbox | Agent 调用 Box 的遥控器 |
架构上,它走的是 Kubernetes controller pattern:用户创建 Sandbox 或 SandboxClaim,controller 再去管理底层 Pod 和 runtime。这个设计很 Kubernetes,也很现实。它不试图重造一套调度系统,而是把 Agent 需要的“单例、有状态、可隔离、可恢复”抽象出来,放回 Kubernetes 生态。
一个最小的 Sandbox YAML 长这样:
apiVersion: agents.x-k8s.io/v1alpha1
kind: Sandbox
metadata:
name: my-sandbox
spec:
podTemplate:
spec:
containers:
- name: my-container
image: python:3.13-slim
这段配置的重点不是“又多了一个 YAML”,而是:平台开始拥有一个统一的对象来表达 Agent 的工作空间。
How:怎么把它用起来
我建议按三层来理解 Agent Sandbox。
第一层:平台层,先把 Box 管起来
平台侧先安装 controller 和 CRD。官方文档给的方式是基于 release manifest:
export VERSION="vX.Y.Z"
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${VERSION}/manifest.yaml
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${VERSION}/extensions.yaml
这里我建议生产环境一定要 pin 具体版本,不要图省事写 main。基础设施最怕“昨天还好好的,今天上游给你加了点惊喜”。
平台层还要做几件事:
- 配好 namespace、RBAC、ResourceQuota。
- 定义
SandboxTemplate,比如 Python、Node、Browser、Jupyter、Coding Agent。 - 根据风险选择 runtime:普通容器、gVisor 或 Kata Containers。
- 配网络策略,默认不要让 Sandbox 随便访问内网。
- 配 TTL、休眠、恢复和清理策略。
这一层的目标不是“让开发者自由发挥”,而是把自由关进一个合理的边界里。
第二层:模板层,把常见环境做成套餐
Agent 最讨厌“每次从零开始”。今天要 pandas,明天要 playwright,后天要 git、ripgrep、node、python、chromium。如果每个调用都现装依赖,用户等得心平气和,账单先不平静。
SandboxTemplate 的价值就是把常见运行时沉淀下来:
python-sandbox-template:适合代码解释器、数据分析、脚本执行。browser-sandbox-template:适合 computer use、网页自动化。coding-agent-template:带 repo、编译工具、测试工具和缓存。jupyter-template:适合交互式分析和研究。
这和云主机镜像、CI runner image 的道理差不多。环境标准化之后,Agent 的行为才容易复现,问题也容易定位。
第三层:Agent 层,把 Sandbox 当成工具
真正有意思的是这一层。Agent 不应该知道底层有多少 Service、PVC、Pod,它只需要一个工具:
create sandbox -> write file -> run command -> read result -> keep or terminate
官方 Python SDK 的最小用法大概是这样:
from k8s_agent_sandbox import SandboxClient
client = SandboxClient()
sandbox = client.create_sandbox(
template="python-sandbox-template",
namespace="default",
)
try:
sandbox.files.write(
"run.py",
"print(sum(range(1, 101)))\n",
)
result = sandbox.commands.run("python3 run.py")
print(result.stdout)
finally:
sandbox.terminate()
这段代码很小,但它背后的边界很重要:Agent 生成的代码没有在你的应用进程里跑,也没有在共享机器上裸奔,而是在一个可管理的 Sandbox 里执行。
如果任务是一次性的,跑完就 terminate()。如果任务是多轮 coding agent,可以保留 Sandbox,让它在同一个工作目录里反复生成、执行、修错。两种模式都合理,关键是生命周期要明确。
Example:一个“数据分析 Agent”的小场景
假设我们要做一个数据分析 Agent。用户上传一个 CSV,然后问:
“帮我看一下最近 30 天哪些接口错误率最高,画个图,再给排查建议。”
一个不负责任的实现可能是:把 CSV 传给应用服务,让 LLM 生成 Python,然后在应用服务容器里 exec。这就像在餐厅后厨修摩托车,理论上空间够,实践上厨师会报警。
更像样的流程应该是:
- 应用服务收到用户问题和 CSV。
- Agent 创建一个
python-sandbox-template。 - 应用把 CSV 写入 Sandbox 文件系统。
- LLM 生成分析脚本。
- Sandbox 执行脚本,返回 stdout、stderr、exit code 和图表文件。
- Agent 根据结果整理解释。
- 如果脚本报错,把 stderr 反馈给 LLM,最多重试 2-3 次。
- 任务结束后按策略删除或休眠 Sandbox。
伪代码可以写成这样:
def analyze_csv(question: str, csv_bytes: bytes) -> str:
client = SandboxClient()
sandbox = client.create_sandbox(
template="python-sandbox-template",
namespace="agent-runtime",
)
try:
sandbox.files.write("input.csv", csv_bytes.decode("utf-8"))
code = generate_python_code(question, "input.csv")
sandbox.files.write("analysis.py", code)
for _ in range(3):
result = sandbox.commands.run("python3 analysis.py")
if result.exit_code == 0:
chart = sandbox.files.read("chart.png")
return summarize_result(result.stdout, chart)
code = fix_code_with_error(code, result.stderr)
sandbox.files.write("analysis.py", code)
return "脚本连续失败,请人工检查数据格式和生成代码。"
finally:
sandbox.terminate()
这段代码不是完整生产实现,但表达了一个关键模式:
LLM 可以犯错,Sandbox 负责把错误关在房间里;Agent 可以重试,平台负责把重试变成可治理的动作。
这里还可以加几个工程细节:
- CSV 大小限制,避免一上来把存储打爆。
- 命令超时,避免死循环。
- 网络默认关闭或只允许访问白名单。
- 输出文件类型检查,避免把奇怪文件当图片返回。
- 日志脱敏,避免用户数据进入模型日志或平台日志。
- 每个 Sandbox 打上
user_id、task_id、ttl标签,方便审计和清理。
这些东西不酷,但救命。
有深度的地方:它改变的是责任边界
很多人看 Agent Sandbox,第一反应是:“这不就是包了一层 Kubernetes 吗?”
这话对一半。它确实没有逃离 Kubernetes,但重点不是包装,而是重新划分责任。
以前做 Agent 执行环境,常见责任边界是这样的:
应用开发者:写 prompt、调模型、跑代码、管容器、管存储、管清理
平台团队:给一个 Kubernetes 集群
安全团队:上线前来皱眉
Agent Sandbox 想变成这样:
应用开发者:申请一个合适的 Sandbox,调用 SDK
平台团队:定义模板、runtime、配额、生命周期
安全团队:制定隔离级别、网络策略、审计规则
这才是它真正有启发的地方。
好平台不是把所有复杂度消灭掉,而是把复杂度放到该放的位置。开发者不该每次都手写 PVC;安全团队也不该每次靠人工 review 祈祷;平台团队更不该把“执行不可信代码”当成普通 Pod 来糊弄。
Agent 时代,where to run 会变成和 which model to use 一样重要的问题。
和普通方案比一比
| 方案 | 适合场景 | 问题 |
|---|---|---|
| 直接在应用容器里执行 | 本地 demo、极可信脚本 | 风险最高,边界最差 |
| 每次起普通 Pod | 一次性短任务 | 状态弱、冷启动慢、生命周期要自己管 |
| StatefulSet + Service + PVC | 长期稳定服务 | 对临时 Agent 太重,模板化差 |
| 独立 VM | 强隔离、重任务 | 启动和资源成本高,Kubernetes 集成弱 |
| Agent Sandbox | Agent runtime、代码执行、Notebook、开发环境 | 需要 Kubernetes 能力和平台治理 |
所以它不是银弹。它适合的是这类场景:
- Agent 会生成并执行代码。
- 任务需要多轮迭代,而不是一次命令结束。
- 运行环境需要保留文件、依赖或缓存。
- 安全边界比裸跑重要。
- 团队已经有 Kubernetes 基础设施,或者愿意为 Agent runtime 建平台。
如果你只是做一个 FAQ bot,Agent Sandbox 可能太重。如果你让 Agent 修代码、跑测试、开浏览器、分析数据,它就开始有味道了。
认证和授权:给 Agent 发“临时工牌”,不是万能钥匙
Agent Box 真正难的地方,不是把代码关进容器。容器只是房间,权限才是门禁。
一个成熟的 Agent Box 访问内外部服务时,应该坚持一个原则:
Agent 不是用户本人,也不是平台管理员。它只是被用户委托完成某个任务的临时执行者。
这句话很重要。很多安全事故就是从这里滑坡的:用户登录了系统,于是系统把用户的长期 token 塞给 Agent;Agent 要查一个接口,于是平台给它开了半个内网;Agent 要跑测试,于是给它一个能改集群资源的 ServiceAccount。表面上事情跑通了,实际上是在给未来的自己埋彩蛋,还是那种拆开彩纸会爆的。
我更建议把访问模型拆成五层:
用户身份 -> 任务授权 -> Sandbox 身份 -> 网络边界 -> 目标服务授权
每一层只解决一件事,不要混在一起。
第一层:用户身份,回答“是谁委托了这个任务”
用户还是要按正常系统登录,比如 SSO、OIDC、企业内部 IAM。应用服务拿到用户身份后,不应该直接把用户 token 原样交给 Sandbox。原因很简单:Agent 会执行模型生成的代码,而模型生成的代码不值得拥有你的完整身份。
正确做法是由控制面记录:
user_id:谁发起的任务。task_id:这次任务是什么。template:允许使用哪种 Sandbox 模板。scope:这次任务能访问哪些服务和动作。ttl:权限什么时候过期。
也就是说,用户身份用于发起和审批,不直接用于执行和横向访问。
第二层:任务授权,回答“这次任务能做什么”
Agent Box 不应该只问“这个用户是不是登录了”,还要问“这个任务被允许做什么”。
例如同一个用户发起两个任务:
| 任务 | 合理权限 | 不该给的权限 |
|---|---|---|
| 分析 CSV | 读写当前 Sandbox 文件、调用 Python、访问模型网关 | 访问生产数据库、访问 Kubernetes API |
| 修复 repo 测试 | 读写指定 repo、跑测试、访问包管理源 | 访问用户私有文档、扫描内网 |
| 生成日报 | 读取指定工单系统和指标 API | 执行 shell、访问任意外部 URL |
这里最好有一个 policy decision point,可以是简单配置,也可以是 OPA/Cedar/自研策略服务。它输出的不是一句“allow”,而是一组细粒度能力:
{
"template": "python-sandbox-template",
"ttlSeconds": 1800,
"tools": ["files.write", "commands.run", "files.read"],
"egress": ["https://model-gateway.example.com", "https://pypi.org"],
"internalServices": ["metrics-reader"],
"kubernetesApi": false
}
这份授权应该随任务生成,随任务结束回收。不要让它变成“永久有效的方便面调料包”,谁拿都能撒。
第三层:Sandbox 身份,回答“这个工作间在集群里是谁”
Kubernetes 里最自然的身份载体是 ServiceAccount。Agent Sandbox 官方也有 Sandbox with Kubernetes Service Account 示例:每个 sandboxed pod 可以绑定不同的 KSA,从而拥有不同的集群身份和 RBAC 权限。
一个最小配置大概是这样:
apiVersion: v1
kind: ServiceAccount
metadata:
name: sandbox-metrics-reader
namespace: agent-runtime
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: sandbox-metrics-reader
namespace: agent-runtime
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: sandbox-metrics-reader
namespace: agent-runtime
subjects:
- kind: ServiceAccount
name: sandbox-metrics-reader
namespace: agent-runtime
roleRef:
kind: Role
name: sandbox-metrics-reader
apiGroup: rbac.authorization.k8s.io
然后在 Sandbox 里指定:
apiVersion: agents.x-k8s.io/v1alpha1
kind: Sandbox
metadata:
name: report-agent
namespace: agent-runtime
spec:
podTemplate:
spec:
serviceAccountName: sandbox-metrics-reader
containers:
- name: agent
image: python:3.13-slim
几个经验规则:
- 默认
automountServiceAccountToken: false,除非这个 Sandbox 确实需要访问 Kubernetes API。 - 优先用 namespace 级
Role,谨慎使用ClusterRole。 - 一个任务类型一个 KSA,不要所有 Sandbox 共用
default。 - Role 里只给必要 verbs,比如
get/list,不要顺手给create/update/delete。 - 让 KSA 名称表达权限意图,例如
sandbox-jira-reader、sandbox-build-runner。
换句话说,ServiceAccount 是 Agent 的“工牌”,RBAC 是工牌能刷哪些门。
第四层:网络边界,回答“它能连到哪里”
RBAC 管 Kubernetes API,不管普通 HTTP 出站。Agent 如果能随便连内网,那就算没有 K8s 权限,也可能访问到不该访问的服务。
Agent Sandbox 文档里有 Composing Sandbox with Network Policies 的例子,思路是把 Sandbox 和 NetworkPolicy、Ingress、Service 组合起来。我的建议是:默认 deny all,然后按任务放行最小出口。
例如只允许访问模型网关和一个内部 metrics 服务:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: report-agent-egress
namespace: agent-runtime
spec:
podSelector:
matchLabels:
agents.x-k8s.io/sandbox: report-agent
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: platform
podSelector:
matchLabels:
app: model-gateway
ports:
- protocol: TCP
port: 443
- to:
- namespaceSelector:
matchLabels:
name: observability
podSelector:
matchLabels:
app: metrics-api
ports:
- protocol: TCP
port: 8443
真实生产里,访问外部服务还可以再加一层 egress gateway 或 HTTP proxy:
Sandbox -> Egress Proxy -> External API
这样做的好处是:
- Sandbox 不直接出公网。
- 代理可以做域名 allowlist、速率限制、审计和脱敏。
- 外部 API key 不必进入 Sandbox 文件系统。
- 可以把所有 LLM API 调用收口到 model gateway,统一做预算、日志和安全过滤。
如果 Agent 需要访问内部业务服务,也不要让它直连一大片内网。更好的做法是让业务服务验证一个短期 token,token 里带上 user_id、task_id、sandbox_id、scope、exp。服务端只相信这些声明里的最小权限,不相信 Agent 自己说“我是来帮忙的”。
第五层:凭证发放,回答“秘密怎么进来、什么时候消失”
最危险的做法,是把长期密钥写进镜像、环境变量或工作目录。Agent 会读文件,会跑命令,会打印日志,长期密钥进去之后,就像把银行卡放在共享打印机旁边。
更稳的做法是引入一个 credential broker:
Agent Runtime -> Credential Broker -> Token Exchange -> Target Service
它负责几件事:
- 根据
user_id/task_id/scope发放短期凭证。 - 凭证只对特定服务、特定动作有效。
- 凭证 TTL 短,任务结束立即撤销或自然过期。
- 不把 refresh token、长期 API key 暴露给 Sandbox。
- 所有发放和使用都进入审计日志。
如果是云环境,可以用 Workload Identity、IRSA、SPIFFE/SPIRE、Vault dynamic secrets 或内部 STS 做这件事。具体用哪套不重要,关键是不要把“长期秘密”交给“会执行不确定代码的房间”。
准入策略:防止权限被悄悄加大
上面这些设计,如果只靠开发者自觉,早晚会被“临时需求”冲垮。安全策略必须前移到 admission。
Agent Sandbox 官方文档里有两个很有参考价值的方向:
ValidatingAdmissionPolicy:强制 Sandbox 满足基础安全要求,例如必须使用runtimeClassName: gvisor、禁止hostNetwork、关闭自动挂载 ServiceAccount token、非 root 运行、drop capabilities。OPA Gatekeeper:阻止给正在被 Sandbox 使用的 ServiceAccount 追加 RoleBinding 或 ClusterRoleBinding,避免运行中的 Sandbox 被悄悄提权。
这两个策略解决的是“别让权限在你没注意的时候长大”。尤其是 OPA Gatekeeper 那个例子很实用:如果一个 ServiceAccount 已经被某个 Sandbox 使用,就不要允许别人再给它绑定新权限。否则攻击路径会变成:
先创建低权限 Sandbox -> 再给它的 KSA 绑定高权限 Role -> Sandbox 立刻变身
这类“后门式提权”很隐蔽,靠人工 review 很难稳住。
一个可落地的访问流程
把上面几层串起来,一个比较靠谱的流程是:
- 用户通过 SSO 登录应用。
- 用户提交任务,例如“分析这个 CSV,并生成图表”。
- 控制面根据用户、任务类型、数据敏感度计算 policy。
- 控制面创建
SandboxClaim或Sandbox,绑定专用 KSA、labels、TTL。 - Admission policy 检查 runtime、hostNetwork、ServiceAccount token、securityContext。
- Controller 创建 Pod,NetworkPolicy 按 label 限制入口和出口。
- Agent 需要访问服务时,向 credential broker 换短期 token。
- 目标服务校验 token scope,只允许本次任务需要的动作。
- 任务结束,Sandbox terminate 或 hibernate,短期 token 过期,审计日志落库。
这套流程看起来比“给它一个 token 让它跑”麻烦,但这是必要的麻烦。工程里很多好设计,本质上都是把未来的事故提前变成今天的约束。
安全这件事,不能只靠“隔离”两个字
Agent Sandbox 支持用 gVisor 或 Kata Containers 增强隔离。gVisor 通过用户态内核拦截系统调用,Kata 则提供更接近虚拟机级别的隔离。这些都很好,但别误会:隔离不是免死金牌。
我会把安全策略分成四圈:
第一圈:输入边界
用户上传的文件、URL、代码片段、prompt 都要限制大小、类型和格式。外部 channel 消息必须标记为 untrusted content,不允许伪装成 system instruction。不要让 Agent 一边读 5GB 文件,一边说“我感觉还行”。
第二圈:执行边界
给 Sandbox 设置 CPU、内存、超时、磁盘容量。能不用 root 就不用 root。能只读挂载就只读挂载。能禁止特权容器就禁止。工具执行要有 allowlist、审批门和参数校验,不能因为模型“想调用”就真的调用。
第三圈:网络边界
默认禁止访问内网敏感服务。需要访问外部网络时,用明确白名单。Gateway / WebSocket / local API 要做 origin 校验、鉴权和限速。Agent 最容易从“帮我查资料”滑到“顺手扫一下内网”,这条线不能靠模型自觉。
第四圈:审计和清理
每次创建 Sandbox,都应该知道是谁、为了什么任务、从哪个模板来、什么时候过期、执行了哪些命令。日志里要避免打印 token、API key 和用户敏感数据。TTL 和 scheduled deletion 不是锦上添花,是防止环境越堆越多的基本卫生。
安全设计的原则很简单:
把 Agent 当成一个会犯错的自动化账号,而不是一个永远善良的小助手。
落地清单:明天怎么开始
如果我要在团队里试点 Agent Sandbox,会按这个顺序来:
- 先选一个低风险场景,例如 CSV 分析、测试执行、文档构建,不要一上来碰生产内网。
- 定义一个最小模板,例如
python-sandbox-template,只放必要依赖。 - 配 namespace、RBAC、ResourceQuota、NetworkPolicy。
- 决定隔离级别:普通容器、gVisor,还是 Kata。
- 给每类任务定义专用 KSA 和最小 RBAC,不要共用
default。 - 配 admission policy,强制非 root、禁止 hostNetwork、按需关闭 ServiceAccount token 自动挂载。
- 用 NetworkPolicy 或 egress proxy 做默认拒绝、按需放行。
- 用 credential broker 发短期凭证,不把长期密钥放进 Sandbox。
- 把外部 channel 输入统一包成 untrusted content,禁止伪造 system / assistant / tool 指令。
- 本地 Gateway / WebSocket 做 origin 校验、限速和显式设备确认,不要默认相信 localhost。
- 用 Python SDK 做一个最小闭环:创建、写文件、执行、读结果、清理。
- 给每个 Sandbox 加
task_id、owner、ttl标签。 - 把失败重试次数写死在代码里,不要让 Agent 无限自我感动。
- 记录 stdout、stderr、exit code,但注意脱敏。
- 压测 WarmPool,看真实启动延迟和资源占用。
- 最后再接入真正的 Agent loop。
这里的关键是顺序:先把盒子做好,再把 Agent 放进去。不要反过来,先让 Agent 到处跑,等出事了再想起来买门锁。
结论:Agent 的未来,不只是大脑,还有工位
过去一年,大家讨论 Agent,最爱聊模型、工具调用、规划、记忆。这些都重要。但越往工程落地走,越会发现另一个问题更基础:
Agent 到底在哪里工作?
如果它只聊天,浏览器窗口就够了。如果它要写代码、跑测试、装依赖、处理用户文件、打开 GUI、长期保持状态,那它需要一个认真设计过的工作间。这个工作间要隔离,要持久,要能快速分配,要能休眠和清理,还要能被程序化地创建和操作。
这就是 Agent Sandbox 给我的启发。
它不是一个花哨的“AI 产品外壳”,而是 Agent 工程化里很朴素的一块地基。地基通常不好看,也不适合做宣传海报,但楼能不能盖高,最后还得看它。
参考资料
- OpenClaw Issue #47052: Sandbox external channel inputs to prevent prompt injection via agentToAgent
- OpenClaw Issue #13683: Sandboxed agents can read resolved API secrets via config
- OpenClaw Issue #12565: Unrestricted Tool Execution Leading to Privilege Escalation
- ClawJacked: How a Website Could Hijack Your OpenClaw Agent
- Agent Sandbox Documentation
- Agent Sandbox Overview
- Agent Sandbox Use Cases
- Python SDK Quickstart
- Code Execution
- Coding Agents
- gVisor Isolation
- Sandbox with Kubernetes Service Account
- Composing Sandbox with Network Policies
- Protecting Agentic Sandboxes with OPA Gatekeeper
- Secure Sandbox Admission Policy (VAP)
AI Agent Agent Sandbox OpenClaw Kubernetes Sandbox gVisor Kata Containers Security RBAC NetworkPolicy