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

3.4 KiB
Raw Permalink Blame History

全面审计报告 — 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 内。