Skip to content

Releases

v1.13.4 — 跨 DSH 环境审计:修掉 7 处缺陷

Choose a tag to compare

@SiriusWJ SiriusWJ released this 18 Sep 17:13

跨 DSH 环境审计:修掉 7 处缺陷

针对「换一台 DSH 环境还能不能用」做了一轮审计,查出七处缺陷,全部修复。每一处都在真实部署上
复现并验证过,不是靠读代码推断的。

1. 设置页导航的小红点永远不刷新

locale.register(ns, dicts) 这个两参形式,会把第二个参数的键当作 locale id、按
dsh-client-locale 的 LOCALE_ID_PATTERN(/^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$/u)校验。
插件传的是合成的 "~nav…" 键,过不了校验 → register() 在 publish() 提升 revision 之前
就抛错,而外层的 catch 把异常吞了。壳程序只在 slot 版本号或 locale revision 变化时才重算导航行,
所以红点既不出现也不消失。现改用三参形式 + 唯一命名空间。

2. locateInstall() 的兜底是死代码

旧实现按「preset 路径以 /agent-presets/<id>/agent.cordis.yml 结尾」反推安装目录,但真实布局是:

  • 随包发布:<dsh>/node_modules/@deepseek-ai/dsh-agent-presets/presets/<id>/agent.cordis.yml
  • 用户自建:<DSH_HOME>/.agent-presets/<id>/agent.cordis.yml

两者都不含 /agent-presets/,后缀判定恒为 false。也就是说只要 argv[1] 不是 bin.js
(打包 / 嵌入式 / 被包装启动),更新、修复、重启、文档工具就全部没有兜底路径。

现改为解析 @deepseek-ai/dsh/package.json 再 realpath。锚点必须是 profile 目录而不是
import.meta.url
:link: 或 junction 安装时 Node 会把模块 URL 改写成包的真实路径,
那里根本没有 node_modules —— 这一版先写错过,是实机探针打出 locateInstall() = null 才发现。

3. buildRestartScript() 收 platform 参数却用宿主的 path.join

生产上只传 process.platform,所以一直看不出来。但在 Windows 上生成 POSIX 脚本时整条路径会被
翻成反斜杠(连开头的 / 都变成 \),脚本随即判 staging incomplete 并整个跳过交换。
现按参数选用 posix.join / win32.join。

4. 暂存包校验的 60 秒上限没生效

verifyStagedBinary() 把数字 60000 当第三个参数传,而该参数早已改成 limits 对象;
(60000).idleMs 是 undefined → 静默回落到 5 分钟 / 60 分钟默认值,暂存包半坏时这一步能挂满一小时。

5. 升级备份把凭据副本摊在默认可读权限下

快照会复制 .credentials.yaml 与整个 sessions/。备份目录用默认 umask 创建(POSIX 上通常
0755),同机其他账号可以直接读走凭据副本。备份根与新目录都收紧到 0700。

6. registry 被硬编码成 registry.npmjs.org

配了镜像站或企业内网 registry 的用户,插件去 npmjs 查版本、npm 自己却从镜像装包:轻则慢,
重则在墙内直接报「registry unreachable」,而更新本来能成功。现按
env → ./.npmrc → ~/.npmrc → 默认 解析,取到的值不是 http(s) URL 就继续往下走。

7. resolveProfileName() 在共享安装布局下判断错误

插件只落在共享的 profiles/node_modules(hoisted 或部分 pnpm 布局)时,逐 profile 探测全部为假,
函数会把 argv 里已确定的 profile 丢掉、退回硬编码的 'web',自更新命令就可能指向错误的 profile。

另外修掉的一处(不限于环境问题)

借 shell 启动时 Node 只把参数用空格拼接、不做转义,含空格的参数会被 cmd.exe 拆开。
--prefix 与 --logs-dir 都落在用户目录下,所以凡是用户名带空格的 Windows 环境
(C:\Users\John Smith\…)都会踩到:npm 报 Unknown command: "Smith\…"。已统一补上转义。

