zhaolei c581bfc4bb docs: README v0.2.4 四轮误报标定记录 + YARA 范围文档移入 docs/
第四轮记录:逐规则独立贡献重放(60 条命中/25 条有独立贡献/前 10 条占 92.6%)、
批量按族退役零 verdict 变化、复核地板重构与复测表;另记 meshsafe-cron 自查结论与
ClamAV 签名库新鲜度机制(库龄不影响处置结果是剩余缺口)。遗留项明确 35 条零贡献规则
暂不退役的原因。YARA 检测范围文档从仓库根目录挪进 docs/。
2026-11-02 20:27:39 +08:00

MeshSafe · 文件安全检测服务

Version Python License

光闸摆渡场景下的独立文件安全守门员。 外网文件进来之前,先过 MeshSafe。


一句话介绍

MeshSafe 是一套独立部署的文件安全检测服务,专为光闸内外网摆渡场景设计。它不修改、不清洗文件,只做一件事:判断"这个文件能不能传进内网",把检测结论交还给摆渡系统决定。

一期只做 外网 → 内网 方向——保护内网不被外网文件伤到。


它能做什么

三层风险把关

维度 回答的问题 举个例子
恶意性检测 这个文件本身是不是病毒、木马、勒索? 已知病毒签名、家族特征
可执行性拦截 这个文件打开会不会自动跑代码? 带宏的 Word、带脚本的 PDF、快捷方式
合规性校验 这个文件在传输层面有没有问题? 路径穿越、压缩炸弹、伪装扩展名

两种判定结果

  • 机器执行:告诉摆渡系统"放行 / 拒绝 / 转人工复核"
  • 人工提示:告诉最终打开文件的内网用户"打开时要注意什么"(比如"此文件包含宏,请勿启用")

两条结论独立返回——系统的事归系统,人的事归人。


它能拦住什么

以下是 MeshSafe 一期覆盖的全部核心风险场景:

文档类最常见风险

文件类型 风险场景 MeshSafe 的处置
Word / Excel / PPT (.docm / .xlsm / .pptm) 打开即执行的宏,自动下载病毒、调用系统命令、关闭安全软件 零容忍直接拦截,不走过场
Word / Excel / PPT 宏代码被故意混淆或加密,意图不可知 零容忍直接拦截
Word / Excel (RTF) DDE / DDEAUTO 字段——打开即启动程序 零容忍直接拦截
PDF 打开即执行的 JavaScript 脚本 零容忍直接拦截
PDF LaunchAction——打开即启动外部程序 零容忍直接拦截
所有 Office 文档 包含外部模板注入——借模板执行恶意代码 零容忍直接拦截
所有 Office 文档 嵌入了可执行文件的 OLE 对象 零容忍直接拦截

快捷方式与容器类风险

文件类型 风险场景 MeshSafe 的处置
快捷方式 (.lnk) 指向程序执行 / 远程路径 / 脚本 零容忍直接拦截
帮助文件 (.chm) 包含可执行脚本 零容忍直接拦截
光盘镜像 (.iso) 内部包含可执行文件——双击挂载后直接运行 零容忍直接拦截,挂载后递归扫描内部
压缩包 (.zip / .rar / .7z 等) 解压路径穿越——试图写出解压目录,覆盖系统文件 零容忍直接拦截
压缩包 压缩炸弹——几 KB 解压出几百 MB 甚至上 GB,耗尽服务器资源 拦截并报警
压缩包 解压深度超过 5 层,内部包含可执行文件 自动进入下一轮检测

伪装与迷惑类风险

风险场景 MeshSafe 的处置
双扩展名伪装:invoice.pdf.exe 实际是程序 识破真实类型,拦截
图标伪装:可执行文件用 PDF 图标诱导用户双击 识破真实类型,拦截
声明扩展名与真实类型不一致 拦截并标记
文件名用 Unicode 欺骗(西里尔字母替换拉丁字母) 识破并标记

链接与二维码分级处置

情形 MeshSafe 的处置
文档/图片中的链接命中恶意地址库 零容忍直接拦截
链接指向非常规端口 / IP 直连 / 短链服务 标记为可疑,转人工复核
二维码解码后内容为上述情形 同上
普通 http / https 链接 放行但提示"请确认来源"

加密文件处理

情形 MeshSafe 的处置
加密的 Office 文档 默认拒绝——无法确认内部安全
加密的压缩包 默认拒绝——无法确认内部安全

Linux 桌面环境特有威胁(一期重点覆盖)

内网终端是麒麟桌面/UOS(纯 Linux),因此 Linux 特有威胁载体必须覆盖:

威胁场景 风险说明 MeshSafe 的处置
Linux 原生可执行文件(ELF) 传统杀毒不认识,可直接在终端运行 零容忍直接拦截
Linux 快捷方式(.desktop 文件) 看起来是桌面图标,实际 Exec= 字段可指向任意命令 零容忍直接拦截,校验 Exec 字段是否含 shell 调用、下载器
Shell / Python / Perl / Ruby 脚本 双击可执行,含恶意系统调用(下载、反弹 Shell) 零容忍直接拦截,识别危险函数
AppImage / Flatpak / Snap 便携包 跨发行版可执行格式,双击即运行 零容忍直接拦截,内部递归扫描
压缩包内混有 shell 脚本 / .run / .bin 解压后用户容易误执行 自动进入下一轮检测

