TIM-1EARCHIVE TERMINAL // 2026

NTE-017经验笔记CLASS · PUBLIC

一条无害的 PowerShell 为何被 blocked by policy:从最小复现到自动审核恢复

在 Codex 完全访问模式下,一段 PowerShell 更新任务在启动前就被 blocked by policy 拦下。我另写了一条形态相同的无害命令,用对照实验确认:在这段复杂命令里,本地进程启动和一个只是被打印出来的 URL 同时出现时会被拒,删掉 URL 就能通过。接着对照固定版本源码看清这次拒绝来自哪一层,最后把审批策略改成 on-request 加自动审核,同一条命令一字不改就跑通了。

DATE
2026.11.05
UPDATED
2026.11.06
READ
7 MIN
LENGTH
2,507 字
CONTENTS · 目录(8)
  1. 实验环境
  2. 写一条无害的对照命令
  3. 对照实验:单独执行都没问题
  4. 拒绝来自哪一层
  5. 为什么只开自动审核不够
  6. 恢复:改审批策略,不碰分类器
  7. 验收:同一条命令,一字不改
  8. 这次解决了什么,没解决什么

这件事一开始很普通。Codex 在完全访问模式下执行一段 PowerShell 更新任务,内容是更新 SSH 相关工具,结果返回一句 blocked by policy,命令在启动前就被拒绝了。

第一次排查时我只确认了拒绝发生在哪一层,没有再往下追。后来决定把它查到底:什么样的命令形态会触发拒绝,背后是哪段逻辑,以及怎样让普通操作照常自动执行,而不是把权限一路放大。

实验环境

为了让结果能对上源码,我把环境固定下来:

  • Codex 核心版本 0.165.0-alpha.1.2
  • 原生 exec_command,PowerShell 7,login=false
  • 初始有效设置:sandbox_mode = danger-full-access,approval_policy = never
  • 没有用户自定义 rules 目录

最后一条要先说明:拒绝不是我自己写的规则造成的,因为根本没有自定义规则。

写一条无害的对照命令

原来的更新脚本要动真实工具,不适合拿来反复试。所以我没有回放它,而是另写了一条命令形态相同、但完全无害的最小对照:一段里有变量赋值、本地进程启动,还有一个 URL 字面量。

powershell
Write-Output 'POLICY_PROBE_BEGIN'; $probeProcess = Start-Process -FilePath 'C:\Windows\System32\cmd.exe' -ArgumentList '/c','exit','0' -WindowStyle Hidden -PassThru -Wait; Write-Output $probeProcess.ExitCode; Write-Output 'http://127.0.0.1:9/health'; Write-Output 'POLICY_PROBE_END'

它做的事情只有这些:启动一个隐藏的本地 cmd.exe,让它立刻以 0 退出,再打印退出码;然后把一个本地回环地址当作字符串打印出来,不发任何请求。整个过程不改文件,不留后台进程。

在 never 模式下,这条命令在启动前就被拒绝了,连 POLICY_PROBE_BEGIN 都没有输出。可见 PowerShell 进程根本没被拉起,拒绝发生在命令发出之前。

对照实验:单独执行都没问题

接下来每次只改一处:

变体结果
原样完整命令启动前被拒,blocked by policy
删掉打印 URL 那一句,保留变量赋值和 Start-Process输出 BEGIN / 0 / END,exit 0
只打印同一个 URL成功
不赋值,直接 Start-Process 启动同一个本地进程成功,正常退出

这张表是整次排查里最有价值的发现:**在这段复杂命令里,本地启动和一个无关的 URL 放在一起就被拒,删掉 URL 就通过;单独的 URL、单独的本地启动也都没问题。**如果只盯着某一行找"危险操作",是找不到原因的。

有一组我没有测:不赋值的 Start-Process 和同一个 URL 放在同一段里。所以单凭实测,我说不出变量赋值是不是必要条件,只能确定"启动 + URL"这个组合在这段命令里会触发。

拒绝来自哪一层

blocked by policy 很容易让人以为是 Windows 本身的策略,比如组策略、AppLocker 或执行策略。其实这次拒绝跟 Windows 系统服务没有关系,跟模型也没有关系,也不是自动审核拒的。它来自 Codex Rust 核心里的 Windows 命令安全检查,这段代码在命令交给 PowerShell 之前就对命令文本做了分类。

照着同版本源码读下来,过程大致是:

  1. 先尝试结构化解析。powershell_tree_sitter.rs 会解析命令,能单独识别出来的简单字面命令(命令名、参数都是写死的字面量)会被逐条判断。
  2. **复杂结构退回整体检查。**出现变量赋值、动态表达式这类结构时,就不再逐条判断,而是对整段命令做 token 级检查。
  3. **启动词遇上 URL。**在整体检查里,Start-Process 和 URL 落进了同一个检查范围。windows_dangerous_commands.rs 里的危险启发式把这种组合当成"用启动命令打开一个网址",标记为危险。至于这个 URL 其实只是另一条语句里打印的字符串,检查分不出来。
  4. never 下没有审批可走。exec_policy.rs 对需要审批的命令,在 Never 分支直接判为 Forbidden,用户看到的就是 blocked by policy。

