返回所有文章
发布于 · 作者 Renaud Deraison

隔离把那条命令跑了

2026 年 8 月 10 日,Manifold Security 公布了 Cursor CLI 智能体的一个缺陷:那个让智能体在隔离的 git worktree 里启动的标志,会从你刚刚克隆下来的仓库读取一个 JSON 文件,并把内容原样交给 shell——发生在 Workspace Trust 询问之前,而且即使你打开了沙箱,它也在沙箱之外。没有模型,没有提示注入,没有劝说。Bromure Agentic Coding 提供同一个 worktree 原语,但仓库没有任何地方可以放进一条命令,而且这一切发生在一台早在仓库到达之前就已存在的 VM 里。

你敲下的那个标志,意思是“把这个隔离起来”。正是那个标志,以你的身份、在你的机 器上、在任何东西问你是否信任这个仓库之前,执行了仓库的 shell 命令。

2026 年 8 月 10 日,Manifold Security 的 Francisco Rosales 公布了 Cursor 命令行编码智能体中的一项发现, 三天后 The Hacker News 在 ThreatsDay 汇总 中做了跟进。概念验证打开的是一个计算器。那是 Manifold 列出的那份清单的客气版 本:读 ~/.ssh、从环境里拿走云凭据、开一个反向 shell、写下持久化。

请把它当成一种投递方式来读,而不是当成一份载荷。没有人注入任何东西,没有人劝说 任何模型,也没有哪句巧妙的话藏在 README 里。仓库里一个被跟踪的 JSON 文件点名了 一条命令,而智能体里那个全部职责就是隔离的部分,把它跑了。

一个标志、一个 JSON 文件,和错误的顺序

cursor-agent 是 Cursor 编码智能体的终端版本。把智能体放到你的工作树上会让人不 安,所以这个 CLI 提供了围堵手段,并写进了它自己的参数说明:

-w, --worktree [name]    Start in an isolated git worktree at
     ~/.cursor/worktrees/<reponame>/<name>

一个新建的 worktree 是一份干净的检出,因此不带你的任何构建产物。为了让它可用, CLI 在创建 worktree 时会默认执行一个设置步骤。这个步骤会从它刚刚检出的仓库里读 .cursor/worktrees.json,并把 setup-worktree 的值直接交给 sh -c

{
  "setup-worktree": "<any shell command>"
}

Manifold 对“那个值和 shell 之间隔着什么”的描述只有三个词:“没有解析、没有白名 单、没有询问。”

.cursor/worktrees.json 是一个普通的被跟踪文件。它会随着一次平常的 git clone 一起到来,就像 README 或一个锁文件。在 2026.07.23-e383d2b 之前的构建里, worktree 的创建与设置发生在工作区解析的过程中——而工作区解析会在那条负责显示 Workspace Trust 对话框的代码路径之前就结束。所以命令先跑。在 Manifold 报告附上 的录屏里,终端还在打印 Running worktree setup commands…,计算器就打开了,之后 才出现那个问你是否信任这个目录的对话框。

Cursor 对那个对话框的用途说得很清楚:仓库能控制的任何东西,都不该在你接受之前 运行。当有东西真的跑了,业界对这类缺陷有个名字——信任前执行——而 Cursor 之前就 在同一个目录里修过一次。2025 年,仓库自带的 .cursor/mcp.json 会在你一打开项目 时就自动启动它所配置的服务器。那件事变成了 CVE-2025-64109, 评为 High,CVSS 8.8。同一个 CLI 对 .cursor/cli.json 过于宽松的处理则变成了 CVE-2025-61592, 同样是 High,同样是 8.8。当时 -w 这条 worktree 路径还不存在。它在那次修复的五 个月后才发布,带着同一个原语,而且没有任何关卡。

Manifold 在 7 月 20 日上报。Cursor 在 7 月 23 日发布了重新排序:信任对话框现在 会先渲染,设置命令则要等到工作区被信任之后。7 月 29 日,Cursor 以仅供参考 (Informative)结案,理由是利用这个问题需要用户去克隆一个由攻击者控制的仓库。值 得记住的是 Manifold 的回答。克隆仓库 正是这个产品存在的意义, 而它同样也是 CVE-2025-64109 的前提条件——那个 Cursor 评为 8.8 并修复了的缺陷。 克隆描述的是投递方式。缺陷在别处。

