ModelScan源码架构解析:ModelScan核心、Scanner、Middleware、Reports四层协作机制完全讲解
【免费下载链接】modelscanProtection against Model Serialization Attacks项目地址: https://gitcode.com/gh_mirrors/mo/modelscan
ModelScan 是一款开源的机器学习模型安全扫描工具,用于检测和防御模型序列化攻击。它通过静态读取模型文件字节流,识别模型中嵌入的危险操作符(如eval、exec、文件读写等),而不会真正加载模型,从而在 CI/CD 和 MLOps 流水线中保护你的 AI 资产。本文带你从源码层面拆解 ModelScan 核心(ModelScan)、Scanner 扫描器、Middleware 中间件、Reports 报告这四层组件的协作机制。
先看懂威胁:模型序列化攻击长什么样
在本地加载一个恶意 PyTorch 模型时,攻击代码会随torch.load()一起执行——模型文件就像一个"特洛伊木马",读取文件、写文件、窃取凭据都可以在加载瞬间完成。
ModelScan 的防御思路非常直接:不执行、只读字节,像杀毒软件扫 PDF 一样逐字节查找危险代码签名,既快速又安全。
ModelScan源码目录结构一览
克隆仓库后即可观察整体布局:git clone https://gitcode.com/gh_mirrors/mo/modelscan
核心代码全部位于modelscan/包内,四层架构一一对应:
| 层 | 目录/文件 | 职责 |
|---|---|---|
| 核心编排 | modelscan/modelscan.py | 遍历模型文件、调度扫描、汇总结果 |
| 扫描器 | modelscan/scanners/ | 按模型格式实现具体检测逻辑 |
| 中间件 | modelscan/middlewares/ | 扫描前的预处理管道(如格式识别) |
| 报告 | modelscan/reports.py | 控制台/JSON 等结果输出 |
| 配置 | modelscan/settings.py | 注册全部组件的默认配置 |
第一层:ModelScan核心——扫描流程的总指挥
ModelScan 类是整个系统的入口。构造函数只做一件事:按配置(DEFAULT_SETTINGS)加载 Scanners 和 Middlewares 两个组件清单。
真正的调度发生在scan()方法中,流程分三步:
- 遍历模型:
_iterate_models()递归遍历目录下的所有文件,逐个包装成Model对象(modelscan/model.py 中的上下文管理器,封装文件流与附加上下文); - 预处理:对每个
Model运行self._middleware_pipeline.run(model),中间件可改写模型上下文; - 扫描:
_scan_source()依次调用每个 Scanner,收集issues(问题)、errors(错误)、skipped(跳过)三类结果。
两个值得注意的设计细节:
- 自动解包 ZIP 类容器:遇到
.zip、.npz等压缩包会自动展开内部文件逐个扫描(zipfile.ZipFile逐条打开),但嵌套压缩包会记录NestedZipError并跳过,这是出于安全考虑——嵌套结构是常见的逃逸手法; - Scanner 故障隔离:任何一个扫描器抛异常都会被捕获为
ModelScanScannerError记入错误列表,continue继续执行其余扫描器,单点故障不会拖垮整个扫描。
第二层:Scanner扫描器——按模型格式分工
所有扫描器继承自 ScanBase 抽象基类,约定统一的接口:name()、full_name()和scan(model) -> ScanResults。ScanResults是扫描器的标准产物,固定包含三个列表:issues、errors、skipped。
内置扫描器按模型格式分目录组织:
- modelscan/scanners/pickle/scan.py:Pickle 家族(
.pkl、.joblib、.pt、.pth等),检查os.system、__import__等危险全局调用; - modelscan/scanners/saved_model/scan.py:TensorFlow SavedModel(
.pb),检测ReadFile、WriteFile等不安全 TF 算子; - modelscan/scanners/h5/scan.py 与 modelscan/scanners/keras/scan.py:Keras H5/keras3 格式的 Lambda 层代码检测。
每个扫描器返回None表示"这个格式我不负责",只有返回ScanResults才计入已扫描——这让多个扫描器可以无脑并行尝试同一个文件。
第三层:Middleware中间件——洋葱式预处理管道
middlewares/middleware.py 定义了两个关键类型:
MiddlewareBase:要求实现__call__(model, call_next),即"处理模型 → 调call_next(model)进入下一层",是经典的洋葱模型;MiddlewarePipeline:from_settings()按配置动态导入中间件类并排队,run()用递归runner(model, index)逐层传递。
内置的 FormatViaExtensionMiddleware 展示了中间件的典型用法:按文件扩展名(如.pb→ tensorflow、.npy→ numpy)向Model写入formats上下文,供下游扫描器使用。中间件不直接产出安全问题,只负责"扫描前加工",与 Scanner 职责清晰分离。
第四层:Reports报告——扫描结果的出口
reports.py 中的Report抽象基类约定静态方法generate(scan, settings),两个内置实现:
ConsoleReport:按严重度分组打印问题明细、错误列表与跳过文件(用rich渲染,无问题时输出 "No issues found! 🎉");JSONReport:调用核心的_generate_results()序列化完整报告,支持--output-file落盘,方便接入 CI 系统。
报告模块通过配置里的模块路径字符串(如"modelscan.reports.ConsoleReport")被importlib动态加载,因此换成第三方报告类无需改动任何核心代码——这是贯穿 Scanner、Middleware、Report 三层的同一套插件化思想。
配置驱动:settings.py如何把四层串起来
settings.py 中的DEFAULT_SETTINGS是一张"组件注册表":
scanners:注册 7 个扫描器,每项含enabled开关与supported_extensions;middlewares:注册格式识别中间件及其扩展名映射;unsafe_globals:危险全局函数名单,按 CRITICAL/HIGH 分级(如os、subprocess全量禁用,eval/exec点名单禁用),Pickle 扫描器据此定级;reporting:指定报告模块与参数。
运行modelscan create-settings-file即可导出 TOML 配置模板,自定义后通过--settings-file传入。CLI 层 modelscan/cli.py 还定义了 0~4 五个退出码(发现漏洞为 1、扫描出错为 2),是流水线集成时的信号契约。
四层协作机制全景
一次完整扫描的数据流如下:
- 配置层:
DEFAULT_SETTINGS声明"有哪些组件、谁启用"; - 核心层:
ModelScan.scan()遍历文件、解包容器; - 中间件层:
MiddlewarePipeline.run()为模型打上格式标签; - 扫描器层:各
Scanner.scan()静态分析字节流,产出Issue(按 issues.py 中 LOW→CRITICAL 四级严重度定级); - 报告层:
generate_report()汇总summary、issues、errors、skipped输出报告。
总结:这套架构值得学习的地方
- 插件化:Scanner、Middleware、Report 全部以"模块路径字符串 + 动态导入"注册,新增格式支持只需实现一个抽象类并在配置中登记;
- 故障隔离:单扫描器异常、坏 ZIP、不支持的文件各有独立的错误/跳过通道,扫描永不中途崩溃;
- 静态安全:全程只读字节流、绝不 import 模型内容,工具自身不会被扫出的恶意代码反噬;
- CI 友好:明确的退出码 + JSON 报告,天然适配流水线卡点。
如果你想深入,可以继续阅读 tests/test_modelscan.py 中的端到端用例,或参考 docs/model_serialization_attacks.md 了解各类序列化攻击的实现细节,动手扩展属于自己的第一个 Scanner。
【免费下载链接】modelscanProtection against Model Serialization Attacks项目地址: https://gitcode.com/gh_mirrors/mo/modelscan
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考