composer的post-install-cmd和post-update-cmd钩子会无条件执行高危指令,如php artisan migrate --force,若未校验app_env或ci环境变量,易在生产环境误触发迁移导致数据丢失;必须通过shell脚本显式校验后才允许执行,或移出钩子改用显式命令。

Composer install 时自动执行的脚本有哪些风险
Composer 的 post-install-cmd 和 post-update-cmd 钩子会无条件执行 scripts 中定义的命令,包括调用 PHP 文件、shell 脚本甚至外部二进制。一旦依赖包被投毒(如恶意维护者或供应链劫持),这些脚本可能在你本地执行任意代码——不经过任何确认,不依赖运行时上下文,只要 composer install 就触发。
常见高危模式:
-
"php artisan clear-compiled"看似无害,但若artisan被篡改或其加载的 bootstrap 文件被污染,就变成入口点 -
"sh ./build.sh"直接执行未校验的 shell 脚本,尤其当该脚本来自 dev-dependency 或私有包时 -
"php -r \"eval(base64_decode(...))\""在某些老旧模板包中真实存在,静态扫描必须能识别这种载荷
用 PHP-Parser + 自定义访客做静态钩子分析
Composer 自身不提供钩子内容的沙箱或签名验证,得靠外部工具提前“看一眼”。推荐用 nikic/php-parser 解析 composer.json,再递归检查所有被引用的脚本文件——不是只读 JSON,而是真正解析执行流。
关键判断逻辑:
- 提取
scripts下所有值,区分是内建命令(如dump-autoload)、PHP 文件路径(含.php后缀或php前缀)、还是 shell 命令 - 对 PHP 路径做
file_exists+is_readable校验,跳过不存在或权限异常的(避免误报) - 对每个可读的 PHP 文件,用
PhpParser\ParserFactory::create()解析 AST,重点访客NodeVisitor检查:Eval_、ShellExec、exec/system调用、require/include动态拼接路径
示例片段(简化):
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$stmts = $parser->parse(file_get_contents($scriptPath));
$visitor = new class extends NodeVisitor {
public $hasDanger = false;
public function enterNode(Node $node) {
if ($node instanceof Node\Expr\FuncCall &&
$node->name instanceof Node\Identifier &&
in_array(strtolower($node->name->toString()), ['exec', 'system', 'shell_exec', 'eval'])) {
$this->hasDanger = true;
}
}
};
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
$traverser->traverse($stmts);
绕过 composer.lock 的“假安全”陷阱
很多人以为锁定了 composer.lock 就万事大吉,但问题在于:钩子脚本的执行逻辑往往藏在 vendor 里的某个 PHP 文件中,而这个文件可能根本没被纳入版本控制(比如生成的缓存文件、构建产物),或者它依赖的配置来自环境变量或远程 URL——静态分析必须覆盖这些间接调用链。
容易忽略的点:
-
autoload-dev下注册的类,可能在post-*钩子中被加载并触发初始化逻辑,但这类类通常不在主 autoload 范围,常规扫描会漏掉 - 使用
composer create-project时,模板包自身的scripts会继承到新项目,但开发者常忘记审计模板源码 - 某些包把钩子逻辑拆成多个小文件,通过
require_once __DIR__ . '/steps/' . $_ENV['STEP'] . '.php';加载,静态分析需识别这种动态拼接
CI 流水线里加一道 pre-install 检查
别等 composer install 在 CI 里跑完才发现问题。应该在安装前插入一步:下载 composer.json 和所有声明的脚本文件(根据 require/require-dev 版本范围估算 vendor 目录结构,或直接 composer show --no-ansi --format=json 获取包元数据),然后跑上述静态分析。
实操建议:
- 用
composer validate --no-check-all先确保 JSON 结构合法,再进深度扫描 - 对私有包,提前 fetch 到临时目录,避免扫描时因网络或权限失败中断流水线
- 把扫描结果转成 SARIF 格式,接入 GitHub Code Scanning 或 GitLab Secure Reports,让问题直接标在 PR 上
- 禁止在生产构建中启用
scripts:CI 中始终加--no-scripts,真要执行也应显式调用composer run-script xxx并人工复核
最麻烦的其实是那些“看起来只是清缓存、重写配置”的脚本——它们往往用最朴素的语法绕过所有正则扫描,必须走 AST 级别分析。别信注释,别信文件名,只信实际 AST 节点。










