权限分析、审批与单调 Guard
谁提出风险、谁能放行、谁只能拒绝
结论:授权是分层的,只有最终否决具有单调性
DeepSeek Harness 并不存在一个中心化的“风险引擎”。在固定基线中,风险提议有两个入口:可扩展的 tools/pre-execute 监听器可以返回 ask;支持沙箱的 shell(Bash/PowerShell)或文件系统调用,也可以在自身已冻结的参数中请求严格更宽的模式。持久权限预设提供常驻的沙箱与询问策略;一次审批最多放行一次调用;同步 guard 则保留最终且不可逆的否决权。这些能力被有意设计成不同层次。
注册表会在策略判断前对解析后的参数做脱离快照并深度冻结;pre、guard 与工具体共享这份不可变 graph,而审批通过 callId 关联更早的耐久 call record:Native tool/call 保留模型的 raw JSON,Code Mode tool/code-dispatch-start 保留 normalized JSON。pre 决策可以允许、拒绝或询问,却不能重写参数。询问获批不会绕过 guard:运行时按全局到 scope 链顺序检查,直到第一条拒绝或全部不表态;guard 没有允许结果。因此,策略判断与实际执行保持参数同一性,owner 的否决也不会被监听器顺序推翻。APPROVAL-EXEC-SNAPSHOTAPPROVAL-PRE-DECISIONAPPROVAL-PREPARE-ORDERAPPROVAL-GUARD-CONTRACT
1. 六个容易被相似产品文案混为一谈的概念
| 概念 | 生命周期 | 权限 | 是否持久 |
|---|---|---|---|
| 工具可见性限制 | scope 在线组合期间 | 移除继承 schema;它是能力呈现,不是执行授权 | 否 |
| 沙箱模式 | 部署默认值或单个 Session 覆盖值 | 通过真正执行约束的后端限制文件效果 | Session 覆盖:是 |
| 审批策略 | 部署默认值或单个 Session 覆盖值 | ask 路由到回答器;never 拒绝所有询问 | Session 覆盖:是 |
| 权限预设 | Session 常驻选择 | 把沙箱模式与审批策略打包 | 是 |
| 审批结果 | 单次请求 | 只有 allowed-once 放行;不会改变常驻策略 | 审计事件对:是 |
| 单调 guard | 在线注册、逐调用判断 | 最终同步、只拒绝的否决权 | 否;产生的工具错误由调用方记录 |
最反直觉的是 approval/policy = never:它表示“永不弹出询问;凡需审批的动作一律拒绝”,而不是“不询问就批准”。全访问预设之所以无需询问,是因为沙箱本身已经开放,并非 never 构成授权。
2. 端到端决策图有两个审批入口
模型工具调用
→ durable tool/call 已存在
→ 参数脱离原引用并冻结
→ tools/pre-execute waterfall ── 拒绝 ─────────────→ 错误结果
│
├─ ask → ApprovalService → allowed-once / 拒绝
│
└─ 允许
↓
全局 + scoped 单调 guard ───────────────→ 错误结果
↓
around wrapper → 工具体
│
└─ shell(bash/pwsh)或 fs sandbox_permissions
→ 严格扩权检查
→ ApprovalService
→ 仅为本次调用标记更宽模式
↓
有外部效果的后端执行
↓
post 策略 → 持久结果
通用入口在 guard 与工具体之前询问。沙箱入口位于 shell(bash/pwsh)或 fs 工具体内部,但仍发生在进程启动或文件变更之前。因此,通用 pre 监听器与工具特定的扩权请求可能对同一次调用各问一次;组合方应避免重复策略。反过来,guard 也可以在工具特定弹窗出现前,根据冻结后的扩权参数直接拒绝。
3. 风险由各层提出,并未被中心化评分
PreToolDecision 只有 allow、deny(reason) 与 ask(reason?)。内建代码里没有风险等级、数值分数、通用命令分类器或带特权的“批准 token”。waterfall 末端默认允许,所以需要广泛风险分类的部署必须组合自己的监听器;内建沙箱工具采用的则是一对显式参数:sandbox_permissions 与非空 justification。
bash 指引把扩权约束为真实沙箱拒绝后的同一命令单次重试,要求选择够用的最窄更宽模式,并要求用户拒绝后不要继续尝试。这些指令有助于模型行为,但真正边界更严格:参数配对、运行时扩权表、审批结果与沙箱后端才负责执行。提示词不计作授权。
4. 为什么 tools/pre-execute 是可扩展策略,而不是不可绕过的闸门
pre 阶段是异步且按 scope 路由的 waterfall。监听器可以调用 next 继续下游、替换下游决策,或直接短路。因此注册顺序与 prepend 会影响结果。这个特性适合 hook、部署策略与机器审查器,但也意味着:若把“绝对拒绝”写成普通监听器,更靠前的监听器可以不调用下游、直接返回允许,从而绕过它。
ctx.tools.guard() 注册同步且只拒绝的检查。运行时先判断全局层,再按从远到近的顺序判断所有适用 Agent scope 层;第一条拒绝原因胜出。任何 guard 都不能返回允许,而且 guard 之后不再运行 pre 监听器。APPROVAL-GUARD-REGISTRY
5. ApprovalService 是封闭且 fail-closed 的询问边界
| 结果 | 含义 | 工具后果 |
|---|---|---|
allowed-once | 某个回答器仅批准这次请求 | 通用 gate 变为允许,或把请求的更宽模式仅标到本次调用 |
rejected | 明确拒绝,或 never 策略的确定性结果 | 拒绝 |
cancelled | 请求被撤回 | 拒绝/中止;迟到回答被丢弃 |
unavailable | 没有回答器、回答器抛错或返回非法值 | 拒绝 |
结果词表是封闭联合类型。服务会在分派前由自身检查 never 并返回 rejected,因此即使 prepended 回答器也无法覆盖它。处于 ask 时,回答器 waterfall 默认落到 unavailable;同步异常、异步拒绝与非法返回值都会被归一化为 unavailable。APPROVAL-OUTCOMESAPPROVAL-DECIDE-WATERFALL
在 ToolRuntime seam 上,缺少 ApprovalService 或调用没有 Agent 也会拒绝,因为此时不存在可审计的 Session 和路由身份。只有精确的 allowed-once 分支继续;rejected、cancelled 与 unavailable 保留不同错误文案,便于诊断。APPROVAL-ASK-MAPPING
6. 审计事件对可持久重放,但在线等待本身不可重放
开放 turn
→ append approval/asked { 新 id, toolName, callId?, reason? }
→ 等待回答器 waterfall
→ append approval/decided { 同一 id, 封闭结果 }
→ 返回结果
审批请求只能发生在开放 turn 内,因为 turn 是 Session 日志的提交与重放边界。请求刻意不重复携带参数:callId 将它链接到已经记录、已经呈现的工具调用,避免两份参数发生漂移。两个审计事件均为 log-only,不是模型 transcript block。模型通过 runtime context 得知常驻策略,并最终收到工具结果。APPROVAL-AUDIT-EVENTSAPPROVAL-REQUEST-SHAPEAPPROVAL-AUDIT-REQUEST
重放日志能够恢复过去的询问、结果与最新常驻策略,但无法在 host 进程重启后复活一个尚未完成的 JavaScript Promise。invariant 允许崩溃尾部保留未匹配的 approval/asked,同时拒绝孤立 decision、重复开放 id、turn 外事件与未知词表。浏览器重连恢复是下文所述的另一套同进程 pending registry。APPROVAL-INVARIANT
7. 取消具有明确的竞态语义
若请求 signal 已中止,审批会在回答器分派前得到 cancelled。否则服务令回答器与 signal 竞速;取消立即结算,之后到达的回答成为无效操作。回答器计算会被隔离,但不会被强制杀死。
工具准备阶段会在策略前、审批后和分派前再次检查调用方取消。如果正是取消导致审批取消,结果是 ABORTED_BEFORE_DISPATCH;已经完成的策略拒绝可能作为信息更充分的结果保留。同进程工具体一旦开始,ToolRuntime 会等待它达到静止点,然后把原本成功的结果改为 aborted。工具作者必须把 signal 传给子进程或 provider。APPROVAL-CANCEL-DISPATCH
8. 预设是覆盖两个独立 knob 的持久常驻意图
预设表条目包含机器 key、沙箱模式、审批策略与可选呈现文本。custom 保留给派生状态:两个有效 knob 不匹配任何条目时才出现;它不能被配置或选择。若 shell 不声明约束能力、默认值无法解析,或组合默认值不匹配任何预设且又未显式指定默认值,服务会在加载期失败。APPROVAL-PRESET-CONFIG
| 事件 | 职责 | 是否直接控制执行 |
|---|---|---|
permission/preset | 记录用户选择意图,并区分相同 bundle 的不同预设名 | 否 |
sandbox/mode | 常驻沙箱覆盖值 | 是,每次受约束调用都会 fold |
approval/policy | 常驻询问行为 | 是,每次 ask 都会检查 |
当前值由真实 knob 状态派生。如果上次记录的选择仍匹配,它会在多个同 bundle 名称中获胜;否则选择表中第一个匹配项,仍无匹配才是 custom。选择预设时先 append 意图事件,只为确实变化的 knob 写事件。APPROVAL-PRESET-DERIVEAPPROVAL-PRESET-WRITE
9. “未来 Session 默认值”与“当前 Session”是两种不同变更
真正新建的 Session 会读取最新用户设置,并在同步 session/created 发布期间固定全部三个权限事实;这早于创建返回、Agent 发布和首次 prompt,但不是早于 SessionStore 入表。带 seed、resume 或仅部分初始化的 Session 会保留自身有效权限,只补充缺失事实。因此,之后修改 Settings 只影响未来 Session,不会改变现有 Session。APPROVAL-PRESET-PIN
可选 permissions projection 暴露预设表选项与有效当前值;projection key 不存在意味着该能力未被组合,客户端应隐藏控制项。裸 /permission 命令报告当前值与可用值;/permission <key> 校验 key,并与 UI 共享同一个服务写入路径。APPROVAL-PRESET-PROJECTION
base bundle 发布了三行:read-only + ask、workspace-write + ask 与 danger-full-access + never。其环境变量派生默认值通常为 workspace-write + ask。权限服务独立 schema 默认值只有后两行,不能把这两个事实混为一谈。APPROVAL-SHIPPED-PRESETS
10. 单次沙箱授权必须严格更宽、明确提出且转瞬即逝
| 有效模式 | 合法单次目标 |
|---|---|
read-only | workspace-write、danger-full-access |
workspace-write | danger-full-access |
danger-full-access | 无 |
schema 公布封闭目标词表,但运行时会相对本次调用的有效常驻模式检查“严格更宽”。两个扩权字段必须成对出现,理由不能为空。同级或收窄请求会在弹窗前失败。APPROVAL-ESCALATION-LADDER
随后 resolver 要求 ApprovalService 与 Agent 都存在,把目标模式与理由记入审批 reason,并且只接受 allowed-once。返回模式只是单次调用的局部值;它不会 append sandbox/mode,也不会改变预设。APPROVAL-ESCALATION-FLOW
执行时,已批准的显式模式优先于 Session 最后一条 sandbox/mode,后者又优先于部署默认值。Session cwd 提供 workspace root。APPROVAL-SANDBOX-PRECEDENCE
11. Shell 与文件系统共享审批编排,再由不同后端真正执行约束
Bash 与 PowerShell 都先校验参数、解析常驻策略,在提出扩权时等待审批,把返回模式标到请求,然后才启动前台进程或提交后台 job starter。因为对象 schema 是开放的、呈现并不等于执行,未公布的扩权字段在当前 executor 不支持沙箱时仍会由工具体拒绝。APPROVAL-BASH-EXECUTIONAPPROVAL-PWSH-EXECUTION
文件系统变更工具使用共享 controller,执行同样的字段配对、常驻策略解析、审批与单次标记。provider 返回沙箱拒绝时,它会转换成结构化错误标记和“精确重试”提示;其他 provider 错误保持原样。APPROVAL-FS-ESCALATION
12. 被委派的 child 不继承 parent 的单次授权
创建 child 时,运行时同步捕获的只有 parent 的显式 Session 沙箱覆盖值——既不捕获部署默认值,也不捕获单次授权——并且只要审批能力存在,就把 child 的审批策略固定为 never。这些带 delegation source 的事件写在任何 fork seed 之后,因此旧 seed 无法重新开放 child。child 还会收到模型可见说明:需要审批的操作会被拒绝。APPROVAL-CHILD-PIN
parent 可以在 child 已固定的沙箱范围内委派工作,但 child 模型无法通过询问自行扩权。之后,可信 runtime/用户仍可 append 更新的策略事件;所以这不是密码学意义上的永久锁。它防止的是通过正常审批路径发生的模型自主扩权,而不是阻止所有更高权限管理。
13. Web 审批是稳定的 server request,不是新的 unary RPC
host 发送带稳定 rpcId 的 approval/requested,其中包含 Session id、approval id、工具名、可选 call id 与 reason。客户端响应复用相同 rpcId,并且只能提交 allowed-once 或 rejected;cancelled 与 unavailable 仍由 host 产生。APPROVAL-WIRE-CONTRACT
proxy 回答器会认领最新、匹配、未决定且未被其他 pending 项认领的 approval/asked 事件。如果找不到这条审计事实,它会交给下游,而不是凭空创建可回答请求。pending 项能跨浏览器断线保留;打开新 mux 时会用同一 id 重放。abort 与 proxy teardown 会将其结算为 cancelled。APPROVAL-WEB-REGISTRYAPPROVAL-WEB-REPLAY
响应先按 rpcId 路由,再经过解析,并且必须回显完全相同的 Session 与 approval id。第一个合法响应删除 pending 项;畸形、外来、过期与迟到响应都会被拒绝。APPROVAL-WEB-RESPOND
14. 审批面板是一次性的,但它展示的证据并不自包含
请求 pending 时,对话 UI 会用可滚动卡片替换 composer,显示 reason、可选命令、“拒绝”与“仅允许一次”。首次点击后按钮锁定;传输失败时重新启用;收到 resolved frame 后面板消失。只有当 root running tool call 的 JSON 参数包含字符串 command 时,界面才会解析并展示命令;参数畸形、嵌套、无法配对或非 shell 调用都不显示命令行。APPROVAL-UI-PANEL
ApprovalRequest 不复制参数,避免了审计漂移,却也让已有工具调用呈现成为知情同意的一部分。对于文件系统或其他通用 ask,面板自身可能只有自由文本 reason 与工具名。更强的产品面可以从已记录调用渲染一份类型化、不可变摘要,而不创建第二份权威参数。
15. 权限选择器提供便利与风险确认,但不是执行边界
当前 Session 选择器过滤 custom 并提交 /permission <key>;可选命令弹窗执行同一路径;Settings 行用 expected revision 写入未来 Session 的 defaultPreset。APPROVAL-PERMISSION-SETTINGS-WRITE三者都消费服务端公布的选项,但其风险对话框只由一个硬编码机器 key 触发:danger-full-access。APPROVAL-PERMISSION-COMPOSERAPPROVAL-PERMISSION-DECORATIONAPPROVAL-PERMISSION-SETTINGS
服务端允许任意预设名,也允许任意沙箱/审批 bundle。因此,另一个名称映射到 danger-full-access + never 时不会触发确认;反过来,一个无害 bundle 若叫 danger-full-access 却会触发。直接命令或 Settings API 变更同样没有复选框。对于发布表,这个弹窗是有价值的 UX 警告,却不是通用授权检查。加固时应从公布的 bundle 或服务端所有的 risk 字段派生风险;真正权威仍应是服务端策略,而非 key 名称。APPROVAL-PRESET-CONFIG
16. 浏览器请求信任防御 origin 混淆,但源码明确说明它不是身份认证
每个 /api 请求都必须通过 Host 校验、显式 cross-site Fetch Metadata 拒绝,以及 Origin 存在时的同 authority 校验。这能阻止 DNS rebinding 与普通恶意网页的跨站请求。源码明确把网络可达性和身份认证留在范围外。APPROVAL-WEB-TRUST
任何能够通过被接受 authority 合法访问本地 API 的客户端,都能看到 mux 并竞速回答 pending 审批;第一个 id 完全匹配的合法响应获胜。这符合其明确的本地单用户服务假设。若 carrier 要暴露给互不信任的用户,就必须在 Host/Origin 围栏之外增加“已认证主体到 Session”的授权。
17. ACP 是带精确所有权与单次选项的机器回答器
只有当 ACP bridge 拥有完全相同的 Agent,且请求携带 call id 时,它才会认领审批。它只呈现两个选项——仅允许一次与仅拒绝一次——并显式映射取消;所有未知的非允许选项都变为拒绝。它绝不会把响应升级成持久授权。APPROVAL-ACP-ANSWERER
这体现了回答器架构的意图:Web UI、ACP 或其他 provider 可以占据同一个 waterfall seam,而 ApprovalService 统一拥有词表、持久审计、取消与不可绕过的 never 策略。
18. 测试编码了负向路径,而不只是成功弹窗
| 测试组 | 在固定基线中证明什么 |
|---|---|
| ToolRuntime 策略 | deny/ask 映射、服务缺失、无 Agent 调用、非法结果、取消顺序,以及 pre 策略后的 guard |
| 审批服务 | 开放 turn 要求、审计事件对、首个回答器、异常/非法值、取消、新 id、prepended 监听器之前的 never,以及策略 context |
| 权限预设 | fold、同 bundle 身份、custom、无变更写入、加载失败、新 Session 固定、seed/resume 保留与 HMR 补齐 |
| 沙箱扩权 | 严格阶梯、字段配对、单次授权、非扩权拒绝、渠道缺失/无 Agent,以及全部结果 |
| Web proxy | 往返、重连重放、畸形/外来/过期响应、abort、teardown、并行配对与无 call-id 请求隔离 |
APPROVAL-TOOLS-TESTSAPPROVAL-GUARD-TESTSAPPROVAL-SERVICE-TESTSAPPROVAL-PRESET-TESTSAPPROVAL-ESCALATION-TESTSAPPROVAL-WEB-TESTS
19. 两处 README 描述已经落后于生产源码
| 文档说法 | 固定源码事实 | 影响 |
|---|---|---|
权限 README 写成 permissionPresets/preset 与 /permissionPresets | 运行时实际使用 permission/preset 与 /permission | 运维或扩展作者可能搜索、集成错误标识符 |
| API proxy README 声称 pending 表只处理 question,没有 approval 项 | proxy 已有完整审批 registry、重连重放、响应校验、abort 与 teardown | 文档中的 wire 能力和恢复模型已过时 |
APPROVAL-DOC-PRESET-DRIFTAPPROVAL-DOC-API-DRIFT
权限代码对命名和生命周期误解尤其敏感。源码与测试内部一致;应修正公开 package README,让安全审阅从真实事件与命令路径开始。
20. 与 Codex 的窄范围比较:分层方式相似,策略丰富度不同
本节只使用 OpenAI 公开的 Agent approvals & security 与 Subagents 文档,读取日期为 2026-08-13。DeepSeek 一侧是固定 commit 的源码级证据;Codex 一侧是截至读取日期的文档级证据。本节不推断私有服务或未公开实现。
| 维度 | DeepSeek Harness 固定源码 | Codex 公开文档 |
|---|---|---|
| 核心分层 | 沙箱模式与审批策略是两个独立持久 knob,由预设打包 | 明确把沙箱模式与审批策略定义成两个独立控制项 |
| 典型平衡模式 | 发布 workspace-write + ask | Auto 记录为 workspace-write + on-request |
| 审批策略词表 | ask 或 never | 记录了 untrusted、on-request、never 与 granular 类别 |
| 审查者 | 可组合回答器 waterfall;发布 Web 与 ACP 回答器;未发现中心自动风险审查器 | 记录了用户审查,以及通过独立 reviewer 完成的合格 auto_review |
| 外部效果领域 | 通用 pre 策略,加显式 bash/fs 扩权 | 记录 shell/沙箱请求,以及带副作用的 app 与 MCP 工具审批 |
| Fail-closed | 回答器缺失/抛错/非法、无 Agent 与 never 都会拒绝 | 记录了无法获得必需审批时阻止动作;自动审查不会扩大沙箱 |
| 委派 | child 审批固定为 never,parent 单次授权不转移 | 公开文档称 subagent 继承当前沙箱策略;无法呈现新审批时,需新批准的动作会失败 |
两者都把技术 containment 与人/机器同意视作不同层。DeepSeek Harness 特别值得复用的设计是可扩展策略之后显式的 deny-only guard;Codex 文档体现的优势则是更丰富的审批策略词表与明确的自动审查角色。这个比较是架构对照而非功能打分:源码证据与产品文档的置信层级不同。
21. 设计评价、加固优先级与验证状态
| 发现 | 评价 | 优先级 |
|---|---|---|
| 冻结参数 + pre 不可重写 | 强审计/执行同一性 | 保持 |
| 可扩展审批之后的 guard | 能抵抗监听器顺序的强单调否决 | 所有 owner 不变量都应采用 |
| 封闭结果与 fail-closed 缺失语义 | 降级安全,且原因可诊断 | 保持 |
| 预设与单次授权分离 | 时间维度权限清楚;child 不继承临时扩权 | 保持并补强文档 |
| 风险弹窗依赖预设名称 | 可配置表下已确认的 UX/策略静态错配 | 高优先级加固 |
| 非 shell 调用的审批面板不自包含 | 同意质量依赖周边 call 呈现 | 增加类型化摘要 |
| 可达但无身份认证 | 只在明确的本地单用户信任模型下可接受 | 多用户暴露前重审 |
| README 标识符/恢复语义漂移 | 实现能力强于文档描述 | 尽快修正 |
本章追踪了 ToolRuntime 准备与取消;审批类型、服务、事件与 invariant;权限配置、projection、命令、Settings 与发布 bundle;共享沙箱扩权及 shell/fs 消费者;child 委派;Web wire/registry/UI;ACP;以及聚焦的生产测试。本次交付还运行了覆盖审批、preset、扩权、文件、进程、Job 与 Terminal 的 15 个 targeted Vitest 文件:508 项通过,1 项跳过。静态发现均明确标注;测试通过不等于完成运行时渗透测试、OS 沙箱验证、已认证多用户行为验证,也不提供任何未公开 Codex 内部实现信息。
我的学习体会
内容仅自动保存到当前浏览器,不上传、不进入仓库。你可以导出 Markdown 自行归档。