信任模型
首用信任:CC 用全局 config 里的 hasTrustDialogAccepted 按目录树记账,Codex 用 config.toml 的 projects.trust_level + hook 内容哈希——Codex 把"信任什么"拆得更细
信任模型
结论
两家都遵循同一原则:仓库内容是不可信输入,必须由用户显式授信后才能影响 agent 行为。CC 的实现是单比特目录信任:首次进入目录弹 TrustDialog,接受后在 ~/.claude.json 的 projects[path].hasTrustDialogAccepted 记账,查验时向上遍历父目录;信任 gate 的对象包括所有 hooks、项目 settings 中的权限规则、项目 skills 的 allowed-tools、MCP headersHelper 等"项目可携带的可执行配置" [一手源码+一手文档]。Codex 拆成两层:目录级 projects."<path>".trust_level = trusted/untrusted 写在用户 config.toml,未授信时项目层 config/hooks/execpolicy 加载但禁用;hook 级再加一道内容哈希信任——每个 hook 的规范化身份哈希须与用户态 trusted_hash 匹配才执行,仓库改了 hook 命令即自动回到 untrusted [一手源码]。此外 Codex 的信任直接改变默认权限档:trusted → workspace-write,未决/untrusted → read-only。harness 应代理而非绕过这套首用信任流程。
研究问题
- 信任记录存在哪里?按什么粒度(目录/git root/repo)查找?
- 信任到底 gate 了哪些能力?
- Codex 的 hook 内容哈希如何防"已授信项目后续篡改 hook"?
- 信任状态如何影响默认权限/沙箱档位?
各 Agent 设计与实现
Claude Code
对话框与存储。首次在新目录启动时渲染 TrustDialog:"Quick safety check: Is this a project you created or one you trust? … Claude Code'll be able to read, edit, and execute files here.",选项只有 Yes, I trust this folder(value enable_all)和 No, exit(components/TrustDialog/TrustDialog.tsx:208-232)[一手源码]。接受后写入全局配置(~/.claude.json)的 projects[<path>].hasTrustDialogAccepted: true(utils/config.ts:111,144)。
查验逻辑。computeTrustDialogAccepted 按序检查:① 会话级内存信任(home 目录场景不落盘,仅本会话有效)→ ② 配置写入点(git root 或原始 cwd)→ ③ 从 cwd 逐级向上遍历父目录,任一祖先已授信即视为可信(utils/config.ts:705-743)[一手源码]。isPathTrusted(dir) 提供任意目录版本(:752-763)。含义:信任父目录等于信任全部子目录,无反向撤销粒度。
信任 gate 什么。源码侧最硬的一条:交互模式下 所有 hooks 都要求已过信任对话框——// In interactive mode, ALL hooks require trust; const hasTrust = checkHasTrustDialogAccepted()(utils/hooks.ts:293-295)[一手源码]。文档侧补充的 gate 清单 [一手文档]:
- 项目
.claude/settings.json/settings.local.json中的CLAUDE_CODE_*路径类设置(memory.md:356) - 项目 skills 目录的 plugin 化加载与
allowed-tools生效(skills.md:113, :353;plugins-reference.md:375) - MCP
headersHelper(执行任意 shell)在 project/local scope 仅信任后运行(mcp.md:697) /goal评估器(hooks 体系一部分)仅在已授信 workspace 可用(goal.md:135)
历史 bug 也佐证 gate 范围:changelog 记录过"trust dialog 静默启用全部 .mcp.json servers"与"home 目录信任未启用 hooks"两次修复(changelog.md:2229, :2946)[一手文档]。
与权限档的关系。CC 的信任不改变 permission mode;它控制的是"项目自带配置是否参与"。权限收口仍靠规则与模式(见 permissions 章)。
Codex CLI
目录信任。配置形态(core/src/config/mod.rs:1888-1892 注释给出目标渲染)[一手源码]:
[projects."/path/to/project"]
trust_level = "trusted" # 或 "untrusted"TrustLevel 仅 Trusted | Untrusted 两值(protocol/src/config_types.rs:544-548);ProjectConfig.is_trusted() 判定(config/src/config_toml.rs:546-560)。TUI 首启 onboarding 渲染 trust 界面,文案明确说明授信效果:"Trusting the directory allows project-local config, hooks, and exec policies to load.",且在 git 子目录时警告"信任将应用到仓库根"(tui/src/onboarding/trust_directory.rs:53-79)[一手源码]。接受后经 ConfigEdit::SetProjectTrustLevel 写回用户 config.toml(core/src/config/edit.rs:867-877)。
查验粒度。ProjectTrustDecision::decision_for_dir 依次查:目录自身的归一化 key → project root(按 root marker 探测)→ git repo root(resolve_root_git_project_for_trust)(config/src/loader/mod.rs:838-882)[一手源码]。
未授信的后果:加载但禁用。config loader 注释直接写明:cwd / 目录树 / repo 三类项目层 config "loaded but disabled when the directory is untrusted"(loader/mod.rs:100-102);disabled_reason_for_decision 生成的提示语限定了被禁面:"To load project-local config, hooks, and exec policies, add <key> as a trusted project in <user config>"(loader/mod.rs:884-900)[一手源码]。另外无论是否授信,项目层 config 永远不允许设置敏感键(openai_base_url、model_providers、notify、otel 等),见 PROJECT_LOCAL_CONFIG_DENYLIST(loader/mod.rs:61-72)[一手源码]——这是"信任也有上限"的设计。
信任 → 默认权限档。default_builtin_permission_profile_name:项目有明确 trust 标记(trusted 或 untrusted)且非 Windows-sandbox-disabled 时默认 workspace profile,否则(未决目录)read-only(core/src/config/permissions.rs:48-60)[一手源码]。即信任决策直接参与沙箱/审批的默认值推导,这是 CC 没有的耦合。
hook 内容哈希信任。每个 command hook 计算"规范化身份哈希":事件名 + matcher + 归一化 handler 配置序列化为 TOML 后哈希,确保 config.toml 与 hooks.json 写法等价的 hook 收敛到同一身份(hooks/src/engine/discovery.rs:556-580 command_hook_hash 与 NormalizedHookIdentity 注释)[一手源码]。信任状态机(discovery.rs:582-596):
fn hook_trust_status(is_managed, current_hash, trusted_hash) -> HookTrustStatus {
if is_managed { Managed } // managed 层 hook 免哈希信任
else { match trusted_hash {
Some(h) if h == current_hash => Trusted,
Some(_) => Modified, // 内容变了 → 重新失信
None => Untrusted,
}}
}仅 Managed | Trusted 状态(或显式 bypass_hook_trust)的 hook 进入执行表(discovery.rs:520-538);用户的授信记录是 [hooks.state.<key>] trusted_hash = "..."(config/src/hook_config.rs:25-30)[一手源码]。效果:即使项目已 trusted,仓库后续偷改 hook 命令也会因哈希失配回到 Modified 状态而被拒。
差异矩阵
| 维度 | Claude Code | Codex CLI |
|---|---|---|
| 存储位置 | ~/.claude.json projects[path].hasTrustDialogAccepted(布尔) | 用户 config.toml [projects."<path>"] trust_level(trusted/untrusted 三态:含未决) |
| 查找粒度 | cwd 向上遍历所有祖先目录 | 目录 key → project root marker → git repo root |
| 拒绝信任的后果 | 退出进程(No, exit) | 可继续用,但项目层 config/hooks/execpolicy 禁用、默认 read-only |
| gate 对象 | hooks(全部)、项目 settings 的可执行配置、项目 skills、headersHelper、.mcp.json | 项目层 config.toml、hooks、exec policy rules |
| 信任的上限 | —(信任后项目 settings 全量生效) | PROJECT_LOCAL_CONFIG_DENYLIST:项目层永远不能改 base_url/providers/notify/otel |
| 二级信任 | 无(目录级单比特) | hook 内容哈希(trusted_hash),改内容自动失信 |
| 与权限档耦合 | 不直接影响 permission mode | 直接决定默认 profile:trusted→workspace-write,未决→read-only |
| home 目录特例 | 信任只存内存、不落盘(config.ts:706-711) | 未见特例 [推断:无对应代码] |
最小复现
# Codex:手动标记 untrusted,观察项目层配置被禁用
cat >> ~/.codex/config.toml <<'EOF'
[projects."/tmp/demo"]
trust_level = "untrusted"
EOF
cd /tmp/demo && codex
# 预期 startup warning:"/tmp/demo is marked as untrusted in ~/.codex/config.toml.
# To load project-local config, hooks, and exec policies, mark it trusted."
# [一手源码 loader/mod.rs:893-896 的格式化字符串]
# CC:删除项目信任记录后重启,应重新弹 TrustDialog
jq 'del(.projects["/tmp/demo"].hasTrustDialogAccepted)' ~/.claude.json(本机未实测;预期输出直接取自源码中的字符串模板。)
Harness 接入建议(Yoda 实践)
- 代理而非跳过首用信任。Yoda 以 PTY/终端方式驱动 CLI(runtime-registry 的
terminalOnly: true),首启时 TrustDialog / trust_directory 屏会出现在终端流里。正确做法是把它识别为结构化事件并渲染成 Yoda 自己的信任卡片(带目录路径与 gate 说明),而不是自动回车——自动接受等于替用户签了"项目配置可执行任意命令"。 - workspace 导入即询问。Yoda 作为 workspace 产品在加入新项目时就知道目录路径,可以提前问一次"信任此项目?",然后分别落到两家的原生存储:CC 写
~/.claude.json(或让对话框自然通过),Codex 用codex配置编辑(ConfigEdit::SetProjectTrustLevel对应的 CLI/app-server 接口)。保持单一事实源在 CLI 侧,Yoda 不另存信任表。 - 显示信任状态与后果。会话头部应显示当前项目 trusted/untrusted 与其含义(Codex:untrusted = read-only + 项目 hooks 不生效;CC:未信任 = 直接没法进入会话)。Codex 的
Modifiedhook 状态尤其值得 UI 化——它意味着"仓库有人改了 hook"。 - 不要用
bypass_hook_trust:该开关存在于 hooks registry(registry.rs:33),是测试/受控环境用的,产品默认路径不应触碰。
失效条件
- CC TrustDialog 的选项与文案来自 2026-03-31 快照,线上版本已知在迭代(changelog 多次修改 trust 流程);
enable_all这种取值属内部实现 - Codex hook 信任 TODO 注明 key 是"positional suffix"将换成 durable hook id(discovery.rs:495-496),届时
hooks.state的 key 格式变化 - Codex
projects信任与 profile 推导的 Windows 分支依赖WindowsSandboxLevel,Windows 沙箱转正后行为可能变 - CC 信任 gate 的能力清单分散在多篇文档(skills/mcp/memory/goal),新能力加入时本章清单需要回归
参考资料
- CC 源码(重建快照 2026-03-31):
utils/config.ts(computeTrustDialogAccepted/isPathTrusted)、utils/hooks.ts:293-295、components/TrustDialog/TrustDialog.tsx - CC 文档:
docs/skills.md、docs/mcp.md、docs/memory.md、docs/goal.md、docs/changelog.md - Codex 源码(b89ce9a):
config/src/loader/mod.rs(trust context / disabled reason / denylist)、config/src/config_toml.rs、core/src/config/permissions.rs、core/src/config/edit.rs、hooks/src/engine/discovery.rs、config/src/hook_config.rs、tui/src/onboarding/trust_directory.rs