
Un helper di installazione di pacchetti locale si fidava troppo dei nomi dei pacchetti forniti dal chiamante. In yeoman-environment, i generatori mancanti potevano essere installati senza conferma dell'utente, trasformando i metadati del progetto controllati dall'attaccante in un percorso di installazione di pacchetti ed esecuzione di codice.
Un helper locale per l'installazione di pacchetti si fidava troppo dei nomi di pacchetto forniti dal chiamante. In yeoman-environment, i generatori mancanti potevano essere installati senza conferma dell'utente, trasformando metadati di progetto controllati dall'attaccante in un percorso di installazione di pacchetti ed esecuzione di codice.
Ho trovato questo problema durante la revisione di generator-jhipster con una semplice domanda di sicurezza in mente:
I metadati di progetto controllati dall'attaccante possono far sì che uno strumento di sviluppo recuperi ed esegua codice di terze parti prima che l'utente lo richieda esplicitamente?
In questo caso, la risposta è stata sì.
Quello che inizialmente sembrava un problema di JHipster si è rivelato avere una causa principale più profonda in yeoman-environment.
Il comportamento vulnerabile si trovava nel flusso di installazione locale dei generatori di Yeoman, dove i pacchetti mancanti forniti dal chiamante venivano installati automaticamente senza conferma dell'utente. In un consumatore a valle che passava nomi di pacchetto controllati dall'attaccante in quel percorso, questo era sufficiente per creare una vera catena di installazione di pacchetti ed esecuzione di codice.
Questo problema è diventato CVE-2026-42089.
yeoman-environment: yeoman-environment su GitHub
Pacchetto: yeoman-environment (npm)
CVE: CVE-2026-42089
Questo ha interessato yeoman-environment, il livello runtime dietro il flusso di caricamento e avvio dei generatori di Yeoman. Il progetto ufficiale lo descrive come il componente che gestisce il ciclo di vita e la scoperta dei generatori e, al 26 giugno 2026, la pagina del pacchetto npm elencava 1.466.426 download settimanali, rendendolo un pacchetto ampiamente distribuito nell'ecosistema degli strumenti JavaScript.
configurazione di progetto controllata dall'attaccante -> nomi di generatori forniti dal chiamante -> yeoman-environment installa silenziosamente i pacchetti mancanti -> lo strumento a valle carica il codice del generatore installato -> installazione del pacchetto ed esecuzione del codice durante l'avvio della CLI
yeoman-environment è il runtime e il livello di caricamento dei generatori alla base degli strumenti basati su Yeoman.
Tra le altre cose, gestisce:
Ciò significa che si trova direttamente su un confine di fiducia.
La domanda rilevante non è se Yeoman sia "solo uno strumento locale".
La domanda rilevante è se l'input non fidato possa influenzare il comportamento di installazione dei pacchetti e caricamento del codice.
In questo caso, poteva.
Non stavo cercando vulnerabilità di corruzione della memoria o bug solo di crash.
L'obiettivo più forte era la superficie di estensione e risoluzione dei pacchetti.
Qualsiasi sistema che:
merita un esame attento.
Questo è particolarmente vero quando il consumatore a valle può derivare quei nomi di pacchetto da dati locali del progetto.
Questo è esattamente il tipo di posto in cui una configurazione ordinaria può tranquillamente diventare un confine di sicurezza.
Quello era il posto giusto dove guardare.
Ho prima riprodotto il comportamento attraverso generator-jhipster.
Il percorso importante era:
.yo-rc.json locale del progetto dichiara un pacchetto blueprintCiò significava che anche un comando benigno come:
jhipster --help
poteva raggiungere l'installazione del pacchetto prima che il comando richiesto fosse completato.
Questo è un vero fallimento del confine di fiducia.
Il trigger a valle ha aiutato a esporlo, ma il comportamento predefinito non sicuro era in Yeoman.
Il bug era semplice.
In yeoman-environment, il metodo vulnerabile era:
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;
}
Questo metodo installava direttamente i nomi di pacchetto forniti dal chiamante tramite:
this.repository.install(specs)
senza prima chiedere all'utente.
Questa è la vulnerabilità principale.
Perché i nomi di pacchetto non devono provenire da una fonte fidata.
Se un consumatore a valle li deriva da metadati di progetto controllati dall'attaccante, la catena di sfruttamento è semplice:
Non è solo "installazione del pacchetto avvenuta".
È input non fidato che attraversa un sink di installazione di pacchetti senza un confine di consenso esplicito.
La distinzione importante è l'installazione silenziosa da input non fidato.
C'è una reale differenza tra:
Quella distinzione conta ancora di più quando il pacchetto diventa caricabile immediatamente dopo.
Il problema non era che esistono generatori di terze parti.
Il problema era che Yeoman trattava i nomi di pacchetto forniti dal chiamante come installabili per impostazione predefinita senza conferma dell'utente.
Ciò rende le ipotesi di fiducia a valle non sicure materialmente peggiori.
Questo è esattamente il motivo per cui la correzione ha aggiunto una porta di conferma.
Ho usato due livelli di prova perché dimostravano sia la causa principale che l'impatto reale a valle.
La prima prova ha usato generator-jhipster non modificato.
Ho creato un progetto con un file .yo-rc.json principale che faceva riferimento a un pacchetto blueprint non ancora installato, quindi ho eseguito:
jhipster --help
Ciò ha fatto sì che JHipster passasse il blueprint mancante nel flusso di installazione locale del generatore di Yeoman prima che l'aiuto fosse completato.
Il risultato importante è stato:
Questo ha stabilito chiaramente la condizione di trigger reale.
La seconda prova ha usato un registro locale controllato e un pacchetto progettato per dimostrare in sicurezza gli effetti collaterali al momento dell'importazione.
Questo contava perché volevo mostrare la storia più forte:
Questa era la catena di prova più forte perché spostava il problema oltre:
"tentativo di installazione inaspettato"
e dentro:
"il percorso di installazione più il caricamento del codice a valle è effettivamente raggiungibile"
Questo è il punto in cui il fallimento del confine di fiducia diventa molto più difficile da ignorare.
Il primo PoC prova il comportamento di installazione silenziosa.
Il secondo PoC prova perché quel comportamento è importante.
Questa suddivisione era importante.
Un report che si ferma a:
"un pacchetto può essere installato"
è più debole di un report che mostra:
Questa è la storia completa.
Durante la gestione dell'advisory e la revisione locale, il comportamento è stato ricondotto all'introduzione di installLocalGenerators() in:
yeoman-environment 2.9.0
L'intervallo interessato era quindi:
>= 2.9.0 e < 6.0.1
La versione corretta era:
6.0.1
La correzione era corretta e minima.
In 6.0.1, installLocalGenerators() è stato modificato per aggiungere un passaggio di conferma prima dell'installazione, a meno che non venga esplicitamente richiesta l'installazione forzata.
La forma corretta era simile a questa:
async installLocalGenerators(packages, forceInstall = false) {
e poi:
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,
});
Se l'utente rifiuta, l'installazione viene interrotta.
Questa è la correzione giusta perché ripristina il confine di fiducia mancante:
Questa correzione è stata inserita in:
78d2af7
tramite:
PR #753
Questo è esattamente il tipo di rimedio che si vuole in un problema di sicurezza come questo:
Questo problema è stato preso ragionevolmente sul serio perché l'impatto è più che estetico o un comportamento sorprendente.
Il comportamento vulnerabile può portare a:
Il vettore CVSS associato al problema era:
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
Ha senso per la storia dello sfruttamento a valle:
Questo problema è iniziato come un report privato contro generator-jhipster, perché quello era il percorso di trigger nel mondo reale che avevo inizialmente validato.
Durante il triage, i manutentori di JHipster hanno sottolineato che il comportamento di installazione automatica si trovava in yeoman-environment e hanno fatto riferimento alla correzione upstream.
Ciò ha portato alla corretta inversione:
I manutentori di Yeoman hanno esaminato il problema, confermato l'intervallo interessato e lo hanno tracciato attraverso un advisory privato.
Il report è stato successivamente assegnato:
CVE-2026-42089
Quell'advisory ha anche documentato il percorso di trigger a valle reale attraverso generator-jhipster.
Questo è stato un buon esempio del motivo per cui la divulgazione coordinata a volte richiede un passaggio extra:
Qui, la riproduzione a valle è stata utile, ma il pacchetto upstream era il posto giusto per il CVE.
La lezione chiave è semplice:
l'installazione di pacchetti è un confine di sicurezza, anche negli strumenti di sviluppo locali
Molte persone istintivamente sottovalutano problemi come questo perché si verificano in strumenti CLI.
Questo è un errore.
La vera domanda non è se lo strumento sia locale.
La vera domanda è:
L'input non fidato può far sì che lo strumento recuperi e si fidi del codice senza una decisione esplicita dell'utente?
In questo caso, sì.
Questo è il vero insegnamento.
Questo problema rafforza anche qualcosa di importante sulla buona ricerca sulle vulnerabilità:
Quella è stata esattamente la forma di questo CVE.
Questa vulnerabilità non riguardava un payload appariscente.
Riguardava il porre la giusta domanda sul confine di fiducia.
Uno strumento a valle ha permesso ai dati locali del progetto di influenzare la selezione del pacchetto. Yeoman ha installato il pacchetto mancante senza conferma. Il resto della catena di caricamento del codice ha fatto il resto.
Ecco perché questo è diventato CVE-2026-42089.
Corretto in yeoman-environment 6.0.1.