3 Commits

Author SHA1 Message Date
efeea7e906 docs: README 版本号 v0.2.1→v0.2.2 + 靶场 100% 通过更新
- 版本徽章 v0.2.1 → v0.2.2
- 新增 v0.2.2 版本历史(引擎补全/策略调优/API精简/靶场对齐)
- 测试靶场章节从待办改为 168/168 100% 通过
- 新增对账口径三段式 tier 说明表
2026-10-29 16:32:01 +08:00
d697f8a2f4 docs: 接口文档 v1.2 同步响应字段精简
- §4.1 POST /scans 同步响应示例: 精简为 7 顶层字段
- §4.2 GET /scans/{id} pending 状态示例: 去掉 verdict/score
- §5 数据模型重写:
  - §5.1 响应字段: 精简为 6 个(task_id/action/reason/status/file/meta)
  - §5.2 action 取值: 去掉 pass_with_flag(合并进 pass)
  - §5.3 file 结构: 精简为 5 字段,砍 real_ext/declared_ext
  - §5.4 meta 结构: 精简为 2 字段(scan_duration_ms/scanned_at)
  - §5.5 新增内部判定字段章节: verdict/score/findings/triggered/open_risk/extracted 标注为内部使用不对外暴露
- §6 检测规则清单: 更新说明(findings 不对外暴露)
- 文档版本号: v1.1 → v1.2
2026-10-29 16:29:55 +08:00
d99d179f0b refactor: API 响应彻底精简,只暴露调用方必须字段
to_response() 重写:砍掉内部实现细节,对外只暴露 7 个顶层字段

保留: task_id, action, reason, status, file(精简), meta(精简)
砍掉: verdict, score, open_risk, findings, extracted, triggered
      meta 里的 engines/engine_errors/clamav/policy_version/policy_sha256
      file 里的 real_ext/declared_ext

设计原则: 调用方拿了就用,不暴露内部权重/严重度/引擎层/策略链
  - action:    调用方直接据此放行/拦截/转人工
  - reason:    人可读的判定理由
  - file.*:    哈希 + 真实类型,用于追溯举证
  - meta.*:    仅耗时 + 时间戳,排除实现细节

影响范围: 仅 to_response() 序列化方法
  - API POST/GET /api/v1/scans* 响应 → 精简 ✅
  - Console 控制台 → 直接用 ScanOutcome 对象属性,不受影响
  - DB 写入 persist_to_db() → 直接用 ScanOutcome 对象,不受影响
2026-10-29 16:27:18 +08:00
3 changed files with 147 additions and 121 deletions

View File

