Supply chain del software nel 2026: lezioni da xz-utils, tj-actions e il threat model che applico alle PMI PHP/Node

Supply chain del software nel 2026: lezioni da xz-utils, tj-actions e il threat model che applico alle PMI PHP/Node

Il 29 marzo 2024, alle 19:32 ora italiana, mi ha scritto su Telegram il CTO di un cliente che segue una piattaforma e-commerce B2B in Laravel su server Debian 12: "Maurizio, ho appena letto della backdoor di xz-utils, siamo affected? Ho liblzma linkata in systemd-resolved e sshd sta aperto su 22". Ho passato le successive quattro ore a rispondere alla stessa domanda per sei clienti diversi. Nessuno di loro - per fortuna - girava xz 5.6.0 o 5.6.1 perché Debian 12 stable era ancora su 5.4.1; la backdoor di Jia Tan documentata da Andres Freund sulla mailing list oss-security non li aveva toccati. Ma ho capito quella sera che la mia concezione di "supply chain security" era vecchia di dieci anni: fino ad allora pensavo alla supply chain come "le dipendenze del mio Composer lock"; dopo quella notte l'ho dovuta ridefinire come "l'intera catena di fiducia che porta un byte dal cervello di uno sviluppatore al processo che gira in produzione sul mio server".

Nei diciotto mesi che sono seguiti, quella definizione è stata messa alla prova due volte ancora: dal compromise di tj-actions/changed-files del marzo 2025 che ha esposto i secrets di centinaia di pipeline GitHub Actions, e da una sfilza di incidenti minori - Polyfill.io, Ultralytics, pacchetti npm di Solana - che hanno tutti in comune una cosa. L'attaccante non ha sfruttato un bug in una libreria: si è inserito prima nel flusso che produce la libreria. Questo articolo raccoglie le lezioni operative che ho estratto da questi due anni, e il threat model che oggi propongo a ogni PMI italiana che voglia seriamente mettere in sicurezza la propria catena di build senza diventare paranoica.

xz-utils (CVE-2024-3094): due anni di social engineering e il bypass del code review

La cosa importante di xz-utils non è la backdoor in sé - quella è tecnicamente elegante ma ormai nota. La cosa importante è come è entrata nel codice, perché quel metodo è ripetibile e quasi certamente in corso su altri progetti mentre scrivi queste righe. L'account "Jia Tan" ha iniziato a contribuire a xz nel 2021, partecipando alla mailing list, proponendo patch ragionevoli, guadagnando la fiducia di Lasse Collin, il manutentore unico (e sovraccaricato) del progetto. Dopo circa due anni di contributi legittimi, Jia Tan è diventato co-manutentore. Poi, nel febbraio 2024, ha rilasciato le versioni 5.6.0 e 5.6.1 con una backdoor nel tarball di release - non nel repository git. La backdoor era nascosta in un file binario di test (bad-3-corrupt_lzma2.xz), scompattato e attivato da uno script di build che rendeva praticamente impossibile individuarla leggendo il sorgente su GitHub, perché su GitHub il tarball malevolo non c'era.

La backdoor è stata scoperta per puro caso. Andres Freund, engineer di PostgreSQL presso Microsoft, stava facendo benchmark su PostgreSQL e ha notato che sshd consumava il 600% di CPU in più del normale e che ssh localhost aveva un ritardo di circa 500 millisecondi. Con una sensibilità che molti sviluppatori non hanno, invece di dire "strano" e passare oltre, è andato a fondo. Ha scoperto che liblzma (parte di xz-utils) era linkata indirettamente in sshd su Debian tramite il patch di libsystemd, e che la backdoor hookava una funzione di autenticazione di OpenSSH permettendo a chi avesse la chiave privata corretta di bypassare tutto. L'entry NIST NVD per CVE-2024-3094 assegna CVSS 10.0 - il punteggio massimo.

