Files
trade-message-center/docs/onetalk-media-message-format-reverification.md

13 KiB
Raw Permalink Blame History

OneTalk 媒体消息格式二次运行态复核方案

日期:2026-09-02
性质:方案讨论与只读运行态复核清单,不是 Trellis task,不包含实现承诺
目标:通过真实运行态证据复核两份调查文档的共同结论、冲突结论和仍不明确的内容

1. 复核对象

本次复核以以下两份文档为输入:

  1. /Users/ybf/code/trade-message-center-worktree/docs/onetalk-media-message-format-investigation.md
  2. /Users/ybf/code/trade-message-center-worktree/docs/onetalk-message-content-formats.md

复核时必须区分三类结论:

  • 运行态事实:能够从真实 WebSocket、SDK 返回、页面桥、IndexedDB、Bright 或 Mind 中直接观察。
  • 代码事实:能够从当前代码路径确认,但尚未完成真实端到端验证。
  • 方案决策:例如 raw content 是否保留、normalized content 放在哪一层。这类结论不能伪装成运行态事实。

2. 安全边界与停止条件

2.1 默认只读

默认允许:

  • 读取当前 Chromium/CDP 目标、版本和已加载 OneTalk SDK 方法。
  • 调用 getConversationListByPagination
  • 调用 fetchMessagesWithoutUpdateToRead
  • 观察历史 WebSocket 响应和同一次调用的 SDK 归一化结果。
  • 读取扩展 IndexedDB、Service Worker 状态和 Bright 的只读查询结果。
  • 在内存中执行 Base64、UTF-8 和 JSON 解码,并只输出字段名、类型、长度和布尔判断。

默认禁止:

  • 调用 fetchMessagesupdateMessageToRead 或其它会改变已读状态的方法。
  • 点击发送、上传、下载、文件选择器或会改变页面 store 的操作。
  • 将 Cookie、SID、token、chatToken、加密账号、消息正文、完整 URL、URL query 值或原始 payload 写入文件、日志或报告。
  • 为了便于复核而修改生产数据、IndexedDB、Local Storage、Service Worker 数据或 Bright 数据库。

图片/文件发送、真实上传、二进制下载和主动制造 live push 必须作为单独步骤,由用户明确同意后再执行;它们不属于默认只读复核范围。

2.2 立即停止

出现以下任一情况时停止当前检查并清除未保存的临时输出:

  • DevTools、终端或脚本准备输出认证值、完整 URL 或消息正文。
  • 所调用的方法实际改变已读状态、会话状态或页面数据。
  • URL 验证开始返回二进制正文,而当前步骤只获准检查响应元数据。
  • 无法区分测试账号、真实业务账号或目标会话。

3. 实际调试复核范围

3.1 环境基线

编号 检查项 需要记录的安全证据 完成条件
R01 Chromium 与 CDP 浏览器版本、CDP protocol 版本、目标页面 URL 的 origin/path 能确认本次检查对应 OneTalk PWA
R02 扩展与代码版本 扩展版本、仓库 commit、工作树是否有影响复核的未提交改动 复核结果能够绑定到唯一代码快照
R03 SDK 方法 方法是否存在及函数类型,不记录函数闭包或运行时凭证 确认只读历史方法可用
R04 数据状态 IndexedDB schema 版本及各表计数,不输出记录正文 能区分本次复核前后的已有数据

3.2 历史消息原始格式

至少选择包含下列样本的历史会话;每个样本只使用 T1I1F1C1 等本地别名,不在报告中记录真实会话 ID 或消息 ID。

编号 样本 必须检查 不得记录
R10 文本 T1 contentTypetext.content 类型、text.extension 的键名 正文、extension 字符串值
R11 图片 I1 contentTypecustom.type、Base64/UTF-8/JSON 是否成立、解码后字段名和字段类型 MD5、fileId、完整 URL 和 query 值
R12 PDF 文件 F1 custom.typecardTypeparams 字段名和类型 文件名原值、文件 ID、完整 URL
R13 非文件卡片 C1 custom.typecardType、SDK subType/msgType 卡片业务值和账号信息

