feat(refit): complete S1-S6 — case abstraction, grounding, log-odds, plugins, coref, multi-source

Consolidates the long-running refit work (DESIGN.md as authoritative spec)
into a single baseline commit. Six stages landed together:

  S1  Case + EvidenceSource abstraction; tools parameterised by source_id
      (case.py, main.py multi-source bootstrap, .bin extension support)
  S2  Grounding gateway in add_phenomenon: verified_facts cite real
      ToolInvocation ids; substring / normalised match enforced; agent +
      task scope checked. Phenomenon.description split into verified_facts
      (grounded) + interpretation (free text). [invocation: inv-xxx]
      prefix on every wrapped tool result so the LLM can cite.
  S3  Confidence as additive log-odds: edge_type → log10(LR) calibration
      table; commutative updates; supported / refuted thresholds derived
      from log_odds; hypothesis × evidence matrix view.
  S4  iOS plugin: unzip_archive + parse_plist / sqlite_tables /
      sqlite_query / parse_ios_keychain / read_idevice_info;
      IOSArtifactAgent; SOURCE_TYPE_AGENTS routing.
  S5  Cross-source entity resolution: typed identifiers on Entity,
      observe_identity gateway, auto coref hypothesis with shared /
      conflicting strong/weak LR edges, reversible same_as edges,
      actor_clusters() view.
  S6  Android partition probe + AndroidArtifactAgent; MediaAgent with
      OCR fallback; orchestrator Phase 1 iterates every analysable
      source; platform-aware get_triage_agent_type; ReportAgent renders
      actor clusters + per-source breakdown.

142 unit tests / 1 skipped — full coverage of the new gateway, log-odds
math, coref hypothesis fall-out, and orchestrator multi-source dispatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
BattleTag
2026-05-21 02:12:10 -10:00
parent 444d58726a
commit 81ade8f7ac
24 changed files with 5137 additions and 244 deletions

305
DESIGN.md Normal file
View File

