夜雨聆风学习资料网

ARTICLE · 1008044

官方办公插件 dsh-univer-office:能力拉满,门却没上锁

官方办公插件 dsh-univer-office:能力拉满,门却没上锁

先把我的判断放在最前面:dsh-univer-office 是我拆过的九个 DeepSeek Harness 插件里能力最猛的一个,但我认为今天只能给到 inspect-only。它由 Univer 的开发方 DreamNum 亲手维护,把电子表格、文档、幻灯片、多维表格和画布整个塞进了 Harness 会话,还能在合并前强制人工确认。能力没有问题,问题出在两份合同上:官方声明的兼容范围停在 0.1.2-rc.1,而现在你装到的 Harness 早已是 0.1.5-rc.2;包里还硬编码了一张 11 月 16 日凌晨到期的商业开发许可证。

一句话概括它是什么:一个把整套办公环境搬进 DeepSeek Harness 的插件,Agent 在会话里就能造表、写文档、做幻灯片,你实时看过程,确认后才落盘。

我为什么挑它:它月下载 7.5 万,是 dsh 生态里少见的"官方厂商下场"型插件,而且它把三种很有代表性的东西压在了一个包里——最干净的安装路径、最透明的自产遥测、和一堆你永远无法逐行核验的混淆构建产物。拆开它,等于把 dsh 商业化插件的标准样板看了个遍。

下面所有数据都来自我这一次的实拆,环境是 macOS、node v25.6.1、pnpm 10.13.1,冻结日期 2026-09-14。

版本钉死:基线和插件分开冻结

基线和插件,要分开钉死。

上游 Harness 我冻结在 deepseek-ai/deepseek-harness 的 dsh-v0.1.5-rc.2,tag 提交 fb2c4b9e,官方发布时间 2026-09-10。这是现在装 dsh 能拿到的最新版,也是我审计的兼容基准。

插件这边,dsh-univer-office@0.2.14 于 2026-09-04 发布到 npm,canonical 仓库是 dream-num/dsh-univer-office,tag v0.2.14 指向提交 62df970d。关键的互证在这里:包内 lib/build-info.json 记录的构建提交正是 62df970d,和 tag 完全一致,发布方还把仓库、Apache-2.0 许可证、作者 DreamNum Co., Ltd. 全部写进了 package.json。

作者身份多说一句。DreamNum 是开源办公套件 Univer 的官方公司,这个插件不是社区爱好者作品,而是厂商把自家商业办公引擎接进 Harness 的"亲儿子"。过去一个月 npm 下载 75,594 次,GitHub 340 star,仓库 2026-08-15 才建,节奏非常快。

npm 侧的身份核验有个遗憾:0.2.14 没有 GitHub OIDC 来源证明(provenance attestation),只有 registry 自己的签名。对一个月下载七万多的包来说,这是个值得记录的缺口。

它的兼容声明写得非常老实。README 中文版原话:"支持的 DSH 版本为 0.1.1-rc.2 和 0.1.2-rc.1",peerDependencies 也钉死在这两个版本。也就是说,对当前 0.1.5-rc.2 基线的兼容性,官方没有承诺。我翻了 0.1.3 到 0.1.5 的全部 release notes,没有点名任何插件接缝的破坏性变更,但那是发布说明,不是兼容合同。这一条是全文最重要的背景。

拆包 172MB:97% 是无法逐行核验的构建产物

安装路径干净,包体另说。

压缩包 39.9MB,解压后 172MB。我在隔离目录里用 pnpm 默认配置真实装了一次:解析 85 个包、装入 71 个,全程 15 秒,没有任何一个包执行安装脚本,连 pnpm 的"Ignored build scripts"警告都没有触发。原生依赖全部走预编译:libsql 按平台拆成 9 个可选包,两个 Rust 公式与交换引擎绑定直接给 .node 二进制——不过要注意,公式引擎钉在 insiders 预发布通道的构建号上,不是稳定版。

