DeepSeek Harness 的权限预设有几档:源码两档、出厂组合三档

2026-08-16

deepseek-harness 的权限相关文档时,很容易撞上一个对不上的地方:子系统文档告诉你默认的预设表是两档,而已发布的出厂组合配置里写着三档。两处都在同一个仓库里,都不是笔误级别的东西,位置也都很好找。

先把结论摆在前面:这两处确实不一样,本文只负责把它们各自的确切位置和字面值指出来,不推断哪一处「才是对的」,也不由此评价这个项目。 下面的行号都对应我们采集的快照 47f9438,核对日 2026-08-16。另外要先说明一层限定:该仓库建立于 2026-08-13,我们采集时版本号是 0.1.0-rc.5,README 自述处于开发者预览阶段并明写未来会出现破坏兼容性的变更,所以本文提到的所有配置字段、默认值、命令名随时可能变,请以仓库最新内容为准。

第一处:包的 Config 默认值,两档

位置是 packages/interaction/permission-presets/src/index.ts:161-178。这个服务的 static Config 里,presets 字段的 .default({...}) 只列了两个条目,每个条目都写了 sandboxapproval 两个键:

预设名sandboxapproval
workspace-writeworkspace-writeask
danger-full-accessdanger-full-accessnever

两条的 description 原文分别是 Write inside the workspace and permitted temporary directories; wider retries require approval.:168-171)与 Full file access without approval prompts.:172-175)。

子系统文档跟这份默认值是同一个口径。docs/subsystems/permission-presets.md:11 的原文是「the default table ships workspace-write (workspace-write + ask) and danger-full-access (danger-full-access + never)」。也就是说,代码默认值这一层是自洽的:源码写两档,文档也写两档。

第二处:出厂组合配置,三档

位置是 packages/bundle/base/cordis.patch.yml:193-205。这份组合配置给 @deepseek-ai/dsh-permission-presets 这个 id 为 permission 的条目显式配了 presets,一共三个键:

- id: permission
  name: '@deepseek-ai/dsh-permission-presets'
  config:
    presets:
      read-only:            { sandbox: read-only,          approval: ask }
      workspace-write:      { sandbox: workspace-write,    approval: ask }
      danger-full-access:   { sandbox: danger-full-access, approval: never }

这里有个容易被忽略的细节:这三条都没有配 namedescription,而上一节抄出来的那两条默认条目各自带着一句 description 原文。docs/subsystems/permission-presets.md:50 说明了缺 label 时 optionOf() 会回落到表键本身,也就是说这三档在客户端侧显示出来的名字就是 read-onlyworkspace-writedanger-full-access 这三个键本身。

所以「两档还是三档」这个问题,准确的问法是「你看的是哪一层的表」:包的 Config 默认值这一层是两档,出厂组合配置这一层写了三档,多出来的那一档是 read-only + ask。两处的位置和字面值就是上面这些,本文只做并排陈述,不推断哪一层最终生效、也不推断为什么两处不一样。

相邻还有一处默认模式的差异

顺着同一份 cordis.patch.yml 往上翻几十行,还有一处口径差值得一并记下来。

SandboxPolicyService 自己的 Config schema 里,mode 的默认值是 'read-only'packages/sandbox/sandbox-policy/src/index.ts:94)。而 packages/bundle/base/cordis.patch.yml:175 把它配成了 process.env.DSH_PERMISSION_MODE ?? 'workspace-write',同处的 workspaceRootprocess.cwd()

紧挨着的审批服务也读同一个环境变量:packages/bundle/base/cordis.patch.yml:188-191@deepseek-ai/dsh-user-approvalpolicy(process.env.DSH_PERMISSION_MODE ?? 'workspace-write') === 'danger-full-access' ? 'never' : 'ask'。而这个服务自身 schema 的默认值是 'ask'packages/interaction/user-approval/src/index.ts:194)。

组合配置里 :166-168 那段注释原文写着:「Every shipped CLI mode starts with the same file-effect boundary… otherwise fresh sessions pin workspace-write + ask through the permission service below.」

把这几处并排看,就能明白为什么「默认是什么」这个问题在这个仓库里必须先问「哪一层」:sandbox 模式在 schema 层是 read-only,在出厂组合层是 workspace-write(或环境变量指定值);审批策略在 schema 层是 'ask',在出厂组合层由同一个环境变量派生。这些都是配置里的字面值,不是运行时表现的保证,据此推算实际会拦住什么、放行什么都是没依据的。

custom 不是第三档

数预设档数的时候还有一个坑:custom

permission-presets/src/index.ts:70 里有 export const CUSTOM_PRESET = 'custom',但它是派生态,不是表里的一项。三条依据:

  • 表里若出现名为 custom 的条目,构造时直接抛错,错误原文是 permission: "custom" is reserved for the derived not-a-preset state and cannot name a table entry:189-191)。
  • 它的展示项是写死的:{ value: 'custom', name: 'Custom', description: 'Current sandbox and approval settings do not match a preset.' }:363)。
  • docs/subsystems/permission-presets.md:48 原文:「custom is derived-only: clients may display it as the current value, but it is never a switch target or an event payload.」

