Dipendenze
Che cos'è una dipendenza transitiva?
Una dipendenza transitiva è un pacchetto che il Suo software usa perché un altro pacchetto ne ha bisogno. Lei non l'ha mai dichiarata, ma è in ciò che distribuisce, con le proprie vulnerabilità e la propria licenza. Ecco come vederle, correggerle e dimostrare quali sono entrate in ogni release.
Aggiornato il
Dipendenze dirette e transitive
Una dipendenza diretta è quella che Lei dichiara nel Suo manifest. Una dipendenza transitiva è quella che dichiarano le Sue dipendenze, e a loro volta le loro, fino in fondo all'albero. Il package manager risolve l'intero albero durante l'installazione e di solito registra il risultato in un lockfile.
Prenda un'applicazione che dichiara un solo pacchetto, express. Eseguendo npm ls --all si vede che cosa porta con sé quell'unica riga:
my-app
└─┬ express ← diretta: l'ha dichiarata Lei
├─┬ body-parser ← transitiva, profondità 1
│ └── raw-body ← transitiva, profondità 2
├── cookie ← transitiva, profondità 1
└─┬ send ← transitiva, profondità 1
└── mime-types ← transitiva, profondità 2 L'albero reale è più lungo. La maggior parte dei pacchetti di una tipica applicazione arriva in questo modo, il che significa che la maggior parte del codice che Lei distribuisce è stata scelta dal package manager, non da Lei.
| Dipendenza diretta | Dipendenza transitiva | |
|---|---|---|
| Chi l'ha scelta | Il Suo team, nel manifest | Il package manager, dai manifest delle Sue dipendenze |
| Dove è elencata | Manifest e lockfile | Lockfile, se esiste |
| Chi controlla la versione | Lei | L'intervallo di versione del pacchetto padre, salvo override |
| Come si corregge una vulnerabilità | Aggiornandola | Aggiornando il padre o forzando la versione con un override |
Perché le dipendenze transitive contano
Comportano gli stessi rischi delle dipendenze dirette, con meno visibilità e meno controllo.
- Vulnerabilità. Quando è stata divulgata Log4Shell, una zero-day in log4j, il team Open Source Insights di Google ha trovato 35.863 artefatti di Maven Central che dipendevano dal codice log4j interessato. Circa 7.000 ne dipendevano direttamente. La maggioranza lo introduceva come dipendenza transitiva, e molti non potevano essere corretti finché un pacchetto intermedio non avesse rilasciato prima una correzione.
- Attacchi alla supply chain. La backdoor di xz utils (CVE-2024-3094) ha raggiunto OpenSSH senza che OpenSSH dipendesse da xz. Diverse distribuzioni Linux applicano una patch a OpenSSH per supportare le notifiche systemd, e libsystemd dipende da liblzma, la libreria fornita da xz.
- Licenze. Ogni pacchetto transitivo ha la propria licenza. Una licenza copyleft tre livelli più in basso è vincolante quanto una che ha aggiunto Lei stesso.
- Modifiche incompatibili. Un pacchetto padre che accetta un intervallo di versione lascia entrare una nuova release transitiva alla successiva installazione da zero, anche quando nel Suo manifest non è cambiato nulla.
Come elencare le Sue dipendenze transitive
Ogni principale package manager può stampare l'albero risolto:
| Ecosistema | Comando |
|---|---|
| npm | npm ls --all |
| pnpm | pnpm list --depth Infinity |
| Python (uv) | uv tree |
| Python (pip) | pipdeptree |
| Go | go mod graph |
| Rust | cargo tree |
| Maven | mvn dependency:tree |
| Gradle | gradle dependencies |
Per scoprire perché un singolo pacchetto è presente, chieda il percorso che porta a esso: npm explain <package>, pnpm why, yarn why o cargo tree -i <crate>. Per lavorare con i percorsi invece di stamparli, veda i grafi delle dipendenze.
Come correggere una dipendenza transitiva vulnerabile
- Aggiorni il padre. Trovi la dipendenza diretta che introduce il pacchetto e passi a una release che richiede una versione corretta. È la correzione più pulita, perché i maintainer del padre l'hanno testata.
- Aggiorni il lockfile. Se l'intervallo di versione del padre consente già la versione corretta, basta aggiornare la voce del lockfile, ad esempio con
cargo update -p <crate>o un nuovonpm update. - Forzi la versione. Se il padre non ha rilasciato una correzione, forzi Lei la versione:
overridesin npm e pnpm,resolutionsin Yarn, un file di vincoli con pip o un requisito più alto ingo.mod. La testi, perché il padre non è mai stato rilasciato con quella versione. - Sostituisca o rimuova il padre. Se nessuna delle due cose funziona, la dipendenza che introduce il pacchetto potrebbe non valere più la pena di essere mantenuta.
Alcuni strumenti di software composition analysis aggiungono l'analisi di raggiungibilità, che verifica se il Suo codice può effettivamente chiamare la funzione vulnerabile, per decidere quale di queste correzioni viene prima. Veda gli strumenti SCA a confronto per le opzioni.
Perché il lockfile non racconta tutta la storia
La maggior parte degli strumenti costruisce la propria vista delle dipendenze transitive a partire dal lockfile. Funziona quando il lockfile è presente, aggiornato ed è l'unica cosa da cui la build installa. In CI spesso non è così:
- Nessun lockfile. Un
requirements.txtcon intervalli di versione o un progetto che non ha mai eseguito il commit del lockfile viene risolto da zero a ogni build, quindi le versioni transitive possono cambiare da una build all'altra. - Cache. Le cache CI ripristinano i pacchetti da esecuzioni precedenti invece di scaricarli, e ciò che contengono non è sempre ciò che dice il lockfile attuale. Veda che cosa nasconde la cache di GitHub Actions.
- Strumenti di build. Linter, compilatori, generatori di codice e passaggi di setup portano nella build i propri alberi di dipendenze. Non sono nel manifest della Sua applicazione, ma vengono eseguiti con accesso ai Suoi sorgenti e secret.
- Script di installazione e download. Lo script di installazione di un pacchetto può scaricare altro codice, e uno step di build può scaricare direttamente un binario. Nessuno dei due lascia una voce in un lockfile.
L'unico registro completo di quali dipendenze transitive sono entrate in una release è quello acquisito mentre quella release veniva compilata. La pagina sulle SBOM in fase di build spiega la differenza tra registrare una build e scansionarne i file a posteriori.
Le dipendenze transitive e il CRA
Il Cyber Resilience Act dell'UE chiede ai fabbricanti di redigere una SBOM che comprenda "almeno le dipendenze di primo livello dei prodotti" (allegato I, parte II, punto 1). Le dipendenze transitive non sono menzionate, quindi la soglia legale è più bassa di quanto suggeriscano molte guide. Ma il CRA Le chiede anche di identificare e gestire le vulnerabilità nei componenti contenuti nel Suo prodotto, e un pacchetto transitivo vulnerabile è uno di questi. Una SBOM che si ferma al primo livello lascia fuori da quel processo la maggior parte del Suo albero delle dipendenze. Veda i requisiti SBOM del CRA per i dettagli.
Come CRACI traccia le dipendenze transitive
CRACI è un runner GitHub Actions che registra la build invece di scansionarla. Mentre ogni job è in esecuzione, un proxy che riconosce i pacchetti registra ogni pacchetto scaricato dal job, a ogni profondità dell'albero, compresi i pacchetti ripristinati dalle cache CI. Questo offre una visione più completa delle dipendenze in uso rispetto agli strumenti basati su scanner.
- Ogni profondità, dichiarata onestamente. La SBOM di ogni job riporta uno stato di completezza, così Lei sa quando una registrazione non ha potuto vedere tutto.
- L'albero, visibile. Un grafo delle dipendenze mostra i livelli di dipendenza della SBOM, in diverse viste.
- Esportazioni che le includono. Le esportazioni CycloneDX e SPDX includono le dipendenze transitive, ciascuna con la sua licenza dichiarata.
- Quali build sono state colpite. Quando arriva una nuova vulnerabilità, la cronologia delle build mostra quali build passate contenevano il pacchetto vulnerabile, diretto o transitivo, così Lei conosce il periodo in cui l'ha distribuito (veda siamo colpiti da una CVE?). La cronologia segue il Suo periodo di conservazione, 180 giorni per impostazione predefinita.
- Policy gate. Una build che contiene una specifica CVE può essere bloccata prima del rilascio.
CRACI non esegue l'analisi di raggiungibilità. Se ha bisogno di sapere se il Suo codice chiama una funzione vulnerabile, lo abbini a uno strumento SCA che la esegue. Veda la generazione di SBOM in CRACI oppure prenoti una demo per registrare una delle Sue build.
Dipendenze transitive: domande frequenti
Che cos'è una dipendenza transitiva?
Una dipendenza transitiva è un pacchetto che il Suo software usa perché un altro pacchetto ne ha bisogno. Se la Sua applicazione dipende dalla libreria A e A dipende dalla libreria B, allora B è una dipendenza transitiva, o indiretta, della Sua applicazione. Lei non l'ha mai dichiarata, ma viene distribuita con il Suo software.
Qual è la differenza tra una dipendenza diretta e una transitiva?
Una dipendenza diretta è quella che Lei dichiara nel Suo manifest, come package.json, pyproject.toml o go.mod. Una dipendenza transitiva è quella che dichiarano le Sue dipendenze dirette, a qualsiasi profondità. Le dipendenze dirette le sceglie Lei; quelle transitive le sceglie il package manager quando risolve l'albero.
Una dipendenza transiente è la stessa cosa di una dipendenza transitiva?
Sì, in pratica. "Dipendenza transiente" è un errore comune per "dipendenza transitiva". Il termine corretto è transitiva, dalla relazione transitiva: se A dipende da B e B dipende da C, allora A dipende da C.
Come si corregge una vulnerabilità in una dipendenza transitiva?
Prima aggiorni la dipendenza diretta che la introduce, se esiste una release con una versione corretta. In caso contrario, forzi la versione corretta con il meccanismo di override del Suo package manager, come npm overrides, Yarn resolutions o un file di vincoli pip, e verifichi che il pacchetto padre funzioni ancora.
Come si elencano tutte le dipendenze transitive?
Ogni package manager può stampare l'albero risolto: npm ls --all, pnpm list --depth Infinity, uv tree o pipdeptree, go mod graph, cargo tree, mvn dependency:tree o gradle dependencies. Per sapere che cosa ha effettivamente installato una build di produzione, registri la build stessa.
Il Cyber Resilience Act dell'UE richiede le dipendenze transitive nella SBOM?
Il regolamento fissa la soglia minima ad "almeno le dipendenze di primo livello dei prodotti" (allegato I, parte II, punto 1). Le dipendenze transitive non sono menzionate, ma un pacchetto transitivo vulnerabile resta comunque nel Suo prodotto, e il CRA Le chiede di gestire le vulnerabilità nei componenti che distribuisce.
Veda ogni dipendenza introdotta dalla Sua build
Esegua un workflow GitHub Actions su CRACI e ottenga una SBOM registrata di ogni pacchetto scaricato, a ogni profondità dell'albero.
Prenota una demo