Agent 想要一条 shell,先给它一间牢房

2026-10-23T07:11:42+08:00 | 7分钟阅读 | 更新于 2026-10-23T07:11:42+08:00

@
Agent 想要一条 shell,先给它一间牢房

你在终端里给 coding agent 派一个活:把依赖升到最新,跑测试,绿了就提交。

它开始动手。读 package.json,改版本号,装包,测试报错,翻错误日志,改一处 API 用法,再跑,绿了,commit。中间它提权了吗?没有。它写脚本绕过什么了吗?没有。它只是拿到了一条 shell,而你账户能读的文件它都能读,你环境变量里的 token 它都能拿,你机器能连的内网它都能连。

整个过程做得对,是因为它这次恰好做对了。这个"恰好",就是这两天榜单上一批项目在赌的东西。

三个项目,同一个问题

10 月 22 日的 GitHub 趋势里,NVIDIA/OpenShell 以 221 个今日 star 排在 Rust 榜第二,trycua/cua 排第四,agentgateway 排第七。三件事看上去不搭:OpenShell 造运行时,cua 造计算机操作的驱动和沙箱,agentgateway 做 agent 和 MCP 之间的代理。

它们回答的其实是同一个问题:agent 到底能碰到什么。

NVIDIA 把 OpenShell 摆在哪一层,比它本身的功能更值得看。官方 FAQ 花了不少篇幅澄清 OpenShell 不是又一个 agent 框架,它坐在 Claude Code、Codex、GitHub Copilot CLI、OpenCode、OpenClaw 这些 harness 底下。harness 随便换,策略层不变。这个定位如果成立,agent 的 harness 就退化成了可替换的进程,真正管事的变成下面那层。

策略必须在进程之外执行

OpenShell 的设计可以压成一句话:安全在环境里,不在模型里。文档原话是策略在 agent 进程之外执行,模型输出改不了它。

三个组件各管一头。Gateway 是控制面,管沙箱生命周期、策略版本、凭据映射、推理路由、审计记录。Supervisor 跑在每个沙箱内部,是本地那道边界,它在 agent 进程之前启动,先把权限降下来,再把 agent 作为受限子进程拉起来。Sandbox 本身是执行边界。

关键在于 Supervisor 的位置。它在 agent 外面,agent 改不动它,提示注入也注入不进去。NVIDIA 官方给的类比是浏览器标签页:会话隔离,资源受限,每个动作在落地之前由运行时校验。

Supervisor 管五层:

  • 进程:降权,关掉提权路径,agent 以受限子进程身份运行
  • 文件系统:策略在 agent 启动前就应用好,没声明的路径不可达,声明的路径分只读和读写
  • 网络:普通出站流量走策略代理,目标、端口、发起请求的二进制身份、L7 请求规则都会在流量离开沙箱前判一遍
  • 凭据:从 Gateway 取,只通过配置好的策略路径或请求时代理规则注入
  • 推理:拦截 https://inference.local,转发到配置好的推理路由

凭据这层值得单独说。agent 从头到尾看不到真的 key,Supervisor 拿到凭据材料,只在请求发往已批准端点时才把它塞进去。被投毒的 agent 想把 key 打印出来,前提是它见过 key,而它没有。推理那层是同一个思路:agent 以为自己在调本地 endpoint,实际用哪家模型、哪个 key、走哪条计费,都在运行时手里,它连换模型的自由都没有。

一个容易看漏的切分:静态和动态

文档里有一句很轻的话,我认为是这份设计里最实用的部分。文件系统隔离和进程隔离这类静态控制,在沙箱启动时建立,要改必须重建沙箱。网络策略、凭据下发、推理路由这类动态控制,可以走活的会话热更新。

这个切分直接决定怎么用。沙箱模板是一次性决定:哪些目录可读、哪些可写、以什么身份跑,开工前定死,事后只能重建。而出口白名单、临时凭据、换模型这些可以边跑边调。

判定粒度是二进制、目标、方法、路径四级。官方举的例子是:agent 能装一个审核过的 skill,但不能执行一个没审核过的二进制。这句话里"安装"和"执行"落在不同维度上,是我在文档里找到的最具体的一句。

形式化验证:把 plan 搬到权限上

