PACKETSTORM 8.6 HIGH

πŸ“„ Yeoman Environment 6.0.0 Code Execution_PACKETSTORM:224376

8.6 / 10
HIGH
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H

Description

Yeoman Environment versions 2.9.0 through 6.0.0 have an issue where missing generators can be installed without user confirmation, turning attacker-controlled project metadata into a package-install and code-execution path...
Visit Original Source

Basic Information

ID PACKETSTORM:224376
Published Jun 26, 2026 at 00:00

Affected Product

Affected Versions # 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**.

πŸ’­ Join the Security Discussion

πŸ”’ Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.