Trivy 扫描 PHP Composer 依赖:composer.lock 与 installed.json 的漏洞、许可证与依赖图检测完全指南
本篇指南基于 Trivy 开源仓库中的 docs/guide/coverage/language/php.md,完整讲解 Trivy 如何对 PHP 项目进行依赖安全扫描。Trivy 通过 Composer 生态的 composer.lock 与 vendor/composer/installed.json 两类清单文件,实现 SBOM 生成、漏洞检测与许可证识别,并支持依赖树与开发依赖(dev dependencies)的精细控制。读完本文,你将掌握 Trivy 扫描 PHP 项目支持的扫描器与文件矩阵、依赖树与直连/间接依赖的判定原理、--include-dev-deps 的用法与配置方式,以及底层解析与后处理的具体实现路径。
PHP 支持的扫描能力概览
Trivy 对 PHP 生态的依赖管理工具 Composer 提供了完整支持。原始文档明确说明 Trivy 支持 Composer 这一 PHP 依赖管理工具,并支持以下三类扫描器:
| Package manager | SBOM | Vulnerability | License |
|---|---|---|---|
| Composer | ✓ | ✓ | ✓ |
也就是说,针对 Composer 管理的依赖,Trivy 既可以生成 SBOM(软件物料清单),也可以逐包匹配漏洞库给出漏洞结果,还能识别每个依赖包的许可证信息,为许可证合规审计提供依据。
依赖清单文件支持矩阵
Trivy 主要识别两种由 Composer 生成的清单文件,其能力矩阵如下:
| Package manager | File | Transitive dependencies | Dev dependencies | Dependency graph | Position |
|---|---|---|---|---|---|
| Composer | composer.lock | ✓ | Excluded | ✓ | ✓ |
| Composer | installed.json | ✓ | Excluded | - | ✓ |
表中信息可以解读为:
- Transitive dependencies:两种文件都能还原传递(间接)依赖,扫描结果不只覆盖直接声明的包;
- Dev dependencies:
composer.lock默认排除开发依赖,但可以通过 --include-dev-deps 开启;installed.json场景下开发依赖始终被排除; - Dependency graph:仅
composer.lock支持输出依赖图(用于定位漏洞来源链路),installed.json不支持; - Position:两者都支持记录包在文件中的位置(行号区间),便于结果回溯到具体清单条目。
在源码层面,这些类型常量统一定义于 pkg/fanal/types/const.go:
Composer(composer)与ComposerVendor(composer-vendor)是两种语言类型(LangType,见该文件 L106-L107);composer.lock、composer.json、installed.json三个文件名常量见该文件 L219-L221。
何时使用哪种文件
- 源码仓库 / 制品目录通常包含
composer.lock,Trivy 推荐以它为主要扫描对象; - 若仓库中没有提交
composer.lock,但已执行过composer install(生成了vendor/目录),则其中的vendor/composer/installed.json可退而成为依赖检测来源,覆盖真实安装到vendor/中的包集合。
composer.lock 检测机制
为探测依赖,Trivy 会在被扫描的目标(镜像、文件系统、代码仓库等)中递归搜索 composer.lock。这是文档明确声明的行为;在实现上,Composer 分析器注册为 analyzer.TypeComposer 类型的后处理分析器(PostAnalyzer),见 pkg/fanal/analyzer/language/php/composer/composer.go:
- 第 25-27 行通过
init()调用analyzer.RegisterPostAnalyzer(analyzer.TypeComposer, newComposerAnalyzer)注册; - 第 31-34 行的
requiredFiles同时包含composer.lock与composer.json; - 第 46-79 行的
PostAnalyze遍历文件系统,凡文件名是composer.lock或命中用户自定义文件模式(FilePatterns.Match)的文件都会送入parseComposerLock解析; - 第 81-92 行的
Required方法补充了一个重要细节:位于vendor目录内部的composer.lock会被主动跳过(第 87-90 行),避免重复或误扫由 Composer 安装在vendor下的嵌套锁文件。
值得注意,composer.json 本身并不产生独立的依赖应用(Application),它只在同目录存在 composer.lock 时作为补充数据被合并解析。
为什么显示准确依赖树需要 composer.json
composer.lock 记录的是 Composer 锁定后的完整依赖集合,但它并不包含每个包是项目直接依赖还是传递依赖的信息。文档指出:为了显示准确的依赖树,Trivy 需要知道每个包是否为项目的直接依赖;由于该信息不包含在 composer.lock 中,Trivy 会解析与 composer.lock 同目录的 composer.json。因此,若你想看到依赖树,请确保 composer.json 与 composer.lock 同时存在。
这一机制在源码中有清晰对应。mergeComposerJson 函数(composer.go 第 106-131 行)在解析锁文件后查找其所在目录的 composer.json,将其中 require(运行时依赖)与 require-dev(开发依赖)的键与每个包名做匹配:
- 命中
require或require-dev→ 标记为RelationshipDirect(直接依赖,Indirect=false); - 未命中 → 标记为
RelationshipIndirect(间接依赖,Indirect=true)。
若找不到 composer.json,Trivy 会以 Debug 级别记录日志并放弃修正(第 110-113 行),此时所有包的关系保持为 RelationshipUnknown。测试用例 composer_test.go 中名为 no composer.json 与 wrong composer.json 的用例验证了这一行为:缺少或无法解析 composer.json 时,结果中包的关系字段为 RelationshipUnknown;而 happy path 用例则展示了在 composer.json 存在时,pear/log 被正确判定为 Direct、其依赖 pear/pear_exception 被判定为 Indirect。
Development dependencies(开发依赖)
默认情况下,Trivy 不报告开发依赖(即 composer.lock 中的 packages-dev 字段)。如需将其纳入结果,使用 --include-dev-deps 标志。
这一标志在源码中的定义位于 pkg/flag/package_flags.go:
- 第 10-13 行声明
IncludeDevDepsFlag,命令行名称为include-dev-deps,对应配置键名为pkg.include-dev-deps; - 第 44-50 行的选项结构体与其绑定,运行时通过 pkg/flag/options.go 第 526 行把该值传递到扫描选项。
因此除了命令行标志外,你也可以在 trivy.yaml 中使用等价配置开启,例如:
pkg:
include-dev-deps: true
为了正确识别直接开发依赖,Trivy 需要解析与 composer.lock 同目录的 composer.json 中的 require-dev 字段(见 composer.go 第 122-123 行的匹配逻辑,以及第 133-136 行 composerJson 结构体对 require-dev 的解析)。
在解析阶段,开发依赖并非简单丢弃,而是先被解析并打上标记:底层解析器 Parser(见 pkg/dependency/parser/php/composer/parse.go)的第 19-22 行定义了 LockFile,同时包含 packages 与 packages-dev 两组;第 50-53 行先解析生产依赖、再解析开发依赖;第 80-96 行注释明确指出:packages-dev 中已经存在于生产包集合里的同名包会被跳过,生产包优先。测试用例 with dev dependencies(composer_test.go)中,开发包 pear/log@1.14.6 与 pear/pear_exception@v1.0.2 均带有 Dev: true 标记。是否在最终报告输出这些 Dev 包,则由 --include-dev-deps 决定。
installed.json(vendor 目录)
除 composer.lock 外,Trivy 也支持对 installed.json 文件的依赖检测。按文档说明,该文件在默认安装布局下的路径是 path_to_app/vendor/composer/installed.json——即执行过 composer install 后由 Composer 生成、位于 vendor/composer/ 目录内的安装清单。
其分析器实现在 pkg/fanal/analyzer/language/php/composer/vendor.go:
- 第 14-16 行以
analyzer.RegisterAnalyzer注册composerVendorAnalyzer(类型composer-vendor,版本 1); - 第 29-31 行的
Required判定文件基名是否为installed.json; - 第 25-27 行复用与
composer.lock相同的composer.NewParser(),保证两种清单产出的包模型与解析口径一致。
installed.json 的典型结构以一个 packages 数组为主,每个元素包含 name、version、require、require-dev、license、type、time 等字段。仓库中的样例文件见 testdata/composer-vendor/happy/installed.json,其结构可以直接对照参考。
对比 composer.lock,installed.json 场景的限制在于:依赖图(Dependency graph)列标注为 -(不支持),且开发依赖被排除,无法像 composer.lock 那样通过 composer.json 的 require-dev 还原直接开发依赖关系。因此,若你的目标是获得完整依赖树或启用依赖图定位漏洞源头,应优先保证 composer.lock(以及同目录的 composer.json)可被 Trivy 访问。
底层解析与实现细节
依赖解析与"php / ext"过滤
通用解析器 pkg/dependency/parser/php/composer/parse.go 负责把 JSON 清单转成 Trivy 内部包模型,其关键逻辑如下:
- 包 ID 生成:第 99 行通过
dependency.ID(ftypes.Composer, lpkg.Name, lpkg.Version)生成形如vendor/name@version的唯一 ID; - 依赖边还原:第 57-71 行遍历每个包的
require字段构建DependsOn,随后在第 75 行统一排序,形成稳定的依赖图输出;因为require里是版本区间而非精确版本,具体版本号会回填自锁文件中实际存在的包(见第 60-62 行); - 平台/扩展过滤:第 109-117 行构造
DependsOn时跳过php本身以及所有ext-*开头的 PHP 扩展(如ext-json、ext-curl),因为这些并不是可匹配漏洞库的第三方包; - 位置信息:第 104 行通过
xjson.Location内嵌捕获每个包在 JSON 文件中的起止行号,这是上文能力矩阵中 Position 列的实现基础。
以上行为可对照真实锁文件样例 pkg/dependency/parser/php/composer/testdata/composer_happy.lock 验证:例如 guzzlehttp/guzzle 的 require 中包含 "php": ">=5.5"、"ext-json": "*" 等平台/扩展条目,解析时它们不会成为依赖边。
许可证字段的多种形态
Composer 的许可证字段(license)形态并不固定:可能是单个字符串(如 "MIT")、带分隔符的复合表达式(如 "MIT or BSD-2-Clause"),也可能是字符串数组(如 ["MIT"])。解析器第 124-141 行的 licenses 函数专门兼容了这两种形态:
- 字符串类型会交给
licensing.SplitLicenses拆分复合表达式(or、and等分隔符); - 数组类型则逐元素提取字符串,忽略非字符串元素。
该函数的注释还指出其格式约定来源于 Composer 的 schema 文档(getcomposer.org/doc/04-schema.md#license)。在 composer_test.go 的 happy path 用例中,pear/log 的许可证 MIT 与 pear/pear_exception 的 BSD-2-Clause 都被正确解析出来,印证了 License 扫描器的数据来源。
测试验证路径
对 PHP/Composer 能力的单元测试覆盖在以下文件中,可作为深入理解与二次开发的参考:
- 分析器集成测试:pkg/fanal/analyzer/language/php/composer/composer_test.go,覆盖 happy path、缺少
composer.json、错误composer.json、损坏的composer.lock、含开发依赖等场景; installed.json测试:pkg/fanal/analyzer/language/php/composer/vendor_test.go;- 底层解析器测试:pkg/dependency/parser/php/composer/parse_test.go。
实战:扫描一个 PHP 项目
将以上机制用于实际项目,只需要保证目标中可被 Trivy 发现 composer.lock(推荐)或 vendor/composer/installed.json。以下命令均为常规查看与运行方式:
# 扫描本地 PHP 项目目录(默认不含开发依赖)
trivy fs /path/to/php-project
# 将开发依赖一并纳入扫描
trivy fs --include-dev-deps /path/to/php-project
# 扫描包含 PHP 应用的容器镜像
trivy image your-registry/your-php-app:tag
# 扫描远端代码仓库
trivy repo https://your-host/your-php-app.git
如果要始终包含开发依赖,可以在 trivy.yaml 中写入上文所述的 pkg.include-dev-deps 配置,从而免去每次追加标志。
需要留意两点:
- 依赖树需要
composer.json:若希望--report dependency-tree或结果中包含准确的依赖关系,请确保composer.json与composer.lock位于同一目录; vendor内的锁文件会被跳过:由于composer.lock的 Required 检查会排除vendor目录下的同名文件,请确保待扫描的项目根目录持有自己生成的锁文件。
当你在报告中使用 --show-origins(显示漏洞依赖来源)等能力时,Trivy 会基于上述依赖边构建的依赖图回溯漏洞的引入链路,具体输出配置可参见文档 docs/guide/configuration/reporting.md。
小结
- Trivy 对 PHP 的 Composer 生态同时支持 SBOM、漏洞与许可证三种扫描器,依赖清单来源是
composer.lock与installed.json; - 两者都能还原传递依赖并记录包位置;
composer.lock额外支持依赖图,且可通过composer.json精确区分直连/间接依赖; - 开发依赖默认不报告,使用
--include-dev-deps(或pkg.include-dev-deps配置)开启; - 从源码看,
composer.lock与installed.json共用同一解析器 parse.go,但由两个不同的分析器(composer.go 与 vendor.go)驱动,前者跳过vendor目录并合并composer.json做直连关系标注,后者仅按installed.json基名触发。
把 PHP 项目的锁文件与 composer.json 一起纳入版本管理,并让 Trivy 在 CI 中常态化扫描它们,是让 Composer 依赖漏洞与许可证风险尽早暴露的低成本做法。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00