# GitHub Actions Workflow Lockfiles: por qué fijar uses@SHA no basta

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

# GitHub Actions Workflow Lockfiles: por qué fijar uses@SHA no basta

Fijar una Action de GitHub a un SHA de commit completo inmoviliza el código en ese punto de entrada. Pero ¿qué ocurre si esa Composite Action ejecuta a su vez `example-org/upload@v1`? El SHA externo no cambia, mientras que la etiqueta interna puede apuntar a otro commit. El YAML del workflow, por sí solo, no revela todo el grafo de dependencias de terceros que se ejecutará.

GitHub presentó los [Workflow Lockfiles en vista previa pública](https://github.blog/changelog/2026-10-14-github-actions-workflow-lockfiles-in-public-preview). El archivo `.github/workflows/actions.lock` registra las dependencias directas y transitivas de Actions, sus commits y la identidad del repositorio de origen.

**¿Ya tienes todos los `uses@SHA` fijados? Inspecciona el grafo antes de decidir la migración.** El valor añadido no consiste en fijar dos veces el mismo SHA, sino en revisar dependencias transitivas e identidades de repositorio.

| Situación actual del CI | Primera decisión | Motivo |
| --- | --- | --- |
| Pocas Actions directas, todas con SHA completo | Inventariar; posponer una migración total | Puede que el coste de mantenimiento supere el beneficio |
| Composite Actions que llaman a `@v1` o `@main` | Probar con un workflow no crítico | Se cubren dependencias transitivas |
| Repositorios que comparten un Reusable Workflow | Revisar responsabilidades del llamador y del repositorio que lo define | El segundo controla sus Actions internas |
| La organización aplica Allowed Actions o reglas de SHA | Revisar la política antes de migrar | Los SHA bloqueados pueden no coincidir con reglas basadas en etiquetas |

Las situaciones pueden darse al mismo tiempo. Esta guía se basa en documentación oficial y código fijado de [gh-actions-lock v0.2.0](https://github.com/github/gh-actions-lock/releases/tag/v0.2.0).

## Un SHA fijo en la entrada no fija todo el grafo

Imaginemos que `ci.yml` usa `example-org/build` con SHA completo, pero el `action.yml` de ese Action llama a `example-org/upload@v1`. La Action exterior es estable; la referencia interior continúa siendo mutable.

| Tipo de dependencia | `uses@SHA` directo | Workflow Lockfile |
| --- | --- | --- |
| Action externa usada directamente | Fija esa referencia | Guarda commit e identidad del repositorio |
| Action invocada dentro de una Composite Action | No fija automáticamente la etiqueta secundaria | Incluye Actions transitivas |
| Reusable Workflow de otro repositorio | Fija la llamada | Registra la llamada; las Actions internas son responsabilidad del **repositorio que lo define** |
| Action propia con `uses: $/…` | Sigue el commit del repositorio en ejecución | Ya está fijada de forma inherente |
| Descargas desde `run:` o etiquetas de imagen mutables | Fuera del alcance | El Lockfile tampoco las protege por sí solo |

La distinción del Reusable Workflow externo es esencial. Según la [documentación conceptual de GitHub](https://docs.github.com/en/actions/concepts/security/workflow-dependency-lockfiles), el Lockfile de un repositorio cubre los workflows que ese repositorio define. Las Actions dentro de un Reusable Workflow externo quedan bajo el Lockfile del **repositorio que define dicho workflow**. Fijar la línea `uses` del llamador no equivale a haber auditado las Actions internas del repositorio compartido.

## Qué hay realmente dentro de actions.lock

`workflows` relaciona la ruta de cada workflow con sus claves de pin. En `dependencies` aparecen `ref`, `commit`, `owner_id`, `repo_id` y, para llamadas internas, `uses`. El siguiente ejemplo es la **fixture oficial completa `benchV003` para probar el parser del esquema v0.0.3**, obtenida del [commit fijo del repositorio de formato](https://github.com/github/actions-lockfile/blob/ac158834765237ee94c36606cfc7d87316aa49ab/go/pkg/lockfile/bench_test.go#L31-L58). Los SHA repetidos con a/b son valores sintéticos de prueba, no un resultado de un Runner de producción.

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

La fixture incluye `hostname: github.com`, aunque un Lockfile generado para el host predeterminado github.com puede omitir ese campo opcional. La lista de Pins de `workflows` y el campo `uses` dentro de una dependencia muestran relaciones distintas.

El código fijado de [`state_marshal.go`](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/internal/lockfile/state_marshal.go) también serializa estos campos:

```go
a := file.Dependencies[k]
entry := &yaml.Node{Kind: yaml.MappingNode}
// se omite el tratamiento de hostname
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 un objeto Git; los identificadores numéricos de propietario y repositorio ayudan a reconocer qué repositorio se oculta tras un nombre legible. El ejemplo antiguo en la documentación conceptual muestra `version: v0.0.2`, mientras que la [versión v0.2.0](https://github.com/github/gh-actions-lock/releases/tag/v0.2.0) y el [README del formato](https://github.com/github/actions-lockfile/blob/ac158834765237ee94c36606cfc7d87316aa49ab/README.md) corresponden al esquema **`v0.0.3`**. La versión de la CLI y la del esquema son cosas distintas.

## Revisar el diff antes de activar una política obligatoria

Empezaría en una rama de pruebas con un workflow no crítico y fácil de revertir. Estos comandos suponen que GitHub CLI está instalado, que la sesión está autenticada y que existe permiso de lectura en los repositorios afectados.

```bash
# Instalar la versión fija de la extensión revisada
gh extension install github/gh-actions-lock --pin v0.2.0
gh actions-lock .github/workflows/ci.yml

# Revisar workflow, Actions locales y archivos nuevos
git status --short
git diff
cat .github/workflows/actions.lock

# Verificar solo el workflow piloto
gh actions-lock --verify .github/workflows/ci.yml
```

La verificación limitada a una ruta está respaldada por el código: [root.go](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/cmd/gh-actions-lock/root.go#L64-L70) acepta parámetros de ruta, y [verify.go](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/cmd/gh-actions-lock/verify.go#L24-L40) y los tests mantienen ese alcance. El `actions.lock` compartido debe tener un formato válido, pero este comando no incorpora automáticamente otros workflows.

La extensión puede modificar más que el archivo de bloqueo: una referencia de versión puede afinarse a una etiqueta concreta y una Action local válida puede pasar de `./actions/build` a `$/actions/build`:

```diff
 steps:
-  - uses: ./actions/build
+  - uses: $/actions/build
```

`$/` vincula la Action al commit del repositorio que se está ejecutando. Una ruta local incorrecta no se corrige mágicamente. Además, un archivo nuevo sin seguimiento no aparece en `git diff`; revisa también `git status --short` y el contenido de `actions.lock`.

La política **Allowed Actions** puede causar un conflicto adicional. Cuando un workflow utiliza Lockfile, las reglas aplicables pueden comprobar el **SHA del commit bloqueado**; una entrada anterior como `actions/checkout@v6` podría dejar de coincidir. Hay que ajustar la lista de permisos al commit aprobado, no abrir toda la organización a cualquier Action externa.

Hay dos modos de verificación con alcance diferente:

```bash
gh actions-lock --verify-local .github/workflows/ci.yml
gh actions-lock --verify .github/workflows/ci.yml
```

`--verify-local` comprueba sin red que las referencias estén cubiertas. No valida la identidad real del repositorio remoto. `--verify` sí realiza verificaciones en línea, por lo que resulta apropiado como condición del PR. Cuando todos los workflows estén incorporados, puede usarse `gh actions-lock --verify` sin ruta para comprobar todo el repositorio. Durante una prueba limitada, ese comando global puede fallar por workflows aún sin incorporar o Pins obsoletos.

Para diagnosticar, usa `--json=valid,findings`. El código de salida 1 indica hallazgos bloqueantes; el 2, un error de comando, E/S o red. Las [categorías del código fijado](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/internal/pipeline/checks/category.go) incluyen `not-pinned`, `stale`, `local-action`, `repo-replaced` y `unreachable-pin`.

**Un piloto reversible de cinco pasos:**
1. **Inventariar**: enumerar `uses` directos, Composite Actions anidadas y Reusable Workflows externos. Confirmar quién mantiene los Pins internos en el repositorio del workflow invocado.
2. **Generar** en una rama de prueba con la versión fijada; comparar el workflow, los `action.yml` locales y `actions.lock`.
3. **Contrastar la política** Allowed Actions con los SHA bloqueados y revisar todos los cambios mediante un PR ordinario.
4. **Verificar en dos niveles**: cobertura con `--verify-local` y confianza en línea con `--verify`. Para `not-pinned`, `stale` o `local-action`, volver a las rutas y al diff; ante `repo-replaced` o `unreachable-pin`, investigar el historial antes de fusionar.
5. **Conservar la salida**: si la política o el parser bloquean CI, deshacer la rama de pruebas. Ampliar el despliegue solo cuando el piloto funcione de forma estable.

## Actualizaciones posteriores: sticky pins y release cooldown

El commit elegido en el alta inicial depende de `uses` y de su resolución. En v0.2.0 el **release cooldown no impone siete días por defecto**. Primero se consulta un `cooldown.default-days` positivo del ecosistema `github-actions` en `.github/dependabot.yml`; después se recurre al `cooldown_days` configurado por el usuario. Sin ninguna configuración no se filtran versiones por antigüedad: una etiqueta publicada en los últimos tres días solo genera un aviso `fresh-tag` que no bloquea. La [resolución de configuración](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/cmd/gh-actions-lock/fresh_tag.go#L16-L32) y el [aviso](https://github.com/github/gh-actions-lock/blob/d5dca22a06ff8590d50b41612038996800cfb336/cmd/gh-actions-lock/fresh_tag.go#L48-L75) están en el código fijado. Conviene comprobar los campos `ref`, `commit` y el diff generado.

Los Pins existentes son **sticky**. Volver a ejecutar `gh actions-lock` no sustituye silenciosamente el commit cuando cambian `main` o `v3` en el origen. Para aceptar una actualización de forma deliberada, utiliza `gh actions-lock --relock` y revisa el diff. Si una reescritura del historial vuelve inalcanzable el commit previo, `--relock --accept-moved` implica otra decisión de confianza. No es un arreglo universal para dejar CI en verde.

Dependabot puede mantener el Lockfile de workflows ya incorporados, pero no registra por sí solo un workflow nuevo. La [guía de aplicación de GitHub](https://docs.github.com/en/actions/how-tos/administer/enforce-workflow-lockfiles) recomienda una prueba pequeña y revisión por CODEOWNERS antes de activar `Require lockfile` en toda la organización. También hay que comprobar el plan y los permisos. Según el README fijado, GitHub Enterprise Server (GHES) no admite esta vista previa; no debe confundirse con Enterprise Cloud.

El Lockfile indica **qué código de Action** se pretende ejecutar, no si ese código es seguro. Las descargas en `run:`, las imágenes sin digest, los paquetes externos y los permisos del token de CI necesitan controles propios. Un piloto útil debería permitir responder: ¿qué Actions internas han quedado fijadas?, ¿quién revisó sus IDs de repositorio? y ¿quién mantiene las dependencias del Reusable Workflow compartido? La diferencia entre detectar un riesgo y bloquear la fusión se aborda también en el [análisis de GitHub AI Scan](/posts/github-ai-scan-pr-security-checks/).

---

*Nota de fuentes y fecha: GitHub anunció Workflow Lockfiles Public Preview el 14 de octubre de 2026. Se revisaron los commits fijados `d5dca22a06ff8590d50b41612038996800cfb336` (gh-actions-lock v0.2.0) y `ac158834765237ee94c36606cfc7d87316aa49ab` (formato) el 18 de octubre. El YAML es una fixture oficial con hashes sintéticos. No se ejecutaron una migración CLI, pruebas reales de Runner ni experimentos de cambio de tags. Las capacidades dependen del plan y de los ajustes de cuenta.*