Policy Prover 是这套东西里最不显眼的部分。改策略之前,它用形式化方法算一遍:这次改动会不会让权限越出运营者画的边界,或者具体是哪一条动作越界了。越界就挂起,等人来看。

写过 Terraform 的人会立刻认出这个模式,apply 之前先 plan。权限策略同样会烂掉,三个月后没人敢删那条允许任意出站的规则,因为没人记得它当初为什么在。有个东西在你合并之前告诉你"这条新增了带着凭据访问某台新主机的能力",比靠人读 YAML 稳。

当然它只验证被建模的那部分。策略模型之外的假设,比如 Supervisor 自己有洞、Gateway 本身就是单点,形式化证明一个字都不会提。

隔离底座的真实差价

OpenShell 支持 Docker、Podman、K8s、VM 几种底座。这几档的差距不是营销话术,有具体数字。

VEE 2020 那篇专门对比 Firecracker 和 gVisor 的论文里,内核触达面是这样的:普通 Linux 容器里,应用几乎可以发起全部系统调用,和宿主共用内核;Firecracker 用 seccomp 白名单把 VMM 压到 68 个系统调用(开网络)或 53 个(不开网络),每个 microVM 有自己的内核;gVisor 走另一条路,用 Go 写了个用户态内核 Sentry,自己实现 237 个系统调用,只向宿主发 53 个。

代价也对得上。容器最轻,逃逸面却是整张系统调用表。gVisor 拦在系统调用这一层,I/O 密集的路径上有可观开销。microVM 最硬,启动和内存是另一档。

选哪一档,标准不是"要多安全",是这批代码有多不可信,以及你愿意为每次冷启动付多少。我的分法:本地跑自己写的 agent,容器加出口白名单够用;跑从网上装来的 skill 和别人的代码,从 microVM 起步;多租户平台没得选,microVM 或者 gVisor。

计算机操作是另一类问题

cua 面对的不是 shell,是一整个桌面。它的沙箱单元就是一台机器:

from cua_sandbox import Image, Sandbox

async with Sandbox.ephemeral(Image.linux(), local=True) as sb:
    print((await sb.shell.run("uname -a")).stdout)

ephemeral 这个词是关键,用完即弃,agent 把桌面点坏了不牵连任何东西。

它有个动作阶梯,我觉得比安全更能说明产品形态。默认后台投递:不抬起窗口,不移动真实指针,不切前台应用,agent 有自己的光标覆盖层。第一档按无障碍树里的元素 token 调 Invoke 或 AXPerformAction,这是驱动唯一能自己验证的一档。第二档照同一张截图的坐标点。第三档切到浏览器工具,走 CDP 直接操作 DOM。都不行才升到前台,抬窗口、投递、再恢复原来那个应用。每次返回都带 effect 字段:confirmed、unverifiable、suspected_noop、partial、refused,还有 escalation 建议下一档。

后台优先的真实价值是并行。agent 不抢焦点,一台机器上就能同时开好几个,各点各的。这条线跟沙箱的安全收益是两码事,但产品上更容易被感知。

如果今天就要加这层

顺序我大概会这么排:

  • 先把出口管住。deny by default 的出站,只放行真正需要的域名。一个动作挡掉大部分数据外传,成本最低。
  • 把凭据从环境变量里挪走。agent 的环境里不该有能直接用的 key,让代理在请求出站时替换。
  • 沙箱模板一次划对。哪些目录可写、以什么用户跑,这是静态的,事后要重建。
  • 对"自我进化"单独授权。装 skill、起 subagent、写新脚本,这三件事应该跟"读文件"用不同策略,它们扩张的是能力边界。
  • 审计日志单独存。沙箱保证越界做不到,不保证越界没发生。

收尾

把 OpenShell 这类东西当安全产品看会低估它。它更像 agent 的操作系统:操作系统从不问程序想干什么,只规定程序能干什么。过去两年我们一直在提示词里写"不要删文件"“不要乱装依赖”,那是在请求模型配合。这条路正在被换掉,倒不是因为提示词没用,是因为 agent 的行为空间在运行时会自己长大,靠行为审查追不上。

OpenShell 目前在早期预览阶段,策略写细了会噪声爆炸,写粗了等于没写,这中间的分寸只能自己试。

参考来源:

Me

Cut out summary from your post content here.

The remaining content of your post.