元数据这一层,包和源码可以互相印证:README、LICENSE、cordis.patch.yml、package.json、docs、skills 和两个脚本,26 个可比文件逐一做了 SHA-256 对比,26 个全部与源码 tag 字节一致。

直接依赖只有 6 个:libsql、两个 Rust 引擎绑定、Univer 命令行资产,以及 puppeteer-core 和 @puppeteer/browsers 这对浏览器工具。peerDependencies 的行为要单独说:9 个 @deepseek-ai 宿主包被钉死在两个旧版本上,在裸项目里安装时包管理器会自动把它们补齐,等于把整个宿主框架拉进依赖树。在真实的 dsh profile 里这些包由模板内置提供,所以实际安装是 71 个包,普通项目里则会更多。

顺带把凭据边界说清楚:这个插件不需要任何模型密钥,它寄生在 Harness 的宿主能力之上。更有价值的是反方向的证据——网关子进程的启动环境是一份白名单,只有 HOME、语言、路径、临时目录加端口号和静态资源目录,模型密钥和会话凭据一概不透传。也就是说,51MB 的网关进程手里没有你的任何秘密,这在我拆过的带子进程的插件里是相当克制的设计。

但真正的大头没法这么验。lib 加 artifacts 约 174MB,占包体的 97%,全是构建产物,源码仓库把这两个目录都列进了 gitignore。51.3MB 的 gateway.cjs 和 30.8MB 的 worker 里混着 Univer SDK 的混淆发行版,lib/index.js 里我数到近四千个混淆标识符。你能读干净的部分,是宿主插件自己的代码和全部配置;读不了的部分,是厂商塞进来的整颗办公引擎。

▲ 拆包清单:172MB 里 97% 是构建产物,能逐字核验的只有元数据层。

这个结构不是坏,而是要认清它的性质:装它,等于信任 DreamNum 的构建流水线,而不是信任你能读的源码。

注入地图:插件往 Harness 里放了什么

一个 cordis 行,拖出整棵服务树。

宿主侧的注册方式极轻:cordis.patch.yml 只插入一行 id 为 univer 的 bundle。真正的扩展都在代码里:它向 cordis 注册了 univer 服务,向 dsh-settings 注册了 19 个配置项,向 dsh-tools 注册了一组 univer_* 工具,向 dsh-skill 注册了 8 个随包技能,还向 dsh-host-webserver 挂了 /univer-api 前缀路由。它消费的宿主能力包括 session(取会话工作目录)、attachment(回传截图)、llm(内容块类型)和 webserver、settings、tools、skill。

客户端侧,package.json 的 dsh.client.inject 声明了 7 个 @deepseek-ai/dsh-client-ui-* 注入点,覆盖会话输入坞、消息渲染和插件设置页。lib/client.js 是干净的、可读的打包产物,通过 window.__ModuleLoader__.load 挂载,里面甚至带了一个双 API 兼容探针,同时适配 0.1.1 的合并事件接口和 0.1.2 拆分后的事件接口——这侧面说明客户端接缝在这一代里挪过一次。

那 8 个随包技能也值得一讲,因为它们是 Agent 行为的护栏。技能指令写得很硬:不许等用户点名工具,不许直接改 .univer 存储文件,不许拿 openpyxl 之类的 Python 库或解压改 XML 的野路子替代插件工具;每次内容写入必须带文件、工作树和单元三个标识;截图必须真的检查过,才能声称验证通过。幻灯片流程被拆成先写规格、再逐页编译、再逐页布局检查的流水线。技能层把"Agent 怎么用这把枪"写进了说明书,而不是全靠工具层兜底。

配置层有 19 个键,我在裸 Node 里直接加载出厂数据并调用 resolveConfig:gatewayPort 默认 9080、autoStartGateway 默认 true、tools/skills/telemetry 全部默认 true,和源码里的 config.ts 一字不差。telemetry 默认开着,想关有三条路,后面细说。

