Repository Wiki
deepseek-ai/deepseek-harness

沙箱与权限隔离

本页说明 DeepSeek Harness CLI(dsh)在启动 profile、转发应用参数以及输出配置时形成的边界控制。当前可见源码主要覆盖启动器的参数解析与配置转储约束;未发现实现操作系统级沙箱、文件系统隔离或细粒度权限策略的源码,因此这些能力不在本页中推断。

Purpose and Scope

本页聚焦 apps/cli/src/args.ts 中由启动器建立的三类边界:

  • Profile 边界:通过 --profile 选择 $DSH_HOME/profiles 下要启动的 profile。
  • 参数所有权边界:启动器只解析自己的参数;第一个无法识别的 token 起,其余参数原样交给已启动的应用插件。
  • 配置观察边界:--dump-config、--dump-default-config 与 --dump-config-schema 只执行配置读取/输出,不挂载应用,也不接受应用参数。

本页不覆盖应用插件自身的权限实现、Electron Desktop 的安全策略、profile 内部插件的运行时隔离,也不把 pnpm 插件管理误认为沙箱。若需了解具体 profile 或插件如何解释应用参数,应查看对应应用/插件页面。

Overview

dsh 的隔离模型首先是启动器级的职责隔离,而不是已被源码证明的进程沙箱。启动器使用 Commander 解析启动相关选项,并把应用参数保持为独立的 args 数组。这样做的关键意图是避免 launcher 抢占应用自己的 flag:例如 dsh --profile web -h 应由 web 应用输出帮助,而不是由 launcher 解释。

配置输出路径则采用更严格的“无应用启动”约束。配置 dump 与 boot invocation 是互斥模式;dump 不能附带应用参数;默认配置 dump 还禁止 patch overlay。该设计避免用户看到一个与实际启动路径不同的配置树,也避免在仅查看配置时触发应用命令行 provider。

源码明确记录的边界可以概括为:

边界源码行为目的
Profile 选择--profile <name> 解析为 profile 名称将启动目标限制在一个明确的 profile 上
Launcher / app 参数未识别 token 后的参数保留在 args防止 launcher 误解析应用参数
Patch 层--patch 可重复收集,按 argv 顺序保存支持显式、可审计的额外覆盖层
配置 dumpdump 模式拒绝 app args;默认 dump 拒绝 patch保证输出语义与请求模式一致
Desktop profileCLI 拒绝名为 desktop 的 profile保留 Desktop profile 的专属管理边界

Architecture

Loading diagram...

该图只表示源码中已确认的对象和数据流:parseDshArgs 将 Commander 收集的 launcher 选项解析成 DshInvocation;profile、patch 和 dump 模式属于 launcher 的配置侧,而 args[] 属于应用侧。源码没有证明 AppPlugins 具备什么具体权限,因此图中不加入文件系统、网络或进程级能力。

Source: args.ts

Main Content

1. 启动器只拥有自己的参数

ProfileInvocation 明确把 args 定义为“传给注入应用插件的所有剩余参数”,而 patches 则独立保存 launcher 的 patch 选项。--patch 使用单值、可重复选项,而不是 variadic 参数;源码注释说明这样可以避免 patch 选项吞掉后续的应用参数。

typescript
1const collect = (value: string, previous: string[] = []): string[] => [...previous, value] 2 3function selectProfile(value: string, previous?: string): string { 4 if (previous !== undefined) throw new InvalidArgumentError('select a profile only once') 5 return value 6}

Source: args.ts

这里有两个已验证的约束:同一次调用只能选择一次 profile;patch 参数会按照出现顺序追加。selectProfile 抛出 InvalidArgumentError 的行为表明重复 profile 是输入错误,而不是后者覆盖前者。

2. Profile 解析与 Desktop 保留边界

CLI 在 profile 解析阶段提供 rejectElectronProfile。当名称(忽略大小写)为 desktop 时,程序直接报告该 profile 由 Electron 应用专属管理。这是当前源码中最明确的 profile 级隔离规则:CLI 不接管 Desktop profile。

