# GitHub Actions Workflow Lockfiles: perché fissare uses@SHA non basta

> Canonical: https://vxi.me/it/posts/github-actions-workflow-lockfiles-transitive-dependencies/
> Locale: it-it
> Published: 2026-10-18
> Updated: 2026-10-18
> Markdown: https://vxi.me/it/posts/github-actions-workflow-lockfiles-transitive-dependencies.md

# 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](https://github.blog/changelog/2026-10-14-github-actions-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](https://github.com/github/gh-actions-lock/releases/tag/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](https://docs.github.com/en/actions/concepts/security/workflow-dependency-lockfiles) 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](https://github.com/github/actions-lockfile/blob/ac158834765237ee94c36606cfc7d87316aa49ab/go/pkg/lockfile/bench_test.go#L31-L58), 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.

```yaml
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`](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/internal/lockfile/state_marshal.go) contiene la serializzazione dei campi:

```go
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](https://github.com/github/gh-actions-lock/releases/tag/v0.2.0) e il [README del formato](https://github.com/github/actions-lockfile/blob/ac158834765237ee94c36606cfc7d87316aa49ab/README.md) 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:

```bash
# 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](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/cmd/gh-actions-lock/root.go#L64-L70) accetta il percorso del workflow; [verify.go](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/cmd/gh-actions-lock/verify.go#L24-L40) 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`:

```diff
 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:

```bash
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](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/internal/pipeline/checks/category.go) include `not-pinned`, `stale`, `local-action`, `repo-replaced` e `unreachable-pin`.

**Un pilota reversibile in cinque passaggi:**
1. **Inventario:** elencare `uses` diretti, Composite Action annidate e Reusable Workflow esterni; confermare chi gestisce le dipendenze interne nel repository chiamato.
2. **Generazione:** usare la versione fissata in un branch di prova ed esaminare le differenze di workflow, `action.yml` e `actions.lock`.
3. **Verifica delle policy:** confrontare ogni commit bloccato con Allowed Actions e sottoporre i cambiamenti a normale Code Review.
4. **Due livelli di controllo:** prima `--verify-local`, poi `--verify`. Per `not-pinned`, `stale` o `local-action` verificare riferimenti e diff; per `repo-replaced` o `unreachable-pin` indagare l'origine e la storia Git prima del merge.
5. **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](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/cmd/gh-actions-lock/fresh_tag.go#L16-L32) e [regola dell'avviso](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/cmd/gh-actions-lock/fresh_tag.go#L48-L75). 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](https://docs.github.com/en/actions/how-tos/administer/enforce-workflow-lockfiles) 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](/posts/github-ai-scan-pr-security-checks/).

---

*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.*