Le lezioni di xz-utils per una PMI sono tre, e nessuna di esse è "patcha velocemente" (che è ovvia):

  1. Il code review su GitHub non è sufficiente se il tarball di release è diverso dal repository. L'unico modo di rilevare questa classe di attacchi è build from source direttamente dal tag git verificato, non dal tarball pubblicato. Oppure, più realisticamente per una PMI: consumare solo package della distro (Debian stable, Ubuntu LTS) che fanno loro questa verifica, oppure pre-compilare internamente da git.
  2. Le dipendenze transitive sono la tua vera superficie di attacco. Nessuno dei clienti che avevo installa xz-utils esplicitamente; ce l'avevano tutti perché systemd lo tira dentro. Il tuo composer.json o package.json di primo livello è la punta dell'iceberg. Un SBOM completo ti mostra l'iceberg intero.
  3. I progetti open source con un manutentore singolo e sovraccarico sono un risk pattern. Non è colpa dei manutentori: è un problema strutturale della community open source, dove critical infrastructure è mantenuta da volontari non pagati e quindi vulnerabile a social engineering mirato da attaccanti che possono investirci anni. Nel triage della tua supply chain, contare i "bus factor" dei progetti critici è diventato uno standard.

tj-actions/changed-files, marzo 2025: quando un GitHub Action compromesso espone 200+ repository

