## https://sploitus.com/exploit?id=PACKETSTORM:224376
# CVE-2026-42089
A local package installation helper trusted caller-supplied package names too much. In yeoman-environment, missing generators could be installed without user confirmation, turning attacker-controlled project metadata into a package-install and code-execution path.
## Intro
I found this issue while reviewing **generator-jhipster** with a simple security question in mind:
**Can attacker-controlled project metadata make a developer tool fetch and execute third-party code before the user explicitly asked for that?**
In this case, the answer was yes.
What initially looked like a JHipster issue turned out to have a deeper upstream root cause in **yeoman-environment**.
The vulnerable behavior was in Yeoman's local generator installation flow, where missing packages supplied by the caller were installed automatically without user confirmation. In a downstream consumer that passed attacker-controlled package names into that path, that was enough to create a real package-installation and code-execution chain.
That issue became **CVE-2026-42089**.
**yeoman-environment:** [yeoman-environment on GitHub](https://github.com/yeoman/environment)
**Package:** `yeoman-environment` (npm)
**CVE:** CVE-2026-42089
This affected **yeoman-environment**, the runtime layer behind Yeomanβs generator-loading and bootstrapping flow. The official project describes it as the component that handles generator lifecycle and discovery, and as of **June 26, 2026**, the npm package page listed **1,466,426 weekly downloads**, making this a widely deployed package in the JavaScript tooling ecosystem.
<img width="840" height="560" alt="photo0" src="https://github.com/user-attachments/assets/7bb6754e-d087-4669-833a-7466d71d4ea1" />
---
## Attack Chain
`attacker-controlled project config -> caller-supplied generator package names -> yeoman-environment silently installs missing packages -> downstream tool loads installed generator code -> package installation and code execution during CLI bootstrap`
---
## What yeoman-environment Does
**yeoman-environment** is the runtime and generator-loading layer behind Yeoman-based tooling.
Among other things, it handles:
- generator lookup
- local repository management
- package installation for missing generators
- registration and loading of generators
That means it sits directly on a trust boundary.
The relevant question is not whether Yeoman is "just a local tool."
The relevant question is whether **untrusted input can influence package installation and code loading behavior**.
In this case, it could.
---
## Why This Surface Was Worth Looking At
I was not looking for memory corruption or crash-only bugs here.
The stronger target was the extension and package-resolution surface.
Any system that:
- accepts package names from another layer,
- installs them automatically,
- and then makes them available for loading
deserves close scrutiny.
That is especially true when the downstream consumer can derive those package names from project-local data.
This is exactly the kind of place where ordinary configuration can quietly become a security boundary.
That was the right place to look.
---
## The Boundary I Focused On
I first reproduced the behavior through **generator-jhipster**.
The important path was:
- a project-local `.yo-rc.json` declares a blueprint package
- JHipster reads that blueprint entry during CLI bootstrap
- missing blueprint packages are passed into Yeoman's install path
- Yeoman installs them silently
- downstream logic then imports blueprint CLI modules
That meant even a benign command such as:
```bash
jhipster --help
```
could reach package installation before the requested command completed.
That is a real trust-boundary failure.
The downstream trigger helped expose it, but the unsafe default behavior was in Yeoman.
---
## Root Cause
The bug was simple.
In `yeoman-environment`, the vulnerable method was:
```js
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;
}
```
That method installed caller-supplied package names directly through:
```js
this.repository.install(specs)
```
without prompting the user first.
That is the core vulnerability.
### Why this is exploitable
Because the package names do not have to come from a trusted source.
If a downstream consumer derives them from attacker-controlled project metadata, then the exploit chain is straightforward:
- attacker controls package names indirectly
- the downstream tool passes them to Yeoman
- Yeoman installs them silently
- downstream code continues with the newly installed package available for loading
That is not just "package install happened."
That is untrusted input crossing into a package installation sink without an explicit consent boundary.
---
## What Makes This a Security Issue, Not Just Tooling Behavior
The important distinction is silent installation from untrusted input.
There is a real difference between:
- a user explicitly deciding to install a package, and
- a framework silently installing a package because project-local data caused a caller to ask for it
That distinction matters even more when the package becomes loadable immediately afterward.
The issue was not that third-party generators exist.
The issue was that **Yeoman treated caller-supplied package names as installable by default without user confirmation**.
That makes unsafe downstream trust assumptions materially worse.
This is exactly why the fix added a confirmation gate.
---
## PoC
I used two layers of proof because they demonstrated both root cause and real downstream impact.
### PoC 1: stock downstream trigger
The first proof used unmodified `generator-jhipster`.
I created a project with a root `.yo-rc.json` that referenced a blueprint package which was not already installed, then ran:
```bash
jhipster --help
```
That caused JHipster to pass the missing blueprint into Yeoman's local generator installation flow before help completed.
The important result was:
- a harmless-looking command reached package resolution and installation behavior
- the project-local metadata was enough to trigger the install path
That established the real trigger condition clearly.
### PoC 2: controlled package execution path
The second proof used a controlled local registry and a package designed to demonstrate import-time side effects safely.
That mattered because I wanted to show the stronger story:
- project-local metadata influences package selection
- Yeoman installs the package silently
- downstream logic loads installed blueprint CLI modules
- code execution becomes reachable during bootstrap
This was the strongest evidence chain because it moved the issue beyond:
> "unexpected install attempt"
and into:
> "install plus downstream code-loading path is actually reachable"
That is the point where the trust-boundary failure becomes much harder to dismiss.
---
## Why the PoCs Were Chosen This Way
The first PoC proves the silent installation behavior.
The second PoC proves why that behavior matters.
That split was important.
A report that stops at:
> "a package can be installed"
is weaker than a report that shows:
- attacker-controlled input reaches the install path
- the install happens without confirmation
- downstream logic makes code execution reachable
That is the complete story.
---
## Affected Range
During advisory handling and local review, the behavior was traced back to the introduction of `installLocalGenerators()` in:
```text
yeoman-environment 2.9.0
```
The affected range was therefore:
```text
>= 2.9.0 and < 6.0.1
```
The fixed version was:
```text
6.0.1
```
---
## Fix Analysis
The fix was correct and minimal.
In `6.0.1`, `installLocalGenerators()` was changed to add a confirmation step before installation unless force-install is explicitly requested.
The fixed shape looked like this:
```js
async installLocalGenerators(packages, forceInstall = false) {
```
and then:
```js
const { aproveInstall } = await this.adapter.prompt({
message: `The following packages need to be installed in the local repository: ${specs.join(', ')}. Do you want to proceed?`,
type: 'confirm',
name: 'aproveInstall',
default: false,
});
```
If the user declines, installation is aborted.
That is the right fix because it restores the missing trust boundary:
- caller-supplied package names are no longer silently installed by default
- explicit user approval is required
- downstream tools can no longer rely on accidental implicit trust
This fix landed in:
```text
78d2af7
```
through:
```text
PR #753
```
That is exactly the kind of remediation you want in a security issue like this:
- small
- direct
- easy to reason about
- tied to the vulnerable sink itself
---
## Severity and Classification
This issue was reasonably taken seriously because the impact is more than cosmetic or surprising behavior.
The vulnerable behavior can lead to:
- attacker-selected package installation
- network access to package infrastructure from benign command paths
- downstream code-loading reachability
- compromise of the developer environment in affected consumers
The CVSS vector associated with the issue was:
```text
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
```
That makes sense for the downstream exploit story:
- local execution context
- low complexity
- no prior privileges required
- user interaction required
- strong confidentiality, integrity, and availability impact once the package execution path is reached
---
## Disclosure
This issue started as a private report against **generator-jhipster**, because that was the real-world trigger path I initially validated.
During triage, the JHipster maintainers pointed out that the auto-install behavior itself was in **yeoman-environment** and referenced the upstream fix.
That led to the correct pivot:
- narrow the root cause to Yeoman
- treat JHipster as a downstream affected consumer
- report the upstream issue privately
The Yeoman maintainers reviewed the issue, confirmed the affected range, and tracked it through a private advisory.
The report was later assigned:
**CVE-2026-42089**
That advisory also documented the real downstream trigger path through `generator-jhipster`.
This was a good example of why coordinated disclosure sometimes needs one extra step:
- first identify the practical trigger
- then identify the true ownership boundary
Here, the downstream reproduction was useful, but the upstream package was the right place for the CVE.
---
## What This Bug Actually Teaches
The key lesson here is simple:
> package installation is a security boundary, even in local developer tooling
A lot of people instinctively downgrade issues like this because they happen in CLI tools.
That is a mistake.
The real question is not whether the tool is local.
The real question is:
**Can untrusted input make the tool fetch and trust code without an explicit user decision?**
In this case, yes.
That is the real takeaway.
This issue also reinforces something important about good vulnerability research:
- the first product you reproduce on is not always the true root cause owner
- downstream PoCs are often what make the risk obvious
- upstream trust-boundary mistakes are where the actual fix belongs
That was exactly the shape of this CVE.
---
## Key Points
- package-install helpers are security boundaries
- caller-supplied package names should not be silently installed by default
- local project metadata can become dangerous when it influences extension loading
- the downstream reproduction in generator-jhipster exposed the issue clearly
- the root cause still belonged to yeoman-environment
- adding an explicit confirmation gate was the correct fix
---
## Final Words
This vulnerability was not about a flashy payload.
It was about asking the right trust-boundary question.
A downstream tool let project-local data influence package selection.
Yeoman installed the missing package without confirmation.
The rest of the code-loading chain did the rest.
That is why this became **CVE-2026-42089**.
Fixed in **yeoman-environment 6.0.1**.