卸载路径是干净的:所有注册都挂在 cordis effect 生命周期上,dispose 时网关子进程先 SIGTERM、等 3 秒、再 SIGKILL,架构文档承诺不留路由、定时器和子进程。

▲ 注入与副作用地图:一行 cordis 注册,牵出工具、技能、路由和三个子进程。

遥测与卸载钩子:一条透明,一条是死代码

它自报家门,但只报两次。

先说透明的部分。这个插件的运行时遥测是我见过的 dsh 插件里披露最完整的:激活时上报一次,之后每天一次日活,事件载荷只有匿名 UUID、系统架构、平台、Node 大版本、包版本和构建提交,没有文件内容、没有路径。匿名 ID 存在 ~/.dsh/telemetry/dsh-univer-office/state.json,目录 0700、文件 0600、原子写入。上报端点硬编码为 univer.ai/api/telemetry/cli,5 秒超时,失败就静默丢弃,绝不阻塞插件加载。关闭方式有三条:配置里 telemetry:false、环境变量 DO_NOT_TRACK、以及把 UNIVER_TELEMETRY_ENDPOINT 设成空串。README 也写了遥测章节。

再说死代码的部分。package.json 声明了一条 uninstall 生命周期脚本,卸载时派生一个分离子进程向同一个端点发一条卸载事件,而且设计上不去重。这个钩子写得再克制,也要先问一句:它到底会不会跑?

还有一个容易漏看的细节:插件配置里的 telemetry 开关不只管运行时。这个开关的取值会被镜像写进状态文件,卸载钩子在发送前也会读同一个文件,所以你在界面上关掉遥测,连那条(本就跑不动的)卸载信标一起被关掉了。设计者显然想过"关了运行时遥测、卸载时又冒一条"这种体验割裂,只是他们没想到钩子本身根本不会被执行。

按直觉,卸载脚本怎么也该跑一次。结果两种包管理器都没给它机会。

我做了个对照实验。我造了一个假包,同时声明 install 和 uninstall 两个脚本,往包目录外写标记文件;install 脚本是阳性对照,证明生命周期管道和环境变量传递都是通的。我把它打成 tarball 装进一次性工程再卸载:pnpm 10.13.1 下 install 和 uninstall 一个都没跑,因为 pnpm 默认阻断构建脚本;npm 11.9.0 下 install 跑了,uninstall 没跑。两种现代包管理器都不会在移除依赖时执行它的 uninstall 脚本。

所以结论有点反直觉:厂商精心设计的卸载遥测信标,在 dsh 的 pnpm 路径下是死代码,在最新版 npm 下也是死代码。

钩子是死的。真正在工作的上报通道只有激活和日活两条,而它们都是可关闭的。

隔离复现:网关真的只听本机,但门没锁

只听本机回环,谁来都放行。

静态审计通过之后,我做了代表性复现。第一段是装和载:出厂数据在裸 Node 里直接可加载,导出的服务面和配置默认值全部符合源码声明。第二段是把 51MB 的 gateway.cjs 直接拉起来:lsof 显示它只监听 127.0.0.1:19080,GET 根路径返回内置 Viewer 页面,隔离的 DSH_HOME 之外没有任何文件落点。

然后是这次复现里最有价值的一条。我按源码里的路由格式对一个 /uf 接口发了一个带恶意 Origin 的 CORS 预检:网关原样回显 access-control-allow-origin: https://evil.example,放行方法 GET/POST/OPTIONS,放行头包括 content-type、x-user-id、authorization。整条 API 没有任何鉴权,协作用户身份直接取自请求头里的 x-user-id,而插件启动器给网关子进程设的环境白名单里根本没有 UNIVER_ALLOWED_ROOT——路径围栏这个开关,对插件用户来说不可达。