typescript
1function rejectElectronProfile(program: Command, profile: string): void { 2 if (profile.toLowerCase() === 'desktop') { 3 program.error('error: profile "desktop" is managed exclusively by the Electron application') 4 } 5}

Source: args.ts

源码中还声明 parseDshArgs 有 manageDesktopProfile 参数,但在当前已读取的实现片段之外,未获得其完整调用路径。因此不能进一步断言 Electron carrier 如何授权或管理插件;只能确认 CLI 对普通 desktop profile 有拒绝路径。

3. 配置 dump 是 boot-free 的只读观察路径

resolveBoot 先收集 patch,并检查空路径;随后统计三个 dump 选项。没有 dump 选项时返回普通 profile invocation;有多个 dump 选项时立即报错。进入 dump 模式后,任何应用参数都会被拒绝。

typescript
1function resolveBoot(program: Command, profile: string, options: BootOptions, args: string[]): DshInvocation { 2 const patches = options.patch ?? [] 3 if (patches.includes('')) program.error('error: --patch needs a path') 4 if (options.fromDefaultProfile === '') program.error('error: --from-default-profile needs a name') 5 const dumps = [options.dumpConfig, options.dumpDefaultConfig, options.dumpConfigSchema].filter(Boolean) 6 if (dumps.length === 0) { 7 return { mode: 'profile', profile, fromDefaultProfile: options.fromDefaultProfile, patches, args } 8 } 9 if (dumps.length > 1) { 10 program.error('error: --dump-config, --dump-default-config, and --dump-config-schema are mutually exclusive') 11 } 12 // The dump is boot-free: it never runs app command-line providers, so it 13 // cannot show what those flags would decide, and printing a tree that differs 14 // from the same invocation's boot would mislead. 15 if (args.length > 0) { 16 program.error(`error: config dumps take no app arguments, got ${args.map(argument => JSON.stringify(argument)).join(' ')}`) 17 } 18 if (options.dumpConfigSchema === true) { 19 return { mode: 'dump-config-schema', profile, fromDefaultProfile: options.fromDefaultProfile, patches } 20 } 21 const defaultOnly = options.dumpDefaultConfig === true 22 if (defaultOnly && patches.length > 0) { 23 program.error('error: --dump-default-config prints the bundle layers and takes no --patch') 24 } 25 return { mode: 'dump-config', profile, fromDefaultProfile: options.fromDefaultProfile, defaultOnly, patches } 26}

Source: args.ts

该路径的设计重点是一致性而非权限提升:配置 dump 不运行应用命令行 provider,因此不会展示应用参数最终可能决定的状态;如果允许 app args 或 default-only patch,输出可能与同一调用实际 boot 的配置不同。源码没有显示 dump 对磁盘的具体读取实现,相关实现细节未在当前证据中找到。

Core Flow

Loading diagram...

Source: args.ts

调试时可按以下顺序定位:

  1. 检查 profile 是否重复或缺失;重复选择会在 selectProfile 阶段失败。
  2. 检查 patch 是否为空字符串;空 patch 会被 resolveBoot 拒绝。
  3. 检查是否同时指定多个 dump 模式;它们是互斥的。
  4. 如果 dump 模式带有应用参数,调用会失败,而不会启动应用。
  5. 如果普通启动的参数包含应用自己的 flag,应确认它位于 launcher 参数之后,以便进入 args[]。

Usage Examples

普通 profile 启动与参数转发

以下帮助文本是源码中实际声明的 launcher 示例,展示了普通 profile、patch、resume 和应用帮助之间的边界。

text
1Examples: 2 dsh web boot the web profile (same as: dsh --profile web) 3 dsh rescue --from-default-profile web 4 create rescue from the shipped web template, then boot it 5 dsh headless "run the tests" answer one task, print the result, and exit 6 dsh tui --patch ./extra.yml boot a custom profile with one extra overlay 7 dsh tui --resume <session> arguments after the launcher flags reach the app 8 dsh web --help the web app's own flags and help 9 dsh plugin --profile tui add <package> install a plugin into the tui profile

Source: args.ts

声明 launcher 选项

typescript
1.option('--profile <name>', 'the profile under $DSH_HOME/profiles to boot', selectProfile) 2.option('--from-default-profile <name>', 'initialize a new custom profile from a shipped profile template') 3.option('--patch <path>', 'extra patch-list overlay applied after the profile layer (repeatable)', collect) 4.option('--dump-config', 'print the composed profile tree and exit') 5.option('--dump-config-schema', 'print JSON Schema for profile entries and patches without mounting') 6.option('--dump-default-config', 'print the profile tree without its user layer or --patch overlays and exit')

Source: args.ts

Configuration Options

选项类型默认值行为
--profile <name>string未提供选择要启动的 profile;缺失 profile 时由解析流程报错。
--from-default-profile <name>string未提供从 shipped profile 模板初始化新 profile。空名称被拒绝。
--patch <path>string[][]可重复;按 argv 顺序形成额外 patch overlay。空路径被拒绝。
--dump-configbooleanfalse输出组合后的 profile tree 并退出。
--dump-config-schemabooleanfalse不挂载 profile,输出 profile entry 与 patch 的 JSON Schema。
--dump-default-configbooleanfalse只输出 bundle layers,不包含 user layer 或 patch overlay。

这些选项的声明和语义来自 args.ts;当前证据没有显示 $DSH_HOME 的默认路径、profile 文件格式,或 patch 合并算法,因此不在表中补充未经验证的默认值。

API Reference

parseDshArgs(argv: readonly string[], version: string, manageDesktopProfile = false): DshInvocation

  • 职责:将 CLI 参数解析为 ProfileInvocation、DumpConfigInvocation、DumpConfigSchemaInvocation 或 PluginInvocation。
  • 参数:
    • argv:待解析的参数数组。
    • version:供 Commander 输出版本信息的字符串。
    • manageDesktopProfile:源码声明的 Desktop profile 管理开关;当前读取范围不足以确认其完整分支行为。
  • 返回值:DshInvocation 联合类型。
  • 错误行为:非法组合通过 Commander 的 program.error 或 InvalidArgumentError 结束解析;已确认的非法组合包括重复 profile、空 patch、多个 dump 选项、dump 携带 app args,以及 default-only dump 携带 patch。

resolveBoot(program: Command, profile: string, options: BootOptions, args: string[]): DshInvocation

该内部函数负责把 launcher 选项收敛为一个 invocation。它不执行 profile,也不执行应用逻辑;它只决定后续是 boot、配置 dump、schema dump 还是 default-only dump。

Failure Modes, Edge Cases & Concurrency

输入失败与边界条件

  • 重复 --profile:selectProfile 抛出 InvalidArgumentError,避免隐式覆盖。
  • 空 --patch:resolveBoot 调用 program.error。
  • 空 --from-default-profile:被拒绝。
  • 多个 dump flag:三者互斥,避免一个 invocation 同时代表多个输出语义。
  • dump 携带应用参数:被拒绝,保持 dump 的 boot-free 保证。
  • --dump-default-config 携带 patch:被拒绝,因为 default-only 模式明确排除 patch layers。
  • desktop profile:CLI 报错并声明由 Electron 应用专属管理。

并发与运行时隔离

在当前读取的 launcher 源码中,没有锁、共享状态、线程/worker 管理或并发控制实现。因此不能声称 profile 启动具备并发安全保证,也不能声称 patch 合并具有事务性。这里能确认的只是单次 argv 解析阶段的确定性约束。

Performance / Operational Notes

参数解析中的 patch 收集是通过数组展开追加完成的;源码没有提供 profile 数量、patch 大小或配置加载耗时的指标。运维上应优先使用配置 dump 检查最终可见配置,但要记住 dump 不运行应用 command-line providers,不能替代一次真实 boot 的全部行为验证。

Extension Points

当前源码暴露的扩展边界是 DshInvocation 联合类型与应用参数转发:launcher 可增加自己的 invocation mode,但必须继续保持 launcher flags 与 app args 的分界;新增 dump 模式也应维持互斥和 boot-free 约束。具体 profile/plugin 的权限扩展点未在已读取源码中找到。

Sources

(1 files)