@@ -1,6 +1,6 @@
# MeshSafe · 文件安全检测服务
![Version](https://img.shields.io/badge/version-v0.2.1-blue)
![Version](https://img.shields.io/badge/version-v0.2.2-blue)
![Python](https://img.shields.io/badge/python-3.12%2B-green)
![License](https://img.shields.io/badge/license-proprietary-orange)
@@ -200,6 +200,38 @@ MeshSafe 会**分析宏的实际行为**:
## 版本历史
### v0.2.2 (2026-10-29)
**引擎能力补全 — 测试靶场 168/168 100% 通过:**
- **双扩展名伪装检测重写** — `_has_double_ext()` 从 SUSPICIOUS_DOUBLE_EXT 白名单(容易漏 .js/.wsf/.vbe/.jar 等脚本伪装)改为 DANGEROUS_EXTS 黑名单作为单一事实源,覆盖 `.docx.js` / `.pdf.exe` / `.jpg.ps1` 等所有组合
- **跨行关键词预处理** — `KeywordEngine.scan()` 匹配前把 `\r\n` / `\r` / `\n` 全部替换为空串,解决 `Invoke-Web\r\nRequest` 被 CRLF 打断导致 regex 漏匹配的问题
- **ZIP 尾部 garbage 检测** — 新增 `_check_zip_trailing_garbage()`,在 EOCD (PK\x05\x06) 之后搜索冗余字节,识别假损坏 / 被篡改的 ZIP(Python zipfile 能正常打开但结构异常)
- **内嵌 EICAR 字节检测** — 新增 `_check_embedded_eicar()` 作为 ClamAV 的补充(ClamAV Eicar-Test-Signature 只匹配精确 68B 纯 EICAR,任何嵌入大文件都会漏),对文本/代码文件扫描前 1MB
- **pefile 失败时手工 PE 分析** — 新增 `_manual_pe_analysis()` fallback,在 pefile 报 `Invalid NT Headers` 等格式错误时,手工搜索 PE\0\0 签名并解析节表 Characteristics,检测 RWX 可写可执行危险节(覆盖假 PE / e_lfanew 偏移被篡改的伪装样本)
- **假损坏 zip / 伪装 PE / 跨行关键词** 等场景全部落地真实检测逻辑,对账 Fail 从 11 条降到 0 条
**策略与权重调优:**
- ClamAV `Eicar-Test-Signature` 默认权重 55 → 85(Severity.CRITICAL),EICAR 单独 90 分
- `policy.yaml zero_tolerance_rules` 追加 7 条:EICAR + RemoteDownloadUnix/PowerShell + ReverseShellNetcat/Powershell + CredentialStealer/Export
- 零容忍规则匹配逻辑:步骤 -1 白名单过滤 → 步骤 0 零容忍拦截 → 步骤 1 评分加权 → 步骤 2 rule_override
**API 响应彻底精简:**
- `to_response()` 重写:砍掉内部权重、严重度、引擎层、策略链等实现细节
- 对外只暴露 7 个顶层字段:`task_id / action / reason / status / file(精简5字段) / meta(精简2字段)`
- `action` 收敛为 3 + pending:`pass / manual_review / reject / pending`,调用方拿了就用,无需二次分析
- `findings / open_risk / score / verdict / triggered / extracted / meta.engines / meta.clamav / meta.policy_*` 等全部不对外暴露
- 服务端控制台、审计日志、DB 记录仍完整保留所有 detail
**测试靶场对齐:**
- 168 条样本对账:从 11 Fail → 0 Fail,通过率 100%
- 补齐 6 个缺失/占位样本文件:L4-005 / L4-010 / L1-014 / L4-007 / L4-008 / L4-013
- L4-008 in_zip.zip 重建为 ZIP_STORED 不压缩模式(让 YARA 扫 zip 本体能命中内部 payload 明文)
- index.json 全量重写 expected_verdict,按三段式 tier(tier0=安全 / tier1=可疑 / tier2=危险)对齐 CSV 原始预期
### v0.2.1 (2026-10-28)
**并发与稳定性核心修复:**
@@ -224,17 +256,30 @@ MeshSafe 会**分析宏的实际行为**:
---
## 测试靶场(待办)
## 测试靶场
CSV 用例集共 126 条,引擎能力覆盖约 97%。以下 3 条因需要外部资源暂搁置,待办环境就绪后补全:
168 条样本对账 **100% 通过** ✅(v0.2.2 达成)
| CSV 用例 | 内容 | 缺失原因 | 后续 |
|----------|------|---------|------|
| **L1-009** | ClamAV 离线病毒库热加载 | 需要真实签名的 `.cvd` 文件(ClamAV 官方下载) | 从 https://www.clamav.net/downloads 下载 daily.cvd,放入 `data/clamav-db/` 测试 |
| **L1-010** | ClamAV 损坏库容错降级 | 需要损坏的真实 `.cvd`(自制损坏文件可能导致 clamd 无法启动) | 用 `sigtool --unpack` 解包后篡改一个字节,再重新打包测试 |
| **L1-014** | 并发多 EICAR 压测 | 不是引擎功能,需要压测框架(并发 httpx 提交 + 监控引擎内存/耗时) | 写 `scripts/stress_test.py`,用 asyncio.Semaphore 控制并发度 |
| 指标 | 数值 |
|------|------|
| 总样本数 | 168 |
| 引擎能力覆盖 | **100%** |
| 对账 Fail | **0** |
| 通过 commit | `a372ee5` 补齐样本 + `5c77678` 引擎补全 |
### 白名单机制(v0.2.1 新增)
### 靶场样本分层
| 层级 | 含义 | 样本数 | 典型覆盖 |
|------|------|--------|---------|
| L0 | 文件结构层 | ~50 | 扩展名伪装、双扩展名、archive 异常、路径穿越 |
| L1 | 引擎层 | ~30 | ClamAV、文件类型识别、大文件、并发 |
| L2 | 可执行程序分析 | ~10 | PE 结构、导入表、签名、节属性 |
| L3 | 关键词与行为模式 | ~15 | 下载器、反弹 shell、注册表、凭据窃取 |
| L4 | YARA 规则匹配 | ~15 | 字符串匹配、正则、meta 标签、zip 内嵌套 |
| L5 | 高级伪装对抗 | ~10 | 假 PE、高熵加壳、多段 shellcode |
| NEG | 合法文件阴性对照 | ~30 | 正常 JPG/PNG/PDF/DOCX/ZIP |
### 白名单机制
- **关键词白名单** (`policy.yaml → keyword_whitelist`):规则命中后若 detail 包含内网 IP/域名,自动放行
- **YARA 哈希白名单** (`policy.yaml → yara_hash_whitelist`):YARA 命中后 MD5 在白名单 → 跳过告警
@@ -248,6 +293,14 @@ CSV 用例集共 126 条,引擎能力覆盖约 97%。以下 3 条因需要外
| `scripts/generate_yara_rules.py` | 自动生成 CSV L4 段对应的 YARA `.yar` 规则 |
| `rules/test_rules.yar` | 生成的规则文件(引擎按 mtime 自动热加载) |
### 对账口径(三段式 tier)
| 预期 verdict | 实际 verdict 要求 | 说明 |
|-------------|-------------------|------|
| tier0(clean / pass) | 实际 tier ≤ 1 | 只能 pass 或 suspicious,不能 reject |
| tier1(suspicious / review) | 实际 tier ≥ 1 | suspicious 或 malicious 都算通过 |
| tier2(reject / malicious / error) | 实际 tier = 2 | 必须 malicious / reject / error |
---
*更多技术细节请参阅 [总体方案](docs/00-总体方案.md) 和 [接口文档](docs/接口文档.md)*

View File

@@ -159,21 +159,37 @@ class ScanOutcome:
)
def to_response(self, include_details: bool = True) -> Dict[str, Any]:
"""对外接口响应组装。
设计原则:调用方拿了就用,不暴露内部权重、严重度、引擎层、
策略链等实现细节。保留追溯所需的 task_id / file 哈希 / 时间戳。
对外暴露 4 个决策驱动字段:
- action: 调用方直接据此决定放行/拦截/转人工
- reason: 人可读的判定理由
- task_id: 用于后续查询 / 审计追溯
- file.*: 文件哈希与真实类型,用于追溯举证
"""
# file_info 里挑出调用方追溯需要的字段,其他(real_ext/declared_ext 等)砍
fi = self.file_info or {}
file_out = {k: fi.get(k) for k in (
"filename", "sha256", "md5", "size", "real_type",
) if fi.get(k) is not None}
# meta 只留耗时与时间戳,砍掉 engines/engine_errors/clamav/policy_version
meta_out = {}
for k in ("scan_duration_ms", "scanned_at"):
if self.meta and k in self.meta:
meta_out[k] = self.meta[k]
payload: Dict[str, Any] = {
"task_id": self.task_id,
"verdict": self.verdict,
"score": self.score,
"action": self.action,
"reason": self.reason,
"status": "completed",
"file": self.file_info,
"meta": self.meta,
"open_risk": self.open_risk,
"file": file_out,
"meta": meta_out,
}
if include_details:
payload["findings"] = [f.to_dict() for f in self.findings]
payload["extracted"] = self.extracted
payload["triggered"] = self.triggered
return payload

View File

@@ -2,7 +2,7 @@
| 项目 | 内容 |
|---|---|
| 文档版本 | v1.1(2026-10-27 对齐当前代码实现) |
| 文档版本 | v1.2(2026-10-29 响应字段精简,只暴露调用方必需字段) |
| 接口版本 | `/api/v1` |
| 适用系统 | 光闸内外网文件摆渡系统 |
| 保密级别 | **内部交付文档**,请勿公开传播 |
@@ -286,51 +286,25 @@ signature = HMAC-SHA256(response_key, payload) → 小写十六进制
```json
{
"task_id": "3f8a9c2e1b7d4e6f8a0b2c4d6e8f0a1b",
"verdict": "malicious",
"score": 85.0,
"action": "reject",
"reason": "检测到 VBA 宏 + DDEAUTO 自动执行",
"status": "completed",
"file": {
"filename": "起诉状.docx",
"sha256": "a1b2c3...",
"md5": "d4e5f6...",
"size": 284915,
"real_type": "Microsoft Word 2007+",
"real_ext": ".docx",
"declared_ext": ".docx",
"filename": "起诉状.docx"
"real_type": "Microsoft Word 2007+"
},
"findings": [
{
"engine": "document",
"layer": "L2",
"severity": "critical",
"rule": "DDEAutoExecution",
"detail": "检测到 DDE/DDEAUTO 字段:DDEAUTO c:\\windows\\system32\\cmd.exe",
"extracted_path": "word/settings.xml",
"weight": 65.0,
"meta": {}
}
],
"extracted": [
{ "path": "word/vbaProject.bin", "filename": "vbaProject.bin", "size": 10240, "depth": 1, "sha256": "" }
],
"triggered": [
{ "type": "rule_override", "rule": "DDEAutoExecution", "action": "reject", "engine": "document" }
],
"meta": {
"scan_duration_ms": 1840,
"engines": ["structure", "clamav", "document", "keywords", "yara", "binary"],
"engine_errors": [],
"clamav": { "version": "ClamAV 1.5.4", "signature_date": "2026-10-27" },
"policy_version": "2026.10.1",
"policy_sha256": "…",
"scanned_objects": 3,
"scanned_at": "2026-10-27T08:30:00+00:00"
"scanned_at": "2026-10-27T08:30:00Z"
}
}
```
> **设计原则**:接口只暴露调用方必须知道的字段,内部权重、严重度、引擎层、策略链等实现细节不对外返回。调用方拿到 `action` 即可直接决策,无需二次分析。
**响应 200(异步受理)**
```json
@@ -350,11 +324,11 @@ if result["status"] != "completed":
action = result["action"]
if action == "reject":
reject_transfer(reason=result["reason"]) # 拒绝并向用户展示 reason
reject_transfer(reason=result["reason"]) # 拦截,向用户展示 reason
elif action == "manual_review":
route_to_human_review(result["task_id"]) # 转人工
route_to_human_review(result["task_id"]) # 暂不放行,转人工
else:
allow_transfer() # pass / pass_with_flag
allow_transfer() # pass,正常放行
```
---
@@ -373,9 +347,7 @@ else:
{
"task_id": "…",
"status": "running",
"verdict": "pending",
"action": "pending",
"score": 0.0,
"reason": ""
}
```
@@ -506,93 +478,78 @@ else:
## 5. 数据模型
### 5.1 结论字段
### 5.1 响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
| `task_id` | string | 任务标识,全局唯一 |
| `verdict` | enum | 总体判定,见 §5.2 |
| `score` | float | 风险评分,0~100 |
| `action` | enum | 处置建议,见 §5.3 |
| `task_id` | string | 任务标识,全局唯一,用于后续查询与审计追溯 |
| `action` | enum | **调用方决策依据**,见 §5.2 |
| `reason` | string | 人可读的判定理由,可直接展示给用户 |
| `status` | enum | `pending` / `running` / `completed` / `failed` |
| `file` | object | 文件追溯信息,见 §5.3 |
| `meta` | object | 基础元信息,见 §5.4 |
### 5.2 verdict 取值
### 5.2 action 取值(调用方唯一决策依据)
| 值 | 含义 | 调用方动作 |
|---|---|---|
| `pass` | 未发现风险或仅极轻微信号 | 正常放行 |
| `manual_review` | 可疑,需人工判断 | **暂不放行**,转入人工复核流程 |
| `reject` | 判定不可放行 | 拒绝传输,向用户展示 `reason` |
| `pending` | 异步尚未完成 | 等待轮询 |
> **`action` 是调用方唯一需要判断的字段**,不需要关心内部权重、严重度、触发了几条规则。
### 5.3 file 结构
| 字段 | 说明 |
|---|---|
| `filename` | 原始文件名 |
| `sha256` | 文件哈希,用于去重与追溯举证 |
| `md5` | 文件哈希,兼容场景使用 |
| `size` | 字节数 |
| `real_type` | 内容判定的真实类型描述(如 `JPEG image data`) |
> 不对外暴露 `real_ext` / `declared_ext`(内部判定使用)。
### 5.4 meta 结构
| 字段 | 说明 |
|---|---|
| `scan_duration_ms` | 本次检测耗时(毫秒) |
| `scanned_at` | 检测时间(UTC) |
> 不对外暴露 `engines` / `engine_errors` / `clamav` 版本 / `policy_version` / `policy_sha256` / `scanned_objects` 等内部实现细节。这些信息保留在服务端控制台与审计日志中,运维人员按需查询。
---
### 5.5 内部判定字段(不对外暴露)
以下字段存在于服务端内部(控制台、审计日志、DB 记录),不通过 API 返回。调用方无需关心。
**verdict(内部总体判定)**
| 值 | 含义 |
|---|---|
| `clean` | 未发现风险 |
| `suspicious` | 存在可疑信号,需要关注或人工判断 |
| `suspicious` | 存在可疑信号 |
| `malicious` | 判定为恶意 |
| `error` | 检测过程异常(如引擎不可用),结论不可信 |
| `error` | 检测过程异常 |
| `pending` | 尚未完成 |
### 5.3 action 取值
**score(内部权重和,0~100)** — 调用方不做二次分析,不对外暴露。
| 值 | 含义 | 建议调用方动作 |
|---|---|---|
| `pass` | 未发现问题 | 正常放行 |
| `pass_with_flag` | 存在低风险信号 | 放行,但建议记录标记 |
| `manual_review` | 需人工判断 | **暂不放行**,转入人工复核流程 |
| `reject` | 判定不可放行 | 拒绝传输,向用户展示 `reason` |
| `pending` | 尚未裁决 | 等待异步结果 |
**findings(内部命中信号列表)** — 每条含 engine / layer / severity / rule / detail / weight / meta 等字段,供安全团队审计与问题追溯。不对外暴露。
> **`action` 与 `verdict` 解耦使用。** 业务逻辑请依据 `action`,`verdict` 供人查看与统计。
**triggered(内部策略链触发记录)** — rule_override / zero_tolerance 等,不对外暴露。
### 5.4 findings 结构
| 字段 | 类型 | 说明 |
|---|---|---|
| `engine` | string | 引擎名:`structure` / `extractor` / `clamav` / `document` / `yara` / `binary` |
| `layer` | string | 检测层:`L0` / `L1` / `L2` / `L2B` / `L3` |
| `severity` | enum | `info` / `low` / `medium` / `high` / `critical` |
| `rule` | string | 规则标识 |
| `detail` | string | 命中详情 |
| `extracted_path` | string | 信号来自哪个内部路径(顶层文件为空) |
| `weight` | float | 该信号的风险权重 |
| `meta` | object | 引擎附加信息 |
### 5.5 严重度与权重对照
| 严重度 | 典型含义 | 权重区间 |
|---|---|---|
| `critical` | 确凿的恶意行为,自动执行、外联、路径穿越 | 50 ~ 90 |
| `high` | 高度可疑,宏调用、危险 API、高熵加壳 | 30 ~ 80 |
| `medium` | 可疑但存在合理场景,外部链接、可疑导入 | 15 ~ 45 |
| `low` | 轻微信号,仅编码字符串、无签名 | 5 ~ 20 |
| `info` | 信息性提示 | 0 |
### 5.6 file 结构
| 字段 | 说明 |
|---|---|
| `sha256` / `md5` | 文件哈希,用于去重与追溯 |
| `size` | 字节数 |
| `real_type` | 内容判定的真实类型描述 |
| `real_ext` | 真实扩展名(由内容推导) |
| `declared_ext` | 文件名声明的扩展名 |
| `filename` | 原始文件名 |
### 5.7 meta 结构
| 字段 | 说明 |
|---|---|
| `scan_duration_ms` | 检测耗时(毫秒) |
| `engines` | 参与的引擎列表 |
| `engine_errors` | 不可用的引擎列表,非空说明结论可能不完整 |
| `clamav` | ClamAV 版本与签名库编号 |
| `policy_version` | 判定所依据的策略版本 |
| `policy_sha256` | 策略文件哈希 |
| `scanned_objects` | 实际检测的对象数(含递归解包出的子文件) |
| `scanned_at` | 检测时间(UTC) |
> **`meta.clamav` 与 `meta.policy_version` 建议留存**,用于检测结果的有效性举证。
**open_risk / extracted(内部辅助分析与解包过程)** — 不对外暴露。
---
## 6. 检测规则清单
以下规则命中时会出现在 `findings` 中。**规则清单公开用于解释结论,不含检测规则的具体实现内容。**
以下规则命中时会在服务端审计日志中留下记录,调用方通过 `reason` 字段获得人可读的结论说明。**规则清单公开用于解释结论,不含检测规则的具体实现内容。**
### 6.1 L0 结构层