把它翻译成人话:网关只从本机回环接客,但本机上的任何一个进程、乃至你在浏览器里打开的任意网页(趁网关运行的窗口期,预检能过,WebSocket 本就不受 CORS 限制),都可以按它的路由格式发请求。要利用它需要知道某个文件的 base64url 绝对路径编码,这抬高了门槛,但防线确实只有"回环加路径难猜"这一层。README 对此没有提示。

这就是门没上锁的意思。锁是好锁,可惜根本没装到门上。

子进程纪律是这份审计里少见的加分项。网关是常驻子进程,健康检查不是简单 ping 端口,而是校验根路径返回的 HTML 里必须含 Univer 标题和挂载点,防止"端口通了但服务是错的";端口占用时从 9080 起逐个上探,其他启动失败直接报错而不是换端口硬试。worker 则是一次性进程,每个内容操作单独起一个,请求从标准输入进、结果从标准输出回,超时或中止直接杀掉,默认 120 秒。网关不可用时上层拿到的是带原因码的 GATEWAY_UNAVAILABLE,错误信息保留了最多四千字符的 stderr 尾巴,排障时不至于两眼一抹黑。

浏览器这一块也有个反直觉的发现。README 写明 Slide 布局检查和 PDF 打印需要本地 Chrome 或 Chromium,出厂数据的解析顺序是:显式配置、UNIVER_RENDER_BROWSER 环境变量、缓存目录、再到系统浏览器路径,全都没有就抛 BROWSER_UNAVAILABLE 关断。我把混淆池翻了一遍:@puppeteer/browsers 的 install 函数在 lib/index.js 里只有 import 那一行、零个调用点,所谓"运行时自动下载 Chromium 150.0.7871.24"的字符串只是打包残留。0.2.14 不会偷偷下载浏览器,缺了就明确报错。

▲ 隔离复现实录:回环监听、任意 Origin 回显、配置默认值三条实测输出。

数据存储的思路值得单独一说:所有办公内容都住在 .univer 文件本身里,它就是一个 libsql 的 SQLite 库,主干、工作树、历史、附件全在里面,靠 realpath 身份做单写者仲裁。Agent 的每次修改先进隔离工作树,合并或放弃时会通过 tools/pre-execute 事件强制发起一次用户确认——这条审批闸门是我在源码里逐行确认过的。

▲ 网关生命周期:懒启动、健康检查、服务 .univer 文件,退出时 SIGTERM 三秒后强杀。

兼容与授权:两个倒计时

两张合同,一张没签,一张快到期。

第一张是兼容。你今天执行 dsh plugin add,pnpm 会顺利把它装进 0.1.5-rc.2 的 profile——pnpm 对 peer 范围只警告不拦截——但装出来的是一个官方没有声明支持、我也没有条件完整验证的组合。出厂数据里那个双 API 探针说明客户端接缝已经追着 Harness 改过一次,服务端接缝是否也有类似适配,出厂数据里看不出来。

第二张是授权,这条更紧。源码 src/workers/unit-content/license.ts 里硬编码了一张 Univer 商业组件的开发许可证,签发给 developer@univer.ai,域名白名单只有 localhost,签发时间 2026-08-14。我解码了包里的令牌:有效期到 UTC 2026-11-15 16:00,也就是北京时间 11 月 16 日零点。源码注释原话是"必须在 90 天轮换时更换",而这张许可证被原样烘焙进了出厂的 lib、worker 和 gateway 全部产物。

说白了,这不是商业授权,是一张 11 月中旬过期的临时工牌。

到期会发生什么,混淆的商业授权模块没有给出可读答案:可能只是水印、可能是协作功能降级、也可能直接不可用。可以确定的只有两件事:UNIVER_LICENSE 环境变量可以覆盖它;以及在那个日期之前,你需要等到一个带新许可证的插件版本。

把我的复评条件写得更具体一点,免得"等完善"变成一句空话。第一,看 README 和 peerDependencies:当官方把支持声明抬到 0.1.5-rc.2 或更新版本时,兼容这张合同才算补签。第二,看许可证:厂商发布带新轮换许可证的版本,或者官方文档正面解释商业组件的授权与到期行为。第三,看网关:只要版本更新里出现鉴权令牌、Origin 校验或 UNIVER_ALLOWED_ROOT 透传中的任意一项,安全边界这条顾虑就可以降级。三条中前两条决定我是否改判 install,第三条决定改判后的推荐语气的强弱。