2026.07.23-e383d2b 之前 — cursor-agent -w demogit clone.cursor/worktrees.json随之到来,被跟踪工作区解析worktree 建好,设置步骤读取该文件sh -c "<他们的命令>"你的账号、你的环境,sandbox: insecure_noneWorkspace信任对话框“信任吗?”对话框出现时,仓库的命令早就跑完了。七月修复之后 — 重新排序Workspace信任对话框现在排在最前你按下 [a] Trust为一个你克隆下来正要阅读的仓库sh -c "<他们的命令>"仍是你的账号、你的环境,仍是 insecure_none — --sandbox enabled 到不了这里同样三件事换了个顺序,而这三件事全由那个本该被它们约束的进程自己决定。
三道控制,全都在它们本该约束的那个进程内部。在 2026.07.23-e383d2b 之前的构建里,worktree 的设置命令在工作区解析期间执行——早于信任对话框——而且执行时的沙箱策略被钉死在 insecure_none 上,--sandbox enabled 也改不了它。七月的修复把命令挪到了信任关卡之后。它仍然是同一个 shell,对同一个用户账号有同样的触及范围。

你打开的那个设置,到不了那条路径

还有第二条腿,而且它在修复之后活了下来。

那个设置步骤是在一个写死的 insecure_none 沙箱策略下执行的。那是 Cursor 自己 给这个值取的名字:在 sandbox.json 里,type 接受 workspace_readwrite(默 认)、workspace_readonlyinsecure_none,最后一个会完全关掉沙箱。worktree 的设置路径被钉死在最后一个上。传 --sandbox enabled 也改不了它。Manifold 这样 描述当前的构建:更新“关上的是信任前的那扇窗,而不是沙箱的那个缺口”。

把这两条腿并排放在一起,你会得到一件比这个产品活得更久的事。信任关卡是一段序列 里的一步,所以它可能落在错的位置。沙箱是一项设置,所以某条代码路径可以让自己豁 免。worktree 带着隔离这两个字,所以用户从中读出了围堵。这三件事都由 Cursor 自己 的代码决定,在它自己挑的时机,在它们本该约束的那个进程内部。那正是两周前 打败某个库的安全标志的同一种失败,也是 七次没有任何东西逃出沙箱的沙箱逃逸背后 的那一种。

一种与你共用账号的隔离

-w 隔离的是工作树。它给智能体一份自己的检出,让它不会踩坏你尚未提交的工作—— 这是个真实的问题,也是个真实的解法。没有人是为了隔离机器才做它的。worktree 住 在 ~/.cursor/worktrees/,在你的用户之下,带着你的环境、你的 ~/.ssh、你的 shell 配置文件、你的云凭据,而你的钥匙串就在一次系统调用之外。Manifold 列出的 “设置命令本来可以做什么”,读起来就像那个家目录的财产清单。

所以:一个你还没读过的仓库选了一条命令,而你为了安全才调用的那个功能,正是执 行它的那个。一个 JSON 文件和一个 shell,模型从头到尾坐在场边。

关于智能体安全的讨论,大半已经飘向模型。它好不好骗、能不能被说服、有没有读到不 该读的东西。这些问题都重要, 我们也写了很多篇。与此同时,2026 年要在开发者笔记本上执行代码,最稳的办法仍然是把它写进某个工具启动时会读的文 件,然后等某个人去克隆。

一个没有地方放命令的 worktree

Bromure Agentic Coding 提供同一个原语,因为这个原语是好的。 在仓库标签页按 ⇧⌘G,输入一个任务名,挑一个智能体,Bromure 就会从当前提交切出一 条 wt/<slug> 分支,把它检出到 ~/.bromure/worktrees/<repo>/<slug>,在那里开 一个标签页,并用你的提示启动智能体。多个任务、多条分支、多个智能体,一个仓库—— 舰队模式

Bromure 的设置步骤和 Cursor 的工作一样:干净的检出缺了智能体需要的那些被 gitignore 的文件,所以总得有东西把它们搬过来。差别在于仓库被允许就此说些什么。 仓库可以附一个 .worktreeinclude 文件,其中每一行非注释的内容都是相对于主 worktree 根目录的路径。Bromure 用 cp -a 逐一复制,而且只在源存在、目标不 存在时才复制。那个文件里没有任何字段装得下一条命令,所以没有需要正确排序的 sh -c,也没有可以搞错的顺序问题。一个充满敌意的 .worktreeinclude 最坏也只能 要求把某个文件复制进检出里。整套词汇就这么多。

更大的差别在于这一切发生在哪里。

你账号里的 worktreesetup-worktree → sh -c由仓库挑选,以你的身份执行只隔一个目录的东西~/.ssh/id_ed25519AWS_SECRET_ACCESS_KEY, ANTHROPIC_API_KEY~/.git-credentials, ~/.kube/config~/.zshrc — 持久化对外:一个普通开发工具发出的一个普通连接工作树隔离了,机器是共用的。Bromure 配置文件里的 worktree.worktreeinclude → cp -a只有路径 — 没有字段装得下命令一次性 Linux VM · 在克隆之前就建好这里没有私钥 — 宿主机 ssh-agent 走 vsock环境凭据都是诱饵(brm_…、假 kubeconfig)持久化随任务一起过期对外:在宿主机代理上被扫描 —诱饵离开它的范围 → 上游调用中止,回 451 给客户机、VM 暂停、红色告警worktree 隔离工作树,Hypervisor 隔离机器。
同一个仓库、同一个设置步骤、两台不同的机器。左边,worktree 是你账号里的一个目录:在那里跑的东西碰得到你的密钥、你的令牌、你的 shell 配置文件。右边,worktree 住在一台早在仓库被克隆之前就创建好的一次性 VM 里——私钥从未进去过,手边够得着的凭据都是诱饵,而带着其中一个出门的外发请求,会在目的地看到任何一个字节之前,就在宿主机的代理上被中止。

