本地包安装助手过度信任调用者提供的包名。在 yeoman-environment 中,缺失的生成器可能在未经用户确认的情况下被安装,将攻击者控制的项目元数据转变为包安装和代码执行路径。
我在审查 generator-jhipster 时,基于一个简单的安全问题发现了此漏洞:
攻击者控制的项目元数据能否让开发者工具在用户未明确请求的情况下获取并执行第三方代码?
在这种情况下,答案是肯定的。
最初看起来像 JHipster 问题的问题,结果在 yeoman-environment 中有着更深层的上游根本原因。
漏洞行为出现在 Yeoman 的本地生成器安装流程中,其中调用者提供的缺失包会在未经用户确认的情况下自动安装。在下游消费者将攻击者控制的包名传入该路径的情况下,这就足以创建一个真实的包安装和代码执行链。
该问题被分配为 CVE-2026-42089。
yeoman-environment: yeoman-environment on GitHub
包: yeoman-environment (npm)
CVE: CVE-2026-42089
此漏洞影响了 yeoman-environment,即 Yeoman 生成器加载和引导流程背后的运行时层。官方项目将其描述为处理生成器生命周期和发现的组件,截至 2026 年 6 月 26 日,npm 包页面显示每周下载量为 1,466,426,使其成为 JavaScript 工具生态系统中广泛部署的包。
攻击者控制的项目配置 -> 调用者提供的生成器包名 -> yeoman-environment 静默安装缺失包 -> 下游工具加载已安装的生成器代码 -> 在 CLI 引导期间实现包安装和代码执行
yeoman-environment 是基于 Yeoman 的工具背后的运行时和生成器加载层。
除其他功能外,它还处理:
这意味着它直接位于信任边界上。
相关的问题不在于 Yeoman 是否“只是一个本地工具”。
相关的问题在于:不可信输入能否影响包安装和代码加载行为?
在这种情况下,答案是肯定的。
我关注的不是内存损坏或仅崩溃的 bug。
更重要的目标是扩展和包解析攻击面。
任何满足以下条件的系统:
都值得密切审查。
当下游消费者可以从项目本地数据中派生出这些包名时,这一点尤其重要。
这正是普通配置可能悄然转变为安全边界的地方。
这就是值得关注的正确方向。
我首先通过 generator-jhipster 重现了该行为。
重要的路径是:
.yo-rc.json 声明了一个蓝图包这意味着即使是一个无害的命令,例如:
jhipster --help
也可能在请求的命令完成之前就触及包安装。
这是一个真实的信任边界失效。
下游触发器帮助暴露了它,但不安全的默认行为在于 Yeoman。
这个 bug 很简单。
在 yeoman-environment 中,易受影响的方法是:
async installLocalGenerators(packages) {
const entries = Object.entries(packages);
const specs = entries.map(([packageName, version]) => `${packageName}${version ? `@${version}` : ''}`);
const installResult = await this.repository.install(specs);
const failToInstall = installResult.find(result => !result.path);
if (failToInstall) {
throw new Error(`Fail to install ${failToInstall.pkgid}`);
}
await this.lookup({ packagePaths: installResult.map(result => result.path) });
return true;
}
该方法直接将调用者提供的包名通过以下方式安装:
this.repository.install(specs)
而无需事先提示用户。
这就是核心漏洞。
因为包名不必来自受信任的来源。
如果下游消费者从攻击者控制的项目元数据中派生它们,那么利用链就很直接:
这不仅仅是“包安装发生了”。
这是不可信输入在无明确同意边界的情况下进入包安装接收器。
重要的区别在于从不可信输入静默安装。
以下两者之间存在真正的区别:
当包在之后立即变得可加载时,这种区别甚至更加重要。
问题不在于存在第三方生成器。
问题在于:Yeoman 默认将调用者提供的包名视为可安装,而无需用户确认。
这使得不安全的下游信任假设变得实质性更糟。
这正是修复添加确认关卡的原因。
我使用了两层证明,因为它们同时展示了根本原因和实际下游影响。
第一个证明使用了未修改的 generator-jhipster。
我创建了一个项目,其根目录 .yo-rc.json 引用了一个尚未安装的蓝图包,然后运行:
jhipster --help
这导致 JHipster 在帮助完成之前就将缺失的蓝图传入 Yeoman 的本地生成器安装流程。
重要的结果是:
这清晰地建立了真正的触发条件。
第二个证明使用了一个受控的本地注册表和一个旨在安全展示导入时副作用的包。
这很重要,因为我想展示更强的故事:
这是最强的证据链,因为它将问题从:
“意外的安装尝试”
推进到:
“安装加上下游代码加载路径实际上是可到达的”
这是信任边界失效变得难以忽视的节点。
第一个 PoC 证明了静默安装行为。
第二个 PoC 证明了为什么这种行为重要。
这种分离很重要。
一个停留在:
“可以安装一个包”
的报告,比展示以下内容的报告弱:
这才是完整的故事。
在咨询处理期间和本地审查中,该行为被追溯到 installLocalGenerators() 的引入点:
yeoman-environment 2.9.0
因此受影响范围为:
>= 2.9.0 且 < 6.0.1
修复版本为:
6.0.1
修复正确且最小化。
在 6.0.1 中,installLocalGenerators() 被修改为在安装前添加确认步骤,除非明确请求强制安装。
修复后的形状如下:
async installLocalGenerators(packages, forceInstall = false) {
然后:
const { aproveInstall } = await this.adapter.prompt({
message: `以下包需要在本地仓库中安装: ${specs.join(', ')}。是否继续?`,
type: 'confirm',
name: 'aproveInstall',
default: false,
});
如果用户拒绝,安装将被中止。
这是正确的修复,因为它恢复了缺失的信任边界:
该修复提交于:
78d2af7
通过:
PR #753
这正是你在此类安全漏洞中想要的修复类型:
该问题被合理对待为严重,因为其影响不仅仅是表面的或令人惊讶的行为。
易受影响的行为可能导致:
与该问题关联的 CVSS 向量为:
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
这对于下游利用故事是有意义的:
此问题最初是作为针对 generator-jhipster 的私有报告提交的,因为那是我最初验证的真实世界触发路径。
在分类期间,JHipster 维护者指出自动安装行为本身在 yeoman-environment 中,并引用了上游修复。
这导致了正确的转向:
Yeoman 维护者审查了问题,确认了受影响范围,并通过私有公告跟踪了它。
该报告后来被分配为:
CVE-2026-42089
该公告还记录了通过 generator-jhipster 的真实下游触发路径。
这是一个很好的例子,说明协调披露有时需要多一步:
在这里,下游复现是有用的,但上游包才是 CVE 的正确位置。
关键教训很简单:
包安装是一个安全边界,即使在本地开发者工具中也是如此
很多人本能地低估此类问题,因为它们发生在 CLI 工具中。
这是一个错误。
真正的问题不在于工具是否是本地的。
真正的问题是:
不可信输入能否使工具在没有明确用户决策的情况下获取并信任代码?
在这种情况下,答案是肯定的。
这才是真正的要点。
此问题还强化了良好的漏洞研究中的一些重要内容:
这正是这个 CVE 的样子。
这个漏洞不是关于花哨的有效载荷。
而是关于提出正确的信任边界问题。
一个下游工具让项目本地数据影响包选择。 Yeoman 在未经确认的情况下安装了缺失的包。 剩余的代码加载链完成了其余工作。
这就是为什么它变成了 CVE-2026-42089。
修复于 yeoman-environment 6.0.1。