@@ -0,0 +1,305 @@
# MASForensics 系统改造设计
> 目标:把当前「单台 Windows 磁盘取证」系统改造为能处理**多设备、多行为人、
> 异构证据、需跨源关联**的复杂取证系统。本文是唯一的权威设计文档
> (已合并早先的 `REFIT_PLAN.md` / `RESEARCH_DESIGN.md` 两份草稿)。
>
> 触发本次改造的实际案件2025 美亚杯资格赛 Individual —— 5 份证据
> 1 USB E01、1 安卓整盘 `blk0_sda.bin`、3 份 iOS 提取、1 组交易截图),
> 跨 LEUNG YL / CHAN MH / FUNG CC 至少 3 人。
---
## 1. 设计原则(贯穿全文的不变式)
1. **LLM 提议,代码裁决**。LLM 负责语言/分类/感知;它**不持有案件状态、
不产出数值、不写入未经核验的事实**。所有「真相」在符号层。
2. **每条记录的事实都可从一次工具调用重新推导**。结论可被独立复核。
3. **推理核心与设备类型无关**。设备特定逻辑全部位于「能力插件」中;
支持一种新设备 = 写插件,绝不改核心。
4. **看似不可逆的操作(如实体归并)实为可逆、带证据的论断**,可被推翻。
这四条不是口号——下文每个设计决策都对应其中一条。
---
## 2. 现状问题诊断
| # | 问题 | 位置 | 后果 |
|---|---|---|---|
| P1 | **单镜像假设深植**:工具是闭包绑死 `image_path`,图是单源,主程序只选一个镜像 | `tool_registry.py:148` `register_all_tools``main.py:91-153` | 无法摄取多份证据,无法跨设备关联 |
| P2 | **反幻觉只写在提示词里** | `base_agent.py` system prompt | LLM 一旦不听话,错误事实进入案件记录且**事后无法识别** |
| P3 | **置信度公式无统计含义且有序依赖缺陷**`delta=weight*(1-conf)`(正)/`weight*conf`(负),正负边混合时更新结果与边的到达顺序有关 | `evidence_graph.py:26-33` | 置信度不可校准、不可辩护 |
| P4 | **工件分类是 Windows 专属**:靠 hive 名 / `.pf` / `mirc` 关键词 | `tool_registry.py:80-107` `_auto_categorize` | iOS/安卓工件全部落入 `other` |
| P5 | **案件信息硬编码** `cfreds_hacking_case` | `config.yaml:35-50` | 换案即需改代码 |
| P6 | **镜像发现靠扩展名 glob**`.bin` 不在列表 | `main.py:28` `_IMAGE_GLOBS` | `blk0_sda.bin` 不被发现 |
| P7 | **Phenomenon 无来源标注** | `evidence_graph.py:85` `Phenomenon` | 不知道某发现出自哪台设备,跨源关联无锚点 |
改造同时解决「接入新证据」与「修掉 P1-P7 这些固有缺陷」。
---
## 3. 目标架构
```
case.yaml ──► Case ──► N × EvidenceSource
├ id / type / owner / path
└ access_mode: image | tree
┌──────────────┴───────────────┐
image-backed tree-backed
(TSK, inode 寻址) (路径寻址:已挂载/已解包)
│ │
└────────────┬─────────────────┘
SourceRegistry ── source_id → SourceHandle解析 path/offset/mode
ToolRegistry ── 工具按 access_mode 注册,调用时绑定 source_id
┌──────────────────────┼───────────────────────┐
▼ ▼ ▼
Knowledge-Source Graph Write Gateway ToolInvocationLog
Agents (LLM) ──► (唯一写入口,强制 (每次工具调用留痕:
只能经网关写图 前置条件 = grounding args / 输出 / sha256
│ │
└──────────────────────┴──► Grounded Evidence Graph (GEG)
Phenomenon / Hypothesis / Entity
置信度 = 对数几率累加
```
**保留**现有的五阶段流水线、断连恢复、运行归档、工具结果缓存、
`AgentFactory` 动态组合——这些设计是好的,不重写,只适配。
---
## 4. 核心设计
### 4.1 证据源抽象(解决 P1/P5/P6/P7地基
新增 `case.py`
- **`EvidenceSource`** 数据类:`id``label``type``owner`(关联人)、
`path``access_mode``meta`(类型特定,如分区 offset / 解包后根目录)。
- **`Case`**:持有 `list[EvidenceSource]` + 案件元数据,从 `case.yaml` 加载。
- **`access_mode` 是关键设计区分**
- `image`:块设备/磁盘镜像,用 TSK 按 inode 寻址USB E01、安卓 `blk0_sda` 各分区)。
- `tree`已挂载文件系统或已解包目录按路径寻址iOS 提取解压后、归档展开后)。
- 工具按 access_mode 分族注册(见 4.2)。一份证据可经「准备」从 image 变为 tree
(如分区 mount、zip 解包)。
`main.py``select_image_interactive`:91-153改为加载/构造 `Case`
`_IMAGE_GLOBS` 改为类型探测(`mmls` 试探 + 文件头嗅探),不再靠扩展名。
`config.yaml` 删除 `cfreds_hacking_case`,案件信息移入 `case.yaml`
### 4.2 工具注册按源参数化(解决 P1
现状:`register_all_tools(image_path, offset, ...)` 把单一镜像闭包进每个工具
`tool_registry.py:159+`)。改造:
- 工具执行器签名增加 `source_id`;执行时经 `SourceRegistry` 解析出真实 path/offset/mode。
- `TOOL_CATALOG``access_mode` 标注工具适用性agent 拿到的工具集由其
负责的源类型决定。
- **「当前源」上下文**:编排器为 agent 设置 current source类比现有
`graph._current_agent`工具默认作用于它——LLM 不必每次传 `source_id`
(减少出错)。跨源工具(时间线合并、实体查询)显式跨源。
- 缓存键 `_cache_key``tool_registry.py:41`)纳入 `source_id`,防止跨源串味。
### 4.3 图写入网关(解决 P2落实原则 1
现状agent 通过 `add_phenomenon` 等工具直接写图,约束只在 prompt。改造
- 所有图变更(`add_phenomenon` / `add_hypothesis` / `link` / `observe_identity` …)
收敛到**一个写入网关**。网关在代码层强制前置条件。
- 现有 prompt 里的「反幻觉规则」下沉为网关的硬校验。LLM agent 的四阶段工作流
INVESTIGATE→RECORD→LINK→ANSWER不变——变的是 RECORD 这一步底下的网关变严。
- `base_agent.py``mandatory_record_tools` 机制保留(它保证 agent 真的记录了东西)。
### 4.4 证据落地约束 Grounding解决 P2落实原则 2
这是系统可靠性的核心机制。
**ToolInvocationLog**:每次工具调用留痕一条记录
`{invocation_id, source_id, tool, args, output, output_sha256, agent, ts}`
现有结果缓存(`tool_registry.py:29`)已存确定性输出,扩展为完整留痕即可。
**Phenomenon 一分为二**——把「事实」和「解读」分开:
- `verified_facts`: `list[{type, value, invocation_id}]`
`type ∈ {path, timestamp, inode, hash, identifier, count, ...}`
- `interpretation`: 自由文本agent 的分析叙述。
**`add_phenomenon` 网关前置条件**
1. 每个 fact 必须引用一次**本 agent 本任务内真实发生过的** `invocation_id`
2. 代码校验 `fact.value` 命中该次调用的输出:
- 文本输出 → 逐字 substring 匹配;
- 结构化/二进制工具输出 → 与解析后的字段匹配。
3. 任一 fact 不通过 → **整条拒绝写入**,返回失败的 factagent 须修正重试。
4. 通过 → 写入;`verified_facts` 每条带 `invocation_id`(可重跑复核),
`interpretation` 标记为「未核验分析」。
**效果**:在系统里「记录一条工具输出未支撑的路径/时间戳/哈希/标识符」
**结构性地不可能**。LLM 仍可能写错 `interpretation`,但报告会把
verified facts带重跑指令的引证与 interpretation明确标注的分析
**分开渲染**,人类调查员一眼可辨。这是诚实划定边界的可靠性保证。
> 现有 `_make_auto_record``tool_registry.py:126`)把工具输出直接转 phenomenon——
> 那是「平凡落地」的特例(描述即输出),新设计是它的一般化与形式化。
### 4.5 假设置信度:似然比 / 对数几率(解决 P3
`evidence_graph.py:26``_DEFAULT_EDGE_WEIGHTS` 从「拍脑袋的 delta」
换成基于**似然比LR**的对数几率累加:
- 每条 `Phenomenon → Hypothesis` 边代表一个似然比。LLM 仍只做**离散分类**
(这条证据对这条假设是 direct_evidence / supports / weakens / contradicts …),
数值 `log₁₀(LR)` 由标定表查得——**LLM 绝不吐数字**延续现有「LLM 选类型、
代码算数值」哲学并赋予统计基础)。
- 置信度更新:
```
L_post = L_prior + Σ log₁₀(LR_i) # 对数几率,可交换 → 无序依赖
confidence = 1 / (1 + 10^(L_post))
```
- 边类型 → `log₁₀(LR)` 标定表(初值,后续可由标注案例校准):
| 边类型 | log₁₀LR |
|---|---:|
| `direct_evidence` | +2.0 |
| `supports` / `consequence_observed` | +1.0 |
| `prerequisite_met` | +0.5 |
| `weakens` | 0.5 |
| `contradicts` | 2.0 |
- 阈值不变≥0.8 supported / ≤0.2 refuted只是改由 `L_post` 推出。
- `prior_prob` 成为可配置量(默认 0.5 → `L_prior=0`)。
- **简化假设说明**:多条边按独立处理(朴素贝叶斯)。同类证据反复出现并非
完全独立——加一个旋钮:同 `(hypothesis, edge_type)` 的边数封顶或衰减,避免
「同一发现被多 agent 重复入图」虚高置信度(现有 Jaccard 去重已部分缓解)。
附带产出一个 **假设 × 证据矩阵**视图,供报告与线索选择使用。
### 4.6 跨源实体解析(解决「复杂场景」的关联难题,落实原则 4
复杂取证的核心难题iPhone keychain 里的 Apple ID、安卓短信库里的号码、
USB 文件作者、交易截图里的钱包地址——**哪些指向同一行为人?**
**关键设计:「身份共指」本身就是一条假设**——于是实体解析不是独立子系统,
而是 4.5 假设机制的复用:
- agent 观察到标识符即经网关 `observe_identity`,记一条**类型化**的标识符
强标识符IMEI / 钱包地址 / email / 电话号;弱标识符:昵称 / 显示名),
挂到暂定 `Entity`。
- 「Entity A ≡ Entity B」登记为一条 `Hypothesis`;共享强标识符 = 强 +LR 边,
共享弱标识符 = 弱 +LR 边,冲突的强标识符 = 强 LR 边——用 4.5 同一套计算打分。
- **不做破坏性归并**:跨阈值时在两个 Entity 间加一条 `same_as` 边(由该 coref
假设背书)。查询时把 `same_as` 连通分量视作同一行为人。**完全可逆、可审计、
可被后续 contradicts 证据推翻**(落实原则 4
- **Blocking**:只在「至少共享一个标识符或名称高相似」的实体对间建 coref 假设,
避免 O(n²)。
跨设备时间线、「谁在何时做了什么」由 `same_as` 连通后的实体图自然涌现。
### 4.7 能力插件层(接入 5 类证据)
每类证据 = 一个 `(摄取 handler, 工具集, 知识源 agent)` 三元组。推理核心不动。
| 插件 | 摄取 | 新工具 | 知识源 agent |
|---|---|---|---|
| **iOS 提取** | `unzip` 解包为 `tree` 源 | `parse_plist`(含二进制 plist)、`sqlite_tables`/`sqlite_query`(sms.db、WhatsApp `ChatStorage.sqlite`、通讯录)、`parse_ios_keychain`、`read_idevice_info` | `iOSArtifactAgent` |
| **安卓整盘** | `mmls` 分区→各分区 `image` 源;可 mount 为 `tree` | 复用 TSKext4/F2FS 读取;`fsstat` 探明加密 | 复用 filesystem + `AndroidArtifactAgent` |
| **磁盘镜像(E01)** | 已支持TSK 含 ewf | 现有 TSK 工具链 | 现有 filesystem/registry |
| **归档** | `unzip_archive` 通用解包 | —— | —— |
| **媒体/截图** | —— | `ocr_image`tesseract注意 DeepSeek 无视觉能力,必须走 OCR | `MediaAgent` |
**安卓风险**`blk0_sda` 的 `userdata` 分区大概率 FBE 加密。先 `fsstat` 各分区
探明未加密→TSK 直接用;加密且无密钥→只能分析 `EFS`/`PARAM`/`system` 等非加密区。
`tool_registry.py:80` 的 `_auto_categorize` 改为可扩展:分类由源插件提供自己的
工件分类表,而非全局 Windows 关键词表(解决 P4
### 4.8 Agent 体系重组
现有 7 个 agent 按 Windows 工件命名registry、communication=邮件/IRC、
network=浏览器/PCAP。改为按**调查职能**组织,并增加平台特定 agent
- `agent_factory.py` 的 `_AGENT_CLASSES`:34-40扩充新增 `ios_artifact`、
`android_artifact`、`financial`(钱包/交易)、`media`。
- `communication` 泛化:邮件 + IM + 短信,跨平台。
- 新增 **源类型 → 适任 agent** 映射,供 Phase 1 逐源派 triage agent。
- `create_specialized_agent`:69的动态组合机制保留——它本就是应对能力缺口的
正确手段,只是工具目录变大后选择空间更丰富。
### 4.9 编排器多源流水线
| 阶段 | 改造 |
|---|---|
| Phase 1 | 「单镜像初勘」→ **逐源并行 triage**,每源派类型适配的 agent |
| Phase 2 | 假设跨源生成;身份共指假设在此首次登记 |
| Phase 3 | leads 派发到源感知 agent假设×证据矩阵实时更新 |
| Phase 4 | 跨源时间线合并,**按源做时区归一**iOS UTC vs 安卓本地时间) |
| Phase 5 | 一案一份综合报告:含假设结论、实体关联图、每条结论的 provenance 引证 |
断连恢复、运行归档逻辑保留,`graph_state.json` 增量纳入新字段。
---
## 5. 数据模型变更汇总
| 节点/结构 | 变更 |
|---|---|
| `EvidenceSource` | **新增**一等节点(`src-*` |
| `ToolInvocation` | **新增**留痕记录(`inv-*`),随 graph 持久化 |
| `Phenomenon` | + `source_id`description 拆为 `verified_facts[]` + `interpretation`;澄清/移除语义含混的 `confidence`(默认 1.0),观测的可靠性由 grounding 表达 |
| `Hypothesis` | + `prior_prob`、`log_odds`(累加量);`confidence` 改为派生值 |
| `Entity` | + 类型化标识符集合;通过 `same_as` 边跨源连通 |
| Phenomenon→Hypothesis 边 | 携带 `edge_type`,映射到 `log₁₀(LR)`(替换 `_DEFAULT_EDGE_WEIGHTS` |
| Entity→Entity 边 | **新增** `same_as`(由 coref 假设背书,可逆) |
`evidence_graph.py` 的 `VALID_EDGE_TYPES`、序列化/反序列化、Jaccard 去重相应适配。
---
## 6. 组件改动清单
| 文件 | 改动 |
|---|---|
| `case.py` | **新建**`Case` / `EvidenceSource` / `SourceRegistry` |
| `main.py` | 选源逻辑改为加载 `Case`;类型探测替代扩展名 glob |
| `tool_registry.py` | 工具按 `source_id` 参数化;缓存键含 source`_auto_categorize` 改可扩展;`ToolInvocationLog` |
| `evidence_graph.py` | 数据模型变更(第 5 节LR/对数几率置信度;写入网关 + grounding 校验 |
| `base_agent.py` | RECORD 走网关;`add_phenomenon` 改为 `verified_facts`+`interpretation` 接口 |
| `agent_factory.py` | `_AGENT_CLASSES` 扩充源类型→agent 映射 |
| `orchestrator.py` | Phase 1 逐源Phase 4 跨源时区归一Phase 5 综合报告 |
| `agents/` | 新增 `ios_artifact.py` / `android_artifact.py` / `financial.py` / `media.py``communication.py` 泛化 |
| `tools/` | 新增 `mobile_ios.py`plist/sqlite/keychain、`media.py`OCR、`archive.py`(解包) |
| `config.yaml` / `case.yaml` | 删除 `cfreds_hacking_case`;新建 `case.yaml` 证据清单 |
---
## 7. 构建顺序(按依赖排序)
| 阶段 | 内容 | 依赖 | 价值 |
|---|---|---|---|
| **S1** | 4.1 证据源抽象 + 4.2 工具参数化 + 修 P6 | —— | 地基;先只在 USB E01 上跑通验证不破坏现有逻辑 |
| **S2** | 4.3 写入网关 + 4.4 grounding + ToolInvocationLog | S1 | 可靠性核心;可量化「零幻觉录入」 |
| **S3** | 4.5 LR/对数几率置信度 | 独立(可与 S2 并行) | 修 P3置信度可辩护 |
| **S4** | 4.7 iOS 插件 + 4.8 agent 重组 | S1 | 覆盖率 1/5 → 4/5 |
| **S5** | 4.6 跨源实体解析 | S1+S3 | 跨设备关联,复杂场景能力成型 |
| **S6** | 4.7 安卓 + 媒体插件 + 4.9 编排器适配 | S1+S4 | 全 5 份证据接入 |
S1+S2+S3 是「把系统改对」S4-S6 是「把能力铺全」。建议严格按序——
S1 不稳,后面全是空中楼阁。
---
## 8. 设计取舍与未决问题
1. **grounding 对自由文本的边界**:只硬核验 `verified_facts` 里的结构化原子,
`interpretation` 不做逐字核验(诚实划界)。可加一个二级 lint扫描
interpretation 中形似路径/时间戳/哈希但未被任何引用调用覆盖的串并告警。
2. **LR 标定表初值人定**:先用第 4.5 节的初值跑通;「从标注案例学习 LR」是后续工作。
3. **安卓 userdata 加密**:能否取得解密密钥决定 4.7 安卓插件的证据深度——需尽早探明。
4. **实体解析的破坏性 vs 可逆**:本设计选**可逆的 `same_as` 边**而非破坏性归并——
牺牲一点查询效率换取完全可审计可回滚,符合原则 4。
5. **报告粒度**:定为「一案一份综合报告」,内嵌每证据小节 + 跨源关联,
而非每证据独立成篇。