--no-plugins是必须加在每条命令末尾的硬开关,用于强制跳过所有插件加载与执行,切断PluginInterface::activate()调用链;漏掉则可能因依赖树中存在旧版hirak/prestissimo等包而静默执行恶意代码,且参数位置错误(如composer --no-plugins install)会被忽略。

为什么 --no-plugins 不是可选,而是必须加在每条命令末尾
因为 --no-plugins 不是“禁用插件功能”,而是切断插件执行链的硬开关。只要漏掉它,Composer 就会扫描 vendor/、读取每个包的 composer.json 中的 plugins 字段、调用 PluginInterface::activate()——哪怕你没显式配置 allow-plugins,只要依赖树里有旧版 hirak/prestissimo 或 fork 的 phpunit/phpunit,它就可能静默运行任意代码。
常见错误写法:composer --no-plugins install 等价于 composer --help,参数被忽略;正确写法只能是 composer install --no-plugins,且必须紧贴子命令之后。
CI 脚本若封装了 bin/composer,要确认该脚本没默认注入 --plugins 或其他 flag;环境变量 COMPOSER_NO_PLUGINS=1 可作备用,但优先级低于命令行参数,部分旧镜像甚至未启用该变量支持。
只加 --no-plugins 仍不安全:必须搭配 --no-scripts
--no-plugins 拦不住你项目自己写的 scripts,比如 post-install-cmd;而 --no-scripts 也拦不住插件绕过 scripts 字段直接注入的钩子——典型如 symfony/flex,它在安装阶段自行注册 hook,完全不走 composer.json 的 scripts。
真正生效的组合只有一种:composer install --no-scripts --no-plugins。缺一不可,顺序不能错(composer --no-scripts install 是无效参数)。
- 插件不是运行时才拉取的远程资源,而是随
composer install解压到vendor/后立即参与构建流程 - 所谓“禁止自动下载”是伪命题——恶意插件代码早已随包一起下载完毕,危险在于安装/更新时的自动执行
- 清空
scripts字段比依赖参数更可靠,命令行参数容易漏,尤其 CI 配置不一致或本地手滑
allow-plugins 白名单怎么配才真正起作用
allow-plugins 是 Composer 2.2+ 的唯一可控插件授权开关,必须写在 composer.json 的 config 下,不是全局配置。设为 true 等同于关掉安全闸门,任何依赖(包括 transitive 依赖)都能悄悄加载并执行代码。
最小化起点应是:"allow-plugins": {"*": false},再显式添加你真正需要的包,例如:"symfony/flex": true。通配符 "vendor/*" 不被支持,"*/*" 会被忽略。
加完白名单后,立刻跑 composer install --dry-run 验证是否仍被拦截;若还报错,说明该插件依赖另一个未授权插件(如某些 scaffold 工具链带二级插件)。
别看到报错就加进白名单——得先确认这个插件是否真有必要、是否可信:
- 查 Packagist 页面:是否有 Verified 标识、LICENSE 是否明确(MIT/Apache-2.0)、GitHub stars ≥ 500 且最近 6 个月有提交
- 运行 composer show vendor/name,检查输出是否含 Plugin 字样,并翻源码看是否调用 exec()、eval() 或写入敏感路径
离线环境加 --no-plugins 还连网?原因和对策
COMPOSER_DISABLE_NETWORK=1 只拦 Composer 主流程的 HTTP 请求,拦不住插件或脚本内部发起的调用。比如 hirak/prestissimo 在 post-install 阶段自己拉包,phpstan/extension-installer 可能读远程配置——这些都不受环境变量控制。
所以离线场景下,--no-plugins --no-scripts 不仅是安全要求,更是功能性前提。否则即使缓存 ZIP 全在,也会因插件主动联网失败。
排查要点:
- 运行 composer install --no-plugins --no-scripts --dry-run,观察是否仍有网络请求
- 检查 vendor/ 下是否存在疑似插件目录(如 hirak/prestissimo、phpstan/extension-installer)
- 若已存在,可临时重命名其目录为 .disabled 后缀,再试安装
真正难缠的不是参数本身,而是插件执行时机早于网络拦截点——它发生在 autoload 注册前、dist 解压后,此时 COMPOSER_DISABLE_NETWORK 还没生效。


