每个样本都需要同时保留两种独立、脱敏的证据:

  1. WebSocket 原始 body.userMessageModels[].message.content 的结构证据。
  2. 同一次请求对应的 SDK list[] 归一化类型和 originalData 字段结构。

不能只根据 SDK 的 msgType/subType 反推 WebSocket 原始类型,也不能只根据 WebSocket 的 custom.type 推断业务含义。

4. 冲突结论的第二次确认

每项冲突必须得到两份相互独立的证据后才能定案。代码引用和同一份运行输出的重复截图不算两份独立证据。

C1. custom.type=10010 是否等于附件

第一次证据

  • 真实 PDF 样本:custom.type=10010,解码后 cardType=12SDK 归一化为文件类型。

第二次证据

  • 真实非文件卡片:同样为 custom.type=10010,但解码后 cardType=2000 或其它非文件值,且 SDK 不把它归一化为文件。

定案条件

file = contentType=101
    && custom.type=10010
    && decoded.cardType=12
    && decoded.params 通过文件 schema

若缺少非文件卡片样本,只能说“当前 PDF 样本是附件”,不能泛化为“所有 custom.type=10010 都是附件”。

C2. 附件样本的 cardType0 还是 12

第一次证据

  • 直接读取 PDF WebSocket 原始 custom.data 的解码结果,只记录 cardType 数字。

第二次证据

  • 对照同一条消息的 SDK originalData.cardType

定案条件

  • 两个边界均为 12:文档中的 0 必须标记为错误或不当占位。
  • 两个边界不一致:记录为 SDK 转换差异,不能先选择其中一个。

cardType 是判别字段,文档示例不得用 0 冒充脱敏占位;未知值应写成 <number>

C3. raw content 应继续保留还是在 MAIN world 归一化

第一次证据

  • 当前观察器确实复制完整 raw content,Bright 当前清洗器确实对字符串原样放行。

第二次证据

  • 真实运行链检查证明序列化 JSON 或 Base64 中的敏感键名能够到达页面桥、IndexedDB、Bright 输入或持久化边界中的至少一处。

定案条件

  • 若敏感内容能跨边界:raw content 不得继续作为跨层合同,必须在 MAIN world 白名单解码。
  • 即使当前样本未穿透,也不能据此证明任意 raw content 安全;还需要负向 fixture 覆盖字符串和 Base64 嵌套敏感键。

建议的目标不变量是:

raw content 仅在 MAIN world 短暂存在
  → 输出 text | image | file | unsupported
  → 下游只校验 normalized contract,不再解析 OneTalk raw payload

C4. content 是 raw 事实源,还是 normalized 事实源

这不是单靠抓包能够决定的事实冲突,而是架构决策。运行态复核只需要确认现有消费者分别读取了什么;方案确认需要满足:

  • 跨层只有一个可写内容事实源。
  • 顶层 message.text 如需兼容,只能从 content.kind === "text" 派生。
  • 有效但暂未支持的 card 使用 kind="unsupported",不伪装成文本或文件。
  • 非法 Base64、非法 JSON、字段错误和超限数据进入明确 anomaly 或受控失败,不静默变成空文本。
  • 合同含义从 raw 改为 normalized 时必须提升协议版本,不能让同一协议版本同时承载两种语义。

C5. 完整 sourceUrl 是否可以持久化和发送给 Mind

第一次证据

  • 解析图片 url、文件 url/downloadUrl/thumbnailUrl,只记录 scheme、host、path 后缀、query 键名和是否为空,不记录 query 值。

第二次证据

  • 分别验证 OneTalk 登录上下文和无登录上下文中的响应状态、跳转次数、最终 host、Cookie 依赖和 CORS;只记录状态与布尔结果。