纵深防御:虽然内网是 Linux,Windows 专属载体(快捷方式 .lnk、帮助文件 .chm)仍保持检测——文件可能通过其他路径流向运维人员的 Windows 工作站。


方案亮点(为什么 MeshSafe 和传统杀毒不一样)

亮点一:「打开即执行」载体零容忍——独立于评分体系

传统杀毒的逻辑是:是不是已知恶意样本? 匹配不到签名就放行。

但在光闸场景里,真正的高频风险不是"已知病毒",而是**"打开就自动运行"的载体**——宏文档、DDE 字段、LNK 快捷方式。这类东西杀毒扫出来说"干净",但用户双击后宏即刻执行。

MeshSafe 的逻辑反过来:只要存在打开即执行的载体,就零容忍拦截。这是一条独立于评分体系的硬性清单——不累计权重、不走过场,命中即拒。

亮点二:宏行为分级——不是"有没有宏",而是"宏做了什么"

内网人员需要打开带宏的公文模板(自动编号、签章定位等),不能简单地把"含宏"等同于"拒绝"。

MeshSafe 会分析宏的实际行为:

宏的行为 MeshSafe 判定
自动执行 + 调用系统命令 / 外联网络 零容忍拒绝
宏代码被混淆 / 加密(意图不可知) 零容忍拒绝
自动执行 + 仅本地无害操作(编号、排版) 放行或标记
无自动执行,仅手动触发 标记放行 / 转复核

亮点三:合法公文模板例外——五道保险受控放行

对于业务上确实需要打开的合法宏模板,MeshSafe 提供受控的例外机制,但有五道保险:

保险 怎么防绕过
指纹绑定 例外按文件内容精确匹配,换一个字节就失效
文件名匹配 指纹 + 文件名双重校验,防止不同文件复用
必须到期 所有例外必须有到期时间,最长不超过一年
审批留痕 谁批的、为什么批、批了什么,全部留痕可追溯
行为前提 就算在例外清单里,如果宏检测到危险行为(调用 Shell、下载文件),照样触发零容忍

亮点四:打开风险独立提示

检测结论分两层:

  • 系统判定层:摆渡系统看这个——放行 / 拒绝 / 转复核
  • 人工提示层:内网用户看这个——"此文件包含宏,请勿启用"、"请勿直接双击此 ISO 文件"

两层独立演进。即使摆渡系统选择忽略人工提示字段,内网侧安全审计面板也能单独展示。

亮点五:防篡改审计

所有检测记录、策略变更、例外增删都进入链式审计日志——每条记录的哈希值包含前一条记录的哈希。任何一条被篡改,后续链全部断裂可检出。


完整的检测能力清单(一期)

能力 覆盖范围
真实文件类型识别(不信任扩展名) 全部文件
递归安全解包(最多 5 层) 常见压缩格式
已知恶意样本特征匹配 全部文件
宏代码提取与行为分析 Word / Excel / PPT 全系列
DDE / DDEAUTO 自动执行检测 复合文档
PDF 脚本与动作检测 PDF
文档内嵌文件检测 复合文档
文档深度解析 外部链接、模板注入、隐藏变量
快捷方式深度解析 目标路径、参数、远程链接
帮助文件类型校验 结构完整性检测
光盘镜像挂载后递归扫描 内部可执行文件检测
未知恶意样本家族匹配 可执行程序 / 脚本 / 内存马
可执行程序结构分析 异常特征、签名目录、导入表
外部链接与二维码提取 文档和图片
恶意链接库查询 分级处置
零容忍规则集(25 条) 打开即执行载体全覆盖 + 高危行为词表
宏行为例外机制 合法公文模板受控放行
引擎故障安全降级 任何检测引擎不可用时整体拒绝(安全优先)
同步 + 异步双模式 小文件同步返回,大文件异步轮询
策略热重载 改配置不用重启
控制台权限分级 管理员 / 运维 / 只读三档
防篡改审计日志 链式哈希校验,任何一条被改后续全部断裂可检出
签名库版本追踪 检测有效性可举证

未来可支持的扩展方向

以下方向架构已预留,但一期不实现:

方向 价值 启动前提
内网 → 外网内容审计 判断内部内容是否允许外传(涉密 / 隐私 / 违规) 需先明确业务密级规则和具体场景,与一期的"技术判据"属不同问题域
动态沙箱深检 在隔离环境中实际运行可疑文件,观察行为(释放文件、外联 C2 等) 需要 Windows 虚拟化环境 + 国产化终端适配验证;场景收益有限(一期以文档为主)
大模型语义分析 辅助识别诱导话术、仿冒公文模板、上下文关联钓鱼 需要内网推理算力;大模型输出仅作为辅助信号,不单独裁决
更多零容忍规则 随新威胁形态出现,可向规则清单中增量添加 规则必须满足"命中即有害 + 可稳定判定"的准入条件
内网特征词表引擎 识别内网特有的模板、关键词、命名规律,辅助例外判断和伪装识别 一期已有初步框架,可独立推进

所有扩展方向的接口和数据结构在一期架构中已预留扩展位,未来接入无需重构。


版本历史

v0.2.4 (2026-11-02)

误报清零专项(现场反馈的三类误判):

