Una pipeline CI/CD compromessa vale più, per un attaccante, di mille credenziali rubate una per una. Un solo workflow GitHub Actions mal configurato può diventare la porta d’ingresso per iniettare codice malevolo in ogni release che un team pubblica, spesso senza che nessuno se ne accorga per settimane. Secondo Sonatype, il 2025 ha chiuso con oltre 454.600 nuovi pacchetti open source dannosi identificati, un numero che porta il totale cumulativo di malware noto e bloccato oltre 1,233 milioni di pacchetti. Gartner, dal suo lato, aveva previsto già qualche anno fa che entro il 2025 il 45% delle organizzazioni nel mondo avrebbe subito almeno un attacco alla propria supply chain software, un triplicarsi rispetto al 2021. Chi scrive workflow YAML ogni giorno, non solo chi lavora in un team di sicurezza, oggi fa parte della superficie che va protetta. In questa guida vediamo, passo per passo, come blindare una pipeline CI/CD su GitHub Actions contro gli attacchi alla supply chain: dal pinning delle action al least privilege del token, dalla migrazione a OIDC alla firma degli artifact con Sigstore, fino a un progetto completo che mette insieme tutti i controlli in un workflow funzionante.
Perché la sicurezza della pipeline CI/CD è una priorità nel 2026
Per anni le pipeline CI/CD sono state trattate come infrastruttura di supporto, qualcosa da configurare una volta e lasciare girare. Quell’epoca è finita. Gartner stima che “by 2028, 85% of software engineering teams in large enterprises will have deployed software supply chain security tools, up from 60% in 2025” (Security Boulevard), un salto che racconta quanto velocemente il tema sia passato da nicchia per team di sicurezza a priorità trasversale per chi scrive codice ogni giorno.
I numeri di Kaspersky confermano che il problema non è teorico. In un’analisi su circa 30.000 repository GitHub popolari, “only 10% of repositories had GitHub Actions automation pipelines that raised no security concerns” (Kaspersky). Vuol dire che nove pipeline su dieci, anche tra i progetti più seguiti, portano almeno una configurazione rischiosa: permessi troppo ampi, action non ancorate a una versione immutabile, secret esposti nei log. Sullo stesso campione più ampio, Kaspersky ha rilevato “more than 250,000 potential deviations from secure CI/CD configuration recommendations across approximately 130,000 GitHub Actions pipelines”.
In Italia il quadro non è diverso, anzi. Il report Data Gathering 2026 di Elmec, basato su dati CERT-AGID, segnala oltre 3.600 campagne malevole registrate nel paese nel 2025, in un anno in cui a livello globale sono stati pubblicati più di 48.000 nuovi CVE, l’8% classificati come critici e il 28% sfruttati entro le prime 24 ore dalla divulgazione (Elmec). Lo stesso report prevede che la NIS2 porterà nel 2026 obblighi più severi di accountability e notifica degli incidenti, con un’intensificazione delle attività di audit e ispezione proprio in Italia. Chi gestisce una pipeline CI/CD esposta a queste regole non può più permettersi configurazioni ereditate da un tutorial di cinque anni fa.
Per un team italiano, questo si traduce in un problema molto concreto: un auditor NIS2 non chiede solo “avete uno scanner antivirus”, chiede come viene costruito e distribuito il software, chi può modificare la pipeline di release e come vengono gestite le credenziali cloud usate in automatico dai workflow. Documentare questi controlli, non solo applicarli, diventa parte del lavoro tanto quanto scrivere il workflow stesso.
Cos’è un attacco alla supply chain in una pipeline CI/CD
Un attacco alla supply chain CI/CD non colpisce l’applicazione finale in modo diretto. Colpisce il processo che la costruisce e la distribuisce. Un’action di terze parti compromessa, un runner condiviso con permessi eccessivi o un secret di lunga durata rubato da un log possono dare a un attaccante la capacità di modificare il codice prima ancora che arrivi in produzione, bypassando ogni controllo applicato a valle.
Le famiglie di rischio più comuni in un ambiente GitHub Actions sono quattro. La prima è il pull dell’action non ancorata a un commit SHA: se referenzi uses: owner/action@v2, il manutentore (o chi gli ruba l’account) può aggiornare quel tag in qualsiasi momento e il tuo workflow eseguirà codice diverso senza che tu te ne accorga. La seconda è il token con permessi eccessivi: per default GITHUB_TOKEN ottiene spesso più privilegi di quanto il job richieda davvero. La terza è l’esfiltrazione di secret tramite log o artifact non ripuliti. La quarta è il workflow eseguito su pull_request_target con checkout del codice di un fork non verificato, che permette a chiunque apra una PR di eseguire comandi con i permessi del repository bersaglio.
Sonatype osserva che il volume di pacchetti malevoli continua a crescere ogni anno, e Kaspersky mostra che la configurazione delle pipeline resta il punto debole più trascurato. Le due cose insieme spiegano perché blindare GitHub Actions oggi significhi molto più che abilitare Dependabot: serve un controllo end-to-end, dal commit al deploy.
Due tecniche meritano una menzione separata perché colpiscono proprio la pipeline, non il codice applicativo. Il typosquatting consiste nel pubblicare un package con un nome quasi identico a uno molto usato (una lettera scambiata, un trattino in più), sperando che un developer lo installi per errore in un passo di build automatizzato. La dependency confusion sfrutta la logica di risoluzione dei package manager: se un progetto usa un registry privato interno ma il tool di build non è configurato per dargli priorità assoluta, un attaccante può pubblicare un package con lo stesso nome su un registry pubblico e farlo installare al posto di quello interno, proprio durante l’esecuzione della pipeline. Nessuna delle due tecniche richiede di violare un server: basta che qualcuno, nel tuo workflow, esegua un comando di installazione senza i controlli giusti. Difendersi da entrambe richiede uno sguardo diverso dal solito: non stai proteggendo un endpoint esposto, stai proteggendo l’intero processo che trasforma codice sorgente in un artifact distribuito, con decine di punti in cui qualcosa di esterno entra nel flusso senza essere verificato a dovere.
Prerequisiti: strumenti e versioni necessarie
Prima di iniziare, prepara l’ambiente. Questa guida assume un repository ospitato su GitHub (piano Team o Enterprise per alcune funzioni di push protection avanzate, ma i passi principali funzionano anche su piano gratuito per repository pubblici) e un minimo di accesso da amministratore sul repository o sull’organizzazione.
| Strumento | Versione consigliata | Scopo nella pipeline |
|---|---|---|
| GitHub CLI (gh) | ultima versione stabile | Gestione workflow, secret e branch protection da terminale |
| Node.js | 22 LTS o successiva | Runtime per gli script di audit e per l’app di esempio |
| actions/checkout | v4, ancorata a SHA | Checkout del codice nel runner |
| CodeQL Action | 2.27.x (ultima disponibile) | Analisi statica del codice (SAST) integrata in GitHub |
| cosign | ultima versione | Firma e verifica di artifact e immagini container |
| OSV-Scanner | ultima versione | Scansione delle dipendenze per vulnerabilità note |
| Dependency Review Action | ultima versione | Blocco automatico di PR con dipendenze vulnerabili |
Ti servirà anche un account cloud (AWS, Azure o GCP, a seconda di dove fai il deploy) su cui configurare un provider OIDC, e i permessi per modificare le impostazioni di branch protection dell’organizzazione. Se lavori in team, coinvolgi chi gestisce l’infrastruttura cloud prima del passo sull’OIDC: alcune modifiche toccano le policy IAM condivise.
Non serve applicare tutti i passi in un solo pomeriggio. L’ordine proposto in questa guida segue il rapporto tra sforzo e rischio evitato: inizia dall’audit e dal pinning, che non richiedono accesso a sistemi esterni, poi passa ai permessi e all’OIDC, che coinvolgono il cloud, e chiudi con firma degli artifact e monitoraggio, che hanno senso solo una volta che i livelli precedenti sono stabili.
Passo 1 e 2: Audit dei workflow esistenti e pinning delle action a SHA
Il primo passo non è scrivere codice, è capire cosa hai già. Esegui questo script dalla root del repository per elencare tutte le action usate nei tuoi workflow e verificare quali non sono ancora ancorate a un commit SHA immutabile.
#!/usr/bin/env bash
# audit-actions.sh - elenca le action non ancorate a SHA
grep -rhoE "uses:\s*[^[:space:]]+" .github/workflows/*.yml | \
sed -E 's/uses:\s*//' | sort -u | \
awk -F'@' '{
if ($2 !~ /^[0-9a-f]{40}$/) {
print "NON ANCORATA:", $0
} else {
print "OK:", $0
}
}'
Output tipico su un repository con 6-8 workflow:
NON ANCORATA: actions/checkout@v4
NON ANCORATA: actions/setup-node@v4
NON ANCORATA: docker/build-push-action@v5
OK: actions/upload-artifact@a8a3f3ad30e3f...
Ogni riga “NON ANCORATA” è un punto in cui il tuo workflow eseguirà automaticamente qualunque codice il manutentore dell’action deciderà di pubblicare su quel tag in futuro. La correzione è sostituire il tag mobile con il commit SHA completo corrispondente alla versione che vuoi usare, lasciando un commento con la versione leggibile per chi legge il file:
# Prima (rischioso: il tag v4 può essere spostato)
- uses: actions/checkout@v4
# Dopo (sicuro: ancorato a un commit immutabile)
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Puoi automatizzare l’aggiornamento periodico degli SHA con Dependabot, impostando package-ecosystem: "github-actions" nel file dependabot.yml: in questo modo ricevi una PR ogni volta che una versione pinnata deve essere aggiornata, senza perdere il controllo granulare sul commit effettivo.
Prima di accettare quella PR di Dependabot, vale la pena dare un’occhiata rapida al diff del codice sorgente dell’action, non solo al numero di versione che cambia. Per action molto popolari e mantenute da organizzazioni note il rischio resta basso, ma per action di piccoli progetti o mantenute da un singolo autore un controllo manuale, anche di pochi minuti, costa molto meno di un incidente. Alcuni team arrivano a forkare le action critiche nel proprio registry interno, così da controllare ogni aggiornamento con la stessa review richiesta per il codice applicativo.
Passo 3 e 4: permessi minimi del token e migrazione a OIDC
Il secondo fronte è il principio del privilegio minimo applicato a GITHUB_TOKEN. Per default molti workflow ereditano permessi di scrittura su contenuti, issue e pull request anche quando il job si limita a eseguire dei test. Dichiara sempre i permessi in modo esplicito, a livello di workflow e, quando serve differenziare, a livello di singolo job.
name: build-and-test
on: [push, pull_request]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: npm ci
- run: npm test
Il passo successivo, più impegnativo ma decisivo, è eliminare i secret cloud di lunga durata (access key AWS, service principal Azure con password statica) e passare a OpenID Connect. Con OIDC il workflow richiede un token temporaneo al provider cloud, valido solo per la durata del job e solo per il repository e il branch autorizzati: non esiste più una credenziale permanente da rubare da un log o da un secret store compromesso.
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: aws-actions/configure-aws-credentials@050d24556b68d46b14a1a766e68a3b6500dd07a1
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: eu-south-1
Sul lato AWS la trust policy del ruolo deve limitare esplicitamente il sub del token OIDC al repository e, idealmente, al branch o all’environment da cui vuoi permettere il deploy, così anche se qualcuno apre una pull request da un fork non riceverà mai credenziali cloud valide.
Un dettaglio che molti team dimenticano, qui, è il claim aud (audience) del token OIDC. Lasciarlo al valore di default può andare bene per iniziare, ma su infrastrutture con più repository e più ruoli IAM conviene differenziare l’audience per ambiente, così un token emesso per il workflow di staging non possa essere riutilizzato, nemmeno per errore di configurazione, per assumere un ruolo di produzione.
Passo 5 e 6: secret scanning, push protection e Dependency Review
Anche con OIDC resteranno secret da proteggere: token API di terze parti, chiavi di firma, credenziali di database. GitHub ha rafforzato in modo sensibile il secret scanning negli ultimi mesi: secondo il changelog ufficiale, il classificatore “context-aware” per i secret ha aumentato il weighted acceptance dell’11,5% riducendo contemporaneamente del 86% il tempo mediano di esecuzione della scansione (GitHub Changelog). Ad ottobre 2026 GitHub ha anche ampliato i detector di secret scanning per coprire servizi come Lovable e Supabase, e ha introdotto un controllo che blocca il merge di pull request contenenti secret esposti.
Attiva push protection dalle impostazioni di sicurezza del repository (Settings → Code security → Secret scanning → Push protection) prima di qualsiasi altra modifica: è il controllo con il miglior rapporto tra sforzo di configurazione e rischio evitato, perché impedisce il commit del secret ancora prima che finisca nella storia di git.
Il secondo controllo da aggiungere è la Dependency Review Action, che blocca automaticamente le pull request che introducono dipendenze con vulnerabilità note o licenze non ammesse dalla tua policy.
name: dependency-review
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/dependency-review-action@3b139cfc5fae8b618d3b0e2b6a5c9b4cea0add83
with:
fail-on-severity: high
deny-licenses: GPL-3.0, AGPL-3.0
Questa combinazione, push protection più dependency review, intercetta sia il secret che scappa a un developer distratto sia la dipendenza compromessa che qualcuno prova a introdurre, volontariamente o no, in una PR.
Resta un ultimo canale di fuga da chiudere: i log del job stesso. GitHub Actions maschera automaticamente i valori registrati come secret nel workflow, ma solo se il secret è passato come variabile d’ambiente o input riconosciuto. Se un comando concatena un secret dentro una stringa più complessa, o se un tool di terze parti lo stampa in un formato inatteso (base64, URL-encoded), il mascheramento automatico può non riconoscerlo e il valore finisce comunque nei log, visibili a chiunque abbia accesso in lettura al repository. Evita di costruire comandi che manipolano secret come testo libero, e preferisci sempre il passaggio tramite variabile d’ambiente dedicata.
Passo 7 e 8: analisi statica con CodeQL e firma degli artifact con Sigstore
CodeQL è il motore di analisi statica nativo di GitHub ed è stato aggiornato più volte nelle ultime settimane: la versione 2.27.2 migliora l’analisi per C++, Go, Rust e JavaScript, la 2.27.1 ha aggiunto supporto a Kotlin 2.4.20, mentre la 2.27.0 ha introdotto il supporto Linux ARM64, secondo il changelog ufficiale di GitHub già citato. Integrarlo richiede poche righe:
name: codeql
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
jobs:
analyze:
runs-on: ubuntu-latest
strategy:
matrix:
language: ['javascript-typescript']
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: github/codeql-action/init@2d47c3d-pinned-sha
with:
languages: ${{ matrix.language }}
- uses: github/codeql-action/analyze@2d47c3d-pinned-sha
L’ultimo anello è garantire che l’artifact che esce dalla pipeline, un’immagine container o un pacchetto, sia davvero quello costruito dal tuo workflow e non sia stato alterato dopo la build. Firmarlo con cosign (parte del progetto Sigstore) produce una firma keyless legata all’identità OIDC del workflow stesso, verificabile da chiunque scarichi l’artifact.
permissions:
id-token: write
packages: write
steps:
- name: Firma l'immagine container
run: |
cosign sign --yes \
ghcr.io/tuo-org/tua-app@${{ steps.build.outputs.digest }}
Chi consuma l’immagine può poi verificarla con cosign verify, confermando che è stata prodotta da quel repository e da quel workflow specifico, non da un fork o da una pipeline alterata. Vale la pena integrare questa verifica anche nell’ambiente di deploy stesso: un cluster Kubernetes può rifiutare di eseguire un’immagine non firmata tramite un admission controller dedicato, portando il controllo di integrità dalla pipeline di build fino al momento dell’esecuzione in produzione.
Passo 9 e 10: isolamento dei runner e branch protection con CODEOWNERS
Se usi runner self-hosted, trattali come infrastruttura di produzione, non come una VM di sviluppo dimenticata in un angolo. Un runner self-hosted condiviso tra repository pubblici è una delle configurazioni più rischiose che esistano, perché chiunque apra una pull request da un fork potrebbe far eseguire codice arbitrario su quella macchina. Usa runner effimeri (una VM nuova per ogni job, distrutta alla fine), isolali per repository o per livello di fiducia, e non montare mai credenziali di produzione su runner che eseguono anche workflow su pull request esterne.
Sul fronte della cache, GitHub ha introdotto a settembre 2026 un controllo cache-mode che permette di limitare chi può scrivere e leggere dalla cache delle Actions, riducendo il rischio di cache poisoning, una tecnica in cui un attaccante inserisce una dipendenza malevola nella cache condivisa sperando che un job successivo la riutilizzi senza riverificarla.
Un altro controllo spesso trascurato è l’egress di rete del runner. Un job compilato ed eseguito senza restrizioni di rete può contattare qualsiasi host esterno, il che significa che una dipendenza compromessa può esfiltrare segreti o scaricare un secondo stadio del malware senza che nessuno se ne accorga finché non è troppo tardi. Alcuni runner self-hosted enterprise permettono di whitelistare i soli host necessari (registry npm, registry Docker, servizi cloud usati) e bloccare tutto il resto per default: un controllo più impegnativo da configurare ma molto efficace contro l’esfiltrazione.
Completa la protezione con regole di branch protection sul ramo principale: richiedi almeno una review approvata, richiedi che i check CI passino, e blocca i push diretti anche per gli amministratori. Aggiungi un file CODEOWNERS che forzi la review di un membro del team di sicurezza ogni volta che qualcuno modifica un file dentro .github/workflows/, così nessuna modifica alla pipeline stessa passa inosservata.
# CODEOWNERS
.github/workflows/ @tuo-org/team-security
.github/dependabot.yml @tuo-org/team-security
Passo 11 e 12: monitoraggio continuo e progetto completo funzionante
Una pipeline sicura il giorno della configurazione può degradarsi in pochi mesi se nessuno controlla i log di audit. Esporta regolarmente l’audit log dell’organizzazione (Settings → Audit log, o via API) e cerca eventi come modifiche ai permessi dei workflow, rotazione mancata dei secret, o nuove action aggiunte senza review. Molti team integrano questi log in un SIEM esistente o in una semplice funzione serverless che invia un alert su Slack quando rileva un pattern sospetto, come un workflow che cambia i permessi da read a write fuori da una PR revisionata.
Il workflow finale: tutti i controlli in un unico file
Per rendere concreto tutto quello che abbiamo visto, ecco la struttura di un repository di esempio che applica i controlli di questa guida a una piccola applicazione Node.js containerizzata:
mia-app-sicura/
├── .github/
│ ├── workflows/
│ │ ├── dependency-review.yml
│ │ ├── codeql.yml
│ │ └── secure-release.yml
│ ├── dependabot.yml
│ └── CODEOWNERS
├── src/
│ └── server.js
├── Dockerfile
├── deploy.sh
└── package.json
Il file dependabot.yml copre sia le dipendenze npm che le action pinnate:
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
Con questa struttura, ogni modifica a .github/workflows/ richiede la review del team di sicurezza grazie al CODEOWNERS visto nel passo precedente, ogni pull request passa prima dalla dependency review e dall’analisi CodeQL, e solo il job finale, con i permessi minimi necessari, costruisce e pubblica l’immagine firmata. Ecco come appare, messo insieme, il workflow di build e deploy che applica tutti i controlli visti fino qui: action ancorate a SHA, permessi minimi, OIDC per il cloud, dependency review e firma dell’artifact.
name: secure-release
on:
push:
branches: [main]
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/dependency-review-action@3b139cfc5fae8b618d3b0e2b6a5c9b4cea0add83
with:
fail-on-severity: high
build-and-sign:
needs: verify
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Build immagine
id: build
run: |
docker build -t ghcr.io/tuo-org/tua-app:${{ github.sha }} .
docker push ghcr.io/tuo-org/tua-app:${{ github.sha }}
- name: Firma con cosign
run: cosign sign --yes ghcr.io/tuo-org/tua-app:${{ github.sha }}
deploy:
needs: build-and-sign
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: aws-actions/configure-aws-credentials@050d24556b68d46b14a1a766e68a3b6500dd07a1
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: eu-south-1
- run: ./deploy.sh ${{ github.sha }}
Verifica il risultato
Dopo il push, il job “verify” blocca la pipeline se la dependency review trova vulnerabilità ad alta severità, “build-and-sign” costruisce e firma l’immagine solo se il controllo precedente è passato, e “deploy” ottiene credenziali AWS temporanee solo tramite OIDC, senza alcun secret statico salvato nel repository. Un attaccante che riuscisse a compromettere una dipendenza o a rubare l’accesso a un singolo job non otterrebbe comunque le credenziali di produzione, perché quelle esistono solo per la durata di un job autorizzato su un branch specifico.
Nei log della Actions tab, un’esecuzione riuscita mostra chiaramente i tre job in sequenza, con il job “deploy” che riporta l’assunzione del ruolo IAM tramite token OIDC invece di un riferimento a un secret salvato: un dettaglio che, durante un audit, permette di dimostrare in pochi secondi che non esistono credenziali cloud di lunga durata nella pipeline.
Tabella comparativa degli strumenti di sicurezza CI/CD
Non tutti gli strumenti citati in questa guida coprono lo stesso rischio. Questa tabella riassume dove si colloca ciascuno, utile quando devi decidere cosa aggiungere prima in una pipeline che parte da zero controlli. Se devi scegliere un ordine di priorità con risorse limitate, parti dalle prime tre righe: sono gratuite, si attivano in meno di un’ora e coprono gli scenari più frequenti osservati da Kaspersky nel suo campione di oltre 130.000 pipeline.
| Strumento | Rischio coperto | Dove si attiva | Costo tipico |
|---|---|---|---|
| Push protection (GitHub) | Secret esposti nei commit | Impostazioni repository/org | Incluso su piano Team/Enterprise |
| Dependency Review Action | Dipendenze vulnerabili o con licenze non ammesse | Workflow su pull_request | Gratuito su repository pubblici |
| CodeQL | Vulnerabilità nel codice applicativo (SAST) | Workflow su push/PR | Gratuito su repository pubblici |
| OSV-Scanner | Vulnerabilità note nelle dipendenze di terze parti | Workflow o CLI locale | Open source, gratuito |
| cosign / Sigstore | Integrità e provenienza degli artifact finali | Step finale della pipeline | Open source, gratuito |
| OIDC cloud (AWS/Azure/GCP) | Secret cloud di lunga durata | Configurazione IAM + workflow | Incluso nel cloud provider |
Errori comuni da evitare
Questi sono gli errori che tornano più spesso quando un team mette mano alla sicurezza della propria pipeline per la prima volta.
- Pinnare l’action ma non aggiornarla mai più. Ancorare a uno SHA blocca gli aggiornamenti automatici impliciti, ma se non pianifichi una revisione periodica resti esposto a vulnerabilità note scoperte dopo il pinning.
- Impostare permessi di scrittura “per sicurezza, non si sa mai”. Il ragionamento opposto a quello corretto: parti sempre da
contents: reade aggiungi permessi solo quando un job fallisce per mancanza di accesso. - Lasciare i workflow di fork con accesso ai secret. Un workflow attivato da
pull_requestsu un fork non ha accesso ai secret del repository per default, ma passare apull_request_targetsenza capirne le implicazioni riapre quella porta. - Trattare l’OIDC come un progetto “quando avremo tempo”. Ogni mese in cui un access key statico resta valido è un mese in cui, se finisce in un log o in un repository pubblico per errore, qualcuno può usarlo.
- Dimenticare la cache delle Actions nel modello di minaccia. Una cache condivisa tra branch e pull request può diventare un canale di persistenza per un attaccante, soprattutto in repository con molti contributor esterni.
- Firmare gli artifact ma non verificarli mai a valle. Una firma che nessuno controlla in fase di deploy ha lo stesso valore di nessuna firma.
- Applicare questi controlli solo al repository principale. Script interni, repository di infrastruttura e piccoli tool “di supporto” usano spesso le stesse credenziali cloud del progetto principale, ma restano fuori dal radar perché nessuno pensa che valga la pena blindarli.
Risoluzione dei problemi più frequenti
Durante l’implementazione di questi controlli capita quasi sempre di incontrare uno di questi blocchi. Ecco come risolverli, con la causa più probabile e la soluzione pratica da applicare prima di cercare altrove.
| Problema | Causa probabile | Soluzione |
|---|---|---|
| Il workflow fallisce con “Resource not accessible by integration” | I permessi del token sono troppo restrittivi per l’azione richiesta | Aggiungi il permesso specifico mancante (es. checks: write) solo al job che lo richiede |
| L’autenticazione OIDC con AWS fallisce con “Not authorized to perform sts:AssumeRoleWithWebIdentity” | La trust policy del ruolo IAM non include il sub corretto del repository/branch | Verifica il claim sub nel token OIDC e aggiorna la condition della trust policy |
| Dependabot non apre PR per gli aggiornamenti delle action | Manca l’ecosistema “github-actions” in dependabot.yml | Aggiungi una entry package-ecosystem: "github-actions" |
| Push protection blocca un commit legittimo (falso positivo) | Un pattern nel codice assomiglia a un formato di secret conosciuto | Usa l’opzione di bypass documentata con una motivazione, poi segnala il falso positivo a GitHub |
| Dependency Review Action non blocca una dipendenza nota come vulnerabile | La severità della CVE è sotto la soglia configurata in fail-on-severity | Abbassa la soglia o aggiungi un controllo manuale per le severità medie |
| cosign verify fallisce dopo un deploy riuscito | L’immagine è stata ri-taggata o ricostruita dopo la firma | Firma sempre il digest immutabile dell’immagine, non il tag mobile |
| Il runner self-hosted resta “offline” dopo l’adozione dei runner effimeri | Lo script di provisioning non registra il nuovo runner prima che il precedente venga distrutto | Usa un orchestratore (es. autoscaling group) che garantisca sempre almeno un runner registrato |
| CodeQL segnala troppi falsi positivi su codice legacy | Le query di default non sono calibrate sul codebase specifico | Personalizza il file di configurazione CodeQL escludendo percorsi o query non rilevanti |
Il job con id-token: write continua a fallire su un fork | I workflow eseguiti da pull request di fork non ricevono permessi di scrittura sul token OIDC per policy di sicurezza di GitHub | Sposta il job che richiede OIDC su un trigger push o workflow_dispatch dal branch principale, non su PR da fork |
Suggerimenti avanzati per team enterprise
Una volta superati i controlli di base, i team più maturi aggiungono un ulteriore livello. Il primo è la revisione obbligatoria dei reusable workflow condivisi tra più repository: se dieci progetti richiamano lo stesso workflow centralizzato, una sola modifica malevola a quel file compromette tutti e dieci contemporaneamente, quindi quel file merita la protezione più alta disponibile nell’organizzazione.
Il secondo è l’adozione di un controllo agentico per il codice generato da assistenti AI. GitHub ha reso disponibile a ottobre 2026 il sandboxing locale per GitHub Copilot in modalità generalmente disponibile, una misura pensata proprio per limitare i danni se un comando generato da un agente AI tenta un’azione non prevista. Microsoft, dal suo lato, ha ampliato in preview il suo harness di scansione agentica multi-modello (nome in codice MDASH), con integrazione in Microsoft Defender per le organizzazioni che lo adottano, secondo quanto descritto sul blog di sicurezza dell’azienda. Se il tuo team usa assistenti AI per generare o modificare workflow, considera questi controlli come parte della stessa superficie di attacco gestita in questa guida, non come un problema separato.
Il terzo suggerimento è operativo: pianifica una revisione trimestrale della pipeline con la stessa checklist usata in questa guida. Le configurazioni si degradano con il tempo, un nuovo collaboratore aggiunge un’action senza pinning, un deploy urgente introduce un permesso “temporaneo” che resta per mesi. Una revisione fissa nel calendario costa meno di un incidente.
Un quarto accorgimento, utile su organizzazioni con decine di repository, è definire un workflow template a livello organizzativo che imponga i controlli minimi (permessi, dependency review, CodeQL) a qualsiasi nuovo repository creato, invece di lasciare che ogni team riscriva la propria versione della pipeline sicura partendo da zero. GitHub supporta i template di workflow a livello di organizzazione proprio per questo scenario, riducendo la varianza tra progetti diversi e semplificando l’audit complessivo.
Infine, prepara un playbook di risposta specifico per una pipeline compromessa, separato da quello generico per gli incidenti applicativi. I primi passi da documentare sono: revocare immediatamente tutti i token OIDC attivi e i secret residui, disabilitare temporaneamente i trigger automatici sui workflow interessati, confrontare gli hash degli artifact pubblicati di recente con quelli firmati e verificati, e controllare l’audit log per individuare l’origine esatta della modifica sospetta. Avere questi passi scritti prima che accada un incidente riduce il tempo di reazione da ore a minuti.
Domande frequenti
Il pinning delle action a SHA rallenta la pipeline?
No. Il tempo di esecuzione non cambia, cambia solo il riferimento usato per scaricare l’action, che GitHub risolve comunque a un commit specifico. L’unico costo è la leggibilità, risolvibile con un commento che indica la versione.
OIDC funziona anche con provider cloud diversi da AWS?
Sì. Azure e Google Cloud supportano entrambi federazione OIDC con GitHub Actions, con una configurazione concettualmente simile: un’identità federata lato cloud che si fida dei token emessi da GitHub per repository e branch specifici.
Serve un piano GitHub Enterprise per attivare push protection?
Push protection è disponibile anche su repository pubblici con piano gratuito. Alcune funzioni avanzate di secret scanning organization-wide richiedono invece un piano Team o Enterprise.
Dependabot e Dependency Review Action fanno la stessa cosa?
No. Dependabot apre PR per aggiornare dipendenze vulnerabili già presenti nel codice. Dependency Review Action controlla le PR in arrivo e blocca quelle che introducono per la prima volta una dipendenza vulnerabile o con licenza non ammessa. Sono complementari.
Cosa succede se un runner self-hosted viene compromesso nonostante l’isolamento?
Con runner effimeri il danno resta confinato a quel singolo job: la macchina viene distrutta alla fine dell’esecuzione, quindi un attaccante non ottiene persistenza sull’infrastruttura. Per questo i runner effimeri sono preferibili a runner persistenti riutilizzati tra job diversi.
Quanto tempo serve per implementare tutti i passi di questa guida?
Per un singolo repository con pipeline già esistente, il pinning delle action, i permessi minimi e la push protection richiedono in genere meno di una giornata di lavoro. La migrazione a OIDC e la firma degli artifact, che toccano l’infrastruttura cloud, richiedono tipicamente da qualche giorno a due settimane a seconda della complessità dell’ambiente.
La firma con cosign richiede un servizio di certificazione a pagamento?
No, la modalità keyless di Sigstore/cosign usa un’infrastruttura pubblica gratuita (Fulcio e Rekor) che lega la firma all’identità OIDC del workflow, senza bisogno di gestire o pagare certificati privati.
Questi controlli valgono anche per GitLab CI o Azure DevOps?
Il principio è lo stesso (pinning delle versioni, permessi minimi, identità federata invece di secret statici, firma degli artifact), ma la sintassi e gli strumenti specifici cambiano. GitLab CI ha il proprio sistema di permessi per i job e supporta OIDC verso i principali cloud provider con una configurazione analoga.
Da dove conviene iniziare se ho un solo giorno a disposizione?
Attiva push protection, dichiara i permessi minimi nei workflow esistenti e lancia l’audit script del Passo 1 per individuare le action non ancorate a SHA. Questi tre interventi non toccano l’infrastruttura cloud, si completano in poche ore e riducono subito la parte più ampia del rischio segnalato da Kaspersky nel suo campione di pipeline.