同一件事的三种做法

同一份 Office 文件,三条路。

第一种是什么都不装。Harness 内置的附件组件会把上传的文件渲染成卡片:图片有缩略图和大图灯箱,而 xlsx、docx、pptx 只显示文件名、扩展名和大小。零风险,也没有任何办公能力。

第二种是装 dsh-univer-office。会话内直接创建和编辑五类内容,实时浮窗预览,导出成 xlsx、docx、pptx 交付,改动的每一步都先落在隔离工作树里,合并前必须人工点头。代价是前文那些:97% 的构建产物信任、未声明的兼容组合、默认开启的遥测、无鉴权的回环网关和 11 月的授权倒计时。

第三种是在 Harness 外用你顺手的办公软件,改完把文件拖回会话。自由度最高、零插件依赖,但 Agent 摸不到文档,预览和校验全都靠人。

▲ 同一任务三种做法:内置卡片、插件全家桶、外部软件,各自的能力与代价。

来源与核验边界

四类事实,各有出处。

本文事实分四类。官方声明类:插件 README、package.json、docs、Harness release notes。源码直读类:注入结构、配置默认值、遥测模块、许可证令牌、审批闸门,均有文件行号证据。直接观测类:26 文件字节对比、71 包安装、模块加载、网关回环与 CORS 回显、卸载钩子对照实验,全部在 2026-09-14 于本机隔离环境实测。编辑推断类:对混淆产物的信任风险评价、到期后果的两种可能,已在上文明确标注。

未能覆盖的边界:完整 Harness 0.1.5-rc.2 运行时下的端到端兼容(需要真实 Harness 会话);WebSocket 跨源连接未实测;许可证到期后的具体表现不可读;npm provenance 缺位意味着没有密码学层面的构建来源证明。

想自己复核的读者,三条路最快。用 npm view dsh-univer-office 看 peerDependencies 是否还在钉 0.1.2-rc.1,一眼判断兼容声明更新了没有;在一次性目录里 pnpm add 之后解包看 lib/build-info.json,构建提交对不上 tag 就要警惕;本地拉起 gateway.cjs 后用 lsof 和一个带伪造 Origin 的 OPTIONS 请求,两分钟就能复现监听地址和 CORS 回显这两个关键行为。

最后的判断

现在先 inspect,别急着装。

给出 inspect-only 而不是 install,不是因为它做错了什么,而是因为它还没有把两份合同签完:对当前 0.1.5-rc.2 基线零兼容声明,商业授权 11 月 16 日零点到期且新证未至。对一个把 97% 不可读产物、一个无鉴权回环服务和默认开启的遥测一起带进你机器的插件,这两件事补齐之前,能力再猛也该等一等。

公平地补一句:如果它出生在一个动作更慢的生态,这份审计大概率是全绿的。安装路径零脚本、遥测三层可关、审批闸门强制、子进程环境白名单,这些都达到了我给 install 的及格线。拖住它的不是做得不好,而是 Harness 本身迭代太快,快到厂商的兼容声明和许可证轮换都追不上。

如果你已经在用它,做三件事:把插件配置里的 telemetry 设为 false,或者环境变量 DO_NOT_TRACK=1;确认你的使用场景能接受"回环无鉴权、无路径围栏"的网关模型,公共机器和多人共用的机器上别开;在日历上给 11 月 16 日前一周设一个提醒,盯 DreamNum 的下一个 release。

重新评估的触发条件很具体:官方发布声明支持 dsh 0.1.5 及以上的新版本,并且许可证轮换落地。到时候重跑一遍今天这套审计,如果两条都补上,这个插件值得一次 install。

相关学习资料