定案条件

  • 未确认 URL 生命周期、授权依赖和 query 敏感性前,sourceUrl 不能同时被定义为必填字段并承诺跨网络/数据库持久化。
  • 如果 URL query 含短期授权或身份值,第一阶段只同步安全元数据和资源 ID;是否增加临时 URL 换取或受控代理另行决策。
  • 如果需要实际 GET、重定向跟随或读取二进制,本项必须先获得用户明确同意;默认只允许检查响应元数据。

5. 不明确内容的再次确认

编号 当前不明确内容 再确认方法 定案所需证据
U01 图片/文件 URL 是否依赖 OneTalk Cookie 登录上下文与干净上下文分别检查响应元数据 两种上下文的状态、跳转和 Cookie 依赖结论
U02 URL 有效期 同一脱敏样本在不同时间点检查响应元数据,不记录 URL 至少能观察有效、过期或续期行为中的一种明确规则
U03 URL 能否跨 Origin 给 Mind 使用 在 Mind 所在 Origin 做只读请求或实际 <img>/链接测试 CORS、Cookie、跳转和 CSP 的真实结果
U04 downloadUrl 为空时 params.url 的真实语义 页面行为与 SDK/bundle 调用点交叉确认 能区分预览、下载、操作入口或临时跳转
U05 TXT 是否与 PDF 同构 获取一条真实入站 TXT 历史样本并按 R12 检查 cardType、params schema 和 SDK 归一化结果
U06 live 图片/文件 push 是否与历史同构 观察真实入站 live 媒体消息,不主动发送 live envelope 与 history content 的字段级比较
U07 群聊媒体是否同构 在明确允许的群聊样本上执行只读历史检查 群聊 raw content、身份和 participant 结构
U08 custom.type/cardType 完整枚举 对已有历史样本做类型和 cardType 计数,只输出数字集合 至少确认当前账号样本范围,不宣称全局完整枚举
U09 confirmed 是否等于真实持久化 将样本 ACK 与 Bright 数据库行只读对应 accepted/duplicate 与数据库事实的区别
U10 Mind 是否能显示媒体 检查真实 history/event 消费和 UI 模型 真实渲染结果,不以服务启动或 JSON 可返回代替
U11 MessagePack live/sync push 的媒体路径 捕获真实媒体 MessagePack 样本并与明文历史消息交叉对应 数字路径和业务字段一一对应,不靠数组位置猜测
U12 图片/文件发送和确认 仅在用户明确授权后执行一次受控发送 上传结果、最终 sent-direction 消息和三态发送结果

U01U04、U06、U07 和 U12 依赖特定运行环境或真实样本;缺少样本时状态必须写为 blocked_by_sample,不能写成已验证或默认同构。

6. 复核输出格式

每个检查项使用以下固定格式记录:

### Rxx / Cx / Uxx:标题

- 状态:verified | contradicted | not_verified | blocked_by_sample | blocked_by_environment
- 环境:浏览器版本、扩展版本、commit
- 样本别名:T1 / I1 / F1 / C1
- 证据 A:来源边界、字段名、字段类型或布尔结果
- 证据 B:独立来源边界、字段名、字段类型或布尔结果
- 结论:只描述证据能够支持的范围
- 未覆盖:明确列出不能外推的消息类型或场景

最终报告必须分别列出:

  1. 两份文档共同成立的结论。
  2. 经两次独立证据确认的冲突及采用哪一侧。
  3. 被新证据推翻的原结论。
  4. 尚未确认、缺样本或缺环境的内容。
  5. 纯方案决策,不得写成运行态事实的内容。

7. 本轮不做

  • 不修改两份原调查文档。
  • 不创建 Trellis task、PRD 或实现计划。
  • 不修改协议、数据库、扩展、Bright 或 Mind 代码。
  • 不执行图片/文件发送、上传或二进制下载。
  • 不以静态代码检查代替真实运行态结论。