Files
danyang-match/docs/Phase-10-Completion-Report.md
T
WorkBuddy fd7195ecb0 fix(safety): Phase 10 复验 —— 修复图片上传魔数校验死代码(生产故障)/ 高风险词写入日志
对 Phase 10(Image & Chat Safety)执行 KEEP-VERIFY 复验:不采信历史 ✅,逐条核对
图片私有访问、类型/大小/魔数校验、审核与水印、风险识别/举报/违规处理、日志脱敏。
Gate 判定通过,发现并修复 2 处缺陷,并推翻旧报告 2 项安全声明。

D1(Critical,生产故障)图片上传的类型与魔数校验整条是死代码
  - PhotoService.uploadAndSave 用 `header = new byte[Math.min(12, stream.available())]`
    仅分配零值缓冲,从不调用 stream.read(...) → validateMagicNumber 对任何文件都收到
    12 个 0x00 → 恒返回 false → 恒抛"无效的图片文件头"。
  - 影响链(已确认线上触发):小程序 local-eligibility-supplement.ts 以
    photoType='local_eligibility' 调 POST /photos/upload → 本地资格佐证上传 100% 失败。
  - 次生问题:isAllowedMimeType(image-only 白名单)在 local_eligibility 分支之后
    无条件执行,即使魔数修好,application/pdf 仍被挡,而 FeatureFlagConfig
    的 localEvidenceAllowedTypes 明确允许 pdf;另有未 mark 的 stream.reset() 无效调用。
  - 测试为何没发现:PhotoServiceTest 只对 validateMagicNumber 传手写字节做单测,
    从不经过 uploadAndSave,该路径缺陷完全无覆盖。
  - 修复:改用 try(InputStream){ header = s.readNBytes(12); } 真正读取;
    MIME 校验按 photoType 分流(local_eligibility 走自己的白名单含 pdf);
    魔数校验仅对图片执行、PDF 跳过;移除死 reset();上传用全新流与校验流隔离;
    generateFileKey 重载按 Content-Type 推断扩展名,PDF 不再拿到误导性 .jpg key。

D2(Low)高风险词写入普通日志
  - ChatSafetyService.scanContent 把命中的高风险词原样写入 log.warn,
    与同文件 CONTACT 分支"只记 pattern 不记内容"矛盾,且违反本阶段 Gate。
  - 修复:改为 log.warn("Chat content matched HIGH_RISK keyword (blocked)")。

推翻的旧声明(docs/Phase-10-Completion-Report.md)
  - 「伪装恶意图片被拒(Magic Number校验)」不成立:该校验运行时恒失败,
    连合法图片也一律被拒(fail-closed 到不可用),安全属性并非由它提供。
  - 「EXIF清理预留(PhotoService.stripExif占位)」不存在:全仓库无该符号。
    且 getUploadToken 客户端直传路径服务端无法介入清理 EXIF。
  - 已在旧报告顶部加状态注记,指向本次复验报告。

测试
  - 新增 PhotoUploadValidationTest(6 用例),用 MockMultipartFile 真正走一遍
    uploadAndSave:合法 JPEG/PNG 通过且确实调用 putObject、伪造 Content-Type 被魔数挡、
    PDF 佐证不被 image-only 挡且 key 以 .pdf 结尾、非法 MIME 被拒、超大被拒。
  - 负向验证:临时把魔数守卫改为 if(false && ...) → 恰好 spoofedContentTypeRejectedByMagic
    一个用例变红,其余 5 个仍绿;随后还原并 grep 确认无 NEGATIVE-CHECK-TEMP 残留。
  - 全量回归(离线容器 danyang-match-backend-test,mvn -o test):
    334 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS(基线 328,+6)。

登记 gap(不自动实现,待产品确认)
  - 图片水印:全仓库无实现;主计划未列硬需求,仅"开发执行计划修订版"提"高级动态水印"。
  - EXIF 清理:旧声明不存在;客户端直传路径服务端无法介入,需改为接收侧重编码。
  - 直传凭证路径(getUploadToken)无服务端内容校验且不落 DB 记录 → 后续 getSignedUrl
    归属校验会查不到 UserPhoto 行,属契约层缺口,超出本阶段安全 Gate 范围。
  - 违规自动升级:ChatSafetyService 注释明确属 Phase 17 范围,当前 high 仅拦截不落库。

文档:新增 Phase-10-V11-Recovery-Completion-Report-2026-09-16.md;
旧报告加状态注记;主计划 Phase 10 复选框置 ✅;STATUS / JIRA_BACKLOG 同步(334 tests)。

Phase 11 未启动,按计划每阶段出报告后停止等待确认。
2026-09-16 21:00:52 +08:00

53 lines
2.2 KiB
Markdown
Raw 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.
# Phase 10 Completion Report
> ⚠️ **状态注记(2026-09-16)**:本报告已被 `Phase-10-V11-Recovery-Completion-Report-2026-09-16.md` 复验并部分推翻。
> 复验发现 **D1(Critical)**:`PhotoService.uploadAndSave` 的魔数校验因 `new byte[n]` 从不读取流内容而**恒失败**,
> 本地资格佐证上传 100% 失败;本文档 "Security Review" 中「伪装恶意图片被拒(Magic Number校验)」一项**当时并不成立**。
> 另:「EXIF清理预留(PhotoService.stripExif占位)」全仓库无对应符号,属未落地声明。
> 本文档其余进度声明仅作历史记录,请以复验报告为准。
## Status
✅ Completed
## Implemented
### P10-T01 ~ P10-T05: 图片安全
- [x] `MessageRiskService` — 关键词风险检测(HIGH/MEDIUM两级)
- [x] Magic Number 校验框架(PhotoService已有JPEG/PNG/GIF/WebP)
- [x] MIME白名单 + 文件大小限制(10MB)
- [x] EXIF清理预留(PhotoService.stripExif占位)
- [x] Signed URL 接口骨架
### P10-T06 ~ P10-T09: 消息风控
- [x] 高风险关键词列表(投资/转账/借钱/博彩等20+关键词)
- [x] 中风险关键词聚合检测(加微信/电话/地址等)
- [x] 风险通知生成(warning/high两级提示文案)
- [x] 限流框架(Redis限流预留)
- [x] 重复消息检测框架
### P10-T10 ~ P10-T11: Block实时失效
- [x] ChatController 集成风险检测
- [x] WebSocket推送占位(Phase 09 Handler已就绪)
### Miniapp
- [x] 聊天页增加安全提示(固定顶部)
- [x] 图片消息上传UI骨架
- [x] 风险关键词提示UI
## Security Review
- [x] 伪装恶意图片被拒(Magic Number校验)
- [x] 图片Private Bucket(Signed URL短时有效)
- [x] 普通快速聊天不误伤(仅2+高风险词或3+中风险词触发)
- [x] 风险关键词只提示/记录,不自动永久Ban
- [x] Block无需刷新即可阻止下一条消息(canSend字段)
## Tests
- `MessageRiskServiceTest`: 7 个用例
## Known Issues
- 图片上传实际对接需在 Phase 10 后续完善(Signed URL生成逻辑)
- Redis 限流需在 Phase 10 后续接入
## Next Phase
Phase 11: 系统通知(NotificationService + 审核/Match/活动/风控通知)