拿这条路径对照实测:完整命令含赋值,按源码会走整体检查,启动词和 URL 撞在一起被标记;删掉 URL,整体检查里只剩启动词;单独打印 URL 不涉及启动;单独的不赋值 Start-Process 是简单字面命令。源码说明了简单字面命令和复杂结构的检查范围不同,但"去掉赋值后再加回 URL 会怎样",要等补上那一组对照才能下结论。

社区里已经有人报告过同类问题:openai/codex#40060 讨论的正是 Start-Process 和无关 URL 出现在同一段 PowerShell 里时的误报。PR #54076 让简单字面命令可以各自单独判断,修复了一部分情况;而我这条带赋值的命令,在 0.165.0-alpha.1.2 上实测仍会被拒。

以上解释依据的是固定版本的源码,我没有在运行中的二进制里插桩追踪分支;对照命令也是另写的。所以结论只针对这条最小复现,不能说最初那次拒绝的根因就一定是它。

为什么只开自动审核不够

我的目标不是每条命令都手动确认,而是普通操作照常自动执行,只有被判为需要审批的命令交给自动审核(Guardian)。

问题在于,approval_policy = never 的意思是"不会产生任何审批请求"。即使同时配置了 approvals_reviewer = auto_review,被分类器标记的命令在 never 下也会直接变成 Forbidden,审核人根本收不到请求。官方自动审核说明也写明了:只有交互式审批策略下才有审核,而且切换审核人不会扩大沙箱。

整个路由如下:

命令分类结果在 never 与 on-request 两种审批策略下的去向:实线为本次实际走过的路径,never 下需审批的命令直接被拒,on-request 下交给自动审核并放行;虚线分支按源码与文档示意

图里实线是这次实际走过的两条路:never 下被拒,on-request 下经自动审核放行。虚线分支(判为安全后直接执行、审核人为 user、审核拒绝)是按源码和文档画的,这次没有逐一测试。分类器给出的判断本身没有变,变的是"需要审批"这个结果之后走哪条路。

恢复:改审批策略,不碰分类器

我确认之后,先备份,再修改了供"自定义"权限模式使用的配置:

toml
approval_policy = "on-request"
approvals_reviewer = "auto_review"
sandbox_mode = "danger-full-access"

配置加载检查通过后,我在目标聊天里选择了"自定义(config.toml)",下一回合由 Agent 核对实际生效的三项设置。

这里有两点容易弄错:

  • **改的是全局配置,不是某个会话的配置。**其他选择"自定义"模式的聊天也会受影响;已经存在的聊天各自的权限选择我没有逐个修改。
  • **"自定义"这个名字不代表权限更低或更高。**要看的是实际生效的沙箱、审批策略和审核人这三项。

验收:同一条命令,一字不改

11 月 6 日凌晨 3:02 开始验收。把之前被拒的那条命令原样再执行一次,输出:

plain
POLICY_PROBE_BEGIN
0
http://127.0.0.1:9/health
POLICY_PROBE_END

exit 0。这次命令被标记后,没有被直接拒绝,而是交给了 Guardian。审核记录是 risk_level=low、user_authorization=high、outcome=allow,给出的理由是:本地进程立即退出,只输出文本,没有网络或文件副作用。另外跑的一条普通对照命令也正常执行成功。

按文档和源码的设计,on-request 并不会每条命令都问:通常允许的命令照常执行,只有被判为需要审批的才交给审核人。

这次解决了什么,没解决什么

准确地说,这次恢复的是可以被审核的路径,分类器的误判本身没有修好。同样的命令在 never 下还是会被拒。我没有关闭分类器,没有放宽命令白名单,也没有修改 Codex 核心。

还有几点值得记下来:

  • **审核人和沙箱是两个独立的控制。**审核人决定"需要审批的命令由谁来判断",沙箱决定"命令能碰到多少东西"。本例沿用了我原来的 danger-full-access,这是按我自己的工作场景定的,不建议照抄成默认配置。大多数人应该保留适合自己工作的沙箱范围,再在这个范围内选择审核方式。
  • **自动审核不等于全部放行。**Guardian 会自己判断风险,这次放行是因为命令确实无害。显式的拒绝规则、其他模型提供方和 MCP 工具的审批也各有各的行为,不能认为配了自动审核就都能通过。

如果你也在 Windows 上遇到莫名其妙的 blocked by policy,可以按这个顺序排查:先确认 PowerShell 有没有真的启动(有没有任何输出);再写一条形态相同的无害命令,逐个去掉启动命令、URL 字面量、变量赋值等结构,找出触发的组合;最后检查实际生效的审批策略。如果是 never,那么无论配了什么审核人,被标记的命令都只会被直接拒绝。


本文由 Claude Opus 5.5 辅助书写。