The only browser-trust guard covered host.pickDirectory, while the consequential methods (session.prompt drives bash) accepted any Host — open to DNS rebinding, where a rebound page reads and writes the API as if same-origin and only the Host header betrays the attacker's domain. The pickDirectory-specific loopback guard becomes a prefix-wide fence: Host must be loopback or an exact host[:port] from the new trustedHosts config, an attached Origin must equal that authority, and explicit cross-site markers are refused; requests without browser markers (curl, tests, native clients) pass, because without a browser there is no confused deputy. The loopback-socket check is dropped — binding policy expresses reachability, and the fence is not an auth layer. The Agent Note records the full threat model and the alternatives.
3.5 KiB
3.5 KiB
Agent Note:整个 /api 面共用一道载体级浏览器信任边界
状态:已实现
English | 中文
问题
Web GUI 宿主以纯 HTTP 提供 /api(默认 127.0.0.1:3080,支持 --host 0.0.0.0),而这个面上有远程代码执行级别的方法——session.prompt 驱动的 agent 可以运行 bash。浏览器会用两种经典方式把操作者变成攻击此类本地 API 的"混淆代理人":恶意页面发出跨站"简单请求" POST(text/plain——不经 CORS 预检即发出),其副作用照常执行、只是响应不可读;以及 DNS rebinding 后的源以"同源"身份直连 socket,CORS 整体失效,只有 Host 头会暴露攻击者的域名。在本决策之前,系统里唯一的浏览器信任检查(isTrustedNativeDialogRequest:回环 socket + 同源 + 回环 Host)只守着一个装饰性的路由——host.pickDirectory,其原生对话框弹在宿主屏幕上——而所有真正要命的方法都在裸奔。按 RPC 逐个设防也活不过即将到来的应用内目录浏览器:它存在的意义就是服务合法的远程客户端,回环规则恰恰会拒绝它们。
决策
在载体层对整个 /api 前缀一次性执行浏览器信任检查——两半各占一个栈式 PR:
- 媒体类型栅栏(dsh-host-apiproxy):每个
/apiPOST 必须声明application/json,否则在解析前以 415 拒绝。跨站"简单请求"由此不复存在:任何跨站尝试都被逼进一次本服务器从不应答的 CORS 预检。 - 权威栅栏(dsh-client-connection,
src/api-request-trust.ts):Host必须是回环地址,或与插件trustedHosts配置中的某个host[:port]精确匹配(rebinding 防御);若带Origin则必须与该权威完全一致;sec-fetch-site: cross-site一律拒绝。不带浏览器标头的请求放行——非浏览器客户端是委托人本人,不是代理人。host.pickDirectory失去专属守卫,与其他请求同栅而行。
两条边界刻意留在范围之外:可达性归 webserver 绑定配置(host: 127.0.0.1 | 0.0.0.0)管辖;真正远程部署的认证是延期工作,记录在 connection README——这道栅栏是混淆代理人防御,不是认证层。旧守卫的回环 socket 检查被放弃而非泛化:绑定表达可达性、trustedHosts 点名远程权威之后,socket 地址提供不了头部栅栏覆盖不到的任何东西。
曾考虑的替代方案
- 按 RPC 设防(延续现状)。 否决:守卫清单永远追着方法清单跑,价值最高的方法本来就没被守住,而 browse RPC 上的回环规则会破坏它们为之存在的远程部署。
- CORS 头 + 省略凭据。 否决:我们根本不想要任何跨源读取,应答预检只会扩大暴露面;拒绝预检严格更强也更简单。
- 现在就上认证令牌。 在本变更中否决:令牌的签发/存储/轮换是真实的产品面;栅栏今天就能封死浏览器代理人漏洞,无需预先决定认证设计。
后果
- 未来任何
/api方法天然在覆盖范围内;不存在会被遗忘的按路由信任决定。 - 非回环部署必须在
trustedHosts中声明服务权威,否则浏览器会被拒绝;curl 形态的自动化不受影响。 - 客户端必须给 POST 体标注
application/json(我们自己的客户端一向如此;裸 fetch 测试补上了该头)。 - 无认证
0.0.0.0部署的"信任网络"假设从隐含变为成文。