DSH 的结构性问题:一切皆插件的原则与预览版实现的信任错置
从 Cordis 的可组合性理想,到 DeepSeek Harness 的插件信任边界
研究时间:2026-08-15 | 对象:DeepSeek Harness(DSH)及其 Cordis 插件体系
资料范围:DSH 官方仓库与文档、Cordis 文档与论文、Koishi 官方资料、GitHub Discussions,以及对于当前版本
@deepseek-ai/dsh@0.1.0-rc.6的隔离环境实测
摘要
大火的DSH最近的分析文章颇多,有些知名的自媒体吹捧的同时不无遗憾的说可惜没有插件生态,但DSH 的核心矛盾已经不是“有没有插件生态”这么简单,而是它选择的核心原则与预览版实现所依赖的基础架构的信任模型之间发生了错置。
DSH 选择的原则非常明确:一切能力都应当插件化,产品由可替换的组件组成,profile 和 bundle 可以重新装配,AI 和开发者可以持续生成新的插件。这个原则要求系统面向开放组合。
但预览版实现这套原则时,仍然全盘继承使用了受信任内部代码的进程内插件模型:第三方 bundle 进入 DSH 核心进程,参与启动期配置组合,并与沙箱、审批、凭据、工具注册表、会话和 agent loop 共用 Cordis 上下文。cordis.patch.yml 也不是纯数据配置,DSH 的 loader 支持在加载时求值的 !!js 表达式。
这套实现对受信任的内部扩展非常强大,但却没有为开放插件所需要的来源、能力和隔离层级提供对应机制。于是形成了一个结构性张力:产品原则要求开放组合,运行时实现却默认插件可信;产品想要生态,安全模型却只能把用户推回“自行审计源码”。
插件生态是否繁荣,只会改变这个矛盾何时彻底暴露,不会决定这个问题是否存在。即使没有任何第三方插件,仅官方 dsh-base 的配置加载机制和安全组件的可组合方式,也已经说明了问题所在。而这个问题如果不修复,那未来的插件生态繁荣之后很有可能会出现塌方式的风险。
一、从 Cordis 开始:插件首先是可逆的副作用
1.1 Cordis 解决的不是“怎么装插件”
Cordis 的官方定义是:
A Meta-Framework of Spatiotemporal Composability
即“面向时空可组合性的元框架”。它的重点不是插件市场,也不是 npm 分发,而是让软件组件能够在运行时被组合、替换、卸载和恢复。

