yinji/docs/AUDIT-2026-07-21.md
wenpai c9c85d5678 docs: 添加 2026-07-21 全面审计报告
记录 4 路并行审计发现、修复清单、刻意不改项及理由、安全结论、通用教训。
配套 cluster-recall #5785。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 10:45:08 +08:00

39 lines
3.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 全面审计报告 — 2026-07-21
> 方法: 4 路并行子代理审计(core / modules / wiring+UI / security),逐条交叉验证后修复。
> 提交: `fix/audit-hardening` (db73b34) | cluster-recall #5785
> 验证闭环: `node --check` 语法全检 + `yinji-cli` 冒烟/状态/批量/端到端 + UI 截图 + CSP 违规检查
## 已修复(8 类, 11 文件)
| 严重度 | 文件 | 缺陷 | 修复 |
|--------|------|------|------|
| HIGH | main.js:1071 | 键盘快捷键不判焦点: 坐标框 Backspace 删印章、方向键移印章、旋转框输不了负号、文本框 Ctrl+C/V/S 被劫持 | handler 开头加焦点守卫(INPUT/TEXTAREA/SELECT/contenteditable/isComposing → return) |
| HIGH | seal-library.js:19 | `getSavedSeals` 不校验数组,脏 localStorage 打死印章库读/写/渲染 | `Array.isArray` 校验 + `isValidSeal` 逐项过滤 + 格式异常自动重置 |
| MED | seal-library.js:260 | `loadAndApplySeal` 无 onerror + 成功 toast 在 onload 外同步弹 | toast 移入 onload、补 onerror、handler 先于 src 赋值 |
| MED | canvas-manager.js:113 / context-menu.js:42 | 右键不选中对象 → 菜单不出现或作用于旧对象 | 开 `fireRightClick`/`stopContextMenu`,菜单前 `findTarget` 命中置活动对象 |
| MED | responsive.js:70-139 | 触摸手势死代码(`_canvas` 从不赋值) + body 全子树 MutationObserver 常驻空转 | 移除死代码链(fabric 自带触摸兜底),保留响应式布局 |
| LOW | exporter/batch-processing/batch-operations/settings (×5) | blob URL `click()` 后立即 revoke → 部分浏览器下载失败 | 延迟 `setTimeout(revoke, 1000)` |
| LOW | batch-processing.js:253 | 下载名 `.replace('.pdf')` 首次匹配+区分大小写+未 sanitize | 统一 `generateSafeExportFilename` |
| MED(加固) | index.html:9 / security.js | CSP 缺 object/frame 限制、无转义助手 | CSP 加 `object-src 'none'; frame-ancestors 'none'`;补 `escapeHtml()` |
| INFO | tmp-global-patch-test-a.js | 根目录遗留测试残file | 删除 |
## 刻意不改(附理由)
- **exporter 恒 `degrees(0)`**: 旋转烘焙进位图(`getRotatedCroppedImage`) + 对象 `lockRotation:true`,正常使用不可能产生非零 angle,当前不可触发。
- **state.js `setState` 浅拷贝**: previousState 与集合/数组字段共享引用,但 batch-operations 已用 `new Set(...)` 防御拷贝规避。
- **seal id `Date.now()` 毫秒碰撞**: 手动操作极难触发。
- **pdf.js `isEvalSupported`**: CSP 无 `unsafe-eval`,eval 本就被拦。
- **SVG 白名单(image/svg+xml)**: 经确认非 XSS 向量(仅经 `<img>`/fabric image 静态渲染,不执行脚本),保留功能。
- **移除 `script-src 'unsafe-inline'`**: 需搬 index.html 内联 lucide 初始化脚本,有回归风险,建议后续单独一项处理。
## 安全结论
未发现任何可实际利用的 XSS/注入。防护可信,但可信来自"全程用 `textContent` 而非 `innerHTML` 渲染不可信数据(文件名/印章名/签名/toast/dialog)"的编码纪律,而非集中转义工具。所有 innerHTML 均为开发者硬编码静态模板。fabric 反序列化为自写、只吃应用自身内存对象,不吃外部不可信 JSON。
## 通用教训(见 cluster-recall #5785)
1. 全局键盘快捷键处理器必须判 `e.target` 是否输入控件 + `e.isComposing`
2. localStorage 读取 `JSON.parse` 后必须校验类型(`Array.isArray`)并逐项过滤脏数据。
3. blob URL 下载应延迟 revoke。
4. `image.onload` 必配 `onerror`,成功提示放 onload 内。