如果你正在给容器镜像做上线前的安全体检,或者需要一台能跑扫描流水线的机器,可以先看看这几家的配置和技术说明:tokyonline(公有云 / 私有云 / 混合云,适合把扫描流水线放在自有环境里)、tmhhost(强调安全与可靠性的云服务,适合承接对外暴露的镜像仓库)
我们公司做的是 SaaS,交付物基本都装在容器里。CI 流水线跑得挺顺,镜像推到仓库、k8s 滚动更新,一切看着都很体面。直到有天安全部门提了个要求:所有上线镜像必须附一份漏洞扫描报告,而且高危项为零。
我第一反应是这事儿不难,装个 Trivy 扫一下不就行了?于是我在本地拉了最新业务镜像跑了一次。输出刷出来的那一刻我沉默了:47 个高危 CVE。而这还只是一个我们自己写的基础镜像,业务镜像叠了七八层之后只会更多。

拿到 47 个高危之后我做的第一件事不是去修,而是先把它们分堆。Trivy 的 JSON 输出里有 Severity、PrimaryURL、Target 几个关键字段,我简单按这几个维度归类:
第一类是基础镜像自带的,比如 Alpine 的 openssl、Debian 的 glibc,这些包在 Target 里会明确写成系统包路径。第二类是语言依赖带进来的,比如 Python 的 urllib3、Node 的 lodash,它们会指向 site-packages 或者 node_modules 下面的路径。第三类是只在特定配置下才 exploitable 的,CVE 描述里如果写着 "requires a specially crafted request" 或者 "only affects versions with X enabled",那基本可以归到"理论风险"。
分完之后 47 个里面,真正必须马上动手的只有 9 个,剩下 38 个里有一半压根没有受影响版本。这中间最大的坑是:扫描工具给你的是"这个包有这个漏洞",不是"你的服务能被这个漏洞打"。两者差着十万八千里,可很多团队直接把扫描报告甩到群里就开始全员加班修,修完发现版本根本没受影响。

那 38 个"修不了"的怎么办?先说清楚什么叫修不了。我遇到的三种情况:
一是上游还没修。比如某个 Go 库的 CVE,社区 issue 开了半年没合并,官方 release 里没有修复版本,你把版本升到最新也还是中招。二是基础镜像发行版已经 backport 了补丁,但 CVE 号还是老的,扫描器只认版本号认不出补丁,于是长期误报。三是依赖路径根本走不到,你 import 了这个包但没调用相关函数,攻击面根本不存在。
这三种要分开对待。Trivy 支持用 .trivyignore 文件按 CVE ID 加过期时间和原因说明,比如:
CVE-2023-5363 expired:2024-12-31 "基础镜像已 backport 修复,等待 24.04.2 滚动更新"
关键点是每一条忽略都必须写原因和到期时间。我见过团队直接在 ignore 文件里堆一长串 CVE ID,没有一条注释。结果半年后没人记得为什么忽略,安全审计的时候直接把这份文件当成了"集体隐瞒漏洞"的证据。

最直觉的做法是在发布流程最后加一步:构建完镜像,推到仓库,然后扫描,扫出问题就失败。这个做法逻辑上没问题,实际用起来特别难受因为流水线已经跑完前面十几分钟了,扫描一报错,前面的构建、测试、推镜像的耗时全白费。
我们的做法是把扫描提前,分两层:构建阶段扫基础镜像,因为基础镜像是我们自己定期更新的,出问题概率低但影响面最大,一旦发现可以直接换 base image;推送阶段再扫最终产物,此时镜像已经在本地仓库里,扫描和推送是并行的,不会额外拖长流水线。
另外有几个实操细节值得说:给 Trivy 挂一个本地缓存目录(--cache-dir ~/.cache/trivy),CVE 数据库每天只更新一次就够,别每次构建都去重新拉几十兆的漏洞库;--skip-db-update 配合定时更新缓存使用;再加 --ignore-unfixed,直接把上游还没出补丁的漏洞过滤掉,剩下的才是你真的该管的。

讲真,如果这次重来一次,我会在项目第一天就定规矩:不用 alpine:latest、不用 python:3 这种浮动标签。每个业务服务用一个自己构建的、带 digest 固定的内部基础镜像,所有通用依赖(curl、openssl、tzdata 这些)在这一层统一升到最新。
这么做的好处是漏洞修复变成了一次性的动作:改内部基础镜像,重新构建,推一次,全公司的服务跟着受益。而不是每个服务的 Dockerfile 里各写各的 apt-get upgrade,最后有的升级了有的没升级,扫描报告永远是一锅粥。
另外补一句,现在很多团队会加 SBOM(软件物料清单)作为附件一起归档,我个人更倾向把 SBOM 直接生成在 CI 里按版本存起来。等哪天某个 CVE 爆出来需要回答"我们哪些线上服务用了这个包",有 SBOM 就是一条命令的事,没有的话就得翻镜像挨个捞。
改完这套流程之后,我们的策略从"扫描报了就修"变成了"高危必须修、中危排期修、低危记录在案"。上线前的扫描报告依然会红,但红的原因变得可控了:要么是你真的需要动手,要么是你写清楚了为什么不修、什么时候必须回头再看。
这套东西说白了没什么技术难度,Trivy 装上十分钟就能跑起来。真正花时间的是想清楚三件事:哪些漏洞跟我有关、哪些可以暂时放过、放过之后怎么记得回头。工具只负责把摊子铺开,剩下的是工程判断,而工程判断恰好是最难写进 SOP 里的部分。