# dsh-tu4-inline-images 1.1.0 四阶段安全自审(M8) - 日期:2026-08-29 - 审计框架:复用社区版审计的 4 阶段策略(`dsh-plugin-tasks/dsh-inline-images/PluginTaskOverview.md` §4) - 对象:自研自审(作者 = 用户本人,可联系;非第三方插件,阶段 0 多数项 N/A) - 结论:**✅ 通过——零高危;中低危全部有缓解或文档化** ## 阶段 0:元信息(自研适配) | 项 | 结论 | |---|---| | 来源 | 本地源码仓库 `dsh-plugin-dev/dsh-tu4-inline-images/`,全量自研,非 fork(算法层面移植社区版扫描器,已对照审计) | | 许可证 | MIT | | 分发 | 本地 `file:` tgz 安装(不上 npm,无注册表风险) | | 作者可联系 | 用户本人 | ## 阶段 1:清单与构建 | 项 | 证据 | 结论 | |---|---|---| | 1.1 生命周期脚本 | package.json 仅 `build/gate/check`;gate.mjs 第 4 条门禁强制拒绝 `preinstall/install/postinstall/prepare/postpublish`(CI 级强制,非口头约定) | ✅ | | 1.2 dependencies | **空**(零运行时依赖) | ✅ | | 1.3 devDependencies | 仅 `esbuild`(构建期,不随 tgz 分发) | ✅ | | 1.4 build.mjs | 两次 esbuild 本地打包(host: node ESM;client: browser CJS + banner/footer 契约注入);无网络调用、无系统目录写入、无动态下载 | ✅ | | 1.5 files 最小化 | tgz 共 9 文件:`lib/`(双 bundle + sourcemap)、`cordis.patch.yml`、`dsh.plugin.json`、README、LICENSE;无源码树、无测试、无 node_modules | ✅(sourcemap 含本地源码路径,无凭证;见风险 R6) | | 1.6 CI/发布 | N/A(本地 tgz,无 npm publish) | N/A | ## 阶段 2:源码安全审计 | 项 | 证据 | 结论 | |---|---|---| | 2.1 网络端点 | gate.mjs 第 1 条:可执行代码行禁 URL 字面量(构建门禁强制);host URL 前缀运行时取 `webServer.host/port` 拼接,`host==='0.0.0.0'` 时强制回环;client 仅同源 `fetch(STATE_PATH)`(相对路径)与同源 ``(origin 运行时取);bundle 结构单测再断言 lib/client.js 零 URL 字面量。无硬编码 IP/域名 | ✅ | | 2.2 文件访问 | `ctx.fs` 为无沙箱 LocalFileSystem——**唯一执行点 = 图片路由 handler**,校验链:token → 扩展名白名单(9 种)→ 多根 realpath 包含校验(`assertSafePath`:净化 → `fs.resolve` 全 realpath → `fs.contains(root, target)` 任一根 → `stat` 常规文件)→ `readBytes` 限流(对齐 `attachments.imageLimits.maxImageBytes`,缺失时 20MB)。符号链接逃逸:targetKey 即完整 realpath,界外链接 realpath 已落界外,contains 必 false。`fs.resolve` 的 cwd 明确不作为 containment boundary。白名单根 = 默认[workspace, 粘贴根]+ 用户自管 settings 追加根,失败根静默跳过。客户端 half **零 fs** | ✅ | | 2.3 token | `crypto.randomBytes(32).toString('hex')`(256 bit);进程级单 token;gate 第 2 条禁 Math.random;响应 `Cache-Control: private, no-store` + `X-Content-Type-Options: nosniff` | ✅(见风险 R1/R2) | | 2.4 路由注册 | 两条 exact 路径(不可前缀模糊);image handler 全输入校验(token/p/扩展名/白名单/尺寸),Content-Type 来自固定映射表(非文件名推导,防嗅探);SVG 附 `Content-Security-Policy: sandbox`;state 路由只返回 token。错误响应 = 固定短文案(400/404/413),**绝不回显路径/token** | ✅ | | 2.5 流拦截 | `llm/stream` waterfall:`options.purpose` 存在(compaction/session-title)→ 零介入;delta 级仅刷新本插件路由 URL;block-end 级先廉价探测(无图片扩展名且无路由 URL → 零成本透传),命中才扫描,每个候选**再过一遍 path-guard**(改写纯展示,handler 仍是唯一执行点);任何异常 → 原样透传,绝不中断流。提示注入面:LLM 输出中的路径只能变成"同一白名单内图片的同源 URL",不产生新读取能力 | ✅ | | 2.6 凭证服务 | 未使用 `ctx.credentials` | N/A | | 2.7 客户端代码 | 全部 React.createElement / document.createElement 构建,**无 innerHTML 拼用户数据**;无 eval/Function;无全局事件监听(MutationObserver 经 ctx.effect cleanup;img.onerror 随节点生命周期);无 localStorage/cookie;网络仅同源(/state + 图片路由);非回环浏览器设置自动只读降级 | ✅ | | 2.8 错误处理 | host:handler 全分支固定短文案;settings 校验失败 → 拒绝写入保留上一有效值,日志仅 host 侧 console(不上 wire);client:fetch 失败静默降级(null → 不渲染/重试),不抛不弹 | ✅ | ## 阶段 3:运行时与供应链 | 项 | 证据 | 结论 | |---|---|---| | 3.1 inject 最小化 | host 仅 `['llm','webServer','fs']`(web profile 必有三项;attachments 机会性 `ctx.get()` 不声明,防 cordis PENDING);client `inject: []`,全部可选服务走作用域 `ctx.inject`(缺失即整段不执行) | ✅ | | 3.2 cordis.patch.yml | 仅一条 `insert`(id/name 挂载 host 插件);无 `remove/replace/disable` | ✅ | | 3.3 清单一致性 | 权威清单 = package.json(`dsh.bundle.patch` + `dsh.client` + exports,DSH 实际读取);`dsh.plugin.json` 系社区兼容件(DSH 源码零引用,已核实),name/version 与 package.json 同步 | ✅ | | 3.4 依赖图 | 运行时零依赖 → 无传递依赖供应链面;构建期 esbuild 由 profile 的 pnpm 锁解析(不随插件分发) | ✅ | | 3.5 更新机制 | 无自动更新/远程加载;更新 = 手动 `dsh plugin --profile web remove/add` 换 tgz | ✅ | ## 阶段 4:决策 **✅ 通过(自审自验,零高危)。** ### 文档化的残留风险(均评估为可接受) | # | 风险 | 评估 | |---|---|---| | R1 | token 比较用 `===`(非常量时间) | 仅回环 + 同源上下文,时序侧信道不可达;接受 | | R2 | token 暴露于 DOM(``)与 `/state` 路由 | 设计使然(客户端需构造 src);与 modlens paste 路由同信任模型;token 仍是唯一门禁;`no-store`;接受 | | R3 | ② 可能为 LLM 回显的白名单外路径插入 img(随后 404) | onerror 自动还原路径文字,自愈;最坏 = 图片不出现、文字仍在;接受 | | R4 | 路径被拆成多文本节点时 ② 不处理 | 路径文字保留(不破坏显示);③ 的 block 级改写不受 DOM 拆分影响;记录为边界 | | R5 | 设置写入 | 仅回环可写(非回环 mode:'memory' 只读);严格 schema 拒绝非法值 | | R6 | tgz 含 sourcemap(本地源码路径) | 无凭证/无秘密;与 modlens 同等暴露级别;接受 | | R7 | `--host 0.0.0.0` 部署时路由对局域网暴露 | README 已知边界明示;token 仍是门禁;建议仅回环部署 |