它从哪来?看 derive() 的逻辑(:310-320):sandbox 取 state.sandbox ?? this.ctx.shell.sandboxMode,approval 取 state.approval ?? this.ctx.approval.config.policy ?? 'ask';匹配时先看「仍然匹配的上次选择」,再按表的声明顺序取第一个匹配项,都不中才返回 CUSTOM_PRESET。所以 custom 表示的是「当前这组 sandbox + approval 组合在表里找不到对应条目」,跟表里配几档是两回事。

顺带一提,构造期还有另外两个会抛错的检查(:192-199):挂载的 bash executor 不做限制(即 ctx.shell.sandboxMode === undefined)时抛「the mounted bash executor does not confine (no sandboxMode)」;组合出的默认值匹配不到任何预设时抛「composed sandbox and approval defaults match no preset; configure defaultPreset explicitly」。按这条报错的字面语义,组合出来的 sandbox + approval 默认值必须能在预设表里找到对应条目,否则不是运行到某一步才出问题,而是构造阶段就抛。

换一档的时候实际写了什么

弄清表里有几档之后,下一个自然的问题是:切换一档,到底动了哪些东西。

set(session, name) 走的是 apply()permission-presets/src/index.ts:376-391),顺序是三步:先在 current(...) !== name 时 append 一条 permission/preset 事件(:382-384),然后仅在该 knob 的有效值确实变化时才分别调 setSandboxMode:386-388)与审批写入器(:389-391)。也就是说,一次切换未必两个 knob 都会写——以出厂组合那张三档表为例,read-onlyworkspace-write 两档的 approval 都写着 ask,在这两档之间切,审批那一路的值并没有变。

新会话那一头由 pinInitialPermission() 负责(:400-430):完全空白的新会话会一次性 append permission/presetsetSandboxModesetApprovalPolicy 三件(:406-412);带 session/end-seed 的恢复会话则只补缺失的那部分事实。permission/preset 这个事件名登记在 packages/core/session/src/known-event-types.ts:39 的已知事件类型清单里,同一份清单里还有 approval/asked:22)、approval/decided:23)、approval/policy:24)。

这里也有一处写入路径上的差异要记一笔:docs/subsystems/permission-presets.md:66 只提到 setApprovalPolicyset() 确实用它(:376);而服务注册的那个斜杠命令(源码里 name'permission':259)的 handler 用的是 this.ctx.approval.setPolicy(agent, policy):274)。两条路径写的是同一个 knob,调用的不是同一个写入器。只记录这一差异,不推断哪条是准的。

顺带一提,这个包的 README 与源码在事件名、命令名、settings 命名空间三处的写法也对不上——那是另一组独立的口径差,我们另有一篇专门讲,这里不展开。

你自己怎么核一遍

不用装、不用跑,git clone --depth 1 之后按这几步对照就行:

  1. 打开 packages/interaction/permission-presets/src/index.ts,跳到 static Configpresets 那段(我们采集时在 :161-178),数 .default({...}) 里有几个键。
  2. 打开 packages/bundle/base/cordis.patch.yml,搜 dsh-permission-presets,看它下面 config.presets 有几个键、各自的 sandboxapproval 是什么。
  3. 同一份 yml 往上找 dsh-sandbox-policydsh-user-approval,把它们的 mode / policy 与各自源码 schema 里的 .default(...) 对一遍。
  4. 想确认 custom 不在表里,直接搜 CUSTOM_PRESET,会看到那条构造期报错。

什么情况说明你看到的不是这个差异? 如果你手上的表既不是那两档也不是那三档,那多半是部署侧自己覆盖了 presets——这个字段本来就是可配的,Config schema 只提供默认值。这时候先去看你所在部署的组合配置文件,而不是回来对照仓库默认值。另外,packages/interaction/permission-presets/README.md:24-28 的 Known Limitations 里写着「The preset table is process-level」,改预设表需要重载插件;同段还写着「Only two mechanism knobs are bundled」——PresetSpec 里目前只有 sandbox 与 approval 两个 knob,agent / profile 之类的选择不在其中。

最后交代边界:dsh 会在本机跑 shell、起子进程与沙箱,预设表控制的正是这一面,所以搞清「你这套部署的表到底是哪一份」是有实际意义的;但沙箱模式与审批策略是配置里的字面值,不等于任何安全保证,本文没有、也无法评价它们实际拦得住什么。至于该把预设配成几档、默认选哪一档,取决于你的用法,仓库没有给通用值。

延伸阅读


本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的 架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。 本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目, 因此不涉及界面外观、操作手感与运行速度的任何描述。 该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更, 文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。 安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。