版本:V1.0
日期:2026-08-28
定位:取证级痕迹净化 + 审计留痕
运行环境:跨平台(Windows / macOS / Linux)/ 100%离线
一、项目定位
全类型制品 取证级痕迹净化 + 审计留痕系统
- 模块名:
opensoma.artifact - 运行层级:OpenSoma(底层工具唯一实现层)
- 调用方:OpenMate 多Agent集群调用
- 运行模式:100% 纯本地离线、零上传、零网络
- 净化策略:取证级重建净化(文件结构重建 > 字段清空),不做降级备选
核心解决问题:
- 标书、交付物、办公制品、图片的隐性指纹、残留碎片、设备ID、作者、编辑痕迹、增量残留
- 统一入口、统一审计、统一安全隔离
- 可随时外挂 MCP 对外服务(不改动核心代码)
二、架构分层
`
┌──────────────────────────────────────────────────────┐
│ OpenMate(多Agent调度层) │
│ – 只传本地文件路径,不传二进制、不传文本 │
│ – 调用 opensoma.artifact 统一API │
└──────────────┬───────────────────────────────────────┘
│ HTTP/子进程
▼
┌──────────────────────────────────────────────────────┐
│ OpenSoma.artifact(唯一业务实现层、唯一真相源) │
│ – 任务队列、并发控制、安全校验 │
│ – 制品路由分发(按文件类型选工具链) │
│ – 取证快照、前后比对、审计报告 │
│ – 隐性风险检测 │
└──────────────┬───────────────────────────────────────┘
│ 子进程调用
▼
┌──────────────────────────────────────────────────────┐
│ 工具层(Python绑定 + exiftool二进制) │
│ – MAT2 (Python库,Office/ODF重建) │
│ – pikepdf (qpdf C++绑定,PDF线性重建) │
│ – pyexiv2 (exiv2 C++绑定,图片重写) │
│ – exiftool / exiftool-rs (外部二进制,通用元数据快照) │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ MCP薄代理(V2.0可选扩展) │
│ – 仅协议转发,0业务逻辑 │
│ – 所有能力复用 opensoma.artifact │
└──────────────────────────────────────────────────────┘
`
分层职责铁律
| 层级 | 职责 | 禁止 |
|---|
|——|——|——|
| OpenSoul | 意图识别 | 禁止读取任何制品内容 |
|---|---|---|
| OpenMate | Agent调度、路由 | 只传路径,不传二进制 |
| OpenSoma.artifact | 唯一业务实现 | — |
| MCP Proxy | 协议转发 | 0业务逻辑 |
三、目录结构
`
opensoma/tools/artifact/
├── __init__.py # 对外API导出
├── models.py # 数据模型(dataclass + enum)
├── core.py # 任务入口、安全校验、路径沙箱
├── dispatcher.py # 文件类型路由 → 选择processor
├── queue.py # 任务队列、并发控制
├── auditor.py # 取证快照、前后比对、审计报告
├── risk_detector.py # 隐性风险检测(修订/批注/OLE)
├── tool_checker.py # 外部工具检测 + 一键安装
├── processors/
│ ├── __init__.py
│ ├── base.py # Processor基类
│ ├── ooxml.py # MAT2 深度重建 Office/ODF
│ ├── pdf.py # pikepdf 取证重建PDF
│ └── image.py # pyexiv2 图片重写
└── tests/
├── test_ooxml.py
├── test_pdf.py
└── test_image.py
`
四、能力矩阵
✅ 可取证重建净化
| 格式 | 工具链 | 策略 |
|---|
|——|——–|——|
| docx / xlsx / pptx | MAT2(Python库) | OOXML解压→清元数据部件→重新打包 |
|---|---|---|
| odt / ods / odp | MAT2(Python库) | ODF解压→清元数据→重新打包 |
| PDF全版本 | pikepdf(qpdf引擎) | 清字段 + 全文件线性重建 |
| jpg / jpeg / png / tiff | pyexiv2(exiv2引擎) | 擦除EXIF + 文件重写 |
⚠️ 仅审计、禁止净化(提示用户手动转换)
| 格式 | 原因 | 处理方式 |
|---|
|——|——|———-|
| .doc(旧二进制) | 自动格式转换会引入新元数据/隐藏标记,取证可信度存疑 | 仅扫描,告警提示用户用Office/WPS另存为docx后重新提交 |
|---|
🚫 仅审计、禁止净化
| 格式 | 原因 |
|---|
|——|——|
| .msg / .eml | 邮件格式结构复杂,无法可靠重建 |
|---|---|
| 加密/密码保护PDF | 无法读取内容 |
| 损坏文件 | 处理可能加剧损坏 |
五、标准化流水线
5.1 Office 制品净化(docx/xlsx/pptx/odt)
`
输入文件
↓
[1] 前置元数据快照(exiftool提取全部元数据)
↓
[2] 风险检测(修订/批注/OLE/隐藏文字)
↓
[3] MAT2 深度重建净化(解压→清元数据部件→重新打包)
↓
[4] 后置元数据校验快照(确认清除干净)
↓
[5] 可选:LibreOffice导出纯净PDF
↓
[6] PDF二次取证重建(exiftool+qpdf)
↓
[7] 生成审计报告
`
.doc 旧格式: 不进入净化流水线,dispatcher直接返回 AUDIT_SCAN,告警提示用户手动另存为docx。
5.2 PDF 净化
`
输入文件
↓
[1] 前置快照(exiftool -json 提取全部字段)
↓
[2] exiftool 清空所有隐藏字段(含DocumentID、Creator、Producer等)
↓
[3] pikepdf 进程内线性重建(等同qpdf –linearize,消除增量残留碎片)
↓
[4] 后置校验快照(exiftool确认字段清空、无增量残留)
↓
[5] 生成审计报告
`
5.3 图片净化
`
输入文件
↓
[1] 前置EXIF快照(exiftool -json 或 pyexiv2读取)
↓
[2] pyexiv2 清空全部元数据(EXIF/IPTC/XMP)+ 重写图像文件
↓
[3] 后置校验(exiftool或pyexiv2确认EXIF/GPS/缩略图全清)
↓
[4] 生成审计报告
`
六、数据模型
`python
from dataclasses import dataclass, asdict
from pathlib import Path
from enum import Enum
class SanitizeCapability(str, Enum):
FULL_PURGE = “FULL_PURGE” # 取证级净化
AUDIT_SCAN = “AUDIT_SCAN” # 仅审计扫描
class FileType(str, Enum):
OFFICE_MODERN = “office_modern” # docx xlsx pptx
OFFICE_ODF = “office_odf” # odt ods odp
OFFICE_LEGACY = “office_legacy” # .doc
PDF = “pdf”
IMAGE = “image”
UNSUPPORTED = “unsupported”
class RiskLevel(str, Enum):
NONE = “none”
LOW = “low”
MEDIUM = “medium”
HIGH = “high”
@dataclass
class MetaSnapshot:
tool: str
raw_meta: dict
timestamp: str
file_hash: str
file_size: int
@dataclass
class RiskWarning:
level: RiskLevel
category: str
message: str
can_auto_fix: bool = False
@dataclass
class PurgeResult:
success: bool
task_id: str
agent_id: str | None
source_path: str
output_path: str | None
capability: str
file_type: str
snapshot_before: dict | None
snapshot_after: dict | None
risk_warnings: list[dict]
audit_report_path: str | None
runtime_errors: list[str]
duration_ms: int
@dataclass
class AuditScanReport:
task_id: str
agent_id: str | None
source_path: str
file_type: str
capability: str
snapshot: dict
risk_warnings: list[dict]
recommendation: str
`
序列化铁律:
`python
def to_dict(obj):
“””所有对外返回必须经过此函数,解决Hermes兼容问题”””
if hasattr(obj, ‘__dataclass_fields__’):
return asdict(obj)
if isinstance(obj, Path):
return str(obj)
if isinstance(obj, Enum):
return obj.value
return obj
`
七、对外API
`python
async def purge_artifact(
artifact_in: Path, # 源文件路径(只读,永不覆盖)
output_dir: Path, # 输出目录
agent_id: str | None = None,
generate_audit: bool = True
) -> PurgeResult:
“””取证级净化 – 生成全新干净文件 + 审计报告”””
async def scan_artifact(
artifact_in: Path,
agent_id: str | None = None
) -> AuditScanReport:
“””仅审计扫描 – 零文件修改,预审风险检测”””
async def check_tools() -> dict:
“””检测所有外部工具安装状态”””
`
八、外部工具检测
Python包(pip安装,wheel自带预编译C++引擎)
| 包名 | 检测方式 | 用途 | 底层引擎 |
|---|
|——|———-|——|———-|
mat2 |
import mat2 |
Office/ODF重建 | Python原生 |
|---|---|---|---|
pikepdf |
import pikepdf |
PDF线性重建 | qpdf C++(wheel自带) |
pyexiv2 |
import pyexiv2 |
图片重写 | exiv2 C++(wheel自带) |
外部二进制
| 工具 | 检测命令 | 安装命令 | 用途 |
|---|
|——|———-|———-|——|
| exiftool | exiftool -ver |
Linux: apt install -y libimage-exiftool-perl / macOS: brew install exiftool / Windows: 官网下载 或 exiftool-rs |
通用元数据快照读取 |
|---|
铁律:
- Python包缺失 →
pip install,不降级 - exiftool缺失 → 提示安装,不降级
九、风险检测器
| 检测项 | 格式 | 检测方法 | 风险等级 |
|---|
|——–|——|———-|———-|
| 修订痕迹 | docx | 解压检查 document.xml 中 w:ins/w:del 标记 |
HIGH |
|---|---|---|---|
| 批注残留 | docx | 检查 word/comments.xml 是否存在 | MEDIUM |
| OLE嵌入对象 | docx | 检查 word/embeddings/ 目录 | HIGH(禁止净化,仅告警) |
| 隐藏文字 | docx | 检查 w:vanish 标记 |
MEDIUM |
| PDF增量残留 | exiftool 检查 IncrementalUpdates 字段 | LOW(qpdf可自动修复) | |
| PDF隐藏字段 | exiftool 检查 DocumentID/Creator/Producer | LOW(exiftool可自动清除) | |
| EXIF/GPS | 图片 | exiftool -json 全量检查 | LOW(可自动清除) |
十、并发安全设计
`python
class ArtifactQueue:
def __init__(self, max_concurrent: int = 3):
self._semaphore = asyncio.Semaphore(max_concurrent)
self._active_tasks: dict[str, dict] = {}
async def submit(self, task_id: str, agent_id: str, coro):
async with self._semaphore:
self._active_tasks[task_id] = {
“agent_id”: agent_id,
“started_at”: datetime.now().isoformat()
}
try:
return await coro
finally:
del self._active_tasks[task_id]
`
安全隔离铁律
- 每任务独立时间戳输出目录,零文件冲突
- 源文件只读,永不覆盖
- 路径穿越防护
- 全局异常捕获,原生异常永不抛到Agent层
- 所有返回可JSON序列化(asdict)
十一、安全规范
- 零网络依赖 — 模块内无任何 http/https/socket 调用
- 源文件只读 — 永不覆盖原始文件
- 路径沙箱 — 每任务独立输出目录,防路径穿越
- LLM隔离 — 永远无法接触制品正文/像素数据,只返回元数据摘要
- 序列化安全 — 所有对外返回必须可JSON序列化
- 异常兜底 — 全局try-except,原生异常永不抛到Agent层
- 断网可运行 — 所有处理可离线完成
十二、边界说明
| 可清除 | 不可清除(仅告警) |
|---|
|——–|——————-|
| 所有元数据属性 | Word批注、修订记录(需手动接受/拒绝) |
|---|---|
| 隐藏DocumentID | 隐藏文字(需手动删除) |
| 设备指纹/设备ID | OLE嵌入对象内部元数据 |
| 文件增量残留碎片 | 招投标平台云端追加指纹 |
| EXIF/GPS/缩略图 | 加密/密码保护文件 |
| — | .doc旧格式(需用户手动另存为docx) |
十三、依赖清单
`
# Python包(pip install,wheel自带预编译C++引擎)
mat2
pikepdf # qpdf引擎绑定,替代qpdf.exe
pyexiv2 # exiv2引擎绑定,替代exiv2.exe
# 仍然需要的外部二进制
exiftool # 或 exiftool-rs(Rust版,无Perl依赖)
`
审计读取 vs 取证净化(关键区分)
| 场景 | 可用工具 | 不可用 |
|---|
|——|———-|——–|
| 审计扫描(只读元数据) | pymupdf / pypdf / piexif / Pillow | — |
|---|---|---|
| 取证净化(FULL_PURGE) | pikepdf / pyexiv2 / MAT2 / exiftool | pymupdf / pypdf / piexif / Pillow |
原因: 纯Python轻量库只修改顶层元数据字段,二进制旧碎片仍保留在文件内部,取证工具可恢复旧指纹。必须用底层C++/C引擎的绑定库做完整文件重建。
十四、MCP扩展(V2.0)
`
外部MCP客户端 → MCP薄代理(仅协议转发)→ opensoma.artifact(唯一核心)
`
MCP代理无业务逻辑,所有安全、审计、队列复用原有实现。
十五、版本路线
V1.0(生产可用)
- 全类型取证净化(Office/PDF/图片)
- 完整审计报告
- 风险检测(修订/批注/OLE)
- 外部工具检测 + 一键安装
- 并发安全
- 离线可用
V1.1
- 批量目录处理
- 配置化(并发数、工具路径)
- 风险等级分级告警UI
V2.0
- MCP标准服务(薄代理)
- 多Agent任务队列(优先级、重试)
- 处理历史记录
十六、对标优势
| 对比项 | metadata-cleaner | 独立脚本 | opensoma.artifact |
|---|
|——–|—————–|———|——————-|
| 净化深度 | 字段清空 | 简单清理 | 取证级重建 |
|---|---|---|---|
| 审计能力 | 无 | 无 | 前后快照+报告 |
| 风险检测 | 无 | 无 | 修订/批注/OLE |
| 多Agent | 不支持 | 不支持 | 并发安全 |
| 离线能力 | 需要GUI | 部分 | 100%离线 |
| MCP扩展 | 无 | 无 | 薄代理 |
| 投标适配 | 无 | 无 | 专为标书设计 |
发表回复