先分清三层控制,别指望一个配置解决所有问题
| 层次 | 管什么 | 在哪配 |
|---|---|---|
| 沙箱 / 权限配置 | 本地命令能读哪些路径、能写哪些路径、能不能联网、能连哪些域名 | default_permissions + [permissions.<name>],或旧的 sandbox_mode |
| 审批策略 | 某条命令执行前是否要你点确认 | approval_policy、CLI 的 --ask-for-approval,以及 auto-review 机制 |
| 能力开关 | 网页搜索、连接器、MCP、内置浏览器、Computer Use、云端环境各自的网络与权限 | 各自的功能配置(沙箱白名单不会自动作用到这些面上) |
新旧两套配置互斥,先看你用的是哪套
Codex 正在把权限体系从 sandbox_mode / [sandbox_workspace_write] 迁移到 permission profiles(目前标着 Beta,可能变)。两套不能混用,判断规则很直接:
- 只要任何一个加载的配置文件里出现
sandbox_mode,或者你传了--sandbox,或者选中的 profile 里设了sandbox_mode,Codex 就走旧沙箱设置,:permissions那套被忽略。 - 唯一例外:企业托管配置里出现
allowed_permission_profiles,会强制按 permission profiles 运行。 - 权限配置和旧的
sandbox_mode不能组合,一份配置里只能选一种写法。 - 企业混版本部署时,可以暂时保留托管的
allowed_sandbox_modes作为兼容约束,等客户端都升到 0.138.0 以上再删。
所以排查「我配了却不生效」时,第一步永远是:搜一遍所有配置文件里有没有残留的 sandbox_mode。两套命名其实是一一对应的:旧写法 read-only 对应 :read-only,workspace-write 对应 :workspace,danger-full-access 对应 :danger-full-access。看到别处文档写旧名,按这张表换过来就行。
三个内置档位,够用就别自己写
| 内置 profile | 允许什么 | 适合 |
|---|---|---|
| :read-only | 本地命令只能读,不能写 | 读代码、做审计、让 Agent 只出方案不动手 |
| :workspace | 可写工作区根与系统临时目录;.git、.agents、.codex、.aws 这些路径保持只读 | 日常开发,绝大多数人用这个就对了 |
| :danger-full-access | 移除本地沙箱限制 | 只在明确需要全权限时用,且要清楚这意味着什么 |
:workspace 那个附带保护值得单独说一句:它把工作区里的版本库与凭证目录钉成只读,能挡掉相当一部分「Agent 顺手改了 .git/config 或读走 .aws 凭证」的事故。自己从零写 profile 反而容易丢掉这层。
default_permissions = "project-edit"
[features]
network_proxy = true
[permissions.project-edit]
description = "工程编辑:可写工作区,可访问 OpenAI API。"
extends = ":workspace"
[permissions.project-edit.filesystem]
":minimal" = "read"
[permissions.project-edit.filesystem.":workspace_roots"]
"." = "write"
".devcontainer" = "read"
"**/*.env" = "deny"
[permissions.project-edit.network]
enabled = true
[permissions.project-edit.network.domains]
"api.openai.com" = "allow"
"*.github.com" = "allow"
"tracking.example.com" = "deny"extends 只能指向 :read-only、:workspace 或另一个命名 profile,不能继承 :danger-full-access,也不会继承父级描述。未知父级和继承环会被直接拒绝。
文件规则:read / write / deny 与优先级
| 值 | 含义 |
|---|---|
| read | 可读可列目录,不能创建、修改、重命名、删除 |
| write | 可读可改,操作系统允许时也能创建与删除 |
| deny | 读写都拒绝,用来从更大的授权里挖掉一块 |
优先级是「越具体越优先」;同一条路径冲突时 deny 赢过 write,write 赢过 read。这条规则让「先给大范围,再挖洞」成为标准写法:
[permissions.project-edit.filesystem.":workspace_roots"]
"." = "write"
"docs" = "read"
"generated" = "deny"
"**/*.env" = "deny"支持的路径形式有 :root、:minimal(常用工具需要的运行时路径)、:workspace_roots、:tmpdir、:slash_tmp、绝对路径和 ~/path。子路径用 . 表示基准路径本身,不允许 .. 往上跳。
反过来说,deny 也能被更具体的路径重新打开:先 ~/Documents = deny,再 ~/Documents/codex = write,结果是只有 codex 那个子目录可写。做「只开放一个项目」的场景时很好用。
网络:开代理和开网络是两件事
这是 Codex 权限体系里最容易配错的地方:permissions.<name>.network.enabled = true 只是允许命令联网,它不会启动代理。不启用代理时,域名规则完全不生效,命令直连。
| 网络 | 代理 | 实际结果 |
|---|---|---|
| 关 | — | 命令完全不能联网,代理设置无所谓 |
| 开 | 关 | 直连,无任何域名过滤(规则被忽略) |
| 开 | 开 | 走代理,按 domains 允许/拦截;没有 allow 条目就全拦 |
所以要让域名策略真正起作用,除了 network.enabled,还得在 config.toml 里打开 features.network_proxy = true,或者由管理员在 requirements.toml 里用 [experimental_network] 启动代理。
example.com匹配精确主机;*.example.com只匹配子域;**.example.com匹配主域和子域;*是仅用于 allow 的全局通配。deny覆盖allow,主机名会被规范化(去空格、转小写、去尾点、去端口)。- 代理默认还会挡本地与私有网段,防 DNS rebinding。要访问本机服务得显式
"localhost" = "allow"或"127.0.0.1" = "allow"。 - 默认监听
http://127.0.0.1:3128(HTTP)与http://127.0.0.1:8081(SOCKS5)。 dangerously_*系列(非回环代理、全部 Unix socket 放行、allow_local_binding)是逃生口,普通开发不要开。- Docker 之类的本地集成需要 Unix socket 白名单,按需逐条加,别整体放行。
审批策略:沙箱之外的第二道闸
沙箱决定「即使跑起来也伤不到哪去」,审批决定「要不要先问你一声」。两者要匹配着设:沙箱越宽,审批就越该保留人工确认;沙箱越窄,无人工审批的自动化才越安全。
- CLI 上可以用
--ask-for-approval控制审批强度,脚本化时常用codex --ask-for-approval never "..."这种完全无人值守的形式——用它之前先确认当前沙箱足够窄。 - auto-review 机制可以在请求审批时先自动评审一遍,适合「大体能自动跑、但想留一层兜底」的场景。
- 企业侧用
requirements.toml托管配置,可以把可选 profile 锁死:一旦写了allowed_permission_profiles,没列出来的 profile 全部被拒,包括未来的新内置项。 - 托管配置的优先级高于用户配置,用户不能通过本地改写放宽组织策略。
把审批理解成「人还在环里」的标志:任何 never 的自动化,都应该建立在一个自己验证过的窄沙箱之上,而不是建立在「它应该不会乱跑」的假设上。
按场景给四套配置
| 场景 | 沙箱 | 网络 | 审批 |
|---|---|---|---|
| 只读审计 / 让 Agent 先出方案 | :read-only(或 extends 后只给 read) | 按需 allowlist 单个 API 域名 | 保留人工确认 |
| 日常开发 | :workspace + 额外 deny .env 与凭证目录 | 关(默认) | 保留人工确认,命令被拦时再放宽 |
| 需要联网的开发(装依赖、拉包) | :workspace + deny 敏感文件 | 开 + features.network_proxy = true + 域名白名单 | 保留人工确认,重点看网络请求 |
| CI / 无人值守脚本 | :workspace 且限定到仓库路径,禁网 | 关,或只放行构建必需域名 | never,但必须配合窄沙箱与独立容器 |
- 01
先选最窄的档位跑一周
从
:read-only或:workspace起步,记录哪些操作被拦。被拦是信息,不是障碍——它告诉你真正需要放宽什么。 - 02
只放宽被证明需要的部分
每次只加一条文件或域名规则,并且加注释。避免一次性抄一份「全开」模板,那样等于没配。
- 03
做写入授权前想一遍后续执行
脚本、构建步骤、包管理器钩子、shell 启动文件、共享目录里的写入,都可能在你离开沙箱后被执行。这类路径的写权限要单独考虑。
- 04
在平台层面确认隔离能落地
macOS 用 Seatbelt;策略落不下去时 Codex 会拒绝执行而不是无沙箱运行。Linux / WSL 用 bubblewrap + seccomp(旧的 Landlock 不能隔离 app-server socket,升级时要关掉
features.use_legacy_landlock)。Windows 用 MXC,elevated/unelevated是旧回退,unelevated的网络隔离更弱。
最后一句实话:权限配置的目标不是「配到最严」,而是「配到刚好够用、且你知道边界在哪」。一份自己看不懂的配置,比默认配置更危险。
常见问题
我配了 permission profile,但看起来没生效?
先查有没有残留的 sandbox_mode。只要任何一个加载的配置文件里出现它、或者你传了 --sandbox、或者当前 profile 里设了 sandbox_mode,Codex 就会走旧沙箱设置并忽略 permission profiles。托管配置里的 allowed_permission_profiles 是唯一会强制切回 profiles 的例外。
开了 network.enabled,为什么域名白名单不拦请求?
因为 network.enabled 只放开网络,不启动代理。没有代理时命令直连,域名规则不生效。要真正过滤,还得打开 features.network_proxy = true,或让管理员在 requirements.toml 里启用代理。
:workspace 和 :danger-full-access 差别有多大?
很大。:workspace 只允许写工作区根与临时目录,并且把工作区里的 .git、.agents、.codex、.aws 保持只读;:danger-full-access 移除本地沙箱限制,等于把整台机器交出去。后者只应在明确需要时使用。
把 .env deny 掉就安全了吗?
不完全是。在 Linux、WSL 和原生 Windows 上,deny 的 glob 是在沙箱进程启动时枚举的,进程运行期间新建的匹配文件不会被拦。要更严格,用固定路径规则、限制深度,或者把凭证放到工作区之外再加 deny。
官方文档与参考
本文依据上述官方文档整理;价格、额度与功能会随官方更新变化,第三方中转站的行为以其自身说明为准。