syft 是 Anchore 出的 SBOM 生成器,把一个目录或者容器镜像里装了哪些软件、什么版本,全列出来。它不需要 Docker daemon,扫描对象可以就是磁盘上的一个文件夹。

拿一个 400 MB、16120 个文件的 Python 环境做实测:13.80 秒扫出 130 个条目,去掉重复后 122 个包。产出的 JSON 是 10023874 字节,平均每个包占 77 KB。

85 MB 的 exe,不需要 daemon

syft version
Application:   syft
Version:       1.55.0
BuildDate:     2026-10-19T18:54:46Z

syft scan dir:D:\lab\venv\Lib\site-packages -o table

可执行文件 89968640 字节,Go 静态编译,不依赖外部工具。扫描目标用前缀区分:dir: 是目录,image: 是容器镜像,file: 是单个文件。

用 dir: 的好处是不用装 Docker。这次实测全程没有 Docker daemon 参与,扫的就是磁盘上一个 Python 虚拟环境的包目录。

400 MB 环境,13.80 秒出 122 个包

指标Python 包目录单个工具目录
目标体积400 MB60 MB
文件数16120 个—
扫描耗时13.80 秒2.25 秒
命中条目130 个2 个
去重后的包122 个2 个
表格输出行数123 行3 行
binary 类型23 个2 个
python 类型107 个0 个
syft exe 体积89968640 字节—
每 GB 扫描耗时34.5 秒37.5 秒
重复条目8 个0 个

130 个条目里 8 个是重复的,去重后 122 个。重复来自同一个包在多个位置出现,比如包的元数据目录和实际代码目录各被认了一次。

耗时和体积大致成比例:400 MB 用 13.80 秒,60 MB 用 2.25 秒,折算下来每 GB 都在 34.5 秒到 37.5 秒之间。瓶颈在读文件和解析元数据,不在匹配规则。

表格看结果,JSON 给机器

同一份扫描结果,四种输出格式的体积差得很远:

格式耗时体积平均每包用途
table12.00 秒13266 字节123 行人看
json10.94 秒10023874 字节75.3 KB进流水线
cyclonedx-json10.93 秒5499250 字节41.3 KB交给下游工具
spdx-json11.17 秒11158358 字节83.8 KB合规归档
单工具目录 json2.25 秒9048 字节4.5 KB2 个 binary
cyclonedx 相对 json—0.55 倍——
cyclonedx 比 json 小—45%——

cyclonedx-json 比默认 json 小 45%,扫描耗时基本一样(10.93 秒对 10.94 秒)。差别在内容取舍:syft 自己的 json 带了很多扫描器元信息,CycloneDX 只留标准字段。

如果下游只是要「包名 + 版本 + 许可证」,没理由存 10 MB 那一份。这个选择能直接把存储成本砍掉一半,而且不影响任何检测逻辑。

体量上的现实是:SBOM 不适合放进代码仓库。一个 Python 环境就能产出 10 MB,跑十几次就是上百 MB 的历史记录,放在制品库或者对象存储里更合适。

只认它认识的包管理器

syft 的识别靠的是包管理器的元数据文件:Python 看 *.dist-info 和 *.egg-info,Node 看 package.json,Go 看编译进二进制的模块信息,系统包看 dpkg / rpm 数据库。

如果目录里没有包管理器的元数据文件,syft 会漏掉里面的 dll,一个 python 包都扫不到。实测那个 60 MB 的工具目录只认出 2 个 binary,剩下的 dll 和配置文件全都在结果之外。

这是设计上的取舍:SBOM 要的是「装了什么软件」,不是「有哪些文件」。想要完整的文件清单,配 --scope all-layers 加上文件清单输出,体积会比这 10023874 字节再翻几倍。

还有一个更细的偏差:版本号读的是二进制里的字符串,跟程序自己报的可能不一致。实测那个目录里的 lnav,运行 lnav -V 报 0.14.1-dirty,syft 认成 0.13.2。拿 SBOM 对漏洞通告时,这种偏差会让匹配漏掉。

什么时候值得跑

持续集成里给每个构建产物生成一份 SBOM,是现在供应链安全的基本动作。漏掉一个依赖的漏洞通告,靠人工翻 lock 文件很难查全,而 122 行的 SBOM 里一条 SQL 就能对上。

13.80 秒扫 400 MB 这个速度,放在构建流水线里不算负担。真正需要提前想清楚的是产出的存放位置和保留周期:10023874 字节一份、每次构建一份,一年下来就是几十 GB。