Il 14 marzo 2025 GitHub Security Lab ha pubblicato un advisory su un compromise del popolare action tj-actions/changed-files (usato da 23.000+ repository all'epoca) in cui un attaccante aveva ottenuto il controllo di un personal access token del manutentore, aveva pushed un commit malevolo al tag v35 (che puntava a un SHA non verificato) e aveva modificato l'action per dumpare i secrets della pipeline in chiaro nei log di GitHub Actions di chiunque la eseguisse. L'attacco è stato scoperto in circa 24 ore perché alcuni utenti hanno notato output strani nei log, ma nel frattempo si stima che almeno 200 organizzazioni abbiano eseguito la versione compromessa con i loro secrets (AWS keys, Docker Hub tokens, npm publish tokens, e altro) finiti in log pubblici di repository open source.

Il pattern di attacco è importante perché la fix apparentemente ovvia - "pin il tag" - non funziona. Quasi tutti i tutorial di GitHub Actions dicono di usare uses: tj-actions/changed-files@v35, dove v35 è un tag mobile che il manutentore può spostare in qualsiasi momento. Quando l'attaccante controlla l'account del manutentore (o si inserisce come maintainer via social engineering come in xz), può spostare v35 su un commit malevolo e ogni pipeline che fa un pull fresco prenderà la versione compromessa. L'unica mitigation reale è pinnare all'hash SHA-1 del commit, non al tag:

- uses: tj-actions/changed-files@0f3f1ed45c3f1b48f96d2f3f2bf9e6d8c1a4e2b7  # v35.7.2

Questo pattern ha un costo: ogni dependabot update deve verificare l'hash, e gli upgrade sono più rumorosi. Ma è l'unico modo di dire veramente "voglio questo codice esatto, niente sostituzioni". Per i miei clienti PMI che usano GitHub Actions ho consolidato questa regola come hard gate: niente tag mobili nei workflow di CI, mai.

La lezione più grande di tj-actions è che la CI è parte della supply chain allo stesso titolo del codice applicativo. Se la tua GitHub Actions ha accesso a AWS_ACCESS_KEY_ID nell'environment, un action compromesso li può esfiltrare. Se ha accesso a NPM_TOKEN per pubblicare, un action compromesso può pubblicare malware a tuo nome sul registry. L'incident di tj-actions è trattato in profondità nel mio articolo sulla minimizzazione dei repository git e dei rischi dei git hooks, dove spiego come un pre-commit hook può essere trasformato in RCE con tre righe.

SBOM, SLSA e provenance: perché nel 2026 "ho il lockfile" non è più sufficiente

Fino al 2023 il mio baseline per una PMI era "commit del composer.lock, commit del package-lock.json, composer audit in CI, dependabot o renovate per gli update". Nel 2026 questo non è più sufficiente, e i motivi sono due: il lockfile verifica cosa installerai la prossima volta che fai composer install, ma non dice nulla sulla provenance del codice (chi l'ha prodotto? è il tarball originale o è stato manipolato?); e il lockfile non copre la pipeline di build, solo le dipendenze runtime. xz-utils ha aggirato il primo livello (il tarball era diverso dal repository), tj-actions ha colpito il secondo (le dipendenze della build, non del runtime).

Il framework di riferimento che ho adottato si chiama SLSA - Supply-chain Levels for Software Artifacts, un'iniziativa della Open Source Security Foundation che definisce quattro livelli di assurance per la supply chain. Senza entrare nei dettagli, il livello SLSA 2 richiede che ogni build sia riproducibile e firmata da una sorgente verificabile (tipicamente, i logs di GitHub Actions con Sigstore). Il livello SLSA 3 aggiunge la non-forgeability della provenance. Per una PMI italiana, l'obiettivo realistico è SLSA 2 sui progetti critici - niente più di questo, perché SLSA 3/4 richiede infrastrutture dedicate che solo grandi organizzazioni possono permettersi.

Il secondo strumento operativo è il SBOM (Software Bill of Materials), ovvero un elenco formale di tutte le dipendenze - dirette e transitive - con versioni, hash, e provenance. Il formato standard de facto è CycloneDX, e per PHP/Composer il tool cyclonedx-php-composer genera un SBOM in JSON partendo dal composer.lock; per Node esiste @cyclonedx/cyclonedx-npm. Il pattern che applico in CI è: genera il SBOM a ogni build, confrontalo col precedente, fallisci la build se sono comparse dipendenze nuove senza approvazione esplicita. Questo trasforma il classico composer install da operazione "silenziosa" in un gate di review obbligatorio.

La combinazione SBOM + osv-scanner + diff CI è dettagliata per il versante Composer nel mio playbook supply chain security per Laravel e Symfony, che include anche le policy allow-plugins e il blocco degli script in CI - altri due vettori di attacco che il lockfile da solo non mitiga.

Cinque regole operative che applico su ogni cliente PMI dal 2025

Dopo xz-utils, tj-actions e una manciata di incidenti minori, ho consolidato cinque regole che applico a ogni nuovo progetto PMI e a ogni audit di legacy infrastructure. Non sono "best practice teoriche" - sono il livello minimo sotto il quale mi rifiuto di firmare una consegna.

  1. Pin hash, non tag. Ogni dipendenza di CI (GitHub Actions, GitLab CI include, Jenkins shared libraries) deve essere pinnata all'hash SHA, non al tag mobile. Dependabot/renovate configurati per proporre aggiornamenti in PR che il team approva manualmente - niente auto-merge, nemmeno per "patch releases". Il costo operativo è tollerabile, il ritorno è infinito.
  2. Proxy registry privato con lista di allow esplicita. Per Composer uso un Satis o un [Packagist.com privato; per npm uso Verdaccio o Nexus come proxy. Tutto il traffico di composer install e npm install passa per il proxy, che ha una whitelist di package autorizzati. Un composer require nuovo/pacchetto non funziona finché un senior non lo approva nella whitelist del proxy. Questo blocca sul nascere il 90% degli attacchi di typosquatting e dependency confusion.
  3. Secrets scanning bloccante su pre-commit e CI. gitleaks su pre-commit client-side, e gitleaks come gate bloccante in CI. Nessun secret in git, mai. Se uno scappa, la CI fallisce, il commit non arriva a main, e parte una rotazione immediata. Il mio hook gitleaks è configurato per rilevare anche i token GitHub e Docker Hub anche quando sono offuscati con base64 triviale.
  4. Dependency review obbligatoria in PR. GitHub ha un'action dependency-review-action che blocca le PR se introducono dipendenze con CVE noti o con licenze non whitelisted. Integrata con CVE database multipli (NIST NVD, OSV, GitHub Advisory Database) per ridurre il single-point-of-failure dopo la crisi del sistema CVE di aprile 2025. Se il gate blocca, la PR non si merge.
  5. SBOM versionato come artifact di release. Ogni release di produzione produce un SBOM CycloneDX che viene salvato in un bucket di artifacts separato, con retention 5 anni. Questo è fondamentale per la parte "obblighi di documentazione" della NIS2 art. 21: in caso di incidente, devi poter rispondere alla domanda "quale versione di quale libreria girava in produzione il giorno X?" senza dover ricostruire il deploy a memoria.

Queste cinque regole insieme alzano il costo di un attacco alla supply chain di un ordine di grandezza senza richiedere investimenti infrastrutturali significativi. Il tempo di setup iniziale è circa 3-5 giorni per un progetto PMI medio; l'overhead operativo dopo è marginale - un'ora la settimana per review delle PR di dependency update.

Il threat model che propongo: quattro perimetri anziché uno

L'errore mentale più frequente che vedo nei clienti è pensare alla supply chain come a un'unica entità monolitica. In realtà sono quattro perimetri distinti, ognuno con attaccanti, motivazioni e mitigations diverse. Quando faccio un audit di supply chain security, chiedo al team di mappare il rischio separatamente su ognuno:

  • Perimetro 1: dev endpoint. Le workstation degli sviluppatori. Attacco tipo: estensioni VS Code malevole, npm install -g senza verification, credenziali GitHub esposte. Mitigation: MDM sui laptop, password manager aziendale, U2F hardware key per GitHub, controllo delle estensioni IDE via policy. Per le PMI italiane questa parte è spesso la più trascurata perché "tanto siamo in 5". I 5 laptop sono 5 porte d'ingresso.
  • Perimetro 2: SCM (git repository). Attacco tipo: repo jacking, push di commit non firmati, compromise di account di maintainer. Mitigation: branch protection rules con required reviewers e required signed commits, CODEOWNERS, audit log di GitHub con alert su eventi sensibili, SSO + SCIM per onboarding/offboarding automatico, U2F obbligatorio per tutti.
  • Perimetro 3: registry (Composer/npm/Docker Hub). Attacco tipo: typosquatting, dependency confusion, package planting, compromise di maintainer. Mitigation: proxy privato con whitelist, lockfile + hash, composer audit e npm audit e osv-scanner in CI, policy allow-plugins esplicita per Composer, --ignore-scripts in CI.
  • Perimetro 4: CI/CD. Attacco tipo: GitHub Actions compromessi, runners self-hosted compromessi, secrets esfiltrati dai log, poisoned build cache. Mitigation: actions pinnate ad hash, runners effimeri (mai self-hosted persistenti per progetti open source!), secrets scoped al minimo privilegio possibile, OIDC federation verso cloud provider invece di long-lived keys, audit log dei workflow run.

Il runtime non è un perimetro di supply chain - è il perimetro di application security. Una volta che il codice è in produzione, i rischi sono altri (SQL injection, XSS, broken authentication). La supply chain si ferma al momento in cui l'artifact è deployato, ed è su quello che devo garantirne l'integrità. Per il pezzo successivo della catena - cosa fare quando qualcosa va storto - rimando al mio piano di incident response in 72 ore per Laravel e Symfony allineato a NIS2, che copre il flusso di contenimento, forensics e comunicazione post-breach.

La sicurezza della supply chain del software non è un problema che si risolve comprando un tool. È un cambio di postura: passare da "mi fido del mio lockfile" a "verifico l'integrità di ogni byte dalla sorgente al runtime, e ho un audit trail per dimostrarlo". Per una PMI italiana nel 2026, questo cambio è diventato obbligatorio non solo per ragioni di rischio reale ma anche per conformità a NIS2, al Cyber Resilience Act e agli obblighi contrattuali con i clienti enterprise che sempre più spesso richiedono evidenza documentata delle pratiche di secure development. L'integrazione di queste pratiche nel flusso di sviluppo quotidiano è quello che chiamo DevSecOps operativo, e sul versante hardening applicativo si salda con la mia checklist NIS2-ready per Laravel e Symfony.

Se la tua PMI sviluppa o mantiene software in PHP, Laravel, Symfony o Node, e non hai ancora fatto un audit serio della tua supply chain - dai dev endpoint fino all'artifact in produzione - scopri come lavoro con i clienti su questi temi: dieci anni di consulenza a PMI italiane mi hanno insegnato che la supply chain è il rischio più sottostimato e più asimmetrico che esiste, perché un incident può fermare tutta l'operatività per settimane mentre la prevenzione costa giorni. Se vuoi partire con un audit concreto del tuo flusso di sviluppo e delle tue dipendenze, contattami per una consulenza: in due settimane di lavoro ti consegno mapping completo dei quattro perimetri, SBOM di tutti i progetti critici, piano di adozione delle cinque regole con tempistiche realistiche per il tuo team.

Ultima modifica: