Un job GitHub Actions è un buon posto da cui rubare secret. Contiene chiavi di deploy, token dei registry e credenziali cloud, ed esegue codice che qualcun altro ha scritto un minuto fa: action di terze parti, pacchetti e i loro script di installazione. La gestione dei secret di GitHub protegge dagli incidenti. Non è stata progettata per fermare codice già in esecuzione all'interno del job.
Questo articolo spiega come funzionano i secret in GitHub Actions, i cinque modi in cui trapelano nella pratica e i controlli che chiudono ciascuno di essi.
Come funzionano i secret di GitHub Actions
I secret esistono a tre livelli: organizzazione, repository e ambiente. Quando lo stesso nome esiste a più livelli, prevale il più specifico: l'ambiente sul repository, il repository sull'organizzazione.
Alcuni limiti e regole da conoscere:
- Dimensioni e numero. Fino a 1.000 secret di organizzazione, 100 secret di repository e 100 secret di ambiente, ciascuno di al massimo 48 KB. Un workflow vede tutti i secret di repository e di ambiente, ma solo i primi 100 secret di organizzazione in ordine alfabetico.
- I fork non ricevono nulla. A parte
GITHUB_TOKEN, i secret non vengono passati ai workflow attivati da una pull request proveniente da un fork. - Dependabot è trattato come un fork. I workflow attivati da Dependabot ricevono un token di sola lettura e solo i secret di Dependabot, non i secret di Actions.
- Ai workflow riutilizzabili i secret vanno passati.
secrets: inheritli passa tutti implicitamente, all'interno della stessa organizzazione o enterprise. I secret di ambiente non possono essere passati tramiteworkflow_call. - Gli ambienti controllano l'accesso. Con i revisori obbligatori, un job non può leggere i secret di un ambiente finché qualcuno non lo approva. Le regole sui branch di deployment limitano quali branch possono usare l'ambiente.
Che cosa fa il mascheramento, e che cosa non fa
GitHub oscura i valori dei secret nei log, ma l'oscuramento si basa sulla ricerca di una corrispondenza esatta. La documentazione stessa di GitHub elenca i casi in cui fallisce:
- un secret all'interno di dati strutturati come JSON o YAML
- un valore derivato da un secret, come un token scambiato con un altro token, a meno che non lo registri con
::add-mask:: - un secret trasformato, per esempio codificato in base64
Il mascheramento protegge dalla stampa accidentale di un secret. Non fa nulla contro il codice che prima codifica il secret.
Come i secret trapelano dai workflow
1. Un'action compromessa li stampa
A marzo 2025 un attaccante ha compromesso tj-actions/changed-files (CVE-2025-30066), un'action usata da oltre 23.000 repository. Ha spostato i tag di versione esistenti dell'action su un commit malevolo, quindi ogni workflow che faceva riferimento a un tag come @v45 ha eseguito il codice dell'attaccante. Si sono salvati solo i workflow fissati a uno SHA di commit completo.
Il payload leggeva i secret dalla memoria del runner e li stampava nel log del job, codificati due volte in base64 così che il mascheramento non li intercettasse. Nei repository pubblici quei log sono leggibili da chiunque. La compromissione è partita da un'altra action, reviewdog/action-setup (CVE-2025-30154), e prima ancora da un personal access token rubato tramite un workflow pull_request_target.
2. Un workflow pull_request_target esegue codice non attendibile
pull_request_target viene eseguito nel contesto del repository di base, con i suoi secret e un token con permessi di scrittura, anche quando la pull request proviene da un fork. È sicuro solo finché il workflow non esegue mai codice della pull request. Quando un workflow fa il checkout della head della pull request e la compila, chiunque possa aprire una pull request può leggere ogni secret del workflow. GitHub Security Lab li chiama "pwn requests".
GitHub sta restringendo questa possibilità. Da dicembre 2025, pull_request_target usa sempre il file di workflow del branch predefinito. Da luglio 2026 actions/checkout rifiuta di fare il checkout di codice dei fork nei workflow pull_request_target che usano pattern notoriamente pericolosi, e le workflow execution protections, disponibili per tutti da settembre 2026, disabiliteranno per impostazione predefinita pull_request_target nei repository pubblici senza una event policy a partire dal 2 novembre 2026.
3. Un input non attendibile viene iniettato in uno script
Le espressioni dei workflow vengono espanse prima che parta la shell. Uno step come questo:
- run: echo "Title: ${{ github.event.issue.title }}"
esegue qualunque cosa contenga il titolo della issue. Un attaccante apre una issue con un comando shell come titolo e il job lo esegue, con i secret del job a portata di mano. Passi invece i valori non attendibili attraverso una variabile d'ambiente, così che la shell li tratti come dati:
- env:
TITLE: ${{ github.event.issue.title }}
run: echo "Title: $TITLE"
4. Cache e artefatti li portano fuori
- Artefatti. Nel 2024 la ricerca ArtiPACKED di Unit 42 ha mostrato che
actions/checkoutscriveGITHUB_TOKENin.git/configper impostazione predefinita, quindi caricare il workspace come artefatto espone il token a chiunque possa scaricarlo.upload-artifactesclude i file nascosti per impostazione predefinita da settembre 2024, eactions/checkoutv6 conserva la credenziale fuori dalla directory di checkout, ma le versioni fissate più vecchie e gli step di upload personalizzati continuano a esporlo. - Cache. Un attaccante in grado di eseguire codice in un qualsiasi workflow di un repository può riempirne la cache, espellere le voci reali e piazzarne una avvelenata con la stessa chiave. Un successivo workflow privilegiato la ripristina. A maggio 2026 questa catena, partita da
pull_request_targete conclusa con un token OIDC letto dalla memoria del runner, ha permesso a un attaccante di pubblicare 84 versioni malevole di 42 pacchetti TanStack.
5. Una dipendenza li invia a un server
Gli script di installazione vengono eseguiti con gli stessi accessi del resto del job. Il worm Shai-Hulud, comparso per la prima volta su npm a settembre 2025, veniva eseguito durante npm install, raccoglieva token npm, token GitHub e chiavi cloud dall'ambiente e li pubblicava in repository GitHub pubblici. La sua ondata di novembre 2025 agiva in fase di preinstall e registrava la macchina infetta come runner self-hosted, così l'attaccante poteva tornare in seguito.
Questa via non richiede alcun errore nel Suo workflow. Basta una sola versione compromessa in un punto qualsiasi dell'albero delle dipendenze, e i secret escono attraverso la rete.
Come proteggerli
Sostituisca le chiavi cloud archiviate con OIDC. Invece di conservare una chiave AWS, Azure o Google Cloud come secret, lasci che il job richieda un token di breve durata all'issuer OIDC di GitHub e lo scambi con il cloud provider. La trust policy decide quali repository, branch o ambienti possono assumere il ruolo, e la credenziale scade con il job. Il job ha bisogno di permissions: id-token: write. I token OIDC possono comunque essere rubati da un job in esecuzione, come ha mostrato TanStack, ma un token rubato vale molto meno di una chiave che non scade mai.
Fissi le action a uno SHA di commit completo. La documentazione di GitHub lo definisce l'unico modo per usare un'action come release immutabile. I tag si possono spostare, uno SHA no. Da agosto 2025 la policy sulle action consentite può imporre il pinning degli SHA e bloccare action specifiche in tutta un'organizzazione. Dependabot e Renovate possono mantenere aggiornati gli SHA fissati.
Dia a GITHUB_TOKEN il minimo necessario. Imposti il valore predefinito su sola lettura e conceda i permessi di scrittura per job. Le nuove organizzazioni e i nuovi repository usano la sola lettura come impostazione predefinita da febbraio 2023, ma quelli più vecchi hanno mantenuto l'impostazione permissiva a meno che qualcuno non l'abbia cambiata. Veda Permessi di GITHUB_TOKEN: privilegio minimo per ogni workflow.
Limiti i secret allo step che ne ha bisogno. Un secret in env: di uno step è solo nell'ambiente di quello step. Un secret impostato a livello di job è visibile a ogni step, compresa ogni action di terze parti.
Metta i secret di produzione dietro gli ambienti. Con revisori obbligatori e regole sui branch di deployment, un workflow su un feature branch, o attivato da una pull request, non può leggerli.
Tratti pull_request_target e gli input non attendibili come esecuzione di codice. Eviti pull_request_target se non ne ha bisogno, non compili mai al suo interno codice di pull request e passi i dati degli eventi tramite variabili d'ambiente. CodeQL può controllare i workflow alla ricerca di pattern di injection e di pwn request; è disponibile per tutti per GitHub Actions da aprile 2025.
Attivi il secret scanning e la push protection. Intercettano i secret inseriti nel repository con un commit, una fuga diversa da quelle descritte sopra ma altrettanto comune.
Limiti dove il job può connettersi. La maggior parte dei job ha bisogno di un breve elenco di destinazioni: registry di pacchetti, container registry e alcune API. Se il job può raggiungere solo quelle, un secret letto da codice malevolo non ha dove andare. Veda come una policy di egress ferma l'esfiltrazione da GitHub Actions.
Ruoti le credenziali dopo ogni esposizione. Se un workflow ha eseguito un'action o un pacchetto compromesso, presuma che ogni secret leggibile sia stato sottratto. Li revochi e li riemetta tutti, non solo quelli che ritiene siano stati usati.
Il ruolo di CRACI
CRACI esegue i Suoi job GitHub Actions sui propri runner: Lei cambia runs-on in craci, e workflow, secret e cronologia delle esecuzioni restano in GitHub. Due sue funzioni mirano alle fughe descritte sopra.
- Una policy di egress in modalità fail closed. Lei stabilisce quali sorgenti di pacchetti, registry e host può raggiungere un job. La policy viene convalidata prima dell'avvio del job e applicata sul runner, quindi una connessione verso qualsiasi altra destinazione viene bloccata, e Lei riceve un avviso quando la policy viene violata.
- Un registro di ciò che il job ha fatto. CRACI registra ogni pacchetto che ciascun job ha effettivamente scaricato, compresi i pacchetti ripristinati dalle cache, e una traccia delle connessioni di rete del job. Quando verrà annunciato il prossimo pacchetto compromesso, potrà vedere quali build lo hanno scaricato e a che cosa si sono connesse, invece di tirare a indovinare dai lockfile.
Ogni job viene eseguito nella propria macchina virtuale, quindi nulla di ciò che un job lascia resta per il successivo. CRACI non cerca secret nel Suo codice: per questo mantenga il secret scanning di GitHub.
Prenoti una demo per vedere il registro di una delle Sue build.
