2026年8月14日 1 分钟阅读

依赖漏洞扫描别只跑一次:用 OSV-Scanner 把源码、镜像与离线环境纳入同一条检查链

tinyash 0 条评论

应用代码没有改动,不代表交付物没有新增风险。一个 Node 项目可能从 package-lock.json 带入受影响的间接依赖;服务打包进镜像后,又会继承基础镜像里的系统包。更棘手的是,开发机能联网、构建节点或隔离环境却未必可以——把扫描结果当成一次性的 CI 附件,往往覆盖不了这些差异。

OSV-Scanner 是 Google 维护的 Apache-2.0 开源命令行工具。它把项目依赖清单与 OSV.dev 漏洞数据关联起来;当前 V2 README 列出 C/C++、Dart、Elixir、Go、Java、JavaScript、PHP、Python、R、Ruby、Rust 等生态,以及 npm、pip、Maven、Go modules、Cargo 等包管理器。它并不是替代代码审查或运行时防护的万能工具,而是适合放在“依赖事实 → 风险发现 → 修复决策”这条链路的前段。

为什么只扫 manifest 不够

依赖风险至少存在三层边界。

第一层是源代码仓库:package.jsongo.modpom.xml 等文件描述了应用需要什么。第二层是锁文件和解析后的依赖图:直接依赖看似安全,真正受影响的版本可能藏在数层间接依赖中。第三层是运行交付物:容器基础镜像的 Alpine、Debian 或 Ubuntu 系统包,以及镜像中携带的语言包,也会改变最终暴露面。

因此更实用的做法不是在每次合并前只检查一个文件,而是按交付阶段选择扫描对象。OSV-Scanner 的 V2 文档明确支持递归扫描源码目录,并支持对容器镜像作分层感知扫描;这让“仓库通过了扫描”与“要部署的镜像通过了扫描”能成为两个独立、可追踪的门槛。

从仓库开始:把扫描范围交给工具识别

安装方式可以按团队环境决定。官方文档提供预编译发行版;使用 Go 工具链时也可从源码安装:

go install github.com/google/osv-scanner/v2/cmd/osv-scanner@latest

在仓库根目录执行递归扫描:

osv-scanner scan source -r .

这里的关键不是手工枚举每种语言的文件,而是让工具递归发现受支持的包文件。对于 monorepo,这一点尤其有用:前端、后端、脚本和独立服务常有各自的依赖清单。建议把命令先放进本地开发或预提交检查,确认输出格式、耗时和误报处理方式,再接入 CI;不要一开始就把“发现任意漏洞即阻断发布”作为唯一规则。

原因是漏洞信息需要结合版本、是否可修复、依赖深度和业务暴露面判断优先级。扫描器报告是证据入口,不是自动替代风险决策的结论。

交付前再扫镜像:检查运行时真实组成

源码检查通过后,镜像仍可能因为基础层或镜像内的依赖产生新结果。官方 README 给出的镜像扫描形式如下:

osv-scanner scan image my-service:2026-08-14

这一步适合紧接在镜像构建之后、推送或部署之前执行。OSV-Scanner 文档说明,容器扫描覆盖支持的操作系统包和语言制品;README 当前列出 Alpine、Debian、Ubuntu,以及 Go、Java、Node、Python 等语言制品支持范围。实际使用时应以项目所用发行版和制品类型为准,而不是把“支持容器扫描”泛化为所有镜像格式和生态都等价覆盖。

一个常见失败模式是只在源仓库执行扫描,随后 Dockerfile 更换基础镜像、构建阶段安装系统包,却没有重新建立镜像层的检查。将镜像扫描绑定到镜像 tag 或不可变 digest,才能让报告对应到真正准备交付的对象。

结果如何进入 CI:用“差异处理”代替“全量告警”

扫描命令容易接入,真正影响团队体验的是结果处理。若每次流水线都把历史遗留项、开发依赖和新引入项混在一起,开发者看到的只是不断重复的长列表,最终很容易绕过检查。更合理的实践是保存基线:首次建立结果后,后续变更只重点审阅新出现的包、版本变化后首次命中的漏洞,以及从“无修复”变成“已有修复版本”的条目。

这不意味着旧问题可以忽略。基线应有负责人、豁免理由与复查日期;当基础镜像升级、依赖解析策略改变或 OSV 数据库更新后,应触发一次全量复扫。对 CI 而言,源码扫描结果和镜像扫描结果也不宜相互覆盖:前者用于定位仓库里哪个依赖文件引入问题,后者用于确认最终交付物实际包含什么。将两者作为同一次构建的关联证据保存,排障时才能回答“问题来自应用依赖,还是来自镜像基础层”。

若团队需要以机器可读格式串接其他系统,还应先从官方 usage 文档核对当前 V2 的输出选项和退出码语义,再把“有发现”映射为告警、阻断或创建工单。不要根据终端上的人类可读输出猜测字段,更不要让 Agent 看到一条漏洞后就直接执行升级;升级会改变依赖图,必须经过测试和构建验证。

隔离网络不是放弃扫描的理由

漏洞数据库查询通常需要网络,但安全环境、离线构建或受限网络并不罕见。OSV-Scanner 提供离线数据库下载和离线扫描路径:

osv-scanner --offline --download-offline-databases ./
osv-scanner --offline scan source -r .

第一条命令用于准备本地数据库;后续扫描使用本地数据。这样做的取舍很明确:可用性和数据边界更可控,但数据库的新鲜度由团队负责。建议在可联网的受控任务中定期更新数据库,记录更新时间、来源和校验结果;在隔离环境中将数据库作为版本化构建输入,而不是长期不更新的缓存。

README 还明确说明,在线模式会向 OSV.dev API 查询包名、版本、生态和文件哈希等信息,且不传输源码;若组织对外联有严格要求,应在采用前把这一数据流、代理策略和离线更新流程写进安全设计。

许可证检查与修复建议:都应有边界

供应链治理不只有漏洞编号。若交付政策要求检查依赖许可证,可使用官方示例的允许列表形式:

osv-scanner --licenses="MIT,Apache-2.0" .

这类检查适合作为早期提醒:许可证兼容性常涉及分发方式、链接方式和法务政策,不能仅靠一个允许列表给出法律结论。把结果导出给依赖治理或人工审批流程,通常比在开发者本地直接“判死刑”更可靠。

V2 还提供实验性的 guided remediation fix 能力,能为部分 npm 和 Maven 文件建议升级路径。官方同时警告:对不可信项目运行修复可能触发包管理器脚本或跟随项目指定的外部 registry。因此更稳妥的流程是:先在隔离分支运行扫描和建议,再审阅 lockfile 或 manifest 差异,最后运行测试、构建和镜像扫描;不要让修复命令直接改动生产分支。

把它接进工程流程,而不是制造一份新报表

一个可落地的最小闭环可以是:开发者在提交前扫描源码目录;CI 在依赖文件或镜像发生变化时保存机器可读结果;发布流水线扫描最终镜像;隔离环境使用有更新时间记录的离线数据库;高严重度且存在可行升级路径的结果创建待处理项,其余结果保留豁免依据和复查日期。

OSV-Scanner 的价值不在于把漏洞清单变长,而在于让同一套公开漏洞数据贯穿源码、镜像和离线环境。只有当扫描对象与实际交付物对应、修复动作经过审阅、数据库更新可追溯时,依赖扫描才会从一次命令变成可维护的工程控制点。

相关链接

发表评论

你的邮箱地址不会被公开,带 * 的为必填项。