La sicurezza della supply chain sembra un problema per organizzazioni che hanno una funzione di sicurezza, e questa impostazione è fuorviante. Un team piccolo che manda avanti una manciata di servizi dipende di norma da centinaia di pacchetti che nessuno al suo interno ha mai letto, scaricati al momento della build da registry che non controlla, e che eseguono script di installazione su macchine dove sono custodite le credenziali di deploy.
L’esposizione non è proporzionale alla dimensione dell’azienda. È proporzionale al numero di dipendenze e al grado di automazione della build, e i team piccoli hanno spesso più del primo e meno controllo sul secondo rispetto alle organizzazioni che di questo tema parlano in pubblico.
L’aritmetica scomoda: la vostra applicazione ha forse una dozzina di dipendenze dirette e diverse centinaia di dipendenze transitive. La dozzina l’avete scelta voi. Il resto no, non ne avete letta nessuna, e ciascuna di quelle librerie, nel momento in cui esegue uno script di installazione, ottiene gli stessi accessi del vostro processo di build. Questa è la superficie d’attacco reale, e dall’elenco di dipendenze che avete scritto voi non si vede.
La sicurezza della supply chain inizia all’installazione
Il momento pericoloso di solito non è eseguire il codice. È installarlo.
I gestori di pacchetti consentono l’esecuzione di script durante l’installazione, il che significa che un pacchetto compromesso o malevolo gira con i privilegi di chi ha lanciato il comando. Su una macchina di sviluppo sono le credenziali di quella persona. In una pipeline di build sono le chiavi di deploy, ed è nettamente peggio.
Tre forme di attacco spiegano la maggior parte degli incidenti reali.
Typosquatting. Un pacchetto con un nome che differisce di un solo carattere da uno molto usato, pubblicato e lasciato lì ad aspettare un comando di installazione digitato male. Costa poco da allestire ed è efficace su larga scala.
Compromissione dell’account di un pacchetto legittimo. Un pacchetto molto diffuso, il cui manutentore si è fatto rubare le credenziali, aggiornato con qualcosa di dannoso. È il caso più difficile da contrastare, perché né il nome né il numero di download presentano alcunché di anomalo.
Dependency confusion. Un pacchetto pubblico pubblicato con il nome di uno dei vostri pacchetti interni, mentre un resolver configurato male preferisce il registry pubblico al vostro. È interamente un problema di configurazione ed è interamente evitabile.
Nessuno di questi attacchi richiede che qualcuno prenda di mira proprio voi, ed è esattamente questo il punto. Sono opportunistici e scalano.
Controlli che ripagano lo sforzo su piccola scala
Mettete il lockfile sotto controllo di versione e installate da lì. Un lockfile fissa le versioni esatte e i relativi hash. Installare dal manifesto risolve invece versioni fresche al momento della build, il che rende la build non riproducibile e lascia che una modifica a monte arrivi in produzione senza alcuna revisione. Usate il comando di installazione che fallisce quando il lockfile non corrisponde, non quello che lo aggiorna in silenzio.
Disattivate gli script di installazione dove potete. Molti ecosistemi lo permettono e la maggior parte dei pacchetti non ne ha alcun bisogno. È la singola riduzione di esposizione più grande disponibile senza strumenti aggiuntivi, e i pochi pacchetti che si rompono sono proprio quelli che vale la pena conoscere.
Automatizzate gli avvisi di vulnerabilità, poi fate triage. Gli scanner producono più risultati di quanti un team piccolo riesca a lavorarne, e la modalità di fallimento non è l’avviso perso: è ignorarli tutti perché quasi nessuno è pertinente. Filtrate su ciò che dal vostro codice è davvero raggiungibile e agite su quello. Una coda che nessuno legge vale meno di nessuna coda.
Fissate anche gli strumenti di build. L’immagine del container, la versione del runtime e l’action o il plugin che la pipeline richiama sono anch’essi dipendenze, e referenziare un tag mutabile invece di un digest immutabile significa che la vostra build cambia senza di voi.
Separate le credenziali di build da tutto il resto. Una pipeline autorizzata a fare deploy non dovrebbe custodire credenziali che permettono di fare qualcosa di più, e una build compromessa non dovrebbe riuscire a raggiungere i dati di produzione.
Cosa dice e cosa non dice un SBOM
Una distinta base del software, in sigla SBOM, elenca cosa c’è dentro la vostra applicazione, e il suo valore sta nel rispondere in fretta a una sola domanda: siamo coinvolti?
Quella domanda un tempo richiedeva giorni. Quando un componente molto diffuso si rivela vulnerabile, un’organizzazione con un inventario aggiornato risponde in minuti e una che non ce l’ha passa una settimana a cercare. Quella differenza è tutto l’argomento, ed è il motivo per cui l’adozione degli SBOM viene spinta come pratica di base e non come controllo avanzato.
Quello che non fa è rendervi sicuri. Un inventario è un elenco, non una difesa. Non vi dice se una vulnerabilità elencata è raggiungibile nel vostro codice, se pesa nella vostra configurazione, né se qualcosa sia già stato compromesso. I team che ne generano uno e lo archiviano hanno aggiunto un documento, non un controllo.
Generatelo dalla build, così descrive quello che è stato davvero rilasciato e non quello che il manifesto dichiarava. Conservate quello corrispondente a ciascuna release. E accettate che il suo valore stia interamente nella velocità con cui risponderete a una domanda che vi verrà posta sotto pressione.
Dove i team piccoli si fanno male davvero
Di rado per una compromissione esotica di un pacchetto. Per cose ordinarie.
Segreti nel repository, che dopo la cancellazione restano nella cronologia e vengono trovati da scanner automatici entro pochi minuti da un push. Una dipendenza ferma a tre anni fa con un exploit pubblicato e nessun percorso di aggiornamento, perché aggiornarla è stato rimandato finché non è diventata un progetto a sé. Pipeline di build con permessi eccessivi, dove una pull request proveniente da un fork gira con accessi che non dovrebbe avere. Nessuno che guardi, per cui una compromissione dura nel tempo perché nessun avviso arriva mai davanti a una persona.
La conclusione poco affascinante è che tenere le dipendenze aggiornate costituisce in pratica quasi tutta la sicurezza della supply chain. Il costo di manutenzione software che i team trattano come facoltativo è esattamente lo stesso lavoro, e rimandarlo trasforma un aggiornamento di routine in un’emergenza con un exploit allegato.
Un punto di partenza proporzionato
Per un team piccolo, in quest’ordine: togliete i segreti dal repository e ruotate tutto ciò che è stato esposto. Mettete i lockfile sotto controllo di versione e installate da lì in modo rigoroso. Attivate gli avvisi sulle dipendenze e fate triage una volta a settimana anziché di continuo. Fissate gli strumenti di build su riferimenti immutabili. Riducete al minimo i permessi della pipeline. Generate un SBOM per ogni release quando il resto è a posto.
Questa sequenza copre le minacce realistiche senza bisogno di una funzione di sicurezza, e ogni passo si misura in ore anziché in settimane. I controlli che vengono dopo, attestazione di provenienza, build riproducibili, artefatti firmati, hanno un valore reale e appartengono a una fase in cui le basi sono già stabili.
Mecanik esamina e irrobustisce le pipeline di build nell’ambito della sua analisi sicurezza applicazioni. Quello che troviamo non è quasi mai una compromissione sofisticata. È un token nel repository e una dipendenza che nessuno ha più aggiornato dal giorno in cui è stata scelta.
Domande frequenti
Perché installare i pacchetti è il momento rischioso? Perché i gestori di pacchetti permettono agli script di girare durante l’installazione, con i privilegi di chi ha lanciato il comando. Su una macchina di sviluppo significa le sue credenziali; in una pipeline di build significa le chiavi di deploy. Il codice non deve nemmeno essere eseguito dalla vostra applicazione per fare danni.
Che cos’è la dependency confusion? Un attacco in cui un pacchetto pubblico viene pubblicato con il nome di uno dei vostri pacchetti interni e un resolver configurato male preferisce il registry pubblico a quello privato. Non richiede che qualcuno prenda di mira proprio voi ed è del tutto evitabile agendo sulla configurazione del resolver.
Cosa vi dà davvero un SBOM? Una risposta rapida a una sola domanda: siamo coinvolti? Con un inventario aggiornato servono minuti; senza, serve una settimana di ricerche. Non vi dice se una vulnerabilità elencata è raggiungibile nel vostro codice né se qualcosa è già stato compromesso, quindi un SBOM generato e archiviato resta un documento e non un controllo.
Devo mettere il lockfile sotto controllo di versione? Sì, e installare da lì in modo rigoroso, con il comando che fallisce quando il lockfile non corrisponde invece di quello che lo aggiorna. Installare dal manifesto risolve versioni fresche al momento della build, rende le build non riproducibili e lascia che una modifica a monte arrivi in produzione senza alcuna revisione.
Da dove dovrebbe iniziare un team piccolo? Togliete i segreti dal repository e ruotate tutto ciò che è stato esposto, mettete i lockfile sotto controllo di versione e installate in modo rigoroso, attivate gli avvisi sulle dipendenze e fate triage ogni settimana, fissate gli strumenti di build su riferimenti immutabili, riducete i permessi della pipeline, poi generate un SBOM per ogni release. Ogni passo si misura in ore anziché in settimane.
Commenti