沙箱与权限隔离
本页说明 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 顺序保存 | 支持显式、可审计的额外覆盖层 |
| 配置 dump | dump 模式拒绝 app args;默认 dump 拒绝 patch | 保证输出语义与请求模式一致 |
| Desktop profile | CLI 拒绝名为 desktop 的 profile | 保留 Desktop profile 的专属管理边界 |
Architecture
该图只表示源码中已确认的对象和数据流:parseDshArgs 将 Commander 收集的 launcher 选项解析成 DshInvocation;profile、patch 和 dump 模式属于 launcher 的配置侧,而 args[] 属于应用侧。源码没有证明 AppPlugins 具备什么具体权限,因此图中不加入文件系统、网络或进程级能力。
Source: args.ts
Main Content
1. 启动器只拥有自己的参数
ProfileInvocation 明确把 args 定义为“传给注入应用插件的所有剩余参数”,而 patches 则独立保存 launcher 的 patch 选项。--patch 使用单值、可重复选项,而不是 variadic 参数;源码注释说明这样可以避免 patch 选项吞掉后续的应用参数。
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。
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 模式后,任何应用参数都会被拒绝。
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
Source: args.ts
调试时可按以下顺序定位:
- 检查 profile 是否重复或缺失;重复选择会在
selectProfile阶段失败。 - 检查 patch 是否为空字符串;空 patch 会被
resolveBoot拒绝。 - 检查是否同时指定多个 dump 模式;它们是互斥的。
- 如果 dump 模式带有应用参数,调用会失败,而不会启动应用。
- 如果普通启动的参数包含应用自己的 flag,应确认它位于 launcher 参数之后,以便进入
args[]。
Usage Examples
普通 profile 启动与参数转发
以下帮助文本是源码中实际声明的 launcher 示例,展示了普通 profile、patch、resume 和应用帮助之间的边界。
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 profileSource: args.ts
声明 launcher 选项
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-config | boolean | false | 输出组合后的 profile tree 并退出。 |
--dump-config-schema | boolean | false | 不挂载 profile,输出 profile entry 与 patch 的 JSON Schema。 |
--dump-default-config | boolean | false | 只输出 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。desktopprofile: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 的权限扩展点未在已读取源码中找到。