把那条命令跑起来。看看它能找到什么。

拿 Manifold 那份清单,让它走一遍配置文件,并且把一切都让给攻击者——信任之前、没 有沙箱、你高兴的话还可以在客户机里当 root。

~/.ssh 那里没有东西可读。一个配置文件的私钥留在宿主机上。VM 拿到的 SSH_AUTH_SOCK 指向 /tmp/bromure-agent.sock,通过 vsock 桥接到你 Mac 上一个 属于该配置文件的代理进程,而 Bromure 刻意不去接上你 macOS 的 launchd 代理。代理 协议里有“列出我的公钥”和“签署这个挑战”这样的请求。它没有任何一种请求的意思是 “把私钥交出来”。打开 Require approval to use,每一次签名都会变成宿主机上的 一个对话框,附带一个有时限的授权——五分钟、一小时、这次会话的其余时间。

从环境里拿走云凭据。 它们就在那里,看起来也很对,而它们是假的。配置文件的 Credentials 面板里的每一项,都是以占位值的形式注入,再由宿主机上的代理在链路上 换成真值:通用 API 密钥用 brm_…、一份带着用完即弃客户端证书的合成 ~/.kube/config~/.docker/config.json 里一个假的 base64 块、 ~/.git-credentials,以及在宿主机侧重新签名、对任何想绕过代理的人返回 InvalidSignatureException 的 AWS 材料。窃取会成功,然后 什么值钱的都没带回去

开一个反向 shell。 现在攻击者得把某样东西送过一条线,而那条线属于宿主机。VM 的每一个外发请求,都会用 Aho-Corasick 自动机与这个配置文件铸出的诱饵逐一比 对——请求头与请求体,整个请求,而不只是看起来像凭据的那些部分。一个诱饵前往它被 铸造时的范围之外的主机,正是一台机器正在把它根本不该知道的东西带出去的签名。代 理会在目的地看到任何一个字节之前中止上游调用,给客户机返回一个 451,暂停 VM,把 冻住的画面染红,并发出一条点名该凭据、它原本被铸给哪台主机、以及它实际去了哪台 主机的告警。由你选择:关机、留存以供调查——磁盘镜像、家目录、共享文件夹,打包并 标记起来,让这个配置文件在被擦除之前不会再开机——或者继续。

写下持久化。 这台机器的寿命就是一个任务。Erase home 会重置 /home/ubuntu,Reset to base 会重新克隆工作区的系统盘。

与此同时,你原本要的工作照样往前走。智能体审阅仓库、跑测试、开拉取请求。仓库的 那条命令执行了——在一个“执行”就是它唯一能做的事的房间里。

打补丁覆盖不到的那部分

Manifold 的文章里还有一个细节,而它的保质期最长。Cursor 没有为这条 worktree 路 径发布任何安全公告,也没把它写进七月的更新日志,所以修复是夹在一次例行构建里发 出去的。任何还在受影响版本上的人,都无从得知更新关掉的是一条信任前执行的路径。 而你今天也无从得知,你笔记本上哪个工具还开着这样一条路径,因为那条路径永远是某 个工具启动时会读、而还没有人审计过的某个文件。

Cursor 三天就把这个关掉了,很快。Cursor 在 2025 年也关掉了 mcp.json 那个版 本,然后五个月后来了一个带着同一个原语的新功能。把功能一路塞进启动序列,看起来 就是这样:每多一种能力就多一步,而每一步都是在某个星期四要多排对一次的一件事。

Hypervisor 不是那个序列里的一步。它不读 .cursor/worktrees.json,不读 .worktreeinclude,也不读你仓库里的任何东西。它在克隆之前就在那里了,它没有一 个能让某条代码路径钉死成 insecure_none 的策略字段,而它的保证——这台机器是一 次性的、这些凭据是假的、这条线有人在看—— 在某个绕过手法被公开的那天,和它被修复的那天,是一样的

继续克隆你还没读过的仓库吧。审阅不熟悉的代码就是这份工作,把它交给智能体则是养 一个智能体的意义。只要别再让这场审阅发生在你放 SSH 密钥的同一个账号里就好。 安装 Bromure Agentic Coding,给每个任务自己的分支、自己的检 出、自己的一次性机器,那么下一次某条启动路径被发现会照着某个 JSON 文件说的话执 行任何东西时——而这一定会发生——它会在一个为它而建的房间里执行。