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 一律写「远程服务器」。
11 KiB
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 已删 |
六、已知限制与后续
- 方案 A(源码安装支持)→ 记入 P2 待办:让
github:owner/repo的 1616 条也能导入,需business_plugins加install_spec列(tgz_path改可空)+ 启用路径分支pnpm add <spec>。代价:安装时以 root 跑第三方构建脚本(慢、需工具链、易失败、供应链风险上升)。当前决策:不做(详见 03 路线图)。 - 安全扫描疑似误报:
FuRongJun-1999/dsh-memory(6988 下载,热门插件)被判 P0 阻断,命中的是package/docs/cordis-patch-profile-web.example.yml—— 从文件名看是文档示例文件而非真实读取行为,疑似规则过宽。已通过「失败原因页面回显」让 admin 可判断;规则本身是否收窄另议。 - 前端未做浏览器人工验收:因红线 R4 禁止用真实 admin 账号登录(last-wins 会顶掉用户会话),本轮验证走 API + HTML/JS 静态校验;视觉与交互验收留给用户。
七、红线遵守
- 不碰官方 dsh 主程序(R2 ✅);2. 不升级 dsh(R1 ✅);
- 未用真实账号做登录测试(R4 ✅,走临时 session + 用后即删);
- 导入的第三方包仍过平台安全扫描,未因「官方白名单」而放行(安全兜底不外包给上游)。
七、方案 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 替代路径(已可用,覆盖"源码类"插件的真实需求)
- admin 本地构建后上传 tgz:现有「插件管理 → 上传 tgz」通道完整可用(走
stageTgzArchive结构校验 + P0/P1 安全扫描,档案 19 §C6),源码类插件由 admin 在受控环境自行构建 → 上传即可进候选池。这是推荐路径。 - npm 预构建路径已覆盖 52.6%(1792/3408,且下载量 TOP10 全部在内)→ 热门插件不受影响。
- 若将来确实要批量支持源码类:必须先满足下列全部前提(作为升级条件记录):
- 构建发生在一次性沙箱内(bwrap + 无网络 + 只读源码挂载 + 无凭据 + 时限 + 内存/CPU 限额);
npm ci --ignore-scripts(禁止安装脚本),只允许显式的npm run build白名单脚本;- 产物仍走
stageTgzArchive全量扫描后才入池; - 仅 admin 触发 + 二次确认 +
audit_log审计 + 构建日志留存; - 失败一律拒绝导入(不得降级为"直接装源码")。
当前状态:关闭(不做)。若用户将来要求开启,按 §7.2.3 的前提清单立项。