发布时间:2026年3月24日
本文合并 2026-03-24 的 Claude Code 狂飙模式原稿与 2026-04-09 的 Codex 完全权限原稿,创建日期保留前者。2026-09-29 根据实际 hook、脚本与敏感读取确认流程重整;本次核对版本为 Codex CLI 0.158.0、Claude Code 2.1.283。
连续开发需要足够的执行能力,也需要在读取凭据时停下来,让用户决定。我的护栏现在把这两件事分开处理:敏感读取需要确认;危险删除、磁盘操作和受保护路径写入仍然拒绝。
Codex 与 Claude Code 不能使用完全相同的询问实现。Claude 支持 hook 返回 ask;本次核对的 Codex 文档明确表示 PreToolUse 尚不支持 ask,该值会使 hook 失败并继续工具调用。因此 Codex 使用“先拒绝并生成请求 → 用户明确确认 → 仅放行一次”的流程。OpenAI Hooks 文档、Claude Code Hooks 文档
这是一层本地防误操作检查。它以当前用户权限运行,不能替代操作系统沙箱,也不保证任意程序的间接文件访问都被检查。
| 层 | 职责 | 边界 |
|---|---|---|
| 工具权限模式 | 决定工具执行能力与默认交互 | Full access 不代表所有破坏性操作都已获授权 |
| PreToolUse hook | 在匹配工具执行前交给检查器 | matcher 命中不等于脚本理解全部输入 |
| 检查脚本 | 识别文件操作、给出拒绝或询问、管理单次读取请求 | 有限语法与路径规则,不是任意代码的安全解释器 |
| 会话中的人工确认 | 用户决定某个具体读取是否必要 | 自然语言同意依靠 Agent 遵守流程,脚本不能证明用户真实意愿 |
破坏性 Git 操作、系统工具链变更和持久数据清理仍遵守项目规则;脚本没有实现完整 Git 或数据库操作授权。
先识别“正在做什么”,再判断路径。文档中出现一个凭据路径,并不意味着读取了它。
echo、printf 的普通文字参数不作为文件读取。rg、grep 等已识别搜索命令,区分搜索模式与文件操作数。tee、原地编辑按写入检查。apply_patch 按 Add、Update、Delete、Move 操作头检查真实目标,不把补丁正文当 Shell。Shell 使用 bashlex 解析为语法树,不执行输入。这样才能区分引号中的 > 与真正重定向、文字参数 tee 与命令位置的 tee。命令参数按输入、输出和无参数开关分别解析;未支持的选项(包括长选项缩写)明确拒绝,要求使用已支持的完整拼写。对于不支持的语法或解析失败,脚本也明确拒绝,不退回按空格猜测。
| 数据或操作 | 处理方式 |
|---|---|
| 受保护登录凭据文件 | 读取需要确认,清单为脚本的 READ_EXACT |
| SSH、AWS、GPG、Kubernetes 凭据目录中的敏感文件 | 读取需要确认;部分公开文件和配置按既有白名单放行 |
| 凭据目录外的密钥文件 | 有限扫描私钥标记;不是同后缀全部拒绝 |
| 环境文件 | 模板和已有信任目录放行;其余有限扫描疑似真实秘密,命中则确认 |
ls 等目录元数据查看 |
不当作读取文件正文 |
| 凭据目录写入、受保护文件删除 | 拒绝,不能通过读取请求放行 |
| Shell 重定向修改启动文件 | 拒绝可识别写入 |
| 直接 Edit / Write 编辑启动文件 | 保留既有允许边界 |
| 简单普通删除 | 尝试改写到回收站;sudo 删除不自动改写 |
ENV_READ_ALLOW_PREFIXES 仍信任 ~/workspaces 下按真实路径判断的环境文件。本次没有扩大或收回这个已有信任范围;复制脚本时必须按自己的开发环境审查它。公开配置也可能包含秘密,白名单和内容扫描都不是秘密发现器。环境文件最多检查前 256 KiB,密钥文件最多检查前 64 KiB。
敏感读取首次命中时,检查器返回合法 deny 和随机 request_id,原工具不执行。Agent 应向用户说明读取目的、文件、原命令和目录,并等待明确确认。
只有用户确认该请求后,才执行:
python3 "$HOME/.codex/scripts/pre_tool_use_guard.py" approve REQUEST_ID
然后原样重试工具调用。不要为携带 ID 改写原命令、增加环境变量或换一种工具绕过。
请求绑定会话 ID、宿主提供的目录、工具名和完整可见 tool_input 的稳定序列化哈希。状态库只存随机 ID、指纹、状态与时间,不存命令文本、文件路径或文件内容。
pending → approved → consumed
创建后最多 15 分钟有效;批准后最多 5 分钟,且不超过原请求期限。重复批准不会续期,过期或已消费的请求不能恢复。SQLite 事务覆盖创建、批准和消费,并发重试只能有一个消费成功。
单次指一次放行,不是一次成功执行。 后续工具失败、其他 hook 拒绝或响应丢失,都不能自动恢复已消费的请求。
本次用无害命令采集了真实 hook 输入:工具调用指定其他 workdir,但 Codex 仍只传入会话 cwd 和 tool_input.command。这与官方仓库中记录的工作目录丢失问题一致。Codex issue #34855
因此不能声称指纹总能绑定宿主没有提供的执行目录。脚本优先使用工具输入里的目录;Codex 未提供实际工具目录时,只为使用绝对文件路径的单个、无展开读取命令建立授权请求。相对路径、通配符、重定向或复杂组合命中敏感读取时,明确拒绝并要求拆成绝对路径读取,再由用户确认。含脚本表达式的 sed / awk / yq,以及实际使用 rg --pre 的调用,也不属于这类可证明为直接读取的命令;搜索模式中出现这些文字不会被当作开关。
这也意味着人工确认前需要检查实际请求形态。不能把“已经出现 ID”解释为所有间接访问都已解析。
将这些文件放在同一目录,例如 ~/.codex/scripts/,将依赖安装到脚本旁的 guard-libs:
python3 -m pip install --target "$HOME/.codex/scripts/guard-libs" bashlex==0.18
python3 "$HOME/.codex/scripts/test_pre_tool_use_guard.py"
bashlex 0.18 的 here-document 定界符没有执行引号移除,检查器仅在这个入口补充处理;引用定界符的文档正文不作为 Shell 语句。不支持的表达式会拒绝,依赖缺失和状态库异常也返回合法拒绝,避免异常退出被宿主当作无决策继续执行。
授权库位于 ~/.local/state/agent-guard/approvals.sqlite3,目录权限 0700、数据库权限 0600。它是持久私有状态,不应加入 Git。
相关 config.toml 字段如下;已有表只合并字段:
sandbox_mode = "danger-full-access"
approval_policy = "never"
[features]
hooks = true
在 ~/.codex/hooks.json 合并以下条目,保留已有会话启动和其他事件,也不要重复追加检查器:
{
"hooks": {
"PreToolUse": [{
"matcher": "^(Bash|Read|Edit|Write|MultiEdit)$",
"hooks": [{
"type": "command",
"command": "python3 \"$HOME/.codex/scripts/pre_tool_use_guard.py\""
}]
}]
}
}
Codex 将 Shell / exec_command 映射为 Bash;补丁工具可匹配 Edit / Write 别名。write_stdin 向已有终端发送内容不会重新触发 PreToolUse,不能假定持续终端也有同等检查。OpenAI Hooks 文档
Claude 使用同一脚本的显式宿主参数,在 settings.json 中合并:
{
"hooks": {
"PreToolUse": [{
"matcher": "^(Bash|Read|Edit|Write|MultiEdit)$",
"hooks": [{
"type": "command",
"command": "python3 \"$HOME/.codex/scripts/pre_tool_use_guard.py\" --host claude"
}]
}]
}
}
敏感读取直接返回原生 ask,不创建 Codex 票据。已有 permissions.deny 仍可能先拒绝,不能为了复制示例自动删掉原规则;hook allow 也不能覆盖已有 deny。Claude Code 权限文档
本机 Claude Code 仍保留原会话启动 hook 和权限规则,本次没有自动接入尚未配置的检查器。输出格式测试通过不等于已验证本机 Claude 弹出询问。
脚本测试全部使用临时目录和伪凭据,不执行测试字符串里的危险操作。覆盖文字误报、补丁副作用、敏感读取请求、单次消费、跨会话与输入变化、过期、并发批准与消费、状态库异常,以及 Claude 的 ask 输出。
本次另外通过真实 Codex 工具调用采集了脱敏输入,确认会话 ID 存在及 workdir 缺失;探针撤下后校验原文件哈希。这验证了宿主输入形态,不等于完整交互式审批链路验收。
回收站改写只用于可识别的简单命令。变量、通配符、重定向和复杂语义不应靠重新拼接“近似还原”;rmdir 改写后也不再保证只能删除空目录。Python 兜底使用 XDG Trash,记录原路径与删除时间,但符号链接和跨设备行为仍需注意。
这份护栏不解析任意 Python、Node、MCP、远程命令或动态路径。Shell 变量、间接路径、部分符号链接和复杂解释器调用仍有边界。同一系统用户也能修改脚本、数据库或调用批准命令,因此“收到用户明确同意才批准”仍需 Agent 遵守,不能宣称是可信审批服务。
对于需要强制隔离的秘密,应将约束放在操作系统权限、受控执行环境与凭据隔离中。本地护栏负责把已识别的具体敏感读取停下来,交给用户控制。