必须通过自定义Plugin监听PackageEvents::PRE_PACKAGE_INSTALL事件,在解压前检查作者邮箱、autoload.files路径及dist.signature有效性,任一异常即抛RuntimeException中断安装;composer audit仅事后扫描已知CVE,无法实时阻断。

composer install 和 composer update 本身不会主动拦截未知恶意包——它只对已知 CVE 做审计,不扫描代码行为。真正能实现“实时预警+阻断”的组合是:allow-plugins 白名单 + composer audit 扫描 + 自定义 Plugin 监听 PackageEvents::PRE_PACKAGE_INSTALL。
怎么让 composer install/update 在装包前就中断恶意包?
靠默认行为做不到。必须用自定义 Plugin 拦在解压前,否则包文件已落地、autoload 已注册、脚本可能已执行。
-
PackageEvents::PRE_PACKAGE_INSTALL是唯一能在单个包写入 vendor 前触发的事件,v2.5+ 稳定支持,v2.4 及以下需退回到监听CommandEvent+ 解析命令行参数 - 插件中需检查:作者邮箱是否匹配钓鱼模式(如
admin@gmail.com)、autoload.files是否含可疑路径(如"./shell.php")、dist.signature是否为空或无效 - 匹配任意一条规则就抛
RuntimeException,Composer 会立即中止当前包安装,并退出整个流程 - 别依赖
composer.json中的scripts字段——恶意逻辑常藏在测试文件、注释、或 base64 编码字符串里,静态扫描容易漏
为什么 composer audit 不能替代实时阻断?
composer audit 是事后扫描,不是前置防护。它只查 composer.lock 中已锁版本是否匹配 FriendsOfPHP 安全数据库里的 CVE 条目,对未入库的 0day、私有包、或含恶意 payload 但无 CVE 的包完全无感。
- 漏洞入库通常滞后几小时到数天,攻击者利用窗口期足够完成供应链投毒
- 它不检查
autoload.files、bin路径、或post-install-cmd中的动态命令拼接 - 运行
composer audit不会中断流程,只输出警告;要阻断必须配合 CI 脚本做exit 1判断 - 若项目没
composer.lock(比如刚 init),audit直接无结果——而此时require正在拉取最新版,风险最高
allow-plugins 白名单为什么必须和 --no-scripts --no-plugins 配合用?
allow-plugins 控制的是“哪些插件允许被加载”,但它不阻止插件内部注册的钩子;--no-scripts --no-plugins 是运行时强制开关,两者作用层不同,缺一不可。
- 仅设
"*": false但没加--no-plugins:某些插件(如symfony/flex)会在activate()阶段直接注册post-autoload-dump,绕过白名单 - 仅加
--no-scripts但没禁插件:插件可调用exec()或写文件,等同于手动执行 shell 命令 - CI/CD 中必须写成
composer install --no-scripts --no-plugins --no-interaction,顺序错误(如composer --no-scripts install)会被忽略 -
allow-plugins是声明式管控,适合长期策略;--no-scripts --no-plugins是执行时兜底,适合部署环境
如何验证签名和 dist.shasum 是否真起作用?
签名验证不是开箱即用,很多项目看似启用实则失效。关键验证点不在配置,而在实际下载行为。
- 运行
composer show --security,输出含Signature verification: enabled才算到位;若显示disabled或无此行,说明 Packagist 元数据未签名或本地缓存损坏 - 检查
vendor/composer/installed.json中每个包的dist.signature字段是否存在且非空——空值代表该包未签名,或签名验证失败后被降级处理 -
composer.lock中每个包的dist.shasum必须与实际下载文件 SHA256 一致,可用sha256sum vendor/package/file.php手动比对 - 私有仓库若用 Satis 搭建,必须开启
signing配置并生成 GPG key,否则dist.signature永远为空
真正难的不是加一行配置,而是让签名验证、插件白名单、预安装钩子三者在不同 Composer 版本、不同仓库源、不同 CI 环境下始终生效——中间任何一环降级或被绕过,整套防线就形同虚设。


















