Replies: 2 comments
|
补充一个同根因、而且比正文里的“截获 approval 后自答 这仍然是同一个 authority confusion:默认模型 shell 能访问 loopback Harness control plane,但 Host 把这个 caller 当成了人类 Web client。 1.
|
|
再补一个同根因的影响方向:这个 loopback human-control principal confusion 不只能够自批准 / 切换 Full Access,还能碰到当前默认启用的 这条不另开 Discussion,因为根因仍然是正文的: 1.
|
Uh oh!
There was an error while loading. Please reload this page.
先说结论:当前默认 Web 组合下,一个仍处于
workspace-writesandbox 中的模型侧进程,可以通过 Harness 自己的 loopback Web approval 通道,替用户回答它自己的 sandbox 升权请求,并把本应等待人工确认的danger-full-access重试变成allowed-once。我在当前
master(47f943859bef60e4160492346772ded9b24f765a)上做了真实组合复现:一个由当前 Landlockworkspace-writeprofile 启动的受限 child,通过真实 Connection WebSocket 订阅/api/events.mux,收到 Host 正在等待用户回答的approval/requested后,自己向/api/respond提交allowed-once。Host 侧原本 pending 的ctx.approval.request()最终确实解析为allowed-once。这会破坏默认
workspace-write + ask的核心权限语义。当前dsh-tool-bash把allowed-once当作 sandbox escalation 的 grant;而dsh-bash-sandbox对danger-full-access明确直接走未包装的super.run()/super.start(),不再经过 sandbox provider。因此,一个本来只能在 workspace/临时目录产生文件修改的 Agent,可以把“需要用户点击允许”的 full-access 重试变成自批准。
可能影响包括:
danger-full-accesssandbox escalation 的一次性确认;workspace-write文件边界限制的模型命令,以 Harness 进程所属 OS 用户的正常文件权限执行;下面的 PoC 全部使用临时目录、假 approval 和本地 loopback,没有读取真实凭据,也没有修改真实用户配置、启动文件或业务数据。
1. 默认 Web 会话就是
workspace-write + ask当前基础 composition:
默认配置为:
Permission preset 也明确把默认受限态定义为:
而 full-access preset 是:
因此默认产品路径本来依赖下面这条边界:
2. sandbox 有意不限制网络
当前 sandbox contract 明确说明
SandboxMode只约束 file effects:其 Known Limitations 中写明:
CLI reference 同样说明默认
workspace-write下:因此 sandbox child 访问宿主 loopback 并不是对 Landlock/bwrap 的另一个逃逸,而是当前策略允许的正常网络能力。
3. Web 组合还主动把 Harness 自己的 URL 交给模型 bash
当前:
在默认
surfaceContext: true下会向模型 shell 注册:并在每次 shell execution 时解析成当前 Web Server 的 canonical loopback URL。
对应设计文档:
也明确说明:
当前
tool-bashdescription 还会告诉模型:所以普通 Web Agent 不需要扫描 localhost 或猜测端口,它自己的 sandbox bash 能直接得到 Harness 控制面的准确 URL。
4. background bash 是默认可用能力
dsh-tool-bash默认:标准 Web Agent preset 同时挂载
tool-bash和tool-jobs,Host 平面挂载dsh-jobs-local。因此 Agent 可以先启动一个仍受
workspace-write约束的 background process,让它保持连接,然后继续发起后续 tool call。利用流程不需要额外的恶意宿主进程:
5. Web approval 请求会广播并重放给任意新的 mux client
当前:
Web approval provider 为每个 pending request mint 一个随机
rpcId,然后把:广播给所有打开的 mux stream。
更重要的是,一个之后才连接的新 mux subscriber也会收到所有仍然 pending 的 approval:
项目自己的测试:
明确覆盖:
因此攻击进程不需要提前窃取某个不可预测的 UUID。它只需要在请求 pending 时连接
/api/events.mux,Host 会主动把当前可应答 request 的完整关联信息发给它:6.
/api/respond没有“人类浏览器”身份,只验证 wire correlation当前
respond():对 approval 的校验是:
如果匹配,就直接:
而
allowed-once是合法的远程答案:这里没有 browser session、human identity、per-page capability 或其他 caller binding。
换句话说,协议证明的是:
而不是:
7. loopback Connection 会接受这个 sandbox child
Connection 的
/api和 WebSocket downlink 都用同一套isTrustedApiRequest()fence。默认 Web Server 绑定
127.0.0.1,sandbox child 与 Harness 在同一个 network namespace 中,因此它可以像普通本机 HTTP/WebSocket client 一样连接该地址。我先做了独立真实网络 control:使用当前仓库配套的 Landlock launcher、按照
workspace-write的真实 grants 启动 child:child 从 sandbox 内连接父进程的
127.0.0.1:<port>HTTP listener。结果:
所以:
不会阻止:
这也与仓库对 sandbox network policy 的明确文档一致。
8. 真实组合 PoC:sandbox child 成功替用户回答 Host approval
为了避免只把独立代码段拼成理论链,我又跑了一个组合测试,使用:
测试顺序:
child 使用的核心逻辑等价于:
真实输出:
这里最关键的两项是:
因此“sandbox child 可以替用户回答 approval”已经在真实 Connection carrier 上动态复现,不是单纯源码推断。
9. Negative boundary:没有
allowed-once时,升权必须 fail closed当前 sandbox escalation helper:
明确把 outcome 分成:
所以
allowed-once是唯一 grant。我另外运行了当前提交自己的 focused tests:
覆盖:
结果:
也就是说,Web self-answer 并不是只改变一个 UI 状态:它正好伪造了 sandbox escalation 用来决定“执行 / 不执行”的唯一授权结果。
10.
allowed-once会直接成为这一次 bash 的 requested mode当前:
执行顺序是:
也就是说,如果 self-answer 把 approval resolve 成:
且请求的 target 是:
那么该次调用实际送到 shell executor 的 policy 就是:
11.
danger-full-access明确完全绕过 sandbox provider当前:
前台路径:
后台路径:
只有受限模式才会进入:
项目测试也专门固定了这一语义:
所以完整权限链是:
12. 为什么这不是“模型本来就有 bash,所以没有额外权限”
默认 bash 并不是 full access。
默认产品专门组合:
并且 UI 会在更宽 retry 前显示用户 approval,这就是当前产品声明的权限边界。
在
workspace-write下,项目自己的 sandbox tests 和现有安全复现都能证明:而
danger-full-access则明确完全跳过 provider。因此这里发生的是实际 authority gain:
被提升为:
中间本应存在的人工 approval 被模型自己回答了。
13. 为什么这个问题在默认
127.0.0.1配置就成立这个问题不要求:
它利用的正是默认结构:
因此官方当前对
0.0.0.0的限制不能缓解这条链。这是一个默认 Web session 内部的 authority-confusion 问题。
14. 设计文档也表明 approval 是人类授权边界
Web permission/approval 的 implemented note:
明确说明 Web 的目标是让 sandbox-denial escalation:
UI 中的 ApprovalPanel 展示:
而 shell 文档也写明:
所以当前设计意图并不是“任何能访问 localhost 的进程都可以代替用户批准”。
问题在于 Web wire 当前把:
同时当成了:
而默认 sandbox child 也属于 loopback client。
15. 修复建议
我认为核心修复点是:approval authority 不能只建立在 loopback reachability 上。
可以考虑:
A. 为浏览器 UI 建立独立的不可转授 approval capability
例如在真正的 browser page/session 建立时 mint 一个高熵 capability,并且只让持有该 capability 的 UI 回答 approval。
关键是这个 capability:
如果使用 cookie,应当确保它是浏览器专属、脚本/普通 loopback client 无法自然取得的 credential,而不是再把 bearer token暴露给 Agent。
B. 把 answer channel 绑定到接收该 request 的已认证 UI connection
目前 mux request 和
/api/respond是两个独立、任何 loopback client 都能建立的通道。更安全的性质应类似:
而不是:
C. 不要把 network sandbox 当成唯一修复
阻止 sandbox child 访问 loopback 能作为 defense-in-depth,但当前 sandbox contract 明确把 network policy 排除在
SandboxMode之外,而且 Agent 可能合法需要网络。根本问题仍然是 approval API 缺少“这个回答来自人类 UI”的身份/能力证明。
D. 增加回归测试
建议加入一个真实 product-level regression:
这能直接固定真正需要保住的边界,而不仅是测试 wire payload 的 schema correlation。
总结
当前默认 Web 组合把几个单独合理的设计拼在了一起:
组合后形成:
当前提交上的动态证据为:
因此建议把 Web approval channel 从普通 loopback trust 中拆出来,给真正的人类 UI 一个 Agent/sandbox 进程无法冒充的独立授权身份。
All reactions