Share
## 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**.