自主编码 Agent 在本地开发环境里跑起来,听起来很酷,直到你意识到它可能删掉你的 .git、装一个恶意 npm 包、或者把 SSH 密钥读走。OpenAI 最近公开了 Codex Windows 沙箱的架构细节——他们没有另起炉灶发明一套隔离机制,而是把 Windows 已有的 SID、ACL、受限令牌(restricted token)和专用沙箱账户组合起来,让 Agent 在真实开发流程中安全执行。这套思路值得每个在做本地 Agent 安全的人看看。
难题:Agent 既要干活,又不能乱跑
Codex Agent 的场景不是"在容器里跑个脚本就完了"。它要:
- 读写项目目录里的源码
- 执行
npm install、pip install等构建命令 - 调用 Git 操作(checkout、commit)
- 但不能访问用户的 SSH 密钥、浏览器 cookie、其他项目的代码
传统沙箱要么隔离太狠(Agent 连文件都看不到),要么太松(和用户同权限)。OpenAI 的方案是在 Windows 安全模型里找到中间地带:给 Agent 一个受限身份,让它只能碰允许碰的东西。
四块积木:SID、ACL、受限令牌、沙箱账户
SID——身份的原子单位
Windows 里一切权限判断的起点是 SID(Security Identifier)。每个用户、组都有一个唯一 SID。Codex 沙箱的核心思路是:给 Agent 创建一个专用 SID,而不是复用用户自己的 SID。
这意味着 Agent 进程在权限检查时,系统看到的是"沙箱账户"的身份,不是"你的登录账户"。即使 Agent 进程被攻破,攻击者拿到的也是一个低权限身份。
ACL——资源上的门禁列表
每个文件、目录、注册表键、管道对象上都挂着一条 ACL(Access Control List),里面逐条写明"谁可以做什么"。Codex 的做法是:
- 项目目录:在 ACL 里给沙箱 SID 加上
Read/Write/Execute权限 - 用户主目录下的敏感子目录(
.ssh、AppData\Roaming等):确保沙箱 SID 不在 ACL 的允许列表里 - 系统关键路径(
C:\Windows\System32):只保留标准受限访问
ACL 的粒度可以精确到单个文件。这让"Agent 能改 src/ 但不能读 .ssh/id_rsa"变成一条声明式规则,而不是靠运行时拦截。
受限令牌——进程级别的权限剪裁
Windows 提供了一个强力但常被忽视的 API:CreateRestrictedToken。它可以从一个基础令牌上:
- 禁用某些 SID(Deny-only:遇到这些 SID 时权限检查直接失败)
- 删除某些特权(如
SeDebugPrivilege、SeLoadDriverPrivilege) - 限制令牌为仅限当前会话
Codex 用受限令牌把沙箱进程的"能力上限"锁死。即使 ACL 里不小心给了多余权限,被禁用的 SID 和被删除的特权也会在权限评估时生效——双重保险。
专用沙箱账户——隔离的身份锚点
把上面三者串起来的是一个本地专用账户。这个账户:
- 不在
Administrators组里 - 不在
Power Users组里 - 只属于一个自定义的低权限组(比如
CodexSandboxUsers) - 密码由系统管理,用户不需要知道
Agent 进程以这个账户身份启动,拿到它的受限令牌,在 ACL 约束下操作文件。整个链条:账户 → SID → 受限令牌 → ACL → 资源。
实践:用 PowerShell 模拟一个最小沙箱环境
下面是一个可以在 Windows 11 上直接运行的示例,模拟 Codex 沙箱的核心步骤:创建沙箱账户、配置 ACL、用受限令牌启动进程。运行前请确保你有管理员权限,且在测试机器上操作(不要在生产环境直接跑)。
步骤一:创建沙箱账户和自定义组
# 创建一个本地低权限组
net localgroup CodexSandboxUsers /add
# 创建沙箱账户,不设密码(由系统管理),不允许交互登录
net user CodexAgent /add /passwordchg:no /expires:never
net localgroup CodexSandboxUsers CodexAgent /add
# 禁止沙箱账户交互登录(防止有人用这个账户直接登桌面)
$regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"
New-ItemProperty -Path $regPath -Name "CodexAgent" -Value 0 -PropertyType DWord -Force
步骤二:为目标项目目录配置 ACL
# 假设项目目录
$projectDir = "C:\Projects\my-app"
# 获取沙箱账户的 SID
$sandboxSid = (New-Object System.Security.Principal.NTAccount("CodexAgent")).Translate([System.Security.Principal.SecurityIdentifier])
# 获取当前 ACL
$acl = Get-Acl $projectDir
# 给沙箱账户授予 Modify 权限(读+写+执行,但不能改权限本身)
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$sandboxSid,
"Modify",
"ContainerInherit,ObjectInherit",
"None",
"Allow"
)
$acl.AddAccessRule($rule)
Set-Acl $projectDir $acl
# 确保沙箱账户无法访问 .ssh 目录(显式 Deny)
$sshDir = "$env:USERPROFILE\.ssh"
$sshAcl = Get-Acl $sshDir
$denyRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$sandboxSid,
"FullControl",
"ContainerInherit,ObjectInherit",
"None",
"Deny"
)
$sshAcl.AddAccessRule($denyRule)
Set-Acl $sshDir $sshAcl
步骤三:用受限令牌启动 Agent 进程
# 用 Start-Process 以沙箱账户身份启动(模拟受限令牌效果)
# 实际生产中会用 CreateRestrictedToken API 剪裁更多特权
# 这里用 PowerShell 模拟基本隔离
$agentScript = @"
Write-Host 'Agent running as:'
whoami
Write-Host 'Attempting to read project file...'
Get-Content 'C:\Projects\my-app\README.md' | Select-Object -First 5
Write-Host 'Attempting to access .ssh...'
try {
Get-Content '$env:USERPROFILE\.ssh\id_rsa' -ErrorAction Stop
Write-Host 'DANGER: .ssh is accessible!'
} catch {
Write-Host 'Good: .ssh access denied.'
}
"@
$agentScript | Out-File "C:\Projects\agent-runner.ps1" -Encoding UTF8
# 以沙箱账户身份启动(需要 SeIncreaseQuotaPrivilege,管理员默认持有)
Start-Process -FilePath "powershell.exe" `
-ArgumentList "-ExecutionPolicy Bypass -File C:\Projects\agent-runner.ps1" `
-Credential (New-Object PSCredential("CodexAgent", (New-Object System.Security.SecureString))) `
-NoNewWindow -Wait
注意:上面的
Start-Process -Credential在 PowerShell 中需要密码。真实生产场景中,OpenAI 使用 Win32 APICreateProcessAsUser+CreateRestrictedToken组合,密码由 LSA secret 管理。这里为了可运行性做了简化。如果你想在本地完整模拟,可以用 C# 调用CreateRestrictedToken,下面给出核心 P/Invoke 片段:
// 核心 Win32 调用示意(需嵌入完整 P/Invoke 声明)
// CreateRestrictedToken 参数:
// baseToken — 沙箱账户的进程令牌
// disableSidCount — 要禁用的 SID 数量
// sidsToDisable — 如 Administrators SID, Power Users SID
// restrictSidCount — 要添加的仅限拒绝 SID
// sidsToRestrict — 如高权限组的 SID
// privilegesToDelete — 如 SeDebugPrivilege, SeLoadDriverPrivilege
IntPtr restrictedToken;
bool ok = CreateRestrictedToken(
baseToken,
0, // flags
disableSids.Length,
disableSids,
restrictSids.Length,
restrictSids,
privsToDelete.Length,
privsToDelete,
out restrictedToken
);
// 然后用 restrictedToken 启动 Agent 进程
CreateProcessAsUser(restrictedToken, ...);
设计权衡与落地建议
Codex 的方案不是银弹,它有几个明确的取舍:
1. Windows 专属,不可移植
SID、ACL、受限令牌全是 Windows NT 安全模型的产物。同样的思路在 Linux 上要换成 UID/GID + seccomp + AppArmor + namespaces。核心原则(专用身份 + 声明式权限 + 进程级剪裁)可以迁移,但具体 API 不能。
2. ACL 配置是运维负担
每个项目目录都要手动或脚本化地配置 ACL。如果项目多了,需要一个 ACL 管理工具来批量操作,否则迟早会漏配。建议写一个项目初始化脚本,在 git clone 之后自动跑 ACL 设置。
3. 受限令牌的调试成本高
进程拿受限令牌运行后,很多"明明 ACL 允许但还是报 Access Denied"的情况,根源是被禁用的 SID 或被删的特权在权限评估时优先级更高。排查时要用 whoami /all 对比令牌内容,或者用 AccessChk 工具逐项检查。
4. 网络隔离没覆盖
文章聚焦在本地文件和进程权限。Agent 如果能自由联网,ACL 再严也挡不住它把文件内容 POST 到外部服务器。生产部署需要加网络层隔离(Windows Firewall 规则或网络命名空间)。
落地检查清单
如果你要在自己的 Agent 系统里复用这套思路,逐项确认:
- [ ] Agent 运行在专用低权限账户下,不在 Administrators / root 组
- [ ] Agent 进程令牌通过
CreateRestrictedToken(或 Linux 对等机制)剪裁了高危特权 - [ ] 项目目录 ACL 给 Agent SID 开了必要权限,敏感目录(密钥、凭据、其他项目)显式 Deny
- [ ] Agent 进程的令牌里禁用了高权限组 SID(Deny-only)
- [ ] 网络出站有防火墙规则限制(只允许必要的域名/IP)
- [ ] 有自动化脚本在项目创建时配置 ACL,避免手工遗漏
- [ ] 有日志记录 Agent 进程的令牌内容和权限检查失败事件
Codex 的做法证明了一件事:为 AI Agent 做安全隔离,不需要发明新范式,把操作系统已有的安全原语正确组合就够了。难点不在原理,在于把每个细节都配对、配全、配到不漏。