对 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 未启动,按计划每阶段出报告后停止等待确认。
53 lines
2.2 KiB
Markdown
53 lines
2.2 KiB
Markdown
# 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/活动/风控通知)
|