Composer 依赖审计必须嵌入 CI、设阈值、出报告;composer-audit 单独运行无效,因不判断可达性、环境适配与 dev/prod 区分;需配合 composer-unused 和锁文件扫描,并基于 OSV 交叉验证;阻断应按 CVSS 分级,报告须含可达性证据。

Composer 依赖审计不是“跑一次 composer audit 就完事”的动作,而是必须嵌入 CI 流程、带明确准入阈值、有可回溯报告的标准化环节。不设阈值=无审计,不进流水线=不可持续。
为什么 composer-audit 不能 standalone 运行
composer-audit 本身只做 CVE 匹配,不判断是否可达、是否被调用、是否影响当前 PHP 版本。它输出的漏洞列表里,常混着“理论上存在但代码里根本没走这条路径”的条目——这类结果直接交给开发,90% 会忽略或误判。
- 它不读
vendor/autoload.php或 AST,无法排除未使用类/函数的依赖路径 - 它不校验运行时环境(如 PHP 8.2 下某个 CVE 实际不可触发)
- 它默认不区分 dev-only 依赖,而
phpunit的漏洞在 prod 环境里根本不存在 - 它的 exit code 在发现任意一条 advisory 时就返回非零,CI 里直接 fail,但你可能只想阻断高危(CVSS ≥ 7.0)
composer-audit 必须配合 composer-unused 和锁文件扫描
真实有效的审计链是:先剔除僵尸依赖 → 再检查剩余依赖的已知问题 → 最后验证升级后是否破坏自动加载。跳过第一步,等于在垃圾堆里找钉子。
-
composer-unused要加--no-dev参数跑两次:一次针对 prod autoloading,一次针对 test suite,避免漏掉测试专用但实际已废弃的包 - 审计必须基于
composer.lock,而非composer.json:后者只声明意图,前者才反映真实安装树;CI 中应校验composer.lock是否被修改但未提交 - 建议在 CI 中用
composer show --locked --format=json提取所有包名+版本,喂给osv-scanner做二次交叉验证——composer-audit有时漏报 OSV ID
如何设置可落地的阻断阈值
别用“发现漏洞就 fail”,要按风险等级分流:低危告警、中危需人工确认、高危/严重直接阻断构建。这个逻辑必须写死在 CI 配置里,不能靠人盯。
- 用
composer-audit --format=json输出,再用jq提取.advisories[].severity过滤:jq 'map(select(.severity == "CRITICAL" or .severity == "HIGH"))' - 对
CRITICAL级别,强制要求 PR 标题含[SEC]前缀,并关联 GitHub Advisory Database URL - 允许临时豁免需写明理由并签名:在
.composer-audit-ignore文件中存 SHA256(vendor/name+version+advisory_id),且该文件不得 commit 到 main 分支
审计报告必须包含可达性证据
一份合格的审计报告 HTML 页面,至少得证明三点:这个包被谁 require、它的哪些类被实际加载、漏洞函数是否在调用栈中。没有这些,就是无效审计。
- 用
php -d zend_extension=xdebug.so -dxdebug.mode=coverage vendor/bin/phpunit --coverage-php /tmp/cov.php生成覆盖率数据,反向映射到依赖包路径 - 在 CI 中注入
XDEBUG_MODE=trace抓取关键请求的 trace 文件,grep 出涉及漏洞包的函数调用 - 报告页里每个 advisory 条目旁,必须显示 “Reachable: yes/no” + 对应的代码路径(如
App\Services\PaymentService::__construct())
真正难的不是发现漏洞,是确认它能不能被业务流量打穿。这点不做实,所有审计动作都在给报告添数字。


















