mirror of
https://github.com/sinanyuntu/trade-message-center.git
synced 2026-09-17 13:22:11 +08:00
221 lines
13 KiB
Markdown
221 lines
13 KiB
Markdown
# 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 解码,并只输出字段名、类型、长度和布尔判断。
|
||
|
||
默认禁止:
|
||
|
||
- 调用 `fetchMessages`、`updateMessageToRead` 或其它会改变已读状态的方法。
|
||
- 点击发送、上传、下载、文件选择器或会改变页面 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 历史消息原始格式
|
||
|
||
至少选择包含下列样本的历史会话;每个样本只使用 `T1`、`I1`、`F1`、`C1` 等本地别名,不在报告中记录真实会话 ID 或消息 ID。
|
||
|
||
| 编号 | 样本 | 必须检查 | 不得记录 |
|
||
| ---- | --------------- | -------------------------------------------------------------------------------- | --------------------------------- |
|
||
| R10 | 文本 `T1` | `contentType`、`text.content` 类型、`text.extension` 的键名 | 正文、extension 字符串值 |
|
||
| R11 | 图片 `I1` | `contentType`、`custom.type`、Base64/UTF-8/JSON 是否成立、解码后字段名和字段类型 | MD5、fileId、完整 URL 和 query 值 |
|
||
| R12 | PDF 文件 `F1` | `custom.type`、`cardType`、`params` 字段名和类型 | 文件名原值、文件 ID、完整 URL |
|
||
| R13 | 非文件卡片 `C1` | `custom.type`、`cardType`、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=12`,SDK 归一化为文件类型。
|
||
|
||
**第二次证据**
|
||
|
||
- 真实非文件卡片:同样为 `custom.type=10010`,但解码后 `cardType=2000` 或其它非文件值,且 SDK 不把它归一化为文件。
|
||
|
||
**定案条件**
|
||
|
||
```text
|
||
file = contentType=101
|
||
&& custom.type=10010
|
||
&& decoded.cardType=12
|
||
&& decoded.params 通过文件 schema
|
||
```
|
||
|
||
若缺少非文件卡片样本,只能说“当前 PDF 样本是附件”,不能泛化为“所有 `custom.type=10010` 都是附件”。
|
||
|
||
### C2. 附件样本的 `cardType` 是 `0` 还是 `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 嵌套敏感键。
|
||
|
||
建议的目标不变量是:
|
||
|
||
```text
|
||
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 消息和三态发送结果 |
|
||
|
||
U01–U04、U06、U07 和 U12 依赖特定运行环境或真实样本;缺少样本时状态必须写为 `blocked_by_sample`,不能写成已验证或默认同构。
|
||
|
||
## 6. 复核输出格式
|
||
|
||
每个检查项使用以下固定格式记录:
|
||
|
||
```md
|
||
### 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 代码。
|
||
- 不执行图片/文件发送、上传或二进制下载。
|
||
- 不以静态代码检查代替真实运行态结论。
|