# 免确认策略(policy) 目标:**日常零打断,红线仍然拦得住。** 共用训练机上「每个操作都问」不可行——人会被问烦,然后一路点「允许」,等于没有审批。所以把「问不问」从运行时决策变成**安装时声明的策略**。 实现:[lib/policy.js](../lib/policy.js)(纯函数判定)、[lib/grants.js](../lib/grants.js)(记忆)、[index.js](../index.js)(接线)。 ## 审计矩阵(默认) | 工具 | action | 风险 | 默认档 | 记忆粒度 | |---|---|---|---|---| | `orca_status` / `orca_read` / `orca_grep` / `orca_ls` | status / read / grep / ls | 只读 | autoAllow | — | | `orca_grants` | grants | 只能列出/撤销授权 | autoAllow | — | | `orca_run` | run | 有副作用 | askOnce | host + workdir | | `orca_launch` | launch | 长任务 | askOnce | host + workdir | | `orca_patch` | patch | 改远端代码(`dry_run:true` 视为只读) | alwaysAsk | — | | `orca_rollback` | rollback | 覆盖文件 | alwaysAsk | — | | `orca_stop` | stop | 杀进程 | alwaysAsk,**不可配置为记忆或免询问** | — | ## 配置 仓库自带的 `cordis.patch.yml` 用 `- insert:` 声明插件行;用户在 profile 的 `cordis.patch.yml` 里用 `- id: orca` 行**覆盖**(覆盖整体替换 config,不是合并,没写的键回到代码默认值): ```yaml - id: orca name: dsh-orca config: hosts: ["13", "16", "17", "21"] # 空 = ~/.ssh/config 里全部具体 Host 别名 policy: autoAllow: [status, read, grep, ls, grants] askOnce: [run, launch] alwaysAsk: [patch, rollback, stop] grantTtlDays: 7 allowedCommands: # 仅 run;每一段都命中才免询问 - "nvidia-smi *" - "git status" - "tail *" deniedCommands: ["rm -rf /*", "sudo *", "shutdown*", "reboot*", "mkfs*", "kill -9 -1*"] trustedWorkdirs: ["/data/local_14T/mengfei/*"] # 空 = 不限制 logging: { structured: true, tailLines: 2000, rotateBytes: 33554432, keepSegments: 5 } ``` ## 判定流程 ``` tool call (tools/pre-execute) → run/launch 命中 deniedCommands? → deny(任何模式都拒绝) → patch 且 dry_run? → 放行 → 档位 = alwaysAsk > askOnce > autoAllow(未列出:只读→auto,写→alwaysAsk;stop 恒为 alwaysAsk) → 只读且 auto? → 放行 → 会话审批策略 never(完全访问)? → 放行 → workdir 不在 trustedWorkdirs? → 改为 alwaysAsk(授权与 allowedCommands 都不生效) → auto? → 放行 → run 且命中 allowedCommands? → 放行 → askOnce 且已有有效授权? → 放行 → askOnce:插件自己调用 approval.request;批准则记住并放行,拒绝则 deny → alwaysAsk:返回 ask,由 DSH 管线询问 ``` 另有一道 `ctx.tools.guard()`:对 run/launch 再查一次 `deniedCommands`。guard 是单调的,其他插件的监听器无法把拒绝翻转为放行。 ### 与 DSH 审批策略的关系 DSH 的 `never` **不是「完全访问」**,而是严格无头模式:它在分发之前确定性地拒绝每个审批请求,不发出提示。插件必须照做,否则就成了绕过会话策略的后门: | 会话审批策略 | 只读 / 白名单命令 / 已有授权 | 其余写操作 | |---|---|---| | `ask` | 放行 | 按上表询问 | | `never` | 放行 | **拒绝**,并在原因里提示改用 `ask` 或写进 `allowedCommands` | | 任意 | — | 命中 `deniedCommands` 一律拒绝 | 要在 `never` 下干活只有两条路:把命令加进 `policy.allowedCommands`,或者先在 `ask` 下批准一次——授权会记住 `grantTtlDays` 天,切回 `never` 后依然有效。 > 2026-10-22 修掉的 P0:早期实现把 `never` 当「完全访问」直接 `pass`,等于把严格无头模式变成无限制模式(实测 `echo`、`mkdir` 未经审批直接执行)。现在只在「本就要询问」的分支上拒绝,只读、白名单、已有授权不受影响。 ### `deniedCommands` 是兜底,不是沙箱 它拦的是明确列出的破坏性命令。默认 `rm -rf /*` 用的是锚定 glob,因此会连带拦住 `rm -rf /home/you/scratch` 这类正常清理——这是有意偏保守。需要删目录时用 `find