验证

  • 本地冒烟新增四组断言:平台路径拼接、registry 解析、locale bump 契约、locateInstall 兜底。
  • test/smoke.mjs:57 passed / 1 failed,那条失败是既有的 EPERM,在未改动的上游提交上同样失败。
  • POSIX 重启 + 交换流程用 Git Bash sh 真机执行:交换、回滚点、暂存清理、拉起新部署全部正确 ——
    上面第 3 条正是这一步暴露出来的。
  • npm pack:7 个文件、89.5 kB。

尚未处理(有意保留)

  • sameOrigin 放宽:会削弱 CSRF 防护,属于安全策略取舍。
  • POSIX 原地 npm -g 与 pnpm-profile 运行模式:需要新增运行模式与一套 POSIX 交换流程。
  • fetch 不走 HTTP(S)_PROXY:Node 全局 fetch 需要 undici ProxyAgent,涉及新增依赖。

v1.13.3 — README 加入当前卡片截图

Choose a tag to compare

@SiriusWJ SiriusWJ released this 18 Sep 12:21

仅文档 + 元数据改动(无代码变更)

README 加入截图

设置页两张卡片的截图(取自正在运行的 v1.13.2 构建):可见当前卡片形态——没有运行日志面板、
没有激活地址行,并且带「插件版本 1.13.2」一行与「上次重启:已交换并启动 v0.1.5-rc.2」。

  • 文件:docs/dsh-update-card-1.13.png(593×565);旧截图 docs/dsh-update-card.png
    (图中还是已移除的日志面板)已删除。
  • 文件名里带版本号是故意的:GitHub raw CDN 对同名文件有缓存,覆盖后一段时间仍会返回旧图。
  • 中英两份 README 均在卡片列表下方引用(绝对 raw URL,npm 页面同样能显示)。

顺带修掉的元数据

package.json 的 description 还写着 v1.12.3 已移除的「live scrolling log panel」,
已改为如实描述:实时进度 / 空闲超时(慢链路不会被杀)/ 暂存交换与可清理的回滚点 / 文档增量同步。

一次自我纠错

v1.13.3 第一次打 tag 时漏了改版本号(提交里仍是 1.13.2),发布工作流以
E403 You cannot publish over the previously published versions: 1.13.2 失败;
已删除该 tag、补齐版本号与 description 后重新打 tag 发布。

v1.13.2 — 让 npm 发布英文 README

Choose a tag to compare

@SiriusWJ SiriusWJ released this 18 Sep 12:11

仅文档改动(无代码变更)

修掉 npm 页面显示中文 README 的问题。

根因

npm 挑包 readme 的逻辑是 glob('{README,README.*}') 后取第一个「像 markdown」的命中项:

  • 该 glob 在根目录同时命中 README.md 与 README.zh.md,且 README.zh.md 排在前面;
  • 判定正则 /\.m?a?r?k?d?o?w?n?$/i 是非锚定的,结尾的 .zh.md 也能命中。

两者叠加 ⇒ 1.13.1 发布出去的包 readme 是中文那份(包内 README.md 本身是英文,只是没被选上)。

修法

中文文件改名 README_zh.md(下划线),不再匹配 {README,README.*};实测 glob 结果只剩
README.md,npm 现在发布英文 README。两个 README 顶部都留了 HTML 注释说明下划线不能改回点号。

仓库侧默认 README 与语言切换链接保持不变:README.md(英文) ⇄ README_zh.md(中文)。

v1.13.1 — 双语 README(英文默认)

Choose a tag to compare

@SiriusWJ SiriusWJ released this 18 Sep 12:06

仅文档改动(无代码变更)

运行时行为与 v1.13.0 完全一致,本版只是把文档改对、并让 npm 页面显示双语 README
(npm 页面只读包内的 README,仓库改了不会同步)。

README 双语化

  • README.md = 英文(默认),README.zh.md = 中文,顶部互相链接;
    两份章节完全对齐(安装 / 使用 / 超时策略 / 自更新闸门 / 文档 / 运行模式 / 升级安全网 / 路由 / 更新日志)。
  • README.zh.md 已加入包白名单,随 npm 包一起发布(此前只有 README.md)。

修正与代码脱节的段落

  • 删除过时的卡片截图:图里还是 v1.12.3 已移除的「运行日志滚动面板」。
  • 超时策略段不再提「静默期往面板写 heartbeat」与 /progress?since= 增量(均已不存在)。
  • 升级安全网第 6 条重写:重启后不再抓取/弹出新激活地址,浏览器自行重新鉴权。
  • 路由表补上 POST /dsh-updater-npm/cleanup-rollback;回滚点改为卡片按钮说明。
  • 新增安全网第 9 条:node 引导启动器——Windows 上 spawn('powershell.exe', …, { detached: true })
    等于 DETACHED_PROCESS,脚本一行不执行却返回 exit=0(静默假成功,曾导致交换永不发生)。

测试:node test/smoke.mjs 58 项全过。

v1.13.0 — 更新不再被超时误杀,重启真正生效

Choose a tag to compare

@SiriusWJ SiriusWJ released this 18 Sep 12:00

重点修复(自 1.10.0 起,本版一并发布)

1. 慢速网络下的 staged 安装不再被超时误杀

旧版对 npm 安装用「10 分钟一刀切」硬超时。实测冷缓存下 9 分 25 秒仍未跑完就被杀掉,界面只显示
暂存安装失败: npm。现在改为:

  • 空闲超时 + 总时长硬上限:默认「连续 10 分钟无任何活动」判卡死、总时长上限 60 分钟;
    可用 plugin-data/dsh-updater-npm/config.json 的 npmIdleMinutes / npmTimeoutMinutes 调整。
  • 三路活动探针取最大:子进程 stdout(--loglevel=http)、npm 的 --logs-dir 日志、
    暂存目录与 node_modules 的写入。实测 npm 的 debug 日志在 Windows 上可能整场只在退出时落盘,
    光看它不够;stdout 在非 TTY 下更是全程静默。
  • 超时/卡死的错误信息带真因与已运行时长,不再是含义不明的 : npm。

2. 「重启 DSH」不再静默假成功

Windows 上 spawn('powershell.exe', …, { detached: true }) 等于 DETACHED_PROCESS:脚本一行都不执行,
spawn 却返回 exit=0。后果是版本交换永远没发生,而 pending-swap.json 已被删掉,
222 MB 的暂存包随后被当成「遗留垃圾」清理——外部重启后仍是旧版本。
现在改为 detached 启动 node 引导,由引导以普通子进程方式运行平台脚本,并等到
restart.log 出现 node bootstrap started <token> 才交出暂存包;没启动就报明确错误、
保留标记与暂存包,并在卡片上显示「已暂存待交换」以便重试。

3. 界面做减法

  • 移除「运行日志」滚动面板:npm 完整输出改为操作结束时落盘到
    plugin-data/dsh-updater-npm/last-run.log(保留 [watchdog] / [exit] 等排查关键行),
    /progress 不再传日志。
  • 移除「新激活地址」整套逻辑:重启后浏览器自行重新鉴权,不再弹窗、不再空转轮询。
  • 新增**「清理回滚点」**:交换成功后卡片显示回滚点占用(实测 222 MB)并可一键释放。
    安全约束写死在宿主端——只删版本与当前运行版本不同的 <leaf>.old-<时间戳>,现役部署永不受牵连。
  • 卡片提示去解释化,只留状态与操作。

升级注意

  • Windows 上仍需点击「重启 DSH」完成暂存交换(现在会真的生效)。
  • 跨 0.1.5 的升级:会话格式升级到 V3 后不可降级读取,升级前会自动写入
    $DSH_HOME/upgrade-backups/ 快照(配置 + 预设 + 体积允许时的会话日志,保留最近 5 份)。
  • 可选配置($DSH_HOME/plugin-data/dsh-updater-npm/config.json):
    npmIdleMinutes(默认 10)、npmTimeoutMinutes(默认 60)、docsEnabled。

验证

node test/smoke.mjs 58 项全过,其中包含真跑启动器(脚本确实被执行)、
真跑交换脚本(假部署上真的换目录)、以及回滚点安全断言(绝不动现役部署与其它包)。