composer audit只读composer.lock,因其实现确定性验证:仅依据锁文件中的包名、版本号和dist.shasum哈希值,比对friendsofphp/security-advisories数据库;不扫描vendor/内容,也不解析composer.json,故手动拷贝vendor、忽略lock、未执行install等均导致失效。

composer audit 为什么只看 composer.lock,不扫描 vendor/
因为 composer audit 的设计目标是「确定性验证」:它不关心你 vendor/ 里实际有什么文件,只信任 composer.lock 中记录的精确包名、版本号和 dist.shasum。这个哈希值才是 Composer 认定“该包确实被安装了哪个确切快照”的唯一凭证。
常见误操作包括:
- 手动拷贝
vendor/进项目,但没生成或更新composer.lock→audit直接跳过,输出空结果 -
.gitignore里写了composer.lock→ CI 拉下来的代码根本没 lock 文件,audit报错Could not find composer.lock - 本地改了
composer.json后直接跑audit,没先composer install→ lock 文件仍是旧的,扫描结果与实际部署环境脱节
为什么 fork 包、dev-main 版本总是扫不出来漏洞
composer audit 只比对 FriendsOfPHP/security-advisories 数据库中的条目,而该库只收录 Packagist 上正式发布的稳定版本(如 guzzlehttp/guzzle:7.8.1),且严格按 vendor/name + version 全匹配。
以下情况它一律静默跳过,也不报错、不提示:
- 包名是
myorg/guzzle(fork)或acme/framework(私有源)→ 数据库无对应条目 - 版本字段含
-dev、dev-main、dev-feature/x→ advisories 不收录不稳定版本 - 用了
path类型仓库(如"type": "path", "url": "../local-package")→ audit 完全忽略
这不是 bug,是明确的设计限制:它不试图做调用链分析,也不支持自定义元数据注入。
CI 中让 composer audit 真正卡住高危漏洞的关键参数
默认行为下,composer audit 遇到任意一条 advisory(哪怕 2018 年的 low 级)就返回非零退出码,这在 CI 中等于误杀。生产卡点必须收窄范围:
- 加
--severity=high和--severity=critical:只对这两个级别中断构建 - 必加
--no-dev:开发依赖(如phpunit/phpunit)常含 RCE 漏洞,不加等于放行攻击面 - 用
--format=json:便于后续用jq提取高危项,例如:jq '.advisories[] | select(.severity == "critical")' - 设
--timeout=30和--no-interaction:避免网络抖动导致流水线挂起
漏掉其中任一参数,都可能导致漏洞漏过,或构建频繁失败。
audit 扫出漏洞后,为什么 composer update 不一定修得了
composer audit 只告诉你「当前锁文件里的这个版本有漏洞」,但它不负责判断升级路径是否存在、是否兼容、是否被其他依赖阻断。
典型卡点场景:
- 报告中
"fixed": "3.5.2",但你的composer.json锁死了"monolog/monolog": "^2.0"→update不会升到 3.x - 想升的版本被另一个 require 的包硬性约束(如
laravel/framework要求symfony/console )→ Composer 会回退并维持旧版 - 漏洞包是子依赖(transitive dependency),未显式声明在
composer.json中 →require新版本可能引发冲突
真正可靠的前置拦截,其实是 roave/security-advisories:它把所有已知漏洞转为 Composer 的版本约束,让 install 阶段就失败——这比事后 audit 更早、更硬。











