composer无法在同进程安全运行post-install-cmd,因其共享全局状态、autoload单例及上下文,必须通过proc_open启动独立进程、新建临时vendor目录并配合--no-scripts--no-plugins三者协同实现隔离。

Composer 本身不提供运行时脚本沙箱,所谓“隔离执行”必须靠分进程 + 独立 vendor + 显式禁用钩子三者协同,缺一不可。
为什么不能在同一个 PHP 进程里“安全运行”第三方 post-install-cmd
post-install-cmd 脚本一旦触发,就和当前进程共享全部上下文:全局变量、已加载类、autoload 映射、甚至 getcwd() 和 $_ENV。哪怕你用 chroot 或 ini_set('disable_functions', 'exec'),也无法阻止它:
- 直接 require 其他 PHP 文件(绕过 disable_functions)
- 写入
vendor/autoload.php或覆盖composer.lock - 调用
eval()、assert()或反射修改自身类结构 - 利用
opcache_reset()清空缓存后重新加载恶意定义
更关键的是:Composer 的 autoloader 是单例注册的,同一进程内无法加载两套 vendor/ —— 这不是权限问题,是语言层限制。
真正可行的沙箱:proc_open() 启动干净子进程
唯一能隔离脚本副作用的方式,是让每个待审计的包在独立进程中运行,且该进程从头开始构建环境:
- 为待测包新建临时目录:
./sandbox-xxx/,内含独立composer.json(仅含该包及其最小依赖) - 执行:
composer install --no-dev --no-scripts --no-plugins --optimize-autoloader - 确认
vendor/composer/autoload_dev.php不存在(否则测试类可能被加载) - 用
proc_open()启动子进程:php -d disable_functions=exec,system,shell_exec ./sandbox-xxx/bin/audit.php - 子进程启动前调用
reset_get_defined_constants()并 unset 所有$_SERVER中敏感键(如HOME、COMPOSER_HOME)
注意:--no-scripts --no-plugins 必须同时出现,顺序不能错——composer --no-scripts install 是无效参数写法。
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
如何判断一个 post-install-cmd 是否“真危险”
别只看脚本字符串是否含 exec,很多恶意逻辑藏得更深:
- 检查是否调用
file_put_contents(__DIR__.'/../public/shell.php', ...)—— 写 Web 可访问路径是常见攻击面 - 搜索
base64_decode(、gzinflate(、str_rot13(,这些常用于隐藏 payload - 运行
composer validate --strict,它会警告含curl、wget、http://的脚本命令 - 对疑似命令加
echo测试真实展开:echo "php -r 'echo getenv(\"PATH\");'",观察是否泄露敏感环境变量
特别留意 post-root-package-install 钩子:它在 root 包安装完成时触发,常被用于探测 CI 环境变量或注入条件逻辑。
CI/CD 中必须强制的最小安全基线
生产部署和自动化构建绝不能依赖“开发人员自觉加参数”,必须固化为流程约束:
- Docker 构建中,
RUN指令必须显式写成:composer install --no-scripts --no-plugins --no-dev --optimize-autoloader - GitLab CI 或 GitHub Actions 中,禁止使用
composer install简写,所有 job 都要带全参数 - 项目级锁定:把
composer.json的scripts字段设为空数组,例如:"post-install-cmd": []—— 不要用null或省略字段,Composer 会回退到默认行为 - 定期扫描:
grep -r "post-.*-cmd" vendor/*/composer.json | grep -v "[]",揪出第三方包自带的未清空钩子
最易被忽略的一点:即使你禁用了所有脚本,只要 composer.lock 里记录了 dev 包,--no-dev 就可能失效 —— 因为 lock 文件优先级高于命令行参数。CI 构建前务必先跑一次 composer update --no-dev --lock 并提交更新后的 lock 文件。