普通插件系统往往只关心加载:
ounter(lineounter(line加载插件-> 注册工具、事件、服务和路由
但它很少认真处理卸载:
ounter(lineounter(lineounter(lineounter(lineounter(line卸载插件-> 监听器还在不在?-> 定时器有没有清理?-> 工具注册有没有撤销?-> 服务和依赖有没有残留?
Cordis 的核心思路是把插件行为看成一组副作用,并要求这些副作用拥有可回收的逆操作。插件加载时注册资源,Cordis 记录清理函数;插件卸载时,框架按照生命周期和依赖关系执行清理。
这就是“可逆插件系统”。
1.2 Context 是资源管理的中心
Cordis 不希望插件直接操纵一堆不可追踪的全局变量,而是为插件提供一个上下文 Context。
在这个上下文里:
服务占据稳定的 ctx.<key>,例如ctx.tools、ctx.llm、ctx.sessions;插件通过 inject声明自己依赖哪些服务;插件通过 ctx.on()注册事件;插件通过 ctx.effect()管理副作用;插件卸载时,属于该上下文的注册内容可以一起撤销。
这种设计解决了大型 JavaScript 应用中的一个真实问题:模块化并不自动带来可拆卸性。模块可能拆开了,但它注册的监听器、路由、适配器和定时器仍然散落在全局环境里。
Cordis 试图让“组件的生命周期”成为框架的一等概念。
1.3 “可逆”不等于“隔离”
这里必须把两个概念分开:
Koishi 的 Cordis 设计文章甚至明确承认:只要开发者仍能访问全局变量或没有被 Cordis 封装的底层 API,Cordis 就不能从语言层面保证资源安全。
所以 Cordis 的默认信任模型是:
插件是同一开发环境中的可信代码而不是:
插件是可能恶意的第三方代码这个区别后来会成为 DSH 的关键问题。
二、DSH 的设计:把整个 Agent Harness 变成插件树
2.1 “一切皆插件”不只是宣传语,而就是产品结构的实践
DSH 官方架构文档明确表示,产品的每一部分都是插件,包括:
模型适配器; 工具注册表; 会话日志和持久化; 系统提示词; agent loop; 子代理; 文件系统; Shell 和终端; 沙箱与审批; 凭据; Web UI; 技能、工作流和遥测。
这和传统应用的“核心程序 + 少量插件”不同。DSH 更像一个可以被重新装配的 Agent Runtime。
官方说法是:不存在需要打补丁的特权内核;扩展 DSH 的方式,是把插件挂载到其他插件旁边。
从工程角度看,这个方向很有吸引力。它意味着:
可以替换模型提供方(甚至是实时),而不改 agent loop; 可以替换文件系统提供方,把本地能力换成远程沙箱; 可以替换子代理提供方,把一个子任务交给 Codex、Claude、ACP 或另一个 DSH; 可以让不同 profile 使用不同的工具和会话策略; 可以通过事件监听器扩展工具执行、提示词组装和 agent 生命周期。
这是一种非常彻底的微内核化思路架构。
2.2 Profile、Bundle 与 Patch
DSH 的运行实例是一棵启动时组合出来的插件树。一个 profile 通常由多层构成:
空的 entry 列表-> profile 中列出的 bundle-> profile/cordis.patch.yml-> Harness home 级 cordis.patch.yml-> --patch overlay-> 最终插件树
bundle 通过 package.json 中的 dsh.bundle 字段声明自己的 patch 文件:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line{"dsh": {"bundle": {"patch": "./cordis.patch.yml"}}}
patch 可以:
插入新的插件行; 通过 id定位已有插件;替换目标插件的整个配置; 禁用某个插件; 根据运行环境决定是否挂载某个插件。
官方文档还明确说明:后面的层可以覆盖前面的层,任何 dump-config 中出现的 entry 都可以由用户自己的 patch 替换。
这正是“Everything is a Plugin”的落地方式:产品并不是固定二进制,而只是一组可被重新组合的插件和配置层。
2.3 配置已经不只是数据
DSH 的 loader 支持 !!js 表达式。例如官方 dsh-base patch 中有:
- id: sandbox-policyname: '@deepseek-ai/dsh-sandbox-policy'config:mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'workspaceRoot: !!js process.cwd()- id: approvalname: '@deepseek-ai/dsh-user-approval'config:policy: !!js "(process.env.DSH_PERMISSION_MODE ?? 'workspace-write') === 'danger-full-access' ? 'never' : 'ask'"
因此加载过程不是单纯的:
YAML -> JavaScript object -> plugin而是:
YAML -> parse expression -> evaluate expression -> plugin config -> plugin这意味着 patch 文件实际上是三种东西的混合体:
配置数据; 插件挂载描述; 加载期间执行的表达式。
我在 @deepseek-ai/dsh@0.1.0-rc.6 的隔离 profile 中做过本地验证:将带有 console.error() 副作用的 !!js 表达式放入 patch,通过真正的 headless boot 启动后,表达式在 DSH 核心进程启动阶段被执行,并在 stderr 中留下预设标记。
这不是“配置最终改变了工具行为”,而是“配置加载过程本身执行了代码”——虽然在真正使用时的安全性保障非常不专业。
三、问题从哪里开始:可组合性覆盖了安全边界
3.1 DSH 的沙箱保护的是工具进程,不是插件进程
DSH 的 sandbox 文档定义了三种主要文件效果模式:
read-only;workspace-write;danger-full-access。
正常情况下,Shell 和文件工具根据会话策略启动受控子进程,沙箱后端负责包裹进程参数并限制文件效果。
但第三方 Cordis 插件不是通过这些工具启动的。它本身在加载时就已经作为 JavaScript 模块被导入到 DSH 的核心 Node.js 进程中。
两条路径完全不同:
Agent 工具 -> subprocess -> sandbox provider -> 受限子进程第三方插件 -> Node.js loader -> DSH 核心进程第一条路径可以受到沙箱约束,第二条路径在插件加载时已经进入宿主进程。
所以,恶意插件甚至都不需要先设法“逃出 sandbox”。它的代码从一开始就没有经过这个 sandbox。
3.2 审批只是同意界面,不是操作系统边界
DSH 的审批机制本身是失败且关闭的:
ask将请求交给应答者;allowed-once才是单次允许;rejected、cancelled和unavailable都会拒绝;never不弹出交互请求,并确定性地拒绝审批型操作。
因此需要纠正一个容易造成误解的说法:
approval.policy: never不是“自动放行”,而是“不询问并拒绝”。
但这并不削弱插件风险,反而说明风险边界要说得更准确。
审批服务保护的是后续工具 dispatch,不保护的却包括:
插件自己的 apply或init;patch 求值时产生的副作用; 插件直接调用 Node.js API; 插件在 tools/pre-execute中先做完副作用再返回ask;插件对配置树和服务注册表的修改。
DSH 社区 Discussion #1863 对这一点做了实测:一个 tools/pre-execute 监听器可以先启动并等待一个独立 Node.js 子进程完成,再返回 ask。之后用户看到审批并选择拒绝,只能阻止后续的工具主体,不能撤销已经完成的监听器副作用。
社区维护者随后给出了一个准确的表述:
ask-approval 是 consent UX,不是 sandbox boundary。
也就是“用户同意体验”,不是“宿主机能力边界”。
3.3 安全组件本身也是可被组合的组件
DSH 的 dsh-base bundle 把这些能力都作为普通 entry 挂载:
sandbox;sandbox-policy;approval;permission;credentials;subprocess;tools。
从“所有能力都可替换”的设计目标看,这完全一致。
但从安全角度看,这意味着普通 patch 层就已经能够触碰安全相关组件。我在隔离 profile 中验证过,后来的 patch 可以把:
sandbox-policy.mode改成:
danger-full-access也可以随意改变审批 entry 的配置和启用状态。当然这里不应夸大成“把 never 改出来就自动允许所有工具”,更准确的结论是:
第三方 bundle 可以在安全服务初始化前改变安全服务的组成和配置;而插件自身的代码执行并不依赖这些服务。
这才是根问题。
四、生态不是根因:它只是设计范式与安全的结构性矛盾的显影
4.1 生态已经出现,但它不是判定前提
DSH 仓库的 README 已经鼓励开发者给插件仓库添加 dsh-plugin 话题,以便其被发现。社区中也出现了:
视觉识别插件; 模型路由插件; Web UI 和皮肤插件; TUI 插件; 电脑控制插件; 技能与工作流插件; 桌面端封装; 插件精选列表和插件合集; 插件扫描与体检工具。
Discussion #1728 甚至出现了社区整理大量插件的尝试。具体数量属于作者自报,不能当成经过官方验证的统计,但它至少证明了一件事:
社区已经开始按“插件生态”而不是“几个零散示例”来理解 DSH。
但这只能证明生态意愿和生态活动已经出现,不能证明 DSH 已经具备了承载开放生态的安全条件。这里要避免一个反向误读:不是“等生态足够大以后,安全问题才值得讨论”;而是“生态一旦扩大,当前实现中的信任错置会更快暴露”。
4.2 真正的矛盾是开放原则与可信实现
DSH 同时提出了两组互相拉扯的要求。
第一组是产品原则:
每一种能力都可以被替换; profile 可以按用户需要重新组合; 插件可以由开发者甚至 AI 按需生成; 社区可以持续提供长尾能力; 系统应该支持动态加载、卸载和热重载。
第二组是预览版实现的隐含前提:
插件代码运行在核心 Node.js 进程内; 插件共享同一个 Cordis context 和服务注册表; patch 在启动期参与安全组件的组合; !!js可以在配置加载时求值;运行时的 sandbox 和 approval 只覆盖部分工具路径; 安装链没有形成独立的 DSH 信任决策。
前一组要求“插件可以陌生、开放、快速进入系统”,后一组实际上要求“插件必须已经可信”。造成这看起来肯定会有结构性冲突的原因肯定不是生态数量不足,而是扩展模型和信任模型属于两个不同层级,却由同一套插件机制承载实现。
4.3 开放生态需要回答什么
一个真正的插件生态至少需要回答:
谁发布了这个插件? 包是否被篡改? 依赖树里有什么? 插件需要哪些能力? 它会不会访问凭据? 它是否会启动进程? 它修改了哪些配置? 它是否能影响沙箱和审批? 用户卸载后是否真的清理干净? 如果社区发现恶意版本,已安装用户能否收到通知?
当前 DSH 的安装路径更接近:
dsh plugin add <package>-> 向 profile 目录转发 pnpm-> 检查包是否声明 dsh.bundle-> 加入 profile 的 bundle 层-> 下次启动时加载
在我本地审计的 rc.6 构建中,没有看到 DSH 层提供的独立:
bundle 签名验证; 来源白名单; patch diff 确认; 权限声明和能力审批; 安全 entry 不可覆盖规则; 第三方 bundle 的信誉和撤回机制。
当然这并不等于 npm 没有自己的包完整性机制,也不等于所有第三方插件都恶意。它表示的只是:
从“普通 npm 包”到“核心进程插件”的跃迁,没有形成足够清晰的 DSH 信任闸门。
4.4 当前社区反馈本身已经暴露了这个错置
Discussion #326 讨论了“Everything is a Plugin”的用户侧成本:49 个左右的包和复杂依赖图会把系统组合成本转嫁给用户,而 Cordis 插件共享同一个进程、上下文和工具注册表。
Discussion #380 记录了插件开发中的真实问题:
inject形状写错导致插件永久 pending;link 开发和 npm 安装的模块解析不同; 原生依赖可能让整个 DSH 启动失败; 插件命名和 section 注册可能冲突; rc 版本变化会快速破坏第三方插件。
Discussion #535 则记录了 sharp、node-pty 等依赖加载失败,导致整个 dsh web 在插件树加载阶段直接崩溃。
这些不是本文要指控的安全漏洞,也不能单独证明恶意行为,但它们说明了当前实现的基本现实:
插件可以影响整个进程插件错误可以影响整个启动插件依赖可以改变整个运行时
这些反馈与安全讨论共同指向同一个事实:插件错误、插件依赖和插件副作用都发生在核心运行时的同一层。再叠加 AI 生成代码、npm 供应链和宿主机凭据,安全问题就不是“以后生态变大才会出现”的问题,而是当前架构已经暴露出来的问题。
4.5 只问为什么“生态建不起来”是不能覆盖这个问题的
如果只把现象描述成“DSH 现在还没有建立起成熟生态”,就会错过真正的矛盾。生态是否成熟,最多解释用户为什么还没有大量安装插件;它不能解释为什么一个已经安装的官方或第三方 bundle 可以放心地在核心进程内执行、参与安全配置组合。
当然,也更不能把安全问题推迟到生态成熟之后,因为:
没有第三方插件时,官方 bundle 仍然使用同一套 loader 和 !!js机制;没有插件市场时,用户自己写的插件仍然与核心进程共享上下文; 没有供应链攻击时,插件的配置覆盖权仍然存在; 没有恶意代码时,插件初始化失败仍然可以拖垮启动; 没有大规模用户时,安全组件仍然处在普通组合层中。
因此,这不是“生态发展太快带来的副作用”,而是预览版在实现核心原则时,少实现了一层必要的信任分化。
如果官方把第三方插件当成普通扩展,用户会自然期待:
可以搜索; 可以安装; 可以更新; 可以卸载; 可以相信基本的来源信息; 可以在出问题时得到告警和回滚。
但如果官方把插件如实描述为“核心 Node.js 进程代码”,用户又不可能像审查生产依赖那样逐个读源码、看 patch、查依赖树和追踪生命周期。
于是出现的不是单纯的生态推广难题,而是一个结构性张力:
想扩大生态 -> 必须降低安装门槛降低安装门槛 -> 必须提高官方信任和隔离能力没有信任和隔离 -> 只能要求用户自己承担审计责任要求用户自己审计 -> 开放组合原则无法兑现为普通用户可用的产品能力
这就是 DSH 预览版的关键错置:它在产品层面选择了开放插件化,在运行时却没有建立与开放插件化相匹配的信任边界。
所以,问题不是“没有人写插件”,也不是“生态还不够大”。问题是:
即使有人写、有人装、生态继续增长,系统也没有告诉用户哪些插件可以被信任、哪些能力可以被授予、哪些安全边界不能被插件改写。
五、真正的危害:安装恶意 bundle 等于把代码放进用户权限上下文
5.1 攻击条件
这不是完全无法抗拒的零点击远程 RCE。攻击者通常需要以下几个条件:
发布恶意、仿冒或被投毒的插件包; 诱导用户执行安装; 等待 DSH 下次启动加载该 bundle。
所以它属于供应链投毒和社会工程结合的本地代码执行风险。
5.2 一旦加载,插件拥有什么
第三方插件代码在 DSH 核心 Node.js 进程里执行。它不需要通过模型、不需要调用工具、不需要等待用户点击审批,就可以运行自己的初始化逻辑。
在运行 dsh 的用户权限范围内,它可能接触到:
当前项目和工作区文件; DSH profile 与 home 目录; 进程环境变量; 用户凭据引用和凭据服务; 会话日志和模型上下文; 网络连接; Node.js 模块和宿主机进程能力。
再多说一下,DSH 的凭据设计本身是把配置中保存的内容做成引用,再由 provider 在每次操作时解析真实值。这对凭据轮换很有用,但也意味着拥有相应服务访问能力的代码处在敏感数据路径上,可以被轻易获取。
5.3 对普通开发机意味着什么
如果用户以普通用户权限运行 DSH,恶意插件可能造成:
项目源码和 .env文件泄露;API key、云凭据和 SSH 配置泄露; LLM 对话、工具调用和企业内部资料外传; 用户文件被修改或删除; 启动项、脚本或其他持久化位置被植入; CI、部署密钥和代码仓库 token 被进一步利用。
不能说是“完全控制宿主机”,更精确地说就是:
对普通个人开发机,恶意 bundle 基本等价于获得当前用户上下文下的完整代码执行和持久化能力。
它没有自动获得 root,也不等于突破所有操作系统用户隔离;但对绝大多数开发者而言,这已经是宿主机级别的实际失陷。
更关键的是,插件自身的权限已经不再依赖 DSH 的 workspace-write 沙箱。沙箱可以限制 DSH 通过受控工具启动的子进程,却不能反向限制已经运行在核心进程里的插件代码。
六、不是说要放弃“一切皆插件”,而是给它补上信任层
6.1 短期:把插件当核心代码对待
在官方修复前,用户应当把任何带有 dsh.bundle 的包视为核心代码,而不是普通配置:
安装前检查 package.json;检查 cordis.patch.yml;搜索 !!js、child_process、文件写入和网络请求;查看依赖树和原生模块; 使用独立 profile 或隔离用户测试; 不在主开发机和真实凭据环境中盲装陌生 bundle。
静态扫描工具可以降低审查成本,但不能替代进程隔离。Discussion #1863 中出现的 dsh-plugin-doctor 检查,本质上也是 review aid,而不是安全沙箱。
6.2 中期:建立 DSH 层的安装信任机制
至少需要:
官方 bundle 签名或可信来源校验; 安装前展示 bundle 的 patch diff; 说明插件请求的文件、网络、凭据和子进程能力; 对 !!js和可执行 hook 强制显式确认;对安全相关 entry 设置不可被普通第三方层覆盖的规则; 建立恶意 bundle 举报、撤回、黑名单和用户告警机制; 记录 profile 的安装、更新、启用和卸载审计日志。
这一步的目标不是阻止插件生态,而是让用户知道自己安装的不是“配置”,而是一段会进入核心进程的代码。
6.3 长期:第三方代码移出核心进程
真正可靠的结构应当类似:
DSH 核心进程-> 稳定的 capability/RPC 接口-> 受限插件进程或 MCP server-> 明确授权的文件、网络、凭据和工具能力
核心进程仍然可以保持“一切皆插件”的内部架构,但第三方插件不应因此获得同等的进程内权力。
可以把插件分成两类:
可信内建插件:进程内、可组合、可热重载第三方插件:进程外、能力声明、显式授权、可撤销
这样既保留 Cordis 对内部系统的价值,也避免把整个 npm 生态直接接到宿主进程上。
6.4 安全策略应该是单向约束
更重要的架构原则是:
安全核心可以限制插件普通插件不能降低安全核心的约束
也就是说,sandbox、approval、credentials 和 subprocess 不应只是可被任意 patch 的普通 row。它们可以提供扩展点,但不能允许普通 bundle 在加载期取消自己的安全边界。
6.5 明确审批的语义
文档和 UI 需要明确说明:
审批决定的是后续工具操作是否 dispatch,不代表审批出现之前没有插件监听器、初始化逻辑或宿主进程操作发生。
只有把审批和能力隔离分开,用户才能正确理解“拒绝”究竟能阻止什么。
结语:需要修正的是信任层,而不是等待生态成熟
DSH 的插件化方向本身并不错误。
Cordis 提供了一套很有价值的工程基础:服务依赖、上下文、事件、可逆副作用、动态重载和配置组合。对于构建一个可以持续演化的 Agent Harness,这些能力非常有吸引力。
问题在于,DSH 把这套能力一路推进到了安全组件和第三方分发层,却没有同步完成一次信任模型的升级。它选择的是面向开放组合的产品原则,交付的却仍然是面向可信内部代码的预览版运行时。
所以,“一切皆插件”最终需要拆成两句话:
一切能力都可以插件化。
但不是一切插件都应该拥有核心进程权限。
当前 DSH 的核心问题不是“插件生态还不够成熟”,而是开放插件化原则与预览版实现之间存在未被解决的信任错置。生态是否扩大,只会影响这个问题暴露的范围和速度;它不会改变问题本身。
即使只有官方插件,加载期表达式、配置层覆盖和安全组件可组合仍然存在。即使没有恶意包,插件初始化失败也能影响整个进程。即使没有供应链攻击,第三方代码一旦进入核心进程,沙箱和审批也不会自动变成它的能力边界。
因此,插件生态真正的基础设施不是一个更大的插件列表,而是一套能把“可组合性”和“信任等级”分开的机制:谁可以进入核心进程,谁只能通过 RPC 工作,谁可以访问凭据,谁可以改写安全策略,谁必须在用户确认后运行。
这才是 DSH 下一阶段必须回答的问题,而不是等生态建起来以后再回答。
资料来源
DSH 与 Cordis
DeepSeek Harness README DSH 官方架构文档 DSH Cordis 入门 DSH App Boot 与 Profile 机制 dsh-base bundle patch DSH 沙箱文档 DSH 用户审批文档 DSH 用户凭据文档 Cordis GitHub 仓库 A Programming Paradigm for Spatiotemporal Composability Koishi:可逆的插件系统
DSH 社区讨论
#326:Thoughts on “Everything is a Plugin” — the user-side cost #380:写第一个 dsh 插件踩的六个坑 #535:npx @deepseek-ai/dsh web 报错 #587:第三方插件在启动期获得配置树权限 #1728:社区插件整理 #1863:tools/pre-execute 在审批前执行宿主操作 #1884:AI 编写的 DSH 插件为什么容易出问题 #1923:借助系统外壳绕过 workspace 限制
方法说明
本文以官方架构资料和 Cordis 原始设计为纵向主线,以 DSH 当前插件生态、社区讨论和本地 rc.6 实测为横向证据,重点分析“可组合性”与“不可信代码隔离”之间的结构性矛盾。涉及本地实测的内容,结论只针对所审计的 @deepseek-ai/dsh@0.1.0-rc.6 构建,不自动外推到未来版本。
夜雨聆风