揭秘 DSH 插件市场:一次看懂插件的安全来源、硬核安装与无痕卸载
【免费下载链接】deepseek-harness-desktop为 DeepSeek Harness (DSH) 插件生态打造的现代化桌面端解决方案。万物皆「插件」,桌面本身也是「插件」。项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness-desktop
如果你关心 DSH 插件市场的插件安装机制与 npm 依赖管理是如何协作的,这篇开源架构解析值得完整读一遍。DSH Desktop(deepseek-harness-desktop)内置的 DSH Community Market 不是一个"逛一逛商店"的 UI 组件,而是一套围绕插件来源、安装与卸载构建的严密防御体系:所有目录数据必须经过归一化才能进入界面,所有安装必须持有一次性防伪令牌才能执行,所有卸载只认不透明的 bundleId、绝不接受包名。本文以"安全防御 + 生命周期"的视角,把这三段链路上的关键决策与源码位置一次拆清楚。
🛡️ 权力边界:Market 模块"管什么"与"不管什么"
把 Market(dsh-community-market/)想象成一家"只负责柜台"的分公司:柜台拥有商品目录、选品展示和下单流程,但仓库(Profile 存储)、物流车队(pnpm 执行)、售后(恢复 checkpoint)和店面重启(进程管理)全部分属总部其他部门。Market 通过受限接口向这些部门"派单",而不是自己下场搬货。
这个边界在入口文件 src/index.ts 中落地得非常直白:Market 通过依赖注入获取pluginManager(官方插件管理器)与profileContext(当前激活的 Profile,即 DSH 中"配置 + 工作区 + 已装依赖"的一个可隔离运行环境),两者同时在场时才创建MarketInstallService;而当这些能力缺席时,Discover、Sources 等只读浏览能力照常工作。这就是"浏览与安装分离"——没有仓库钥匙的门店,依然可以开灯营业,只是柜台上的"安装"按钮自动消失。
同样被划在 Market 之外的还有持久化存储:源码中的DeferredMarketStateStore先以会话内(session-only)模式运行,只有storageDomain这个可选对等依赖出现时才"升级"为持久存储,缺失时来源选择不跨重启保留,但绝不因此阻塞启动。
🔄 数据流水线:目录归一化与"拒绝货主遥控器"原则
Market 的运行时由两条互不交叉的数据流构成:
catalog source -> Host adapter -> normalized catalog -> Market Client | +-> npm latest preview Desktop Profile inventory -> Installed view -> opaque bundleId -> pnpm remove这里有一条铁律:界面(Renderer)只接收标准化数据,从不接触任何 provider 的原始 wire format。任何来源——无论是实现 public v1 契约的标准 HTTP 来源、dshfind 适配器,还是 DSH 1024Store——其原始响应都必须在 Host 侧经 adapter 校验、归一化为一等公民CatalogSnapshot(可以理解为"所有货架商品贴统一标签后的总清单")后才允许下发。
本地有界索引与远程分页并存
不同来源走不同的数据获取策略:
- 标准 cursor 来源与 dshfind:Host 会把整个目录扫描进一个"有界本地索引",搜索、多分类 OR 筛选和 50 条/页的 UI 分页全部在本地完成。扫描时 Host 只发送
cursor、limit等扫描字段,跟随page.nextCursor直到耗尽——用户的搜索词和已选分类绝不外传。dshfind 适配器则以per_page=100拉取,记录并回传data_version,任一页失败(如409 stale_data)即整体拒绝,绝不发布部分索引。 - DSH 1024Store:改用 provider 当前的分页 v2 API(
/api/v2/plugins)逐次服务查询,单分类筛选直接转发,多分类 OR 筛选合并有界前缀并维护本地不透明 cursor。"可安装(Installable)"视图每批请求 200 条远程 registry 记录,只保留直接 npm 目标,Client 凭下一枚不透明 cursor 续读——Host 与 Renderer 都不会一次性物化完整目录。
网络层同样有硬约束:两个 reviewed 适配器各自使用独立的 HTTP client,体积上限分别为MAX_DSH_1024STORE_BODY_BYTES = 2 MiB与MAX_DSHFIND_BODY_BYTES = 16 MiB(见 host/routes.ts);其主机名是编译期写死的常量,用户自行添加的来源永远不会继承这些本地代理例外。
只提取货单上的商品名,拒绝货主的遥控器
来源可以提供目录身份(attribution),但绝不能提供可执行 argv、凭据、adapter 代码或安装权限。经过审查的 1024Store 适配器只会解析一种严格的惰性命令形状(精确的dsh plugin --profile … add <npm-package>)来提取 npm 包名,然后立即丢弃该命令,绝不转发或执行。类比一下:货单上印着"商品名 + 一台遥控启动器",Market 只抄走商品名,把遥控器当场销毁。归一化契约层面,snapshot 条目明确禁止包含安装命令、shell 片段、HTML 或脚本,详见 catalog-provider-contract.md。
📦 铁腕安装:单一 npm 身份与"一次性防伪令牌"机制
入场前的四道资格审查
一个可安装条目只贡献一个 npm package 身份。进入自动安装预览前必须同时满足四条:
- 从条目中归一化出恰好一个合法 npm 包名(包名用严格正则校验,见 install/service.ts 的
PACKAGE_NAME_PATTERN); - 该包不属于产品自有包黑名单
BLOCKED_PRODUCT_PACKAGES(dsh-plugin-desktop、dsh-plugin-desktop-beta、dsh-desktop-next、dsh-community-market)——防止市场"自我覆盖"; - npm 官方
latest(解析https://registry.npmjs.org/<package>/latest)返回同名包且为精确稳定版本,prerelease 一律出局。来源列出的版本永不作为安装目标; - 该 npm manifest 声明了合法的
dsh.bundle.patch(相对安全的 DSH bundle patch 路径)。
反过来,lifecycle 脚本、deprecated 元数据、engine 范围、provider 验证徽章等都不构成安装门槛——pnpm 只负责解析并安装所请求的精确 npm 版本,验证责任在 Market 这一侧闭环。
previewId:一次性防伪入场券
Preview 把一组事实绑定到一枚短时一次性 token(previewId)上:匹配的包名、精确稳定版本、合法的 bundle patch、已被观察到的目录条目{ sourceRecordId, itemId }、当前激活的 Profile。执行阶段只认{ previewId }这一个字段——Renderer 永远无法把任意 argv 递给 pnpm。
这枚入场券有明确的防伪参数(install/service.ts):INSTALL_INTENT_TTL_MS = 5 * 60 * 1000(5 分钟过期)、CANDIDATE_TTL_MS = 30 * 60 * 1000、MAX_INTENTS = 256、MAX_PNPM_STREAM_OUTPUT_BYTES = 32 KiB。若 Profile 在 preview 之后发生变化,令牌直接作废,映射为intent-expired/ 409 / 410 等错误码(状态码映射逻辑见 host/routes.ts)。
执行时 Host 构造精确的pnpm addargv 调用desktopPnpm.run(),保存精确版本,把包 reconcile 进dsh.profile.bundles,随后确认它已成为 Profile 直接依赖。注意一个重要的"负设计":Market 安装不创建任何 receipt、快照、重试、清理或回滚路径。pnpm 失败只保留 stdout/stderr 的有界尾部、写入日志并展示诊断,绝不自动重试已消耗的确认。恢复由 Desktop 统一的"三个健康启动 Profile checkpoint"机制兜底,且永远是恢复页面中的显式动作。
🧹 无痕卸载:基于 Profile 依赖的逆向操作与无状态兜底
Installed 视图:一份"与来源无关"的投影
Installed 视图读取的是当前 Profile 的直接依赖与 bundles,因此天然覆盖本 Market、其他 Market 以及 DSH CLI 安装的全部插件——不需要任何市场间集成协议,也不依赖 Market receipt。产品自有 bundle(core bundles)只读;其余直接插件依赖获得当前 generation 有效的不透明bundleId。
bundleId可以理解为"防篡改的盲盒编号":界面拿到的只是一串此刻有效的凭证,它背后对应哪个包、哪个版本,完全由 Host 在操作前重新解析。凭证本身不携带任何包管理语义。
卸载的五步闭环
- Desktop 读取激活 Profile 的
dependencies与dsh.profile.bundles; - 每个直接 bundle 被签发一个 generation 作用域的
bundleId; - Renderer 只提交该
bundleId(卸载请求只接受{ action: 'uninstall', bundleId },且字段经过boundedIdentifier校验——1–240 字符、禁止\0的"限长身份串"检查,实现位于 host/routes.ts); - Host 在 preview 前重新对照当前清单解析
bundleId,并再次校验target.uninstallable,防止已被移除或改动的 bundle 被重复操作; - 确认后执行
pnpm remove <packageName>,移除 bundle 引用,并确认 Profile 不再引用该包。
这套流程对本 Market、其他市场或 DSH CLI 安装的插件一视同仁,卸载界面只提供卸载,不提供启用/禁用开关——把"开关"留在官方插件页,Market 只做减法。
故障边界:浏览可用、启动不受阻、恢复唯一
Market 的故障语义在 install-and-uninstall.md 中给出了完整的行为表,核心五条:
- 能力缺失时浏览仍可用:未注入
desktopProfiles/desktopPnpm时,只读能力照常工作; - 来源故障不阻塞启动:首次启动只读本地来源设置、不联系 provider,启动后按需拉取;
- 错误不触发自动清理或回滚:pnpm 失败仅展示有界诊断,对话框提供 Host 派生的精确命令和"打开 DSH Terminal"入口供手动跟进,绝不自动粘贴、执行或静默重试;
- 三个健康启动 Profile checkpoint 是唯一恢复机制;
- Renderer 确认后关闭:Host 拥有的 package 操作继续执行,只是响应可能丢失。
来源请求的失败也被严格隔离且按来源归因:超时返回catalog-timeout(504)、响应无效返回catalog-invalid-response(502)、其余返回catalog-unavailable;失败附着在所选来源上并提供 Retry,绝不回退到其他已保存来源。所有 Market 路由还统一经过marketAuthority检查——仅接受 127.0.0.1 回环、端口匹配、非 cross-site 的本地请求;来源出站仅允许无凭据的标准 HTTPS 443,并逐跳校验重定向。
⚙️ 工程底线:Headless 约束下的 CI 验证与代码溯源
Market 包的开发与发布全流程必须 headless-safe:契约生成(Schemas → TypeScript 类型)、类型检查、单元测试、package build、export 验证、Loader smoke test——以上任何一步都不得启动 Electron 或图形应用。这正是它能被稳定集成进 DSH Desktop 发布链的前提:目录读取、来源管理、安装编排全部运行在 Host 侧,不依赖任何 GUI 事件循环。
验收测试集中在 tests/ 目录,包括contracts.spec.ts(契约校验)、market-install.spec.ts(安装编排 fail-closed 行为)、dshfind-adapter.spec.ts(有界索引与整页失败拒绝)、host-routes.spec.ts(路由鉴权与错误码映射)等,覆盖无默认来源、显式选择、失败不回退、安装候选 fail-closed 等关键场景。
想亲手溯源,按这条路径走最快:入口与注入边界看 src/index.ts,路由、鉴权与错误码看 host/routes.ts,资格审查、一次性令牌与 pnpm 编排看 install/service.ts,三份文档——catalog-provider-contract.md、catalog-adapter-guide.md、install-and-uninstall.md——分别回答"来源能说什么"、"适配器怎么接"、"安装卸载每一步会发生什么"。读完源码与文档再回看本文,你会发现这套市场的安全感并非来自某一条防线,而是来自"归一化 → 令牌化 → 依赖投影"三层防御的层层收敛。
【免费下载链接】deepseek-harness-desktop为 DeepSeek Harness (DSH) 插件生态打造的现代化桌面端解决方案。万物皆「插件」,桌面本身也是「插件」。项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness-desktop
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考