Repository navigation
Releases
v1.13.4 — 跨 DSH 环境审计:修掉 7 处缺陷
跨 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 加入当前卡片截图
仅文档 + 元数据改动(无代码变更)
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
仅文档改动(无代码变更)
修掉 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(英文默认)
仅文档改动(无代码变更)
运行时行为与 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 — 更新不再被超时误杀,重启真正生效
重点修复(自 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 项全过,其中包含真跑启动器(脚本确实被执行)、
真跑交换脚本(假部署上真的换目录)、以及回滚点安全断言(绝不动现役部署与其它包)。