Files
dsh_shenxian/dsh-server-docs/04-调整方案/29-官方白名单插件来源.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

11 KiB
Raw Blame History

29 · 官方白名单插件来源(方案 C · 方案 B 口径)

  • 日期:2026-09-11(初版)/2026-09-11(修订:数据源与导入口径)
  • 触发:用户问「dsh 官方白名单推荐插件能否作为插件来源接入门户插件管理」→ 选定方案 C
  • 状态:✅ 已实施并端到端验证

TL;DR|结论:dsh 官方白名单插件接入门户插件管理(方案 C):官方目录 3408 款,npm 预构建路径覆盖 52.6%(下载 TOP10 全覆盖)。 关键:源码安装(github:)评估结论 = 不做:会让第三方构建脚本以 root 在平台机执行(供应链风险),与红线冲突;替代路径 = admin 本地构建后上传 tgz。 状态:✅ 已实施并端到端验证(§七为关闭结论)

一、需求与背景

需求:在 admin 门户「插件管理」(#/plugins)增加一个官方可信来源——直接浏览/挑选 dsh 官方精选清单里的插件,选中后导入功能插件候选池。

背景:dsh-market 是 dsh 实例插件(UI 在实例设置页,一键装到当前实例)。若把它装给普通用户,等于用户可自助装任意社区插件,绕过「admin 投放候选池 → 用户按需启用」三层管控(档案 16),安全面失控。故不装 dsh-market,改为把它的数据源接入门户。

二、调研结论(修订依据)

初版按「git clone 仓库 + 解析 3431 个 *.yml + 取 tarball 字段」实现,端到端验证时发现两个致命问题,遂修订:

结论 证据
① 初版覆盖率仅 6% 3431 个 yml 中只有 211 条声明 tarball(上游它是可选字段,仅 6.1%)→ 一键导入功能基本不可用
② 有更好的规范数据源 官方把完整目录发布为 https://awesome-dsh-plugin.com/plugins.json(3.1 MB,3408 条,字段含 name/owner/category/description{zh,en}/npm/version/stars/downloads/install/added),并镜像为 npm 包 dsh-plugin-catalog。一次 HTTP 拿全,比 git clone + yaml 解析更快、字段更全
③ 安装形态分布 install 字段 3408/3408 全覆盖:npm 包名 1659 + github:owner/repo 源码 1616 + 多包 monorepo 等其他 133
④ 热门插件全是 npm 包 下载量 TOP10 全部有 npm(dshmarket 383k、dsh-better-sidebar 273k、@linxin666/dsh-remote-web-ui 247k…)
⑤ npm 官方 tarball 可直接复用现有链路 实测把 registry tarball 喂给现有 stageTgzArchive:通过(package/ 包装层已被 findPackageJson 兼容)→ 无需改 DB schema、无需跑构建脚本
⑥ 上游对无 tarball 条目的官方回退 上游 scripts/probe-tarballs.mjs 注释明确:「without the field a user falls back to the github:owner/repo install command」

上游免责声明(记录在案):收录 ≠ 安全审计。上游 README 明确「Installing a plugin runs third-party code on your machine with your own permissions… Being on this list is not a security review」。→ 我们的安全兜底完全依赖平台侧 stageTgzArchive 扫描。

三、设计(修订后 = 方案 B)

导入口径 = 仅预构建(平台侧绝不执行第三方构建脚本):

分支 判定 取包方式 条数
1 有 npm 包名 registry.npmjs.org/<name> → dist-tags.latest → dist.tarball 1659
2 无 npm、有 tarball 直接用 release 资产直链 133
3 两者皆无 不支持,返回明确原因(含官方 install 命令),前端标「需源码构建」 1616

可导入 = 1792/3408 = 52.6%,官方热门插件全部覆盖。其余可由 admin 自行构建后走「上传 / 替换」投放(同一条校验链路)。

后端(src/web/routes/whitelist.ts,重写):

接口 作用
GET /api/plugins/whitelist?q=&category=&onlyImportable=1 返回 {total, importable, count, stale, categories, plugins[≤300]};条目含 id(=owner/name)/importKind/npm/version/stars/downloads/install
POST /api/plugins/whitelist/refresh 强制重拉(绕过 TTL)
POST /api/plugins/whitelist/import {names[]} 逐个解析 tarball → 下载 → 复用 stageTgzArchive 校验 → 同名替换策略 → 入候选池;逐条返回 ok/失败原因(单条失败不中断整批)
  • 缓存:<dataRoot>/whitelist-cache/index.json,TTL 6h;带 version 结构版本守卫(改字段必须 +1,否则 TTL 内旧格式缓存会被复用 → 过滤时抛异常)。
  • 降级:官方站不可达但有旧缓存 → 用旧缓存并返回 stale: true(前端提示「官方站暂不可达,使用本地缓存」);无缓存才报错。
  • 排序:按 downloads 降序 → 未发 npm 的条目 downloads 为 0 自然沉底,默认前 300 条即最热门。
  • 上限:单次导入 ≤ 20 个;单包 ≤ 150 MB。

前端(web/portal.html → #/plugins):

  • 搜索框(插件名/描述/npm 包名/owner)+ 分类下拉 + 「只看可导入」开关(默认开) + 搜索 / 刷新清单;
  • 表格 7 列:勾选 | 插件(名称 + npm 包名@版本副行)| 分类 | 导入方式徽章(npm 预构建 / release 资产 / 需源码构建)| 下载量 | ★ | 说明;
  • 「需源码构建」行置灰 + 勾选框禁用;
  • 统计行:清单 N 条 | 可导入 M 条 | 匹配 K 条 | 显示 J 条;
  • 导入结果:失败项在页面内展开列出原因(而非只打 console),P0 阻断原因原样回显,交 admin 判断;
  • 原有「上传 / 替换」卡片保留在下方。

四、改动文件

文件 改动
src/web/routes/whitelist.ts 重写(数据源换 plugins.json;缓存版本守卫;仅预构建导入口径;stale 降级;逐条结果)
web/portal.html #/plugins 区块重写(导入方式徽章、只看可导入、失败原因回显)— 31446 B → 33203 B
src/web/routes/business-plugins.ts stageTgzArchive 改为 export(复用)
src/web/server.ts import + register(whitelistRoutes)

五、验证(2026-09-11,端到端)

方法:DB 直插临时 admin session(user_agent=poc-curl,用完即删),curl -sk --resolve alotbuy.com:443:127.0.0.1 走真实域名 + TLS。

用例 结果
GET whitelist?onlyImportable=1 200 | total=3408 importable=1792 count=1792 stale=false 23 分类;前 300 条 100% npm
GET whitelist?q=dsh-status-rotator 200,精确命中 01Virex/dsh-status-rotator(npm 名亦可搜)
GET whitelist?q=dsh-quality-review 200,importKind=source + install 原样返回
POST import 正常 npm ✅ OK,dsh-status-rotator @ 0.17.2,落盘 115257 B
POST import P0 命中 ✅ FAIL,原因回显:安全检测未通过(P0 阻断):读取实例会话密钥(package/docs/cordis-patch-profile-web.example.yml)
POST import 需源码构建 ✅ FAIL,原因:该插件需从源码构建安装(dsh plugin --profile web add github:CAI-MH/dsh-quality-review),暂不支持一键导入
POST import 不存在 ✅ FAIL,不在官方清单:nobody/nothing
DB / 审计 / 落盘 business_plugins 1 行 + tgz 文件 1 个 + audit_log.import_whitelist_plugin 1 条;stage 残留 0
前端语法 portal.html 内联 JS node --check 通过;线上实拉 200 / 35894 B
缓存守卫 部署前旧 v1 缓存被正确判为未命中 → 重拉并写入 {"version":2,…}
清理 测试插件已删(候选池归零 / tgz 移除)+ 临时 session 已删

六、已知限制与后续

  1. 方案 A(源码安装支持)→ 记入 P2 待办:让 github:owner/repo 的 1616 条也能导入,需 business_plugins 加 install_spec 列(tgz_path 改可空)+ 启用路径分支 pnpm add <spec>。代价:安装时以 root 跑第三方构建脚本(慢、需工具链、易失败、供应链风险上升)。当前决策:不做(详见 03 路线图)。
  2. 安全扫描疑似误报:FuRongJun-1999/dsh-memory(6988 下载,热门插件)被判 P0 阻断,命中的是 package/docs/cordis-patch-profile-web.example.yml —— 从文件名看是文档示例文件而非真实读取行为,疑似规则过宽。已通过「失败原因页面回显」让 admin 可判断;规则本身是否收窄另议。
  3. 前端未做浏览器人工验收:因红线 R4 禁止用真实 admin 账号登录(last-wins 会顶掉用户会话),本轮验证走 API + HTML/JS 静态校验;视觉与交互验收留给用户。

七、红线遵守

  1. 不碰官方 dsh 主程序(R2 ✅);2. 不升级 dsh(R1 ✅);
  2. 未用真实账号做登录测试(R4 ✅,走临时 session + 用后即删);
  3. 导入的第三方包仍过平台安全扫描,未因「官方白名单」而放行(安全兜底不外包给上游)。

七、方案 A(源码安装支持)评估结论:不做(2026-09-11 定稿)

档案 29 §六.1 把"让 github:owner/repo 的 1616 条也能导入"记为 P2。经评估,结论为不做,理由与替代路径如下。

7.1 为什么不做的代价不可接受

维度 问题
供应链 pnpm add github:owner/repo 会执行仓库自带的构建/安装脚本(prepare/postinstall)→ 等于让第三方代码以 root 在我们的平台机上执行(不是在用户沙箱里)。上游 README 已声明"收录 ≠ 安全审计",我们无法对 1616 个仓库做人工审计
可复现性 需要完整 Node 工具链、网络、各仓库自定义构建(有的还要 Rust/Go/Python)→ 失败率高、排障成本高
性能/资源 每次导入都要拉仓库 + 装依赖 + 构建(分钟级),且构建产物无法复用(每版本重来)
管控口径 与"平台不执行第三方构建脚本"这一既定红线冲突(档案 29 §二⑤ 之所以选预构建 npm tarball 正因如此)

7.2 替代路径(已可用,覆盖"源码类"插件的真实需求)

  1. admin 本地构建后上传 tgz:现有「插件管理 → 上传 tgz」通道完整可用(走 stageTgzArchive 结构校验 + P0/P1 安全扫描,档案 19 §C6),源码类插件由 admin 在受控环境自行构建 → 上传即可进候选池。这是推荐路径。
  2. npm 预构建路径已覆盖 52.6%(1792/3408,且下载量 TOP10 全部在内)→ 热门插件不受影响。
  3. 若将来确实要批量支持源码类:必须先满足下列全部前提(作为升级条件记录):
    • 构建发生在一次性沙箱内(bwrap + 无网络 + 只读源码挂载 + 无凭据 + 时限 + 内存/CPU 限额);
    • npm ci --ignore-scripts(禁止安装脚本),只允许显式的 npm run build 白名单脚本;
    • 产物仍走 stageTgzArchive 全量扫描后才入池;
    • 仅 admin 触发 + 二次确认 + audit_log 审计 + 构建日志留存;
    • 失败一律拒绝导入(不得降级为"直接装源码")。

当前状态:关闭(不做)。若用户将来要求开启,按 §7.2.3 的前提清单立项。