vendor/bin是Composer源码级硬编码的符号链接目标目录,由BinLinker类强制写入,不依赖配置;其安全机制依赖vendor只读、composer.lock锁定及CI禁用update,而非软链路径校验。

composer install 为什么默认只在 vendor/bin 做软链
因为 vendor/bin 是 Composer 硬编码的符号链接目标目录,不依赖任何配置项——哪怕你删掉 composer.json 里的 config.bin-dir,它依然存在、依然生效。这不是“约定”,是源码级强制行为:Composer\Installer\BinLinker 类在安装每个含 bin 字段的包时,无条件往 vendor/bin/ 写链接(Linux/macOS)或脚本(Windows)。安全沙箱的第一层就在这里:所有依赖二进制被隔离进 vendor/ 子树,不污染系统 PATH 或用户家目录。
global 安装的 bin 目录不在 vendor 下,为什么更危险
composer global require 把二进制文件放进 ~/.composer/vendor/bin/(或你用 composer config --global home 指定的路径),这个位置天然脱离项目边界,且默认不在 PATH 中——但一旦你手动把它加进 PATH,就等于把所有全局工具暴露给整个 shell 会话。问题在于:
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
- 多个项目可能依赖不同版本的同一工具(比如
phpstanv1.8 和 v1.10),全局装一个就必然冲突 -
~/.composer/vendor/目录权限若被其他进程写入(如恶意脚本),vendor/bin/下的链接可能指向任意可执行文件 - Docker 构建中若用
composer global,会破坏分层缓存,且无法审计该目录下实际装了什么
如何让 vendor/bin 的软链真正受控
关键不是“能不能改路径”,而是“改了之后是否还受项目约束”。实操上只有两条路:
- 保留默认
vendor/bin,并在 CI/CD 或本地开发中用PATH="./vendor/bin:$PATH"临时提升优先级——路径是相对的,只对当前工作目录生效,不会跨项目泄漏 - 用
composer exec替代直接调用,例如composer exec phpunit。它自动在vendor/bin/中查找并执行,不依赖PATH,也不需要你手动维护链接 - 如果非要自定义 bin 目录(如
"config": {"bin-dir": "tools"}),必须确保tools/目录存在且只由composer install修改——不能手动生成或编辑其中文件,否则链接可能指向不可信路径
软链目标路径没校验,这是最大盲区
Composer 创建软链时,只检查源文件是否存在、是否可执行,**完全不校验目标路径是否属于当前项目 vendor 子树**。这意味着:如果某个依赖包的 composer.json 里写了 "bin": ["/tmp/malware"](虽然极少见,但包作者恶意或被劫持时可能发生),composer install 仍会照常创建指向该路径的链接。真正起作用的安全机制其实是:vendor/ 目录本身应设为只读(生产环境)、composer.lock 必须提交 Git 并经人工审核、CI 中禁用 composer update——软链只是表象,约束力来自整个锁定链条。

















