Cada job de GitHub Actions recibe un token. GitHub crea un GITHUB_TOKEN nuevo al inicio de cada job, limitado al repositorio en el que está el workflow, y cualquier paso del job puede usarlo. Lo que puede hacer depende de ajustes que muchos equipos nunca han mirado, y en los repositorios antiguos la respuesta sigue siendo «escribir en casi todo».
Eso importa cuando algo del job se vuelve malicioso. En el compromiso «s1ngularity» de Nx de agosto de 2025, el token del repositorio aún tenía permisos de lectura y escritura. Una inyección en un workflow pull_request_target lo utilizó para activar el workflow de publicación, que filtró el token de npm con el que se publicaron versiones maliciosas de Nx. Un token de solo lectura habría detenido ese paso.
Este artículo explica cómo se definen los permisos del token, qué necesita cada tarea habitual y cómo encontrar el mínimo para sus workflows.
Qué es GITHUB_TOKEN
GITHUB_TOKEN es un token de acceso de instalación de la app GitHub Actions. Algunas de sus propiedades pasan fácilmente desapercibidas:
- Existe tanto si lo usa como si no. Los workflows hacen referencia a él como
secrets.GITHUB_TOKEN, pero cualquier acción puede leerlo a través del contextogithub.tokensin que se le pase. - Se escribe en disco por defecto.
actions/checkoutguarda el token para comandos git posteriores salvo que establezcapersist-credentials: false. Antes de la v6 iba a.git/config; la v6 lo trasladó a un archivo independiente bajo$RUNNER_TEMP. En 2024, la investigación ArtiPACKED de Unit 42 encontró tokens filtrados a través de artefactos que incluían.git/config. - Caduca con el job. En los runners alojados en GitHub eso significa 6 horas como máximo, el límite de tiempo del job. En los runners autoalojados se puede renovar hasta 24 horas.
Como el token siempre está ahí, lo único que usted controla es cuánto puede hacer.
El valor por defecto: permissive o restricted
Cada empresa, organización y repositorio tiene un valor por defecto para el token:
- Permissive: lectura y escritura en todos los permisos.
- Restricted: solo lectura en
contentsypackages, y nada más.
Desde el 2 de febrero de 2023, las empresas, organizaciones y repositorios nuevos usan restricted por defecto. El cambio no afectó a los existentes, así que los repositorios y organizaciones creados antes de esa fecha mantienen el valor permissive salvo que alguien lo cambiara. Si un nivel superior elige restricted, los niveles inferiores no pueden elegir permissive.
Compruébelo en Settings → Actions → General → Workflow permissions, primero a nivel de organización. La misma sección incluye Allow GitHub Actions to create and approve pull requests, que debe permanecer desactivado salvo que un workflow lo necesite.
La clave permissions
La clave permissions de un workflow ajusta el token respecto al valor por defecto. Funciona en dos niveles:
permissions:
contents: read
jobs:
release:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<full-commit-sha>
Las reglas:
- El nivel de job prevalece sobre el de workflow. Establezca el valor por defecto del workflow en solo lectura y eleve los permisos únicamente en los jobs que los necesiten.
- Todo lo que no incluya pasa a ser
none. En cuanto define un permiso, se eliminan todos los demás.permissions: {}los elimina todos. - Los valores son
read,writeonone.writeincluyeread.read-allywrite-alllo establecen todo a la vez; evitewrite-all.
Los permisos disponibles, a septiembre de 2026:
| Permiso | Controla |
|---|---|
actions | Ejecuciones de workflows, artefactos y cachés |
artifact-metadata | Registros de metadatos de artefactos (sustituyó a contents para estas API en 2026) |
attestations | Atestaciones de artefactos |
checks | Check runs y check suites |
code-quality | Resultados de calidad del código, como informes de cobertura |
contents | Contenido del repositorio, commits, ramas, etiquetas y releases |
deployments | Despliegues |
discussions | Discussions |
id-token | Solicitud de un token OIDC (solo write o none) |
issues | Issues y comentarios |
packages | GitHub Packages, incluido el registro de contenedores |
pages | Builds de GitHub Pages |
pull-requests | Pull requests, etiquetas y revisiones |
security-events | Alertas de code scanning y subidas de SARIF |
statuses | Estados de commits |
vulnerability-alerts | Alertas de Dependabot (solo read o none) |
Cuando a un job le falta un permiso que necesita, la llamada a la API falla con 403 Resource not accessible by integration. Ese error es la señal para añadir el único permiso que necesita el paso, no para cambiar a write-all.
Qué necesitan las tareas habituales
| Tarea | Permisos |
|---|---|
| Hacer checkout del código | contents: read |
| Crear una release o subir una etiqueta | contents: write |
| Comentar en una issue o pull request | issues: write (los comentarios de pull requests pasan por la API de issues) |
| Añadir etiquetas o revisiones a una pull request | pull-requests: write |
| Autenticarse en un proveedor de nube con OIDC | id-token: write |
| Crear atestaciones de artefactos | id-token: write, attestations: write, contents: read, además de packages: write para imágenes de contenedor |
| Subir una imagen a GitHub Container Registry | packages: write |
| Subir SARIF a code scanning | security-events: write |
| Desplegar GitHub Pages desde un workflow | pages: write, id-token: write |
La mayoría de los jobs de test y de build no necesitan nada más que contents: read.
Forks, pull_request_target y Dependabot
- Las pull requests desde forks reciben un token de solo lectura en el evento
pull_request, pida lo que pida el workflow, salvo que un administrador active «Send write tokens to workflows from pull requests». Déjelo desactivado. pull_request_targetrecibe un token de lectura y escritura incluso para pull requests de forks públicos. Por eso está detrás de muchos de los robos de tokens más conocidos: el compromiso de Nx, el de Ultralytics en diciembre de 2024 y el de la acción de Trivy en marzo de 2026 empezaron todos con un workflowpull_request_target. GitHub lo está restringiendo: desde julio de 2026,actions/checkoutse niega por defecto a hacer checkout del código de un fork en estos workflows y, a partir del 2 de noviembre de 2026, los repositorios públicos sin política propia tendránpull_request_targetdesactivado por defecto.- Las pull requests de Dependabot se tratan como forks: un token de solo lectura y ningún secreto de Actions. Si está valorando otras herramientas de actualización, consulte alternativas a Dependabot.
Cómo encontrar los permisos mínimos
No hace falta averiguarlo leyendo el código fuente de cada acción:
- actions-permissions de GitHub Security Lab monitoriza cómo usa un workflow su token y recomienda los permisos mínimos. Está en beta pública.
- La comprobación Token-Permissions de OpenSSF Scorecard señala los workflows que no definen permisos de solo lectura en el nivel superior. Clasifica la comprobación como de alto riesgo.
- zizmor, un analizador estático de workflows, señala los permisos excesivos o por defecto y los checkouts que conservan las credenciales.
- Secure-Repo y Harden-Runner de StepSecurity recomiendan permisos a nivel de job a partir de una base de conocimiento de acciones y de las llamadas a la API que hace un job.
Un despliegue práctico: establezca el valor por defecto de la organización en restricted, añada permissions: contents: read al principio de cada workflow y ejecute los workflows. Los que fallen con Resource not accessible by integration le dirán exactamente qué job necesita qué permiso adicional.
Dónde encaja CRACI
El mínimo privilegio limita lo que puede hacer un token robado. No impide que lo roben, y los demás secretos del job, como los tokens de npm y las credenciales de la nube, están igual de expuestos.
CRACI ejecuta sus jobs de GitHub Actions en sus propios runners. Usted cambia runs-on a craci; GITHUB_TOKEN, la clave permissions y los ajustes de su repositorio funcionan como antes, porque GitHub sigue emitiendo el token. Lo que CRACI añade está en el runner: una política de egress que decide a qué hosts puede llegar un job, se valida antes de que empiece el job y, si falla, bloquea, y un registro de todos los paquetes que descargó el job, con una traza de sus conexiones de red. Si un código malicioso de un job llega a hacerse con un token, la política limita adónde puede enviarlo, y el registro muestra lo que ocurrió.
Para el resto de los secretos del job, consulte secretos de GitHub Actions: cómo se filtran y cómo protegerlos. Reserve una demo para ver el registro de uno de sus propios builds.
