来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)
本内容改编自小山学堂《学 AI 产品,从入门到精通》,为二次演绎配音版
本集属于技术侧(S2)模块 T3「解剖 OpenAI Codex:把安全观写进类型系统」,取材自 1 节课:
这节课回答的是跨平台 Agent 里最容易被自欺的一件事:
Windows 上没有 seatbelt,也没有 bubblewrap —— 那这里的「沙箱」到底是什么?
答案是三道关卡叠起来:令牌管写(第一关)、专用系统用户管你是谁(第二关)、WFP 按这个身份滤网络(第三关)。而且整套东西默认关着。
一句话概括:Windows 没有进程视图,隔离只能按身份锁资源。
读完本集(听完音频),你应该能够:
| 能力 | 具体表现 |
|---|---|
| 识别三种失败语义 | 区分 Operation not permitted / Access is denied / Forbidden-or-Prompt 各自对应的隔离深度 |
| 解释隔离原语转向 | 说清「主体能做什么」与「客体能被谁碰」两套原语,以及为何 Windows 走后者 |
| 复述三个标志 | DISABLE_MAX_PRIVILEGE / LUA_TOKEN / WRITE_RESTRICTED 各管什么 |
| 说出管辖边界 | 管写,不管读和网络——以及 RestrictedToken 档为何如此 |
| 区分两档身份来源 | RestrictedToken 从当前用户裁;Elevated 先登专用账户再裁 |
| 解释为什么要两个账户 | 一个 SID 只能挂一套网络策略 → 身份数必须覆盖策略组合数 |
| 复述 WFP 覆盖面 | 出站阻断、代理端口白名单、12 条过滤器(ICMP / DNS 53 / DoT 853 / SMB) |
| 说出运维风险 | 持久标志 → 重启后仍在;仓库无拆除函数;WFP 失败非致命 / 防火墙失败致命 |
| 解释默认关 | AppContainer 对不上权限模型;Elevated 安装摩擦太贵 |
| 说清兜底位置 | 沙箱不可用时,策略层把它判成 Forbidden 或 Prompt |
| 横向对比三档答法 | 说清不做 / 只做写限制 / 全做进去 三者的取舍依据 |
| 机器 | Agent 想做的事 | 结果 |
|---|---|---|
| macOS(Seatbelt) | 读 ~/.ssh/id_rsa | Operation not permitted |
| Windows(受限令牌/ACL) | 同上 | Access is denied |
| Windows(未启沙箱) | 同上 | 根本走不到 ACL,execpolicy 先判 Forbidden / Prompt |
危险之处:模型看到的下一句话不一样,它下一步的行为就会跟着变。
如果 Windows 上假装有和 Unix 一样的默认沙箱,模型就会按「自己被关着」去规划,实际命令却在裸跑。承诺了做不到的墙,模型和用户都会按那道墙做决策。
| 报错 | 对应隔离深度 |
|---|---|
Operation not permitted | 有进程级沙箱 |
Access is denied | 只有身份 + ACL |
Forbidden / Prompt | 没有后端,策略层兜底 |
这不是 API 差异,是语义差异。
| 平台 | 限制的是什么 |
|---|---|
| macOS | 这个进程能对哪些路径做哪些操作 |
| Linux | 这个进程能看见什么、能调哪些系统调用 |
| Windows | 没有 namespace,没有 seccomp → 只能问:这个身份能碰哪些资源 |
两种隔离原语:「主体能做什么」 vs 「客体能被谁碰」。Windows 走的是后者。
| 档位 | 起点 |
|---|---|
RestrictedToken | 从当前用户令牌往下裁 → 天然继承这个用户对工作区的可见性,只把写卡住 |
Elevated | 先建专用账户并登录,拿到那一份令牌,再从它往下裁 |
差距不是强度高低,而是「以谁的身份起步」——这决定了后面所有 ACL 对谁生效。
| 标志 | 作用 |
|---|---|
DISABLE_MAX_PRIVILEGE | 把不必要的特权削薄 |
LUA_TOKEN | 标准用户常规模式 |
WRITE_RESTRICTED | 写必须同时通过普通 ACL 与 restricting SID 的交叉检查 |
源码写得很清楚:WRITE_RESTRICTED 令牌上的 capability SID deny-read ACE 不参与读检查。
所以在 RestrictedToken 这一档:读工作区之外的文件会穿过,往外发网络也会穿过。
这一档能挡住的:模型误改仓库文件
这一档挡不住的:读出密钥、把东西往外传 —— 要拦必须换 Elevated。
| Unix 网络隔离 | Windows WFP | |
|---|---|---|
| 做法 | 给进程换一张网络命名空间视图 | 进程还在主机网络栈上,只是这个 SID 发出的特定流量被内核滤掉 |
| 进程可见性 | 看不见宿主网络 | 仍能看见网卡、遇到解析失败、连别的端口 |
需要沙箱时,用自己的 exe + 隐藏参数 --run-as-windows-sandbox 当包装器 —— 与 Linux 改 arg0 是同一思路,不需要额外二进制。
硬事实前置:整套默认关着。开关没开时,平台沙箱返回空,后面不会再包任何包装器。
WFP(Windows Filtering Platform)是内核包过滤框架。关键在于:
它和防火墙规则都按 SID 匹配,而一个 SID 只能对应一套网络策略。
同一个身份没法同时表示「完全没网」和「可走代理 / 自由出网」——身份与策略是一对一的。
| 账户 | 何时选用 | 装什么 |
|---|---|---|
CodexSandboxOffline | 强制走代理,或网络策略未启用 | 出站阻断 + 代理端口白名单 + 12 条 WFP(ICMP / DNS 53 / DoT 853 / SMB) |
CodexSandboxOnline | 需要联网但仍要独立身份 | 不装这套阻断 |
账户名在 setup.rs 中写死。
把「读 SSH 私钥」留在原地,档位从 Elevated 降到 RestrictedToken:
第二关和第三关直接消失,那条读取一路穿过。
因为在令牌档根本没有那两个专用账户 —— 第三关连存在的条件都不满足。
同理,用户配置根默认挖空 .ssh 等目录,但这份排除列表只有在登录了专用账户之后才生效(它挂在那个账户的 ACL 上)。在令牌档,这不是「没配好」,而是那一层压根没有这个挂载点。
从 Elevated 降到 RestrictedToken 时,不要以为只是「少几层保护」——第二关的 ACL 挖空和第三关的 WFP 是挂载在专用账户身份上的,身份不存在,这两关整体消失,而不是「变弱」。
做降级决策前,请把「这一层依赖哪个身份」问清楚。
按身份做访问控制时,身份数量必须覆盖策略组合数。网络策略有两档,就需要两个身份。
这和用两张门禁卡进两栋楼是同一件事,与 WFP 这个具体 API 无关。
| 事实 | 含义 |
|---|---|
| 用稳定 GUID 标识 + 持久标志 | 重启后仍在 |
| 卸载 / 进程崩溃 | 不会自动拆掉 |
| 仓库中检索不到删除用户或拆除 WFP 的函数 | 清理靠人工或其他方式 |
| WFP 失败 = 非致命 | 安装继续往下走 |
| 防火墙失败 = 致命 | setup 整段失败 |
结论:这套方案的摩擦不在运行时,在安装和卸载。选它之前先确认环境允许建本地账户(企业策略可能直接禁止)。
| 要求 | AppContainer 的实际情况 |
|---|---|
| workspace 可读、平台根可读,再按 deny 列表挖空 | 可读范围默认很窄,任意路径读取必须改宿主 DACL |
受限令牌 + ACL 明显更贴合这份权限模型。在业务代码里检索 AppContainer,零命中。
对照 DSH 把同一判断写成明文:AppContainer 令牌没有环境读权限,任意路径读取必须预先授权,对不上它的读模型。(另一方案 mxc 被否是因为系统版本底线太新。)
要把读和网络都管住,就要:建账户 → 弹 UAC → 改防火墙 → 装 WFP。每一条都是安装摩擦,且企业策略可能禁止本地建用户。
pub enum WindowsSandboxLevel {
#[default]
Disabled,
RestrictedToken,
Elevated,
}
没写 windows.sandbox、两个 legacy flag 也没有时 → 落到 Disabled。legacy flag 已被标成 Removed。
产品判断:把默认设成关,等于把整套摩擦推迟到用户第一次真正启用时。好处是装上就能跑;代价是大多数用户从未走到那一档,也就从未拿到对应保护。
get_platform_sandbox(false) → Windows 上返回空
→ select_initial 变成 SandboxType::None
→ 命令仍可能被策略层拦住
测试锁死这条:没有后端时,只读档案 + Never 审批,未匹配的 cmd.exe /c dir 必须是 Decision::Forbidden。
跨平台能力不对等时,对外说法必须按实际启用的那一层写。 macOS 默认有 Seatbelt,Windows 默认可能是 Disabled —— 用户听到「有沙箱」,会默认三台机器是同一道墙。
| 方案 | 做法 | enforcement | ||
|---|---|---|---|---|
| DSH | 同组 CreateRestrictedToken 标志:`DISABLE_MAX_PRIVILEGE \ | LUA_TOKEN \ | WRITE_RESTRICTED`;不建账户、不装 WFP | README 写明读/网络/进程可见性不受限,标为 partial |
| Claude Code | 原生 Windows 上写明没有沙箱;企业策略要求时 PowerShell 直接拒绝执行(文案 sandboxing is not available on native Windows) | 不假装有一层隔离 | ||
| Grok Build | 沙箱 crate 平台写成 linux/landlock、macos/seatbelt,其余标 unknown;检索不到 AppContainer / RestrictedToken 实现 | 不做 Windows 文件系统沙箱 | ||
| Codex | 身份 + ACL + WFP + 专用账户全做进去 | 摩擦更大,默认关 |
三档答案:
技术栈高度重叠,差别全在产品判断:选择的依据不是能不能做,而是愿意让用户在安装时付多少代价。
Windows 上部署前逐项确认:
WindowsSandboxLevel 实际是 Disabled / RestrictedToken / ElevatedElevated(令牌档做不到)CodexSandboxOffline / CodexSandboxOnline)from_permissions 强制代理时选 Offline)openai/codex,commit 4f39251a01,核对日期 2026-08-22;代码块保留源码原文。token.ts / win32-abi.ts / README / 设计笔记;Claude PowerShellTool.tsx;Grok types.rs。Windows 没有 seatbelt 和 bubblewrap,隔离只能按身份锁资源。RestrictedToken 那三个标志管写,不管读和网络。要拦密钥和出网,必须再上专用账户、ACL 和按 SID 装的 WFP。AppContainer 对不上这份可读集合,Elevated 又要 UAC,所以默认关着,execpolicy 在没有后端时改成 Forbidden 或 Prompt。
来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)
本内容改编自小山学堂《学 AI 产品,从入门到精通》,为二次演绎配音版