Files
dsh_shenxian/dsh-server-docs/04-调整方案/29-官方白名单插件来源.md
T

130 lines
11 KiB
Markdown
Raw Normal View 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 ✅);
3. 未用真实账号做登录测试(R4 ✅,走临时 session + 用后即删);
4. 导入的第三方包**仍过平台安全扫描**,未因「官方白名单」而放行(安全兜底不外包给上游)。
---
## 七、方案 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 的前提清单立项。