ZF sun
8cfc144ba9
fix(frontend): 端用户附件走 inline base64 路径, 避开 MediaGuard SSRF 闸门
PR-FRONTEND-MEDIA-INLINE-BASE64 (2026-09-16)
PR-WB-MP-INBOUND-MEDIA 配套
症状: 用户在前端上传图片成功后 (OSS URL 已返回), AI Agent 仍回复
"我这边并没有收到或看到任何图片附件".
根因链路:
1. backend Upload.php::buildPublicUrl 用 request()->domain() 拼 URL
(LAN 部署返回 http://192.168.x.x:8050/upload/..., 公网部署返回公网域名)
2. 前端把 upload 响应直接透给 AI chat attachments[] 字段
3. backend MediaGuard::urlAllowed 把 192.168.x.x 当内网 IP
(FILTER_FLAG_NO_PRIV_RANGE) 拒绝 → attachments 整批丢
4. 静默失败: MediaGuard 在 '白名单过滤后空集' 时返回 ok=true + media=[]
→ Kefu.php 不 return error, 业务继续走
5. WB 收到 0 个 media block → Agent 看不到附件
修复:
- media-upload.js 新增 fileToBase64(filePath, opts) helper
- H5: fetch(blobUrl) → blob → FileReader.readAsDataURL → strip data: 前缀
- MP-WEIXIN/APP-PLUS: uni.getFileSystemManager().readFileSync(base64)
- SIZE_LIMIT_INLINE_BASE64 = 5MB, 与后端 INLINE_MEDIA_LIMIT 对齐
- 超限 / 失败返回 null, 调用方退回 URL 路径
- ai-chat-message.vue sendMessage 改为 await Promise.all 模式:
- 对每个 pending item 调 fileToBase64(tempPath) 拿 base64
- 把 data + encoding='base64' 注入 attachment
- enrichedPendingAttachments 同时用于 readyAttachments + userMessage.attachments
- 复用同一份 base64, 不重复读 file
后端接收 data 字段后整条链路已对齐:
- MessageMedia::normalizeInput 直接透传 data 字段
- MediaGuard::check data 非空时无条件放行, 不参与 URL 校验
- WbAgentOsClient::buildMediaBlocks (A 路) 路径 1 优先 data + image/audio
- WbAcpClient::buildMediaBlocks (B 路) data 非空也走 inline base64
- inline_media_sanity.php 39 个断言全部覆盖 (test 3.1, 3.2, 4.1, 4.2, 7.6)
测试:
- E2E test_inline_base64.py 10/10 通过
- mock 上传返回 LAN URL 模拟 MediaGuard 拦截场景
- 抓 /api/kefu/chat POST body, 验证 attachments[].data 是有效 PNG base64
- magic bytes 89504E47 验证通过
- 既有 test_ai_chat_upload.py 20/20 通过 (无回归)
边界:
- >5MB 图片 fileToBase64 返回 null, 退回 URL 路径 (buildMediaBlocks 走
路径 2 fetchInline / 路径 3 resource_link); LAN 部署下仍可能被 MediaGuard
拦截, 但 5MB 以下的图片(主流微信/支付宝截图等)不受影响
- H5/MINIProgram/APP 三端都加了 #ifdef 编译条件
2026-09-16 17:55:49 +08:00
..
2026-06-29 15:29:24 +08:00
2026-09-16 16:38:34 +08:00
2026-07-03 11:23:05 +08:00
2026-07-13 10:22:45 +08:00
2026-09-16 16:38:34 +08:00
2026-08-14 09:36:04 +08:00
2026-07-30 11:43:25 +08:00
2025-10-30 18:23:35 +08:00
2025-10-27 15:55:29 +08:00
2026-07-01 16:50:01 +08:00
2026-07-07 14:56:13 +08:00
2026-05-25 14:19:52 +08:00
2026-07-18 11:58:09 +08:00
2025-10-27 15:55:29 +08:00
2026-07-18 10:55:39 +08:00
2026-06-30 15:29:45 +08:00
2026-07-20 08:44:15 +08:00
2026-05-08 16:09:19 +08:00
2026-07-18 16:18:35 +08:00
2026-08-03 16:24:18 +08:00
2026-04-24 15:46:42 +08:00
2026-06-30 14:04:52 +08:00
2026-07-02 16:33:22 +08:00
2026-07-23 09:46:16 +08:00
2026-06-26 13:49:26 +08:00
2026-09-16 17:55:49 +08:00
2026-06-11 14:55:32 +08:00
2026-07-15 09:05:50 +08:00
2026-06-26 17:32:35 +08:00
2025-10-27 15:55:29 +08:00
2026-06-26 17:32:35 +08:00
2026-07-24 15:10:49 +08:00
2026-07-15 17:55:27 +08:00
2025-10-27 17:43:43 +08:00
2026-06-26 13:49:26 +08:00
2026-05-08 16:09:19 +08:00
2026-07-24 16:11:41 +08:00
2025-10-27 15:55:29 +08:00
2026-08-15 10:37:55 +08:00
2026-07-07 14:56:13 +08:00
2026-09-12 11:03:34 +08:00
2026-07-18 18:19:38 +08:00
2026-07-16 11:47:29 +08:00
2026-08-29 16:04:20 +08:00
2026-08-12 15:12:00 +08:00
2025-10-27 15:55:29 +08:00
2026-08-15 09:46:12 +08:00
2026-05-08 16:09:19 +08:00
2026-06-26 13:49:26 +08:00