导语:readlink 看上去无害,找错程序就不同了
安全审计里,我们更常搜 eval、exec 或拼接命令的代码。pnpm 的 POSIX 命令垫片揭示另一类汇点:内部辅助程序用"裸名称"执行,信任它们是系统版本,却把实际选择权交给了调用者的 PATH。如果依赖声明了 readlink 等同名 bin,甚至不用改业务的 build 脚本,也有机会改变某个垫片启动目标之前的执行步骤。
【事实】pnpm Issue #14837于 2026-09-11 指出垫片头部裸名称调用 readlink、dirname、sed、uname;pnpm 12.4.2于 2026-09-15正式发布主要 POSIX 路径的修复。
背景:什么是 bin shim
依赖包可以声明一个 bin,项目安装后 node_modules/.bin 出现可调用入口。pnpm 的垫片不仅转发执行,还要知道自己对应的包、解决符号链接、处理路径形式,再运行真正目标。因此,它是由包管理器生成、后来由项目脚本执行的代码制品,不只是一个静态链接。
【推断】团队审计依赖时习惯看安装脚本,但生成脚本中的外部命令调用也值得纳入可信工具链范围。此处并非断言所有包管理器采用相同头部,或者所有平台都遵守完全相同的 PATH 规则。
源码级拆解:哪些函数必须被信任
【事实】已合并修复 PR #14845于 2026-09-13 解释了完整变更:旧头部根据调用者 PATH 寻找四种辅助程序;新的头部将 readlink、sed、uname 交给 command -p,而 dirname 改用 ${link%/*} 等 shell 参数展开,不再启动外部 dirname。跳过安装脚本时使用的 pnpm 占位垫片也曾以同样方式找 readlink、dirname,并纳入修复。
| 程序 | 原来在垫片中的作用 | 改变后的安全边界 |
|---|---|---|
readlink |
解析垫片的符号链接目标 | command -p 不从依赖 bin 优先查找 |
dirname |
从链接和路径求目录 | 参数展开,不启动这个外部程序 |
sed |
规范化路径分隔符等文本 | command -p 在系统默认路径查找 |
uname |
判断运行环境分支 | command -p 在系统默认路径查找 |
command -p 是 POSIX shell 的搜索行为,不应在文章里写成"绝对路径白名单 /usr/bin"。参数展开减少外部进程数,也使原来的 dirname 入口不再存在。但改成更安全的查找策略仍需要处理兼容性 :PR 专门处理 $0 是裸名称时目录应为 . 的情形,并测试通过符号链接调用垫片的情况。
边界未闭合:cygpath 与 wslpath
【事实】12.4.2 发布说明明确承认,在 Cygwin、MSYS2、WSL 的路径转换分支,辅助程序仍依赖 PATH。PR #14845解释为何没有简单把 cygpath、wslpath 也直接改成 command -p:这些工具在特定平台可能不处于 POSIX 系统默认路径,误改可能让垫片无法向原生 node.exe 传入可用路径。官方用 Issue #14866追踪剩余风险。
【推断】安全修复不仅要通过 Linux CI,也要把实际使用的跨平台 shell 和原生运行时组合纳入测试,否则"更安全的搜索"可能变成另一种可用性故障。残余问题需要单独的版本与配置判断,不能凭本文推测未来补丁发布日期。
安全案例:用无害标记验证不变量
无需生成恶意包。在隔离的代码审查中列出"垫片头部会启动的外部程序"和"程序从哪份搜索路径找到":
before = {"readlink": "caller_PATH", "dirname": "caller_PATH",
"sed": "caller_PATH", "uname": "caller_PATH"}
after = {"readlink": "system_default", "dirname": "shell_builtin_logic",
"sed": "system_default", "uname": "system_default"}
assert all(after[x] != "caller_PATH" for x in before)
这是依据 PR 绘制的决策模型 ,不执行二进制、不联系包源,也不覆盖 cygpath/wslpath 的残余路径。真正的回归测试还应让 PATH 前部出现同名但无害的诱饵,断言目标包路径不变;PR 作者已描述这类负向测试,但本文不公开可投毒制品或攻击构造。
风险与行动建议
【事实】风险是"不可信依赖的同名 bin 可能先于原定目标运行",并可影响垫片最终执行的路径。实际发生须同时满足安装相应依赖、运行相关垫片和搜索顺序条件。发布资料没有给出在野利用、CVE 或正式 CVSS 评分。
【建议·P0】12 系列升级至确认的 12.4.2或更高版本,按发布说明重新安装依赖以替换旧垫片,抽查生成头部。对 WSL 等保留额外依赖准入及 Runner 隔离措施。
【建议·P1】开展"生成脚本源代码---安装后脚本制品"的双向审计,标出所有裸名称外部辅助命令、PATH 的来源、符号链接和路径转换分支。依赖包的 bin 字段也应接受代码评审,不只审查生命周期脚本。
【建议·P2】给内部垫片/封装脚本建立不变量测试:诱饵 PATH 不能改变辅助程序身份和最终目标;裸 $0、符号链接、Linux/macOS 与 Windows 兼容 shell 必须分别测试;维护者确认未修复分支的后续处置,而不是宣称单次提交覆盖全部平台。
总结
同样是四个普通工具名,安全问题不在名字,而在谁能决定它们指向哪个文件。pnpm 的修复让主要 POSIX 垫片把内部辅助程序从依赖可影响的搜索路径迁出;源码审计还要把生成文件、跨平台分支和旧文件替换连成同一个验收对象。