真实 PPT(内嵌图表对象)、电脑上正常无病毒的 EXE/DLL、写有安装命令的 skill.md 文档,在旧策略下分别被判「中危可疑」「严重拒绝」「零容忍拒绝」。根因不是某一个规则,而是弱信号可以靠权重堆叠越过 block_score=70,以及关键词/YARA 用裸子串匹配。本轮把"载体信号"和"行为信号"彻底分档,行为如下:

  • 文档内嵌对象分级 — documents.py _scan_ooxml() 不再把 zip 目录条目当成内嵌对象;对 ppt/embeddings/* 逐条做魔数嗅探:MZ/ELF/Mach-O 或 .exe/.dll/.js/... → EmbeddedExecutable(严重 75,零容忍);Office 部件(.bin 里装的是 OLE/子文档)→ EmbeddedObject(低危 5);其余 → 中危 20。正常 PPT 的图表/嵌入对象不再产生 20 分可疑
  • YARA 判定改用导入表邻接窗口 — pe.yar 全面重写:API 名必须在同一张导入名表内相邻 512B才算命中($b in (@a[i]-512..@a[i]+512)),导出表、i18n 资源、语言包里的字符串堆不再触发;RAT 规则要求真实 C2 痕迹(IP:端口 / .exe URL / 网关面板 php),打包器 UPX 降为 low
  • Linux/脚本规则去恒真 — linux.yar 修掉 $a and $b 这类"两个独立字符串同时存在即命中"的恒真写法;ettercap/responder/nmap 加词边界并需与攻击参数成组;LD_PRELOAD 只在指向可写临时目录或出现 ld.so.preload 时命中;C7_Misc_Ruby_Lua_ReverseShell 需要 exec 与 TCPSocket/loadstring 同窗口
  • PE 信号分档(不再单独定罪) — binary.py:导入 API 分三档,单个注入级 API 只记 5 分(低危),≥2 个才记 20 分;WinExec/ShellExecuteEx/CreateService/InternetOpenUrl 从"高危"降到能力档;IsDebuggerPresent/GetTickCount/FindWindow 等反调试探测记 0 分;调试信息、异常时间戳 10 → 0;未签名 20 → 10
  • YARA 严重度取 meta.risk_level — 旧版只读 tag 和规则名,规则文件里的 risk_level 被完全忽略;现在 severity/weight 一律先取 meta,再按 policy.yaml yara_rule_weights 覆盖,档位统一为 critical=85 / high=60 / medium=30 / low=10,44 条规则全部按 meta 重新归位
  • 可执行扩展名降为"载体分" — DangerousExecutableExtension 45 → 35:单独命中仍进人工复核(≥30),但不再充当把后续中低置信度信号顶过 70 分的底座
  • 关键词引擎不再无条件删换行 — v0.2.2 的"整篇文本去换行"是 skill.md 误报的直接原因(上一行行尾和下一行行首黏成一句话)。改为默认按行匹配,仅显式 cross_line: true 的规则额外跑一遍换行压成空格的文本(保留分隔符,兼容续行绕过,不再产生黏连)
  • 下载类词表重写 + 移出零容忍 — 新增 RemoteDownloadExec(下载即执行:curl|sh、wget -O … && chmod、iwr|iex、certutil -urlcache … .exe、mkfifo+nc,40 分)与 PayloadDropToDisk(下载落地成 .exe/.ps1/.so/…,45 分);RemoteDownloadUnix / RemoteDownloadPowerShell 从"零容忍直接拒"降为 12 分信息项。裸 curl https://… 出现在 README/运维文档里不再拦截,内网源站另有白名单放行
  • 反弹 Shell 词表只认执行原语 — ReverseShellNetcat 要求 nc|ncat|netcat 带 -e/-c 且跟 shell 路径(/bin/、*sh、cmd.exe),或 /dev/tcp/、bash -i >&;裸 nc、nc -l、nc -z(速查文档、二进制字符串表里必然出现)不再命中;英文公文里的 "NC -E" 之类缩写组合也不再误触
  • rules/ransom.yar / webshell.yar 收尾 — 卷影删除不再接受裸 vss / ShadowCopy 子串与加密 API 同框(正常备份组件的必然组合),要求 vssadmin … delete … shadows;混淆 PHP 马要求存在 <?php 或超全局变量上下文

验证结果(离线,ClamAV/DB 不在本机):

指标 修复前 修复后
32 个干净样本(系统 DLL/EXE + Git/node 工具 + 真实 PPTX)裁决 18 malicious/reject、14 suspicious、0 pass 0 reject、31 manual_review(仅因"是可执行文件")、1 pass(PPTX 全程 0 命中)
2162 个真实 Windows 二进制 YARA 误报 407 条命中 / 14 条规则(C1_PE_Downloader_Loader 单独 290 条,ActionCenter.dll 一类正常组件全中) 4 条,全部为 C1_PE_ProcessInjection 命中调试/注入基础设施组件(dbgeng.dll、ttdinject.exe、PayloadRestrictions.dll、VsGraphics*),属真实能力特征
靶场 91 个恶意样本 YARA 覆盖 全部命中 全部命中,无一条漏检
168 条样本逐条对账(同一离线管线,HEAD vs 本轮) 124 Fail 124 Fail,其中 2 条由 Fail 转 Pass,仅 L5-005_injection_imports.exe 从 reject 变为 manual_review(928B 占位假 PE,无导入表,旧结论完全靠权重堆叠),expected_verdict 已同步为 review

处置口径(本轮已确认,不再作为开放项):

  • 干净 EXE/DLL 落人工复核。 可执行文件本身是载体,单独命中 DangerousExecutableExtension(35 分)落在 review_score 区间是设计目标,不改自动放行;"严重/拒绝"只留给真实恶意特征。
  • 调试/注入基础设施组件的命中保留,不做白名单。 dbgeng.dll、ttdinject.exe、PayloadRestrictions.dll、VsGraphics* 这 4 个 Windows 组件确实具备进程注入能力,C1_PE_ProcessInjection 命中它们属真阳性,维持现状(critical=85,单条即判恶意拒绝)。不使用 yara_hash_whitelist 豁免,也不在规则里判断 Microsoft 版本信息——版本信息可伪造,任何基于它的放行都会开出一条绕过路径;这类文件本就不该走通用摆渡通道传输,如业务确有需要,由管理员单独确认后处置。

容器内实测(v0.2.4 部署后,ClamAV + PostgreSQL 全部可达):

指标 结果
6 个引擎可用性 engine_errors 0 条(168 个样本全程无引擎降级)
三个原始误报复测 北京简介 (1).pptx → clean/pass、0 命中(现已真正展开 61 个 OOXML 部件仍 0 命中);SKILL.md → clean/pass、0 命中;干净 PE(dxdiag.exe 50 分、shell32.dll 65 分、cmd.exe 45 分)→ manual_review,均低于 block_score=70
靶场 168 条(控制台对账口径) 168/168 通过(初测 4 条未过,见下)

第二轮修补的 4 条检出缺口(容器内才暴露):

  • zip/OOXML 成员从未参与扫描(HEAD 既有缺陷) — extractor.py 的成员写盘循环用 while True: … if not chunk: break 正常结束,else: 分支因此永不执行,解出来的成员一个都没进扫描队列:压缩包和 docx/xlsx/pptx 正文里的高危串全部放行。改为显式 overflow 标志后 scanned_objects 才真正大于 1;tar 分支同修(原先超限成员既不丢弃也不报,还把截断文件塞进队列)。这条修复让 L3-015(docx 正文的 Invoke-WebRequest)、L3-023(zip 内的 mimikatz)从 0 命中变检出。
  • 不可解析即放行 — 畸形 OLE 触发 olefile 内部异常后被 documents.py 的宽 except 兜成 OleToolsUnavailable,权重 0 → clean/pass。改为 medium/30,宏解析不了的文档一律进人工复核。
  • 换行拆分绕过 — L3-009 把 Invoke-WebRequest 拆成两行,-OutFile evil.exe 因跨行漏检。RemoteDownloadExec/PayloadDropToDisk 恢复 cross_line,并让 cmdlet 词容忍 invoke-web_request/换行拆分形态;仍要求"下载工具 + 执行或落地动作"同框,因此 SKILL.md、README 类文档保持 0 命中。
  • 裸下载命令权重回正 — 为压误报把 RemoteDownloadPowerShell/RemoteDownloadUnix 降到 12 分是矫枉过正:靶场里没有任何"期望放行"的样本含下载命令,误报源头是 curl|wget -o 任意值 这种松散写法而非权重。现改回 30/medium 并删掉裸 -o 分支,L3-018/L3-024/L3-025 随之回到 review。

第三轮:生产日志暴露的 4 个问题(现场上传 go1.25.0.windows-amd64.msi / nvidiaInspector.exe):

  • 被拒绝的文件在库里查不到记录(留痕丢失) — scan_tasks.real_type 是 VARCHAR(256),而这个 MSI 的 libmagic 描述串有 632 字符(CFB 会把 Title/Subject/Creating Application/Number of Pages 全部拼进描述)。Postgres 对超长值不是截断存储而是整条 INSERT 报错回滚,因此这次扫描的记录连同 3 条 scan_findings 一起丢失——一个 malicious/reject 的文件没有任何痕迹。修复三处:模型列宽 256 → 512(app/models.py)、线上 ALTER TABLE scan_tasks ALTER COLUMN real_type TYPE VARCHAR(512)(已入 scripts/migrate.sql 并在 meshsafe-postgres 执行)、落库前按列宽截断 filename/declared_ext/real_type/extracted_path(scanner.py 新增 _clip())。外部可控长度(libmagic 串、归档成员路径、用户文件名)从此不可能再撑爆 INSERT:实测 600 字符文件名 + 900 字符 reason + 1600 字符成员路径 → 记录与 findings 全部入库(512/512/1024)。
  • verdict=malicious 却给出 open_risk.level=low — 同一条日志里的自相矛盾。compute_open_risk() 只看命中因子,不看裁决。补上裁决下限:malicious/error/reject 一律至少 critical,并把 .msi/.jar/.ps1/.lnk/.hta/… 这类真实载体扩展名纳入 contains_executable。
  • C8_Ransomware_Note_Filename 把 Go 官方安装包判成勒索 — 单文件实测命中量:$wallet 76,526 次(1[A-Za-z0-9]{25,34} 无边界,任何 GUID/哈希子串都算比特币地址)、$bitcoin 44 次(压缩流里的 BTC/Xmr 随机字节)、$n4 命中安装包内字面量 README.txt。三者拼出 70 分 → 官方 MSI 被拒。仓库自己的 tests/samples/index.json 同样中招(ransom 是 Ransomware 的子串)。重写为:文件名只保留家族专有名 + 扩展名(去掉裸 README.txt/_readme.txt,\b_readme 不再命中 normal_readme.txt);赎金语言必须是完整词组(bitcoin address、send the money、\bransom\b note);地址按 base58/bech32/ Monero 真实字符集与长度并用 \b 锚定整个 token;条件要求文件名 + (加密 API + 赎金语言)或(赎金语言 + 钱包地址)。修复后 MSI 该规则 0 命中,index.json/_scan_results.json 0 命中,真勒索样本 ransom_dropper.exe.txt 仍命中(100 分拒绝)。
  • PeParseFailed 打在非 PE 容器上 — BINARY_EXTS 里的 .msi/.msp/.jar/.apk/.war/.so/.elf 本来就不是 PE,pefile 解析必然抛错,于是每个合法安装包/共享库白送 10 分。新增 _is_native_container():内容魔数与声明格式一致(MSI=CFB、JAR/APK=ZIP、SO/ELF=ELF)→ 不做 PE 分析。真正结构损坏的 PE(L5-010_bad_mz.exe、0 字节 exe、截断 exe)仍照常命中。
  • RemoteDownloadPowerShell 把 .NET 标识符当下载动作 — 该词表原本是裸子串 DownloadString|DownloadFile,nvidiaInspector.exe 命中 add_DownloadFileCompleted、DownloadFileAsync 两处(任何托管程序的标识符表都有),白得 30 分,把 45 分的人工复核顶成 75 分拒绝。现改为:cmdlet 名(Invoke-WebRequest/Invoke-RestMethod/Start-BitsTransfer/BitsAdmin)带词边界单独命中;.NET/Win32 方法名必须与 URL/IP 同框(±40 字符)才算下载。攻击链没有削弱——macro_alert.docm 的 DownloadString(http://evil.com/ps1)、IEX (New-Object Net.WebClient)… 仍分别命中本规则与 RemoteDownloadExec。

第三轮复测(容器内,策略版本 2026.10.2-9affe243):

指标 结果
靶场 168 条(控制台 tier 口径) 168 Pass / 0 Fail,engine_errors 0 条,总耗时 34.5s(最慢 L0-020_zip_bomb.zip 13.4s)
go1.25.0.windows-amd64.msi malicious/reject 70 → suspicious/manual_review 30,仅剩 DangerousExecutableExtension;real_type 632 字符被截断到 512,记录 + findings 正常入库
.NET 标识符噪声(add_DownloadFileCompleted/DownloadFileAsync/URLDownloadToFileA) clean/pass,0 命中(对应 nvidiaInspector.exe 由 75 拒绝回到 45 人工复核)
干净 PE 基线 cmd.exe 45 / dxdiag.exe 50 / shell32.dll 65 → 全部 manual_review,均低于 block_score=70
勒索真样本 ransom_dropper.exe.txt 仍 malicious/reject 100(C8_Ransomware_Encryption 85 + VolumeShadow 60 + Note_Filename 30)

ClamAV(L1)命中的处置链路与本轮两处修复:

  • 机制 — ClamAV 签名名是动态的(Win.Trojan.Agent-1、Txt.Malware.Sustes-6779550-1…),进不了 25 条静态零容忍名单,所以走权重分档 + 阈值:critical=95 / EICAR=90 / 默认 malware=85 / heuristic·packed=70 / PUA=25。block_score=70 意味着除 PUA 外单条 ClamAV 命中即 malicious/reject;PUA 档 25 分落 pass_with_flag,与其他信号叠加后进人工复核。零容忍名单里只显式登记了 Eicar-Test-Signature(等保"检测到即阻断",config/policy.yaml:63),命中直接 100 分。
  • 没有绕过口子 — 步骤 -1 的两类白名单只作用于 keywords(关键词白名单)和 yara(哈希白名单)引擎,clamav 的 finding 不会被过滤;宏行为例外表只覆盖 VBA 零容忍规则。engine_failure_mode: fail_closed 下 clamd 连不上/超时会判 error/reject。
  • 修复一:分档实际是死代码 — _judge_signature() 用小写关键字去比对 signature.upper(),因此 95 与 70 两档永不命中,除 EICAR/PUA 外所有签名一律落默认 85(注释里的档位与真实行为不符)。关键字改为大写后实测:Win.Ransomware/Win.Trojan/Win.Dropper(含 Win.Trojan.GenericKD)→ 95 critical,Win.Heur.Gen/Win.Packed.Upx/Win.Riskware.Tool → 70 high,Unix.Coinminer(无家族关键字)→ 85 默认,PUA-OSX.iAdWare → 25 medium。
  • 修复二:1G~1.5G 的文件在 L1 完全裸奔 — clamd 默认 MaxFileSize=1G,超限拒扫时引擎返回 ok=True + meta.skipped 且 0 条 finding,而策略 max_file_size 是 1.5GB → 这段区间的文件其他 5 个引擎照跑,结论可以是 clean/pass,即"AV 没查过"被当成"AV 没查出"。现补 ClamAVSkippedSizeLimit(medium/30):实测仅此一条 → suspicious/manual_review 30(修复前同一场景 clean/pass 0),与"不可扫描不放行"口径一致。

第四轮:规则独立贡献数对账 + 复核地板解耦(策略版本 2026.10.2-d3b747a4):

  • 口径 — 从 scan_findings 重建 findings,用 PolicyEngine.decide() 对近 14 天 3,661 条真实扫描(排除 test/test_center,其中 console_upload 3,586 条、真实摆渡通道 75 条)做反事实重放:逐条规则剔除其全部命中后重判,处置档位变化即该规则的独立贡献。先验证重放器可信度:改造前 3,660/3,661 = 99.97% 与历史入库 action 一致(唯一差异是本轮之前收紧的 RemoteDownloadUnix)。
  • 裁决高度集中 — 60 条有命中的规则里只有 25 条有独立贡献,前 10 条承担 92.6%(1,700/1,836 次跨档)。DangerousExecutableExtension 一条就贡献 1,074 次(其中 1,070 次是"把可执行文件送进人工复核"这条口径本身);OfficeEncrypted 103 次走的是策略步骤 4 的加密分支而不是它的 25 分权重。35 条规则独立贡献为 0,分三类:同族冗余(C8_*/Email*/Pdf*/PathTraversalBlocked/C7_..._NC 总与另一条同批命中)、分数被挡(其余信号已 ≥70)、纯留痕(PeParseFailed 806 次命中、610 次连自己的档位都没到;OoxmlParseFailed/OleToolsUnavailable/PEArchInfo 权重 0)。
  • 退役必须按族,不能按条 — 按族只保留权重最高代表(C8 族留 C8_Ransomware_Encryption、Email 族留 EmailAttachmentWebshell、PathTraversalBlocked、PdfOpenAction、Sustes)在 3,661 条上重放:90+90+92+76+76 条扫描裁决全部不变,零影响;但把 35 条零贡献规则同时退役会让 258 条 reject 降级(255 条直接变 pass,全是 .eml/.txt/.pdf)——它们互为备份,批量删等于整族失明。Txt.Malware.Sustes-* 这类是 ClamAV 签名名,属于外部信号映射,不在可退役范围。
  • 结构风险修复:manual_review 覆盖从"注 65 分"改成"处置地板" — 旧实现(app/services/policy.py 步骤 5)把评分强推到 65 再累加其余权重,同时跳过该规则自身权重:注进去的 65 分不是证据,任何 ≥5 分噪声叠加就翻成 reject,且显示分数与真实证据脱钩。现在步骤 5 只登记地板标志,步骤 6 全量累加权重,步骤 9(新增)在阈值裁决后把低于复核的处置抬到 manual_review(已是 reject 的不回退)。同时把 DangerousExecutableExtension 登记为 manual_review 覆盖——光闸"EXE/DLL/脚本不自动放行"从此由显式地板保证,不再依赖 weight=30/35 恰好等于 review_score 下界 30 这个数值巧合。
  • 第四轮复测:
指标 结果
靶场 168 条(控制台 tier 口径) 168 Pass / 0 Fail,engine_errors 0 条,总耗时 34.6s(最慢 L0-020_zip_bomb.zip 12.3s)
3,661 条真实扫描重放 diff 只有 1 条变化,且是本轮之前那处 RemoteDownloadUnix 收紧的预期结果;处置分布与改造前完全一致:reject 2,005 / manual_review 1,091 / pass 561 / pass_with_flag 4(放松 0 条降到 pass,收紧 0 条)
分数纠偏 14 条 ArchiveContainsExecutable 扫描 action 不变、分数由人为的 65 回落为真实的 30
干净 PE 基线(实扫) cmd.exe 45 / dxdiag.exe 50 / shell32.dll 65 → 全部 manual_review,与第三轮逐项一致
解耦反证 把 review_score 下界抬到 36/40/50,或把 DEE 权重降到 10:无地板时干净可执行文件静默变 pass_with_flag(10~30 分放行),有地板时恒为 manual_review

meshsafe-cron 自查:定时任务自引入以来一次都没成功执行过。

  • 根因 — Settings.admin_password 是必填字段(app/config.py:51),但 cron 容器的 environment 里没有 MESHSAFE_ADMIN_PASSWORD。于是任何走 app.db → get_settings() 的路径在 import 阶段就抛 ValidationError,archive_old_tasks.py --apply 与 VACUUM ANALYZE 双双崩溃,run() 只往 docker 日志写一行 ❌ 返回码 1,容器状态照常 Up——任务挂了但没有任何存活信号。时间线:必填字段来自初始提交 16f134e(10-28),cron 来自 da8496a(10-29),即从上线第一天起就没跑过。旁证:policy_versions 现存 10 行而归档策略是"留 5 删 5",scan_tasks_archive 空表。
  • 修复一(env) — docker-compose.yml cron 补 MESHSAFE_ADMIN_PASSWORD=cron-unused-placeholder。cron 不做控制台认证,用占位值而不是复制真实口令(容器 env 会出现在 docker inspect 里)。实测归档 dry-run 在 cron 容器内跑通。
  • 修复二(VACUUM 事务块) — run_vacuum() 原来用 engine.begin() 包住 VACUUM ANALYZE,Postgres 直接 ActiveSqlTransaction: VACUUM cannot run inside a transaction block,即使 env 修好,VACUUM 仍会整批失败。改为 engine.connect().execution_options(isolation_level="AUTOCOMMIT");实测 6 张表 VACUUM ANALYZE 共 0.25s 全部完成。
  • 修复三(日志泄密) — pydantic 的 ValidationError 会把整个 settings dict 打进 stderr,而 run() 原样逐行 log.warning,等于把 DB 连接串(含口令)与 MESHSAFE_MASTER_KEY 尾部明文写进 cron 日志。新增 _redact() 按 env 值替换为 ***(覆盖 MESHSAFE_DB_URL/MASTER_KEY/ADMIN_PASSWORD/API_KEY/API_CLIENTS),实测两类密钥串都不再出现在输出里。

