GitHub Actions Workflow Lockfiles: perché fissare uses@SHA non basta
GitHub Actions Workflow Lockfiles: perché fissare uses@SHA non basta
Quando si sostituisce uses: some-org/action@v2 con uno SHA completo, il codice di quella Action viene fissato a un commit preciso. Ma se si tratta di una Composite Action che al suo interno richiama example-org/upload@v1? Lo SHA esterno resta uguale, mentre il tag della dipendenza può spostarsi. Dal solo YAML del workflow non è possibile ricostruire sempre l’intero grafo delle Action che verranno eseguite.
GitHub ha annunciato i Workflow Lockfiles in Public Preview: .github/workflows/actions.lock registra dipendenze dirette e transitive, commit bloccati e identità dei repository di origine.
Hai già fissato tutte le Action con uses@SHA? Prima guarda il grafo delle dipendenze e poi valuta la migrazione. Il vantaggio non è fissare due volte lo stesso commit, ma rendere verificabili i riferimenti transitivi e l’identità del repository.
| Situazione attuale della CI | Prima scelta | Motivo |
|---|---|---|
| Poche Action dirette, tutte con SHA completo | Fare un inventario e rimandare la migrazione totale | Il costo di manutenzione potrebbe superare il beneficio |
Composite Action che richiamano altri @v1 o @main | Provare su un workflow non critico | Si possono bloccare anche le Action transitive |
| Più repository condividono un Reusable Workflow | Chiarire i ruoli di chiamante e repository che lo definisce | Le Action interne dipendono dal Lockfile del secondo |
| Esiste una policy Allowed Actions o di pinning obbligatorio | Controllare prima la policy | Le regole possono confrontare lo SHA bloccato anziché il tag |
Le condizioni possono sovrapporsi. Per le verifiche faccio riferimento alla documentazione ufficiale e al codice fissato di gh-actions-lock v0.2.0.
Uno SHA sull’Action diretta non protegge automaticamente l’intero grafo
Immaginiamo che ci.yml utilizzi example-org/build tramite SHA completo, ma nel suo action.yml sia presente example-org/upload@v1. Il sorgente esterno è immutabile; il tag interno rimane potenzialmente mobile.
| Dipendenza | Pinning diretto uses@SHA | Workflow Lockfile |
|---|---|---|
| Action esterna chiamata direttamente | Blocca quel riferimento | Registra commit e identità del repository |
| Action richiamate dentro una Composite Action | Non blocca automaticamente i tag annidati | Include le dipendenze Action transitive |
| Reusable Workflow esterno | Blocca la chiamata | La chiamata è registrata, ma le Action interne sono gestite dal repository che definisce il workflow |
Action dello stesso repository con uses: $/… | Si riferisce al commit in esecuzione | Già intrinsecamente fissata, nessun Pin aggiuntivo |
Script scaricati con run: o immagini con tag mobile | Fuori ambito | Non coperti dal solo Action Lockfile |
Il confine dei Reusable Workflow è particolarmente importante. La documentazione concettuale GitHub indica che il Lockfile di un repository copre i workflow definiti in quel repository. Le Action che si trovano all’interno di un Reusable Workflow definito altrove sono coperte dal Lockfile del repository che lo definisce. Bloccare la riga uses di un deployment condiviso non equivale a revisionare tutte le sue Action interne.
Com’è fatto realmente actions.lock
La sezione workflows associa ogni percorso di workflow alle chiavi Pin. In dependencies compaiono ref, commit, owner_id, repo_id e, per Action annidate, ulteriori relazioni uses. Il seguente YAML è l’intera fixture ufficiale benchV003 del parser in un commit fissato, che mostra lo schema v0.0.3. Gli SHA composti da lettere a/b ripetute sono dati sintetici di test, non risultati generati su un Runner reale.
version: v0.0.3
workflows:
.github/workflows/ci.yml:
- actions/checkout@v4
- actions/setup-go@v5
- actions/cache@v4
dependencies:
actions/checkout@v4:
hostname: github.com
ref: v4
commit: sha1-11bd71901bbe5b1630ceea73d27597364c9af683
owner_id: 44036562
repo_id: 197814629
actions/setup-go@v5:
hostname: github.com
ref: v5
commit: sha1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
owner_id: 44036562
repo_id: 249058325
actions/cache@v4:
hostname: github.com
ref: v4
commit: sha1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
owner_id: 44036562
repo_id: 251882839
uses:
- actions/checkout@v4
Questa fixture specifica hostname: github.com, ma un Lockfile prodotto per il server github.com predefinito potrebbe omettere tale campo facoltativo. L’elenco dei Pin in workflows e il campo uses sotto una dipendenza rappresentano due viste distinte.
Il sorgente Go fissato state_marshal.go contiene la serializzazione dei campi:
a := file.Dependencies[k]
entry := &yaml.Node{Kind: yaml.MappingNode}
// ramo hostname omesso
if a.Ref != "" {
addQuotedField(entry, "ref", a.Ref)
}
if a.Commit != "" {
addQuotedField(entry, "commit", a.Commit)
}
addIntField(entry, "owner_id", a.OwnerID)
addIntField(entry, "repo_id", a.RepoID)
Un commit identifica l’oggetto Git, mentre gli ID numerici di owner e repository verificano l’identità dietro un nome leggibile che potrebbe cambiare. Nel vecchio esempio concettuale di GitHub appare ancora version: v0.0.2; la release v0.2.0 e il README del formato documentano invece v0.0.3. La versione della CLI e quella dello schema non vanno confuse.
Prima di imporre nuove policy, esamina il diff di migrazione
Comincerei da un branch di prova e da un workflow poco critico, facile da ripristinare. Gli esempi richiedono GitHub CLI già installata e autenticata, oltre ai permessi di lettura sui repository coinvolti. La versione dell’estensione è fissata intenzionalmente:
# Installa la versione dell'estensione verificata qui
gh extension install github/gh-actions-lock --pin v0.2.0
gh actions-lock .github/workflows/ci.yml
# Controlla workflow, Action locali e nuovi file
git status --short
git diff
cat .github/workflows/actions.lock
# Verifica soltanto il workflow pilota
gh actions-lock --verify .github/workflows/ci.yml
Il controllo limitato a un percorso è confermato dall’implementazione fissata: root.go accetta il percorso del workflow; verify.go e i test utilizzano lo stesso ambito. Il file condiviso actions.lock deve comunque essere valido, ma verificare un workflow non registra automaticamente tutti gli altri.
L’operazione non crea soltanto un Lockfile. Un tag di versione può essere ristretto a una versione specifica e una Action locale valida, dotata di action.yml, può essere convertita da ./actions/build a $/actions/build:
steps:
- - uses: ./actions/build
+ - uses: $/actions/build
La forma $/ lega la Action al commit del repository in esecuzione. Non corregge automaticamente un percorso ./ errato. Inoltre un nuovo actions.lock non ancora tracciato da Git non compare nel normale git diff; è necessario controllare anche git status --short e il contenuto del file.
Un’altra fonte di problemi è la policy organizzativa Allowed Actions. Per i workflow che utilizzano Lockfile, le regole possono valutare gli Action rispetto allo SHA del commit bloccato; una precedente eccezione come actions/checkout@v6 potrebbe non coincidere più. Meglio aggiornare la lista in base ai commit effettivamente approvati che disattivare il controllo su tutte le Action esterne.
Le verifiche locali e online hanno scopi diversi:
gh actions-lock --verify-local .github/workflows/ci.yml
gh actions-lock --verify .github/workflows/ci.yml
--verify-local controlla offline la copertura delle dipendenze, ma non può verificare l’identità reale del repository remoto. --verify effettua i controlli online ed è più adatto come gate sul PR. Solo quando tutti i workflow sono registrati conviene eseguire gh actions-lock --verify senza percorso per l’intero repository. Durante una prova ristretta, il controllo globale può fallire per workflow non ancora registrati o Pin obsoleti.
Per diagnosticare i problemi aggiungi --json=valid,findings. Il codice di uscita 1 segnala Finding bloccanti; il 2 riguarda errori di comando, I/O o rete. Il catalogo di Finding del codice fissato include not-pinned, stale, local-action, repo-replaced e unreachable-pin.
Un pilota reversibile in cinque passaggi:
- Inventario: elencare
usesdiretti, Composite Action annidate e Reusable Workflow esterni; confermare chi gestisce le dipendenze interne nel repository chiamato. - Generazione: usare la versione fissata in un branch di prova ed esaminare le differenze di workflow,
action.ymleactions.lock. - Verifica delle policy: confrontare ogni commit bloccato con Allowed Actions e sottoporre i cambiamenti a normale Code Review.
- Due livelli di controllo: prima
--verify-local, poi--verify. Pernot-pinned,staleolocal-actionverificare riferimenti e diff; perrepo-replacedounreachable-pinindagare l’origine e la storia Git prima del merge. - Piano di ritorno: se parsing o policy interrompono la CI, annullare le modifiche del branch pilota; estendere l’adozione soltanto dopo una prova stabile.
Come aggiornare i Pin: sticky pins e release cooldown
Il commit scelto al primo inserimento dipende dal riferimento uses originale e dalla sua risoluzione. Con v0.2.0 non esiste una pausa predefinita obbligatoria di sette giorni. Il programma preferisce un cooldown.default-days positivo per github-actions in .github/dependabot.yml, quindi cooldown_days nella configurazione utente. In assenza di entrambi non applica filtri per età delle release; una tag pubblicata meno di tre giorni prima riceve solo un avviso informativo fresh-tag non bloccante. Si vedano risoluzione della configurazione e regola dell’avviso. Occorre ispezionare ref, commit e il diff generati, non presupporre che venga scelto sempre il commit più recente.
I Pin sono sticky: rilanciare gh actions-lock non cambia automaticamente il commit salvato quando main o v3 si spostano a monte. Per un aggiornamento intenzionale si utilizza gh actions-lock --relock, rivedendo ogni commit modificato. Se il precedente commit è diventato irraggiungibile dopo una riscrittura della storia, --relock --accept-moved rappresenta una decisione di fiducia separata, non una scorciatoia per far tornare verde la CI.
Dependabot aggiorna i Lockfile dei workflow già registrati, ma non registra automaticamente un workflow nuovo. La guida GitHub sull’applicazione delle policy suggerisce prove limitate e revisione con CODEOWNERS di actions.lock prima di attivare Require lockfile a livello organizzativo. Bisogna inoltre controllare il piano e i permessi dell’account. Il README della versione fissata segnala che GitHub Enterprise Server (GHES) non è ancora supportato in questa preview; non va confuso con Enterprise Cloud.
Un Lockfile stabilisce quale codice di Action dovrebbe essere eseguito, non se tale codice è affidabile. Gli script scaricati via run:, le immagini senza Digest fisso, le dipendenze dei package manager e i permessi del token CI richiedono controlli separati. La domanda utile al termine del pilota è: quali Action interne abbiamo bloccato in più, chi ha verificato l’identità dei repository e chi possiede i Pin dei Reusable Workflow condivisi? Per la differenza tra rilevare rischi e bloccare il merge, rimando anche al caso GitHub AI Scan.
Nota su fonti e date: GitHub ha annunciato la Public Preview il 14 ottobre 2026. Il controllo del codice ha usato il commit d5dca22a06ff8590d50b41612038996800cfb336 di gh-actions-lock v0.2.0 e ac158834765237ee94c36606cfc7d87316aa49ab del repository del formato, verificati il 18 ottobre. Lo YAML è una fixture di parser con SHA sintetici. Non sono stati eseguiti Runner reali, migrazioni CLI o esperimenti di spostamento dei tag. La disponibilità della preview dipende dal piano e dalla configurazione.