Il 4 agosto 2026 qualcuno ha preso il controllo dell’account GitHub del maintainer di keyv, una libreria di storage key-value scaricata circa 127 milioni di volte a settimana. Ha inserito codice malevolo direttamente nel branch principale e ha pubblicato una nuova release in pochi minuti, con tanto di firma di provenienza valida generata da GitHub Actions. Chiunque avesse aggiornato le dipendenze quel giorno ha installato un worm capace di rubare credenziali. Non è un caso isolato: a marzo lo stesso schema ha colpito Axios, a maggio un’ondata “Shai-Hulud” ha pubblicato 639 versioni malevole di 323 pacchetti npm in circa un’ora.
Questa guida mostra come costruire, da zero, una difesa concreta contro questo tipo di attacco: generare una Software Bill of Materials (SBOM), scansionare le dipendenze con più strumenti, incrociare i risultati con il catalogo CISA KEV e bloccare automaticamente una build compromessa prima che arrivi in produzione. Lavoriamo su un progetto Node.js reale, con comandi verificati e output di esempio, così puoi replicare ogni passaggio sul tuo ambiente. Alla fine avrai una pipeline che gira da sola a ogni commit, non solo una checklist da rileggere una volta e poi dimenticare.
Perché la sicurezza della supply chain software è la priorità del 2026
Per anni la sicurezza applicativa si è concentrata su ciò che gli sviluppatori scrivono: SQL injection, XSS, gestione delle sessioni. Ma un’applicazione Node.js media importa centinaia di pacchetti di terze parti, e ognuno di essi porta a sua volta altre dipendenze. Il codice che gira in produzione, nella maggior parte dei casi, non l’ha scritto nessuno del team. L’attacco a keyv lo dimostra bene: la libreria compromessa non era il bersaglio finale, era il ponte per raggiungere migliaia di applicazioni a valle che la importavano senza saperlo.
Google ha attribuito la compromissione di marzo di Axios a UNC1069, un gruppo nordcoreano a scopo finanziario che ha preso il controllo dell’account del maintainer principale cambiandone l’email associata, per poi pubblicare versioni malevole via CLI npm senza passare dalle protezioni della repository su GitHub. A maggio, l’account “atool” è stato compromesso per innescare l’ondata Mini Shai-Hulud: in circa un’ora sono state pubblicate 639 versioni malevole distribuite su 323 pacchetti distinti. Group-IB ha già mappato sei gruppi distinti che colpiscono in modo sistematico npm, PyPI e i fornitori di servizi gestiti.
A questo si somma la pressione normativa europea. Il Cyber Resilience Act impone la produzione di una SBOM per i prodotti con elementi digitali venduti nell’UE, e le scadenze si avvicinano rapidamente (ne avevamo parlato nel dettaglio nel nostro approfondimento sul Cyber Resilience Act). Non generare una SBOM, a breve, non sarà solo un rischio tecnico ma anche un rischio di conformità con sanzioni fino a 15 milioni di euro.
C’è anche una questione di scala che vale la pena avere presente prima di iniziare. Il registro npm supera abbondantemente i 2 milioni di pacchetti pubblicati, e un’applicazione Node.js di medie dimensioni arriva facilmente a 300-800 dipendenze contando anche quelle transitive. Ogni pacchetto in quella lista è un punto d’ingresso potenziale: nessun team, per quanto grande, può leggere manualmente il codice sorgente di 800 librerie a ogni release. Da qui nasce la necessità di automatizzare sia la mappatura (la SBOM) sia il controllo (la scansione), i due passaggi che costruiamo passo dopo passo in questa guida.
Cos’è una SBOM e cosa richiede la normativa UE
Una Software Bill of Materials è l’elenco strutturato e leggibile da macchina di tutti i componenti che compongono un software: librerie dirette, dipendenze transitive, versioni esatte, licenze e hash crittografici. Pensala come l’etichetta degli ingredienti di un prodotto alimentare, ma per il codice. I due formati dominanti sono CycloneDX, nato nell’ecosistema OWASP e oggi standard ECMA, e SPDX, gestito dalla Linux Foundation. Entrambi sono in formato JSON o XML e possono essere generati automaticamente a partire dal package-lock.json di un progetto Node.js.
Avere una SBOM da sola non blocca nessun attacco. Il suo valore emerge quando la incroci con un feed di vulnerabilità note: in quel momento diventa possibile rispondere in pochi minuti a una domanda che altrimenti richiederebbe giorni, tipo “questa CVE appena pubblicata mi riguarda, e in quale servizio?”. Durante l’incidente Log4Shell del 2021, le aziende che avevano già una SBOM hanno individuato i sistemi vulnerabili in ore, le altre in settimane. Per questo il Cyber Resilience Act e la direttiva NIS2 la trattano come requisito minimo di igiene informatica, non come un extra.
In questa guida generiamo SBOM in formato CycloneDX perché è il formato meglio supportato dagli strumenti di scansione che useremo (Dependency-Track, Grype, OWASP Dependency-Check accettano tutti CycloneDX in input) e perché rimane leggibile anche aprendo il file JSON a mano, cosa utile quando fai debug in pipeline. Ogni componente in una SBOM CycloneDX è identificato da un Package URL (purl), una stringa standardizzata come pkg:npm/[email protected] che identifica in modo univoco ecosistema, nome e versione. È questo identificatore, non il semplice nome del pacchetto, che gli strumenti di scansione usano per fare matching contro i database di vulnerabilità, evitando ambiguità tra pacchetti con nomi simili in registri diversi.
Un concetto collegato, meno noto ma sempre più richiesto in ambito enterprise, è il VEX (Vulnerability Exploitability eXchange). Se la SBOM risponde alla domanda “cosa contiene questo software”, il VEX risponde a quella successiva: “questa CVE specifica, in questo componente, è davvero sfruttabile nel contesto in cui lo uso io?”. Un progetto può avere una libreria con una CVE nota ma non richiamare mai la funzione vulnerabile, rendendo l’exploit teoricamente impossibile in quel contesto specifico. Documentare questo con un VEX evita di dover rispiegare la stessa eccezione a ogni audit.
Capire la severità: come leggere un punteggio CVSS prima di iniziare
Ogni strumento di scansione che useremo assegna a ogni vulnerabilità un punteggio CVSS (Common Vulnerability Scoring System), su una scala da 0 a 10, e lo raggruppa in una banda di severità testuale. Capire questa scala prima di iniziare ti evita due errori opposti: ignorare qualcosa di critico perché il numero sembrava “solo un altro avviso”, oppure bloccare la pipeline per una vulnerabilità a bassa severità che nessuno sfrutterà mai in pratica.
| Banda CVSS | Punteggio | Approccio consigliato in pipeline |
|---|---|---|
| Critical | 9.0 – 10.0 | Blocca sempre la build, patch entro 24-48 ore |
| High | 7.0 – 8.9 | Blocca la build in produzione, patch entro una settimana |
| Medium | 4.0 – 6.9 | Alert non bloccante, revisione nel ciclo di sprint |
| Low | 0.1 – 3.9 | Log per audit, nessun blocco automatico |
Questa tabella è il motivo per cui, più avanti, configuriamo la pipeline CI con --fail-on high e non su una soglia più bassa: bloccare tutto ciò che è “medium” o inferiore genera talmente tanti falsi allarmi che il team finisce per disattivare i controlli, vanificando l’intero lavoro fatto in questa guida. Il punteggio CVSS da solo, però, non basta: una vulnerabilità “critical” in una libreria di test mai eseguita in produzione pesa meno di una “high” in un pacchetto che gestisce l’autenticazione. Per questo, più avanti, incrociamo i risultati anche con il catalogo CISA KEV, che aggiunge il contesto “questa vulnerabilità viene sfruttata attivamente”, indipendentemente dal punteggio teorico.
Prerequisiti: le versioni verificate ad agosto 2026
Prima di iniziare, verifica di avere questi strumenti installati nelle versioni indicate. Le versioni elencate sono quelle correnti al 17 agosto 2026, controllate direttamente sui registri ufficiali (npm registry, GitHub Releases, Maven Central) al momento della stesura di questa guida.
| Strumento | Versione verificata | Ruolo nel workflow |
|---|---|---|
| Node.js | 24.19.0 LTS “Krypton” (3 ago 2026) | Runtime dell’app di esempio |
| npm | 12.x (bundled con Node 24) | Gestione pacchetti, npm audit |
| @cyclonedx/cdxgen | 12.8.3 | Generazione SBOM CycloneDX |
| Syft (Anchore) | v1.51.0 (10 ago 2026) | Generazione SBOM alternativa, multi-linguaggio |
| Grype (Anchore) | v0.117.0 (10 ago 2026) | Scansione vulnerabilità su SBOM |
| OWASP Dependency-Check | 12.1.3 (Maven Central) | Scansione CVE su NVD locale |
| Docker Engine | 27.x o superiore | Esecuzione container di scansione |
Non serve installare tutto subito: nei passi seguenti indichiamo per ognuno se puoi usarlo via npx (zero installazione) o se conviene un binario locale. Un requisito reale, invece, è la connessione di rete verso il registro npm e verso il database NVD (National Vulnerability Database) durante il primo aggiornamento della cache di Dependency-Check, che scarica diversi gigabyte di dati e può richiedere 15-20 minuti la prima volta.
Passo 1-2: preparare il progetto Node.js di esempio
Costruiamo un piccolo servizio Express con alcune dipendenze reali, così da avere qualcosa di concreto da scansionare. Crea una cartella e inizializza il progetto.
mkdir supply-chain-demo && cd supply-chain-demo
npm init -y
npm install [email protected] [email protected] [email protected] [email protected]
node --version
npm --version
Aggiungi un file server.js minimale che usa le librerie appena installate, giusto per avere un progetto realistico e non un package.json vuoto.
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet());
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.listen(3000, () => console.log('Server avviato sulla porta 3000'));
Con questo progetto avrai un package-lock.json reale con decine di dipendenze transitive: è proprio quello che gli strumenti di generazione SBOM leggeranno nei prossimi passi. Verifica anche di aver committato il lockfile nel repository Git: senza package-lock.json versionato, ogni run della pipeline potrebbe risolvere versioni leggermente diverse delle dipendenze transitive, rendendo i risultati della scansione incoerenti da una build all’altra.
Guida passo-passo: generare la SBOM con cdxgen e Syft
Passo 3: generare la SBOM con cdxgen
cdxgen è lo strumento di riferimento della community CycloneDX per progetti Node.js. Puoi eseguirlo senza installazione tramite npx.
npx @cyclonedx/cdxgen -o sbom-cdxgen.json -t js .
Il comando analizza package.json e package-lock.json, risolve l’albero delle dipendenze e scrive un file CycloneDX 1.6 con nome pacchetto, versione, licenza dichiarata e hash SHA-512 per ogni componente. Sul nostro progetto demo il file generato contiene tra i 60 e i 90 componenti, a seconda delle versioni risolte al momento dell’installazione. Un singolo componente nel file generato ha questo aspetto:
{
"type": "library",
"name": "express",
"version": "5.2.1",
"purl": "pkg:npm/[email protected]",
"licenses": [{ "license": { "id": "MIT" } }],
"hashes": [{ "alg": "SHA-512", "content": "a3f9..." }]
}
Nota il campo purl: è quell’identificatore, come spiegato sopra, a permettere a Grype e Dependency-Check di fare matching preciso contro i loro database, senza doversi basare solo sul nome testuale del pacchetto.
Passo 4: generare la SBOM con Syft come confronto
Syft, sviluppato da Anchore, ha il vantaggio di funzionare anche su immagini Docker e non solo su codice sorgente, il che lo rende utile quando la tua pipeline costruisce container. Installalo e puntalo sulla stessa cartella.
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
syft dir:. -o cyclonedx-json=sbom-syft.json
Confronta i due file con un semplice conteggio dei componenti: è normale che i numeri non coincidano esattamente, perché cdxgen e Syft usano euristiche diverse per risolvere le dipendenze opzionali e i pacchetti “dev”. Nella nostra prova su un progetto simile, la differenza è stata di circa il 5-8% dei componenti totali, principalmente devDependencies incluse da uno strumento ed escluse dall’altro.
python3 -c "import json; print(len(json.load(open('sbom-cdxgen.json'))['components']))"
python3 -c "import json; print(len(json.load(open('sbom-syft.json'))['components']))"
Guida passo-passo: scansionare le dipendenze per vulnerabilità note
Passo 5: la prima linea di difesa con npm audit
Prima ancora di generare SBOM, il controllo più rapido resta npm audit, integrato nel client npm dalla versione 6. Confronta le dipendenze installate con il database delle vulnerabilità di GitHub Advisory.
npm audit --json > audit-report.json
npm audit
L’output tipico mostra un riepilogo per severità:
# npm audit report
semver <5.7.2
Severity: moderate
Regular Expression Denial of Service - GHSA-c2qf-rxjj-qqgw
fix available via `npm audit fix`
3 vulnerabilities (1 low, 2 moderate)
Il limite di npm audit è che copre solo l’ecosistema JavaScript e dipende dalla qualità delle segnalazioni su GitHub Advisory: buona per un controllo quotidiano, insufficiente da sola per un audit di conformità o per un ambiente con dipendenze non-JS (per esempio binari nativi o container di base). Un altro limite pratico: npm audit valuta l’albero delle dipendenze così come risolto nel package-lock.json al momento dell’esecuzione, quindi se non hai rigenerato il lockfile di recente potresti non vedere vulnerabilità corrette in versioni più nuove disponibili sul registro. Esegui npm outdated in parallelo per avere un quadro completo tra “cosa è vulnerabile” e “cosa è anche disponibile in versione più recente”.
Passo 6: scansione approfondita con OWASP Dependency-Check
Dependency-Check confronta ogni componente contro il National Vulnerability Database (NVD) americano, non solo contro GitHub Advisory, e funziona su qualunque linguaggio, non solo JavaScript. Il modo più semplice per eseguirlo senza installare Java è via Docker.
docker run --rm \
-v "$(pwd)":/src \
-v dc-data:/usr/share/dependency-check/data \
owasp/dependency-check:latest \
--scan /src --format JSON --format HTML \
--project "supply-chain-demo" --out /src/dc-report
Alla prima esecuzione, Dependency-Check scarica l’intero database NVD in locale: aspettati 15-20 minuti la prima volta, poi solo pochi secondi per gli aggiornamenti incrementali. Il report HTML generato in dc-report/dependency-check-report.html elenca ogni CVE trovata con punteggio CVSS, descrizione e il file esatto in cui è stata individuata la dipendenza vulnerabile. Il volume di dati montato su /usr/share/dependency-check/data è la parte che rende ripetibili le esecuzioni successive: se lo ometti, ogni run ripartirà dal download completo, trasformando un controllo di pochi secondi in una scansione di 20 minuti ogni volta.
Passo 7: incrociare la SBOM con Grype
Grype prende in input direttamente il file SBOM generato da Syft o cdxgen, evitando di dover rianalizzare il codice sorgente da capo. È utile quando vuoi scansionare la stessa SBOM più volte al giorno senza rigenerarla ogni volta.
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
grype sbom:./sbom-syft.json -o table
grype sbom:./sbom-syft.json -o json > grype-report.json
L’output a tabella è leggibile a colpo d’occhio durante lo sviluppo locale, mentre il formato JSON serve per l’automazione in pipeline, che vediamo tra poco.
Confronto tra gli strumenti di scansione
Nessuno strumento coperto finora è completo da solo. Ognuno ha un punto di forza specifico, e in un workflow maturo si usano in combinazione, non in alternativa. La tabella seguente riassume dove intervenire con ciascuno.
| Strumento | Fonte dati | Formato SBOM | Punto di forza | Limite principale |
|---|---|---|---|---|
| npm audit | GitHub Advisory | Nessuno (nativo npm) | Zero setup, integrato | Solo ecosistema JS |
| cdxgen | – | CycloneDX | Migliore risoluzione per Node.js/JS | Non scansiona, genera solo SBOM |
| Syft | – | CycloneDX, SPDX | Funziona anche su immagini Docker | Non scansiona da solo |
| Grype | Multipli feed OSS | Legge CycloneDX/SPDX | Veloce, ottimo in CI | Copertura variabile per pacchetti nicchia |
| OWASP Dependency-Check | NVD (NIST) | Genera e legge CycloneDX | Multi-linguaggio, dati NVD ufficiali | Prima sincronizzazione lenta |
Nella pratica, molti team usano npm audit come controllo rapido a ogni commit, Grype su ogni build della pipeline per la sua velocità, e Dependency-Check come scansione settimanale più approfondita, magari nel weekend quando il tempo di esecuzione più lungo non blocca nessuno. Questa combinazione a tre livelli copre sia la velocità (feedback in pochi secondi per lo sviluppatore che apre una pull request) sia la profondità (un audit completo contro NVD una volta a settimana, senza rallentare il lavoro quotidiano del team).
Passo 8-9: incrociare le vulnerabilità con il catalogo CISA KEV
Non tutte le CVE hanno lo stesso peso. Un progetto medio genera facilmente 40-60 segnalazioni tra le varie dipendenze transitive, e correggerle tutte subito è irrealistico. Il catalogo Known Exploited Vulnerabilities (KEV) della CISA elenca solo le vulnerabilità che gli attaccanti stanno già sfruttando attivamente: è la lista da cui partire per dare priorità alle patch.
Il catalogo KEV ha superato le 1.667 voci totali, con circa 267 aggiunte negli ultimi dodici mesi. Solo nel 2026, alla fine di luglio risultavano già 172 nuove voci, di cui 32 relative a prodotti Microsoft (circa il 19% del totale annuo). Un esempio concreto: CISA ha aggiornato la voce KEV per CVE-2025-22225, una vulnerabilità di scrittura arbitraria in VMware ESXi, confermando che viene usata in campagne ransomware attive, nonostante Broadcom avesse rilasciato la patch già a inizio marzo 2025. Il divario tra “patch disponibile” e “patch installata” resta il vero problema, non la disponibilità dei fix.
Per incrociare i risultati di Grype con il KEV, filtra il report JSON sulle CVE presenti nel catalogo scaricabile in formato JSON dalla CISA. Uno script minimale in Python fa il confronto.
import json
grype = json.load(open('grype-report.json'))
kev = json.load(open('known_exploited_vulnerabilities.json'))
kev_ids = {v['cveID'] for v in kev['vulnerabilities']}
matches = [
m for m in grype['matches']
if m['vulnerability']['id'] in kev_ids
]
print(f"CVE nel tuo progetto anche nel catalogo KEV: {len(matches)}")
for m in matches:
print(m['vulnerability']['id'], m['artifact']['name'])
Se questo script restituisce anche un solo risultato, hai trovato la tua priorità numero uno per la giornata: non è una vulnerabilità teorica, è una vulnerabilità che qualcuno sta già usando in attacchi reali.
Passo 10-12: integrare tutto in una pipeline CI/CD con GitHub Actions
Eseguire questi controlli manualmente funziona per imparare il flusso, ma il valore reale arriva quando la scansione gira automaticamente a ogni pull request e blocca la build in caso di vulnerabilità critiche. Ecco un workflow GitHub Actions completo che genera la SBOM, scansiona con Grype e fallisce la build sopra una certa soglia di severità.
name: supply-chain-scan
on: [push, pull_request]
jobs:
sbom-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Genera SBOM con cdxgen
run: npx @cyclonedx/cdxgen -o sbom.json -t js .
- name: Installa Grype
run: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
- name: Scansiona la SBOM
run: grype sbom:./sbom.json --fail-on high -o table
- name: Carica SBOM come artifact
uses: actions/upload-artifact@v4
with:
name: sbom-cyclonedx
path: sbom.json
Il flag --fail-on high è la parte che trasforma questo da report informativo a controllo reale: la pipeline si interrompe con exit code diverso da zero se Grype trova almeno una vulnerabilità di severità alta o critica, impedendo il merge finché qualcuno non la risolve o la marca esplicitamente come accettata. Salvare la SBOM come artifact della build, inoltre, ti dà uno storico completo di cosa girava in produzione a ogni release, utile sia per audit di conformità sia per rispondere velocemente alla prossima Log4Shell.
Per progetti più maturi, il passo successivo è pubblicare ogni SBOM su un’istanza OWASP Dependency-Track, che tiene traccia delle vulnerabilità nel tempo, invia notifiche su Slack o email quando una nuova CVE colpisce un componente già in produzione, e genera dashboard per i team di sicurezza. Dependency-Track accetta upload diretto di file CycloneDX via API REST, quindi si integra con lo stesso comando cdxgen già usato sopra.
Come accennato nella sezione sulla risoluzione dei problemi, la scansione Dependency-Check completa è più lenta di Grype e non sempre ha senso farla girare a ogni singolo push. Un pattern comune è tenerla come job separato, schedulato una volta a settimana, mentre Grype resta sul percorso rapido di ogni pull request.
name: weekly-deep-scan
on:
schedule:
- cron: '0 3 * * 1'
jobs:
dependency-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scansione completa OWASP Dependency-Check
run: |
docker run --rm -v "$(pwd)":/src \
-v dc-data:/usr/share/dependency-check/data \
owasp/dependency-check:latest \
--scan /src --format JSON --format HTML \
--project "supply-chain-demo" --out /src/dc-report
- name: Carica report come artifact
uses: actions/upload-artifact@v4
with:
name: dependency-check-report
path: dc-report/
Questo job gira ogni lunedì alle 3 di notte (orario UTC), quando nessuno aspetta il risultato per fare merge, e produce un report scaricabile dalla scheda Actions del repository per la revisione settimanale del team di sicurezza.
Errori comuni da evitare
Questi sono gli errori che vediamo più spesso nei team che adottano SBOM e dependency scanning per la prima volta. Nessuno di questi è un problema di strumenti: sono tutti errori di processo, e per questo sono anche i più facili da correggere una volta identificati.
- Generare la SBOM una sola volta e dimenticarla. Una SBOM è una fotografia. Se non la rigeneri a ogni build, quella che hai in mano descrive un progetto che non esiste più dopo il primo
npm update. - Scansionare solo le dipendenze dirette. L’attacco a keyv ha colpito applicazioni che non importavano keyv direttamente, ma tramite altre librerie che lo usavano come dipendenza transitiva. Assicurati che lo strumento scelto risolva l’intero albero, non solo il primo livello.
- Bloccare la build su ogni severità, comprese le “low”. Un progetto reale ha sempre decine di vulnerabilità a bassa severità senza fix disponibile. Se blocchi tutto, il team inizia a ignorare gli alert, che è peggio che non averli.
- Ignorare le licenze nella SBOM. CycloneDX registra anche la licenza dichiarata di ogni componente. Un pacchetto con licenza GPL importato in un prodotto commerciale chiuso è un rischio legale tanto quanto una CVE, e viene scoperto quasi sempre troppo tardi.
- Fidarsi ciecamente della provenienza firmata. L’attacco a keyv aveva provenienza valida firmata da GitHub Actions, perché il codice malevolo era stato inserito prima della build, non dopo. La firma di provenienza garantisce che il pacchetto pubblicato corrisponda al codice buildato, non che quel codice sia innocuo.
- Trattare la scansione come un evento una tantum invece che come un processo continuo. Una dipendenza pulita oggi può ricevere una CVE domani, senza che tu abbia cambiato una sola riga di codice. Programmare la scansione solo al lancio di un progetto, e mai più dopo, è uno degli errori più comuni nei team che iniziano da poco con questo workflow.
Risoluzione dei problemi più comuni
Se hai seguito i passi precedenti e qualcosa non torna, qui sotto trovi le cause più frequenti riscontrate configurando questo workflow su progetti reali, insieme alla soluzione diretta per ciascuna.
- cdxgen genera una SBOM vuota o con pochissimi componenti. Controlla di eseguire il comando nella cartella con
package-lock.jsonpresente e non in.gitignore. Senza lockfile, cdxgen non può risolvere le versioni esatte. - Dependency-Check resta bloccato al primo avvio. Il download iniziale del database NVD può richiedere 15-20 minuti su connessioni lente. Verifica che la porta in uscita verso
nvd.nist.govnon sia bloccata da un firewall aziendale. - Grype segnala “no vulnerability database found”. Esegui
grype db updateprima della prima scansione: il database dei feed va scaricato separatamente al primo utilizzo. - Il numero di componenti differisce tra cdxgen e Syft. È normale, come spiegato sopra. Se la differenza supera il 20%, controlla che entrambi puntino alla stessa cartella e che nessuno dei due stia includendo
node_modulesdi ambienti diversi (per esempio residui di test locali). - npm audit segnala vulnerabilità senza fix disponibile. Non tutte le CVE hanno una patch immediata. In questi casi valuta mitigazioni alternative: un override di versione con
overridesnelpackage.json, oppure la sostituzione della libreria se il maintainer è inattivo da mesi. - La pipeline CI fallisce sempre, anche senza vulnerabilità critiche. Controlla la soglia impostata con
--fail-on: valori comemediumsono spesso troppo aggressivi per un primo rollout. Parti dacritical, poi restringi gradualmente. - Il report HTML di Dependency-Check non si apre correttamente. Verifica di aver passato sia
--format JSONsia--format HTML: alcuni script di CI salvano solo il JSON e poi si aspettano l’HTML per errore di configurazione. - Falsi positivi ricorrenti sullo stesso pacchetto. Sia Dependency-Check sia Grype supportano file di soppressione (
suppression.xmlper Dependency-Check,.grype.yamlper Grype) per marcare in modo permanente un falso positivo verificato, evitando di doverlo rivalutare a ogni scansione. - Timeout della pipeline durante la scansione Dependency-Check. Se il tuo runner CI ha un limite di tempo stretto, valuta di spostare Dependency-Check su un job schedulato separato invece che su ogni push, e mantieni solo Grype (più veloce) sul percorso critico che blocca le pull request.
- La SBOM generata su Windows contiene percorsi diversi da quella generata su Linux. Alcuni strumenti registrano il percorso assoluto del file system nei metadati del componente. Se confronti SBOM generate su ambienti diversi, normalizza i percorsi prima del confronto o esegui sempre la generazione dallo stesso tipo di runner in CI.
Consigli avanzati per team enterprise
Il workflow descritto finora copre un singolo progetto. Su scala enterprise, con decine o centinaia di repository, alcuni accorgimenti aggiuntivi fanno la differenza tra un controllo che il team rispetta davvero e uno che viene silenziosamente ignorato dopo qualche settimana.
Una volta che il flusso base gira in modo affidabile, ci sono alcuni accorgimenti che separano un setup da laboratorio da uno pronto per la produzione. Primo: firma le tue SBOM con Sigstore/Cosign, così chiunque le consumi a valle può verificare che non siano state alterate dopo la generazione, chiudendo esattamente il tipo di gap sfruttato nell’attacco Axios.
Secondo: centralizza le SBOM di tutti i microservizi in un’istanza Dependency-Track condivisa, invece di tenere scansioni isolate per repository. Quando una nuova CVE viene pubblicata, vuoi rispondere in un colpo solo alla domanda “quali dei nostri 40 servizi la usano”, non ripetere la stessa ricerca 40 volte.
Terzo: monitora attivamente i cambi di maintainer sui pacchetti critici. Strumenti come Socket.dev e OpenSSF Scorecard analizzano segnali comportamentali, come un maintainer che pubblica improvvisamente da un nuovo indirizzo IP o introduce script di post-installazione mai usati prima, spesso il primo indicatore di un account compromesso, prima ancora che una CVE venga assegnata.
Quarto: fissa una cadenza di revisione, non solo un blocco automatico. Le vulnerabilità a bassa severità che non bloccano la build vanno comunque riviste, per esempio ogni due settimane, altrimenti si accumulano silenziosamente fino a diventare un problema quando la loro severità viene riclassificata verso l’alto.
Quinto: separa gli ambienti di scansione per criticità del servizio. Un microservizio interno che gestisce report statistici non ha bisogno della stessa soglia di blocco di un servizio che elabora pagamenti o dati personali. Applicare la stessa policy rigida ovunque rallenta i team senza aumentare davvero la sicurezza dove conta di più, mentre calibrare la soglia per criticità concentra l’attenzione (e i tempi di revisione umana) dove il rischio reale è più alto.
Il progetto completo: struttura finale della repository
A fine guida, la repository del progetto demo dovrebbe avere questa struttura, pronta per essere adattata a un’applicazione reale.
supply-chain-demo/
├── .github/
│ └── workflows/
│ └── supply-chain-scan.yml
├── node_modules/
├── server.js
├── package.json
├── package-lock.json
├── sbom-cdxgen.json
├── sbom-syft.json
├── grype-report.json
├── audit-report.json
├── dc-report/
│ ├── dependency-check-report.html
│ └── dependency-check-report.json
└── .grype.yaml
Da qui, i passi successivi realistici sono: aggiungere il job GitHub Actions al repository reale del tuo team, collegare l’output a Dependency-Track per lo storico, e programmare la scansione Dependency-Check completa come job settimanale separato, dato il tempo di esecuzione più lungo rispetto a Grype.
Per rendere il progetto immediatamente riutilizzabile, aggiungi al package.json alcuni script npm che racchiudono i comandi visti finora, così ogni sviluppatore del team può lanciare l’intero controllo con un solo comando invece di doversi ricordare ogni flag.
{
"scripts": {
"sbom": "npx @cyclonedx/cdxgen -o sbom.json -t js .",
"scan:audit": "npm audit",
"scan:grype": "grype sbom:./sbom.json -o table",
"scan:all": "npm run sbom && npm run scan:audit && npm run scan:grype"
}
}
Con questa aggiunta, npm run scan:all diventa il comando unico che qualunque membro del team, anche chi non ha seguito questa guida passo per passo, può eseguire prima di aprire una pull request. È lo stesso principio che rende utile un linter o un formatter: uno standard condiviso e facile da lanciare vince quasi sempre su una procedura documentata ma manuale.
Domande frequenti
Una SBOM sostituisce npm audit?
No. Sono complementari. La SBOM è l’inventario, npm audit (o Grype, o Dependency-Check) è lo strumento che confronta quell’inventario con un database di vulnerabilità note. Senza SBOM puoi comunque eseguire npm audit, ma perdi la possibilità di condividere l’inventario con strumenti esterni o con i tuoi clienti enterprise che la richiedono per contratto. In pratica, la maggior parte dei team che segue questa guida finisce per usare entrambi in parallelo: npm audit per il feedback rapido durante lo sviluppo, la SBOM come artefatto formale da allegare a ogni release.
CycloneDX o SPDX: quale formato scegliere?
Per progetti Node.js, CycloneDX ha il supporto più maturo tra gli strumenti trattati in questa guida. SPDX resta preferibile se lavori già in un ecosistema che lo richiede per conformità legale, per esempio in ambito Linux Foundation o governativo.
Quanto spesso va rigenerata la SBOM?
Idealmente a ogni build, integrata nella pipeline CI come mostrato sopra. Come minimo accettabile, a ogni modifica del package-lock.json.
Il Cyber Resilience Act si applica anche a progetti open source non commerciali?
Il regolamento si concentra sui prodotti con elementi digitali immessi sul mercato UE a scopo commerciale. I progetti open source mantenuti senza finalità commerciale diretta hanno, in generale, obblighi ridotti, ma se il tuo progetto open source viene poi integrato in un prodotto commerciale, la responsabilità della conformità ricade su chi lo distribuisce commercialmente.
Grype o Trivy: qual è la differenza pratica?
Sono strumenti concettualmente simili. Grype si integra più naturalmente con l’output di Syft nello stesso ecosistema Anchore, mentre Trivy (che abbiamo trattato nella nostra guida sulla sicurezza cloud con Prowler e Trivy) copre anche la scansione di immagini container e infrastruttura come codice in un unico strumento. Molti team li usano entrambi in contesti diversi della stessa pipeline.
Cosa fare se una dipendenza critica non ha patch disponibile?
Prima opzione: verifica se esiste un fork mantenuto attivamente. Seconda: valuta una mitigazione a livello di configurazione, per esempio disabilitando la funzionalità vulnerabile se non la usi. Terza, come ultima risorsa, isola il componente dietro un layer di rete più restrittivo finché non arriva una patch.
Questi strumenti funzionano anche su progetti Python o Go?
Sì. Syft, Grype e Dependency-Check sono tutti multi-linguaggio. cdxgen è ottimizzato per JavaScript/Node.js ma supporta anche altri ecosistemi tramite plugin dedicati.
Quanto costa mettere in piedi questo workflow?
Gli strumenti trattati in questa guida (cdxgen, Syft, Grype, npm audit, OWASP Dependency-Check) sono tutti open source e gratuiti. L’unico costo reale è il tempo di calcolo in CI e, se scegli di centralizzare i risultati, l’infrastruttura per ospitare un’istanza Dependency-Track, che puoi far girare anche su un singolo container Docker per un team di piccole dimensioni.
Copertura correlata
- Attacco Supply Chain OAuth: 700 Aziende Colpite [2026]
- OWASP Top 10 2025 in Node.js: 10 Vulnerabilità, 12 Difese [2026]
- Cyber Resilience Act: 83 Giorni alla Scadenza, Sanzioni fino a €15M [2026]
- SQL Injection in Node.js: Come Prevenirla in 12 Step [2026]
- Rate Limiting in Node.js: API Sicura in 12 Step [2026]
- Wazuh: Installazione e Configurazione SIEM/XDR in 12 Step [2026]
- Sicurezza online: violazioni, password, HTTPS e phishing
Fonti esterne verificate: note di rilascio Node.js 24.19.0, specifica CycloneDX, progetto OWASP Dependency-Check, Unit 42 sul threat landscape npm, analisi Wiz sull’attacco a keyv e le linee guida NCSC sulla supply chain security.