ClamAV 签名库"过期":机制、阈值与一个真实缺口。

  • "过期"不是失效 — .cvd 库不会到期停用,clamd 照样拿它扫描,结论照常产出。所谓过期只是新鲜度告警,两处阈值:引擎 health() 里 signature_days_old > 7 → status=degraded(app/engines/clamav.py:111,硬编码),诊断规则 R002 用 config/diagnostics.yaml:47 的 clamav_db_stale_days: 7 出 WARN;控制台签名库面板显示"已 N 天"(dashboard.html:408)。当前库 ClamAV 1.5.4/28166/Tue Oct 27 14:24:12 2026,今天 6 天,11-04 起变 8 天开始告警。
  • 内网的更新方式只有离线导入 — scripts/init_clamav.sh 仅在库为空时试一次 freshclam,失败即静默降级;光闸环境里它注定失败。可用路径:外网下载 main.cvd/daily.cvd/bytecode.cvd → 摆渡进内网 → 控制台「📤 上传签名库」(app/console.py:1487,落 /var/lib/clamav 后给 clamd 发 SIGHUP 立即热加载,无需重启容器)。官方 daily 库每天发布,按周导入即可把库龄压在 7 天内。
  • 缺口(未修,需先定运维周期) — 库龄只影响展示与诊断告警,decide() 完全不看 signature_days_old:签名库 30 天没更新时 L1 事实上认不出新病毒,文件照样可能是 clean/pass。这与"不可扫描内容不得放行"的口径不一致,同类先例是 ClamAVSkippedSizeLimit(clamd 超大小限制没查过 → 补 medium/30 进复核)。建议补 ClamAVSignatureStale(medium/30),阈值取真实摆渡周期 + 缓冲(按周导入填 10~14 天),不要直接填 7——那会让每次告警窗口把全量文件推进人工复核。

遗留项(已确认,未在本轮处理):- scripts/init_db.py:71 用 scalar_one_or_none() 取最新策略版本,表内多于 1 行即抛 MultipleResultsFound → compose 注释里的"YAML 灌库"步骤在已有环境上跑不通;本轮改用 PolicyStore.save_from_dict() 灌库(注意 /api/v1/policies/reload 只刷新收到请求的那一个 worker,UVICORN_WORKERS=8 下仍需重启才全量生效;另外 docker compose restart api 不会换用刚构建的新镜像,改了 app/ 必须 docker compose up -d api 重建容器)。

  • allow_encrypted_archive: false 实际未生效 — scanner.py:391 只在 document 引擎报 PdfEncrypted/OfficeEncrypted 时置 encrypted=True,压缩包侧的 EncryptedArchive 不触发这条策略,于是策略明令禁止的加密压缩包仍以 20 分放行(L0-024_encrypted.zip → pass_with_flag)。补一行即可生效,但会把所有加密包从"复核"改成"拒绝",涉及真实业务流量,需先定口径。
  • ExtractMemberFailed 权重 0 — 成员解压失败零成本,与上面同一类 fail-open。
  • 纯启发式签名单条即拒 — Win.Heur.*、Win.Packed.*、Win.Trojan.GenericKD-* 这类 ClamAV 启发式命中现在分别是 70/95 分,等于"AV 一家之言直接拦截",与"干净可执行文件只进人工复核"的口径不完全一致(启发式是 AV 误报集中区)。降档需要先有内网真实误报样本做标定,否则会把真木马放到复核队列里;本轮保持拒绝。
  • 深扫成员后的耗时 — 解包递归真正生效后:PPTX(61 个对象)5.3s、8MB DLL 2.3s、zip 炸弹样本 10.7s;扫描循环上限 2000 个对象,理论上大压缩包可在同步请求里跑到分钟级,而 sync_max_size 现值 512MB 基本等于全部走同步。建议按实测流量把同步阈值下调(例如 20MB)或给对象数配一个更低的上限。
  • 35 条零独立贡献规则暂不退役 — 单条退役在这 3,661 条上零影响,但它们是同族互为备份(整族一起退役会让 258 条 reject 降级),退役单位是"族"不是"条";而且本语料 97.9% 是控制台上传样本、真实摆渡通道只有 75 条、ClamAV 只在 81 条上命中,样本偏斜不足以证明这些规则在真实恶意样本上也没用。等内网积累到足量真实流量再按簇退役。

v0.2.3 (2026-10-29)

Bug 修复:

  • Policy 加载失败(每次启动都回退 YAML) — policy.py _load_db_or_yaml() 用 scalar_one_or_none() 加载最新策略版本,但 policy_versions 表有多条历史版本 → SQLAlchemy 抛 Multiple rows → 永远回退 YAML 配置文件,导致不同重启间规则数波动。修复为 db.query(PolicyVersion).order_by(created_at.desc()).first(),直接取最新一条 ORM 对象
  • 控制台任务历史被内部测试记录淹没 — scan_tasks 表累积 5,800+ 条内部对账/诊断脚本产生的测试数据(channel 包括 test_center / v / verify / diag / debug / final 等 19 个),dashboard 和 tasks_page 无过滤全部展示。修复:① DB 清理所有内部 channel 记录(保留 3,488 条生产数据);② console.py 加 INTERNAL_TEST_CHANNELS 黑名单常量,dashboard 6 个查询 + tasks_page 2 个查询全部 .filter(ScanTask.channel.notin_(...))
  • 测试靶场显示 "未知" 层和 "—" 编号 — 控制台用 iterdir() 遍历目录收集样本,但目录里混入 4 个 YARA 调试时的临时文件(L4-005_hotreload.txt 等),index.json 没登记 → category 取不到显示 "未知"。修复:① 清理 4 个临时文件;② test_center_page 改为只从 index.json 收集样本(目录遍历替换为 index.json 遍历),以后不会再出这个问题

DB 与运维增强:

  • ScanTask 复合索引 — 新增 ix_scan_tasks_channel_created ON (channel, created_at DESC),解决 dashboard/tasks_page 高频查询的全表扫描。models.py ScanTask.__table_args__ 也同步更新,新建实例会自动带上
  • 归档表 + 归档脚本 — 建 scan_tasks_archive 表(含 4 个索引);新增 scripts/archive_old_tasks.py 支持 4 项清理策略:
    • scan_tasks 90 天前:清 detail JSONB(每条 1-5 KB,省空间)
    • scan_tasks 365 天前:移到 scan_tasks_archive 归档表(CASCADE 删 ScanFinding)
    • idempotency_records:清 7 天前(幂等缓存过期可以重扫)
    • policy_versions:只留最新 5 个(历史版本无保留价值)
    • 支持 --dry-run(默认,只报告不动数据)+ --apply(真正执行),分批 1,000 条处理避免长事务锁表
  • 自动定时任务 Cron 服务 — docker-compose.yml 新增 cron 服务(共享 meshsafe:latest 镜像,零额外依赖,restart: unless-stopped),scripts/cron_entrypoint.py 纯 Python 调度器:
    • 每天 03:00 跑 archive_old_tasks.py --apply
    • 每周一 03:10 VACUUM ANALYZE 5 张表(维护表统计信息,加速查询)

数据增长实况:

指标 值
scan_tasks 当前 3,660 行 / 4.5 MB
日均新增 ~523 条
年增长 ~19 万条 ≈ 910 MB
归档策略 90 天清 detail / 365 天归档 / 幂等 7 天 TTL / policy 留 5

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)

并发与稳定性核心修复:

  • 诊断定时器分布式锁 — 多 worker 部署时,每个时间槽位只有一个 worker 执行,Redis 时间槽位设计(key=<timer>:<int(time()//interval)> + SET NX + EX),无主动释放,下一槽位自然放行
  • JSONL 存储跨进程文件锁 — fcntl.flock 排他锁 + errors="replace" 容错解码 + 临时文件 + os.replace 原子重写,彻底根除多 worker 并发写导致的 UnicodeDecodeError 和数据膨胀
  • 移除 SQLite fallback — 统一 PostgreSQL 作为唯一数据库后端
  • API_CLIENTS 脱敏 — docker-compose.yml 中硬编码的 MASTER_KEY 改为环境变量 ${MESHSAFE_API_CLIENTS:-test-client:test-secret},真实密钥移入 .env(已 .gitignore)
  • Docker 网络固定网段移除 — 删掉 subnet: 172.28.0.0/16 + ipam,IP 由 Docker 自动分配

产品与文档:

  • 新增 /console/about 产品介绍页(Hero + 痛点叙事 + 4 大卡片 + 6 层检测管线 + 架构 3 小卡)
  • 新增 deploy/OPERATIONS.md 运维部署指南
  • .gitignore 补 /data/、benchmark_report*.json、out.json、/.screenshots/

v0.2.0 (2026-10-27)

  • v1.0 初始架构落地:FastAPI + PostgreSQL + Redis + Docker Compose
  • 6 层检测管线(L0-L5)、零容忍规则集、客户端 HMAC-SHA256 签名鉴权
  • 控制台 UI:登录 / Dashboard / 诊断中心 / 策略管理 / 任务列表 / 测试中心

测试靶场

168 条样本对账 100% 通过 ✅(v0.2.2 达成)

指标 数值
总样本数 168
引擎能力覆盖 100%
对账 Fail 0
通过 commit a372ee5 补齐样本 + 5c77678 引擎补全

靶场样本分层

层级 含义 样本数 典型覆盖
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 在白名单 → 跳过告警
  • 生产部署时按需补充内网地址段 / 已知安全工具哈希

靶场样本生成脚本

脚本 用途
scripts/generate_csv_samples.py 从 CSV 批量生成 CSV 对齐样本 + index.json
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

更多技术细节请参阅 总体方案 和 接口文档

Description
No description provided
Readme 1.4 MiB
Languages
Python 77.7%
HTML 12.9%
YARA 7.6%
Shell 1.4%
Dockerfile 0.2%