Cambiar tema

Gestión de GitHub Actions Secrets: de riesgos de filtración a despliegue sin claves con OIDC

Easton editorial illustration: modular system blueprint

Un fin de semana de marzo de 2025, el equipo de seguridad de GitHub envió un correo urgente a los propietarios de más de 23.000 repositorios.

Sus secrets podían haberse filtrado.

El culpable era tj-actions/changed-files: una action muy usada que los atacantes comprometieron para robar en silencio todas las variables de entorno y secrets del workflow. Si también la usabas, tenías que comprobarlo.

Este incidente sacó a la luz una pregunta que muchos posponen: ¿cómo deberías gestionar realmente los secrets en GitHub Actions?

En este artículo hablamos de tres cosas: cómo elegir la arquitectura en tres capas, cómo cumplir las 8 reglas de seguridad y cómo OIDC te permite dejar atrás la pesadilla de las credenciales estáticas filtradas.

1. Arquitectura en tres capas de GitHub Actions Secrets

GitHub ofrece tres niveles para almacenar secrets: Repository, Environment y Organization. ¿Cuál elegir? Depende de tu escenario.

Repository Secrets: la opción preferida para proyectos personales

Es el nivel más simple. Los secrets se guardan a nivel de repositorio y todos los workflows pueden acceder a ellos. Si tienes un proyecto personal, un monorepo y no necesitas desplegar en varios entornos, con esto basta.

El único inconveniente: no puedes distinguir un secret con el mismo nombre entre staging y production. Por ejemplo, si tienes un DATABASE_URL con valores distintos en staging y production, ¿qué haces? Ahí entran los Environment Secrets.

Environment Secrets: imprescindibles para despliegues multi-entorno

Los environment secrets se aíslan por entorno y también admiten flujos de aprobación. Puedes crear los entornos staging y production en la configuración del repositorio y definir secrets distintos para cada uno.

Una característica clave: solo los jobs que referencian ese entorno pueden acceder a los secrets correspondientes. Así los límites de seguridad quedan más claros.

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging  # Referencia al entorno staging
    steps:
      - run: echo "Desplegando en staging..."
      - env:
          API_KEY: ${{ secrets.API_KEY }}  # Secret del entorno staging

  deploy-production:
    runs-on: ubuntu-latest
    environment: production  # Referencia al entorno production (puede requerir aprobación)
    steps:
      - run: echo "Desplegando en production..."
      - env:
          API_KEY: ${{ secrets.API_KEY }}  # Secret del entorno production

En la configuración anterior, deploy-staging solo accede a los secrets de staging, y deploy-production solo a los de production. No se interfieren.

Además, los Environment admiten «reglas de protección»: por ejemplo, production puede configurarse para exigir aprobación manual antes de ejecutarse. Muy útil en trabajo en equipo.

Organization Secrets: compartir en equipo y gestionar de forma centralizada

Si tu equipo tiene decenas de repositorios y cada uno debe configurar el mismo AWS_ACCESS_KEY, copiar y pegar decenas de veces y volver a cambiarlo todo al actualizar es un dolor de cabeza.

Los organization secrets resuelven esto. Configuras una vez a nivel de organización y todos los repositorios pueden usarlos. También puedes controlar qué repos tienen acceso: todos o una lista concreta.

# En el workflow del repositorio, el uso es idéntico al de repository secrets
steps:
  - env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

¿Cómo elegir?

En resumen:

EscenarioNivel recomendado
Proyecto personal, un solo entornoRepository Secrets
Despliegue multi-entorno (staging/production)Environment Secrets
Varios repos en equipo, credenciales compartidasOrganization Secrets

Por experiencia, muchos proyectos empiezan con Repository Secrets y migran a Environment Secrets cuando necesitan varios entornos. La migración no cuesta mucho, pero conviene planificarlo con antelación.

2. Buenas prácticas de seguridad para secrets — 8 reglas de oro

Ya vimos dónde guardar los secrets; ahora, cómo usarlos. Resumo 8 reglas sacadas de la práctica, cada una con su lección.

1. Nombra de forma consistente

Todo en mayúsculas y con guiones bajos, por ejemplo AWS_ACCESS_KEY_ID. Nada de minúsculas ni camelCase. Así se ve de un vistazo que es un secret y no se confunde con variables normales.

2. No guardes JSON en un solo secret

Es una trampa clásica. Algunos meten un archivo de configuración entero en un secret:

{"api_key": "xxx", "db_url": "yyy", "token": "zzz"}

Y luego lo parsean en el workflow con fromJson. El problema: si ese secret se filtra, se filtra toda la información sensible de golpe. Lo correcto es guardar cada valor como secret independiente.

3. Pasa explícitamente, no en línea

# Mal ❌
- run: my-cli --token ${{ secrets.MY_TOKEN }}

# Bien ✅
- env:
    MY_TOKEN: ${{ secrets.MY_TOKEN }}
  run: my-cli --token $MY_TOKEN

¿Por qué? La investigación de GitGuardian muestra que los argumentos de línea de comandos pueden verse por otros procesos en la misma máquina con ps x -w. Las variables de entorno son mucho más seguras.

4. Rota con regularidad

Cada 30 a 90 días. Sé que rotar es molesto, pero frente a remediar una filtración, ese esfuerzo no es nada. El equipo de Blacksmith sugiere que, si usas servicios en la nube (AWS/GCP), puedes combinar OIDC y saltarte por completo este paso.

5. Fija las Actions al SHA

La primera línea de defensa contra ataques a la cadena de suministro.

# Mal ❌
- uses: tj-actions/changed-files@v45

# Bien ✅
- uses: tj-actions/changed-files@b827595e0a7e97537d7c7a2f458b5a8e6d5c8e39

Usa el SHA del commit, no la etiqueta de versión. Las etiquetas pueden ser manipuladas por atacantes; el SHA es inmutable.

6. Da a GITHUB_TOKEN solo el mínimo privilegio

GitHub proporciona automáticamente GITHUB_TOKEN en cada workflow. Los permisos por defecto son demasiado amplios: puede escribir código y modificar issues. Conviene limitarlo a solo lectura en el workflow o en la configuración del repositorio:

permissions:
  contents: read

7. Comprueba que los secrets queden enmascarados en los logs

GitHub sustituye automáticamente ${{ secrets.XXX }} por *** en los logs. Pero si escribes así:

- run: echo "Token is $MY_TOKEN"

El valor real del token aparecerá en los logs. Prueba tus workflows y confirma que no haya exposición accidental.

8. Registra valores sensibles derivados

Si tu workflow genera nuevos valores sensibles a partir de un secret (por ejemplo, un JWT a partir de una API key), registra ese valor también como secret; no lo pases solo en memoria.


Estas 8 reglas no son teoría: tras el incidente tj-actions, el equipo de StepSecurity auditó miles de repositorios públicos y encontró que una proporción considerable las incumplía. Corregirlo no es difícil, pero hay que revisar punto por punto.

3. OIDC — autenticación de despliegue en la nube sin claves

En las reglas anteriores mencionamos «rotar secrets periódicamente». Siendo honestos, rotar es tedioso: cada vez hay que cambiar la consola de AWS, actualizar los secrets en GitHub y avisar al equipo.

OIDC (OpenID Connect) ofrece una salida: simplemente no guardar secrets.

¿Cómo funciona OIDC?

Enfoque tradicional: creas un usuario IAM en AWS, generas una access key y la guardas en GitHub secrets. Cada vez que corre el workflow, usa esa clave estática para acceder a recursos de AWS.

Enfoque OIDC: GitHub actúa como proveedor de identidad y demuestra a AWS que «este workflow proviene del repositorio eastondev/my-repo». Tras verificar, AWS emite un token JWT de corta duración (válido minutos u horas). El workflow usa ese token temporal para completar la tarea y expira solo.

Sin credenciales de largo plazo, sin rotación, sin riesgo de filtración.

Ejemplo de configuración OIDC en AWS

Los pasos se dividen en dos partes: configurar la relación de confianza en AWS y solicitar el token en GitHub.

Lado AWS (operaciones en consola):

  1. Crea un IAM Identity Provider con URL https://token.actions.githubusercontent.com
  2. Crea un IAM Role con política de confianza limitada a tu repositorio:
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::123456789:oidc-provider/token.actions.githubusercontent.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
        "token.actions.githubusercontent.com:sub": "repo:eastondev/my-repo:ref:refs/heads/main"
      }
    }
  }]
}

Esta configuración significa: solo la rama main del repositorio eastondev/my-repo puede asumir este role.

Lado GitHub (configuración del workflow):

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Obligatorio: solicitar token OIDC
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
          aws-region: us-east-1
      - run: aws s3 sync ./dist s3://my-bucket

Fíjate: aquí no hay ningún secrets.AWS_ACCESS_KEY — autenticación directa con el role.

GCP y Azure

Las tres grandes nubes admiten OIDC con configuraciones similares:

PlataformaGitHub ActionPalabra clave en documentación oficial
AWSaws-actions/configure-aws-credentialsOIDC federation
GCPgoogle-github-actions/authWorkload Identity Federation
Azureazure/loginFederated Identity Credentials

Un beneficio inesperado de OIDC

Según pruebas de johal.in, la latencia de autenticación con OIDC es un 87% menor que con secrets tradicionales. La razón es simple: no hace falta leer credenciales desde la API de secrets de GitHub; se intercambia el JWT local por un token temporal.

Por mi experiencia, OIDC es la opción preferida para despliegues en la nube. La única barrera es que la configuración inicial es un poco más compleja, pero una vez hecha, te olvidas.

4. Protección contra ataques a la cadena de suministro — repaso del incidente tj-actions

Volvamos al incidente tj-actions/changed-files del principio. ¿Cómo ocurrió? ¿Qué podemos aprender?

Cronología del incidente

En marzo de 2025, los atacantes obtuvieron permisos de maintainer del repositorio tj-actions (el método concreto sigue en investigación, posible filtración de credenciales o compromiso de cuenta). Insertaron un script malicioso en el código de la versión v45 que, al ejecutarse el workflow, leía en silencio todas las variables de entorno y secrets y los enviaba a servidores controlados por los atacantes.

El análisis de Semgrep muestra que todos los workflows que usaban tj-actions/changed-files@v45 se vieron afectados: tanto si usabas GitHub secrets como OIDC, si esa action podía acceder a variables de entorno, las robaba.

El informe de Unit42 Palo Alto señala que más de 23.000 repositorios usaban esta action, incluidos forks de proyectos conocidos.

Lecciones aprendidas

El incidente dejó varios problemas al descubierto:

  1. Las etiquetas de versión de las actions no son fiables — un atacante puede redirigir la etiqueta a un commit malicioso
  2. Las actions de terceros pueden acceder a tus secrets — una vez comprometidas, quedan todos expuestos
  3. Los logs históricos de ejecución pueden filtrar información sensible — aunque lo corrijas ahora, registros pasados pueden conservar rastros

Tu lista de comprobación

Si alguna vez usaste tj-actions/changed-files, conviene revisar punto por punto:

□ Revisar logs del workflow y confirmar que no haya secrets en la salida
□ Rotar todos los secrets posiblemente expuestos (API keys, tokens, etc.)
□ Fijar la action al SHA del commit, no a la etiqueta de versión
□ Auditar el origen del maintainer de otras actions de terceros
□ Valorar Dependabot o Renovate para automatizar la revisión de versiones

Para proyectos aún no comprometidos, fijar el SHA es la medida más importante. Aunque el SHA cuesta más leer que un número de versión, es la única forma de evitar la manipulación de etiquetas.

Además, GitHub mencionó en su hoja de ruta de seguridad 2026 una dirección importante: separar permisos de contribución de código y de gestión de credenciales. En el futuro podría haber controles de acceso más granulares para que las actions de terceros solo accedan a los secrets necesarios, no a todos. Buenas noticias, pero de momento la defensa sigue en nuestras manos.

Conclusión

Gestionar GitHub Actions Secrets no es un problema técnico complejo, sino una práctica de seguridad que exige atención continua. Resumen:

Cómo elegir la arquitectura en tres capas: Repository Secrets para proyectos personales, Environment Secrets para multi-entorno, Organization Secrets para compartir en equipo.

Cómo cumplir las reglas de seguridad: fijar SHA, paso explícito y rotación periódica — estas tres son las más importantes.

Cómo autenticar despliegues en la nube: OIDC es la primera opción, sin almacenar secrets ni rotaciones.

Cómo prevenir ataques a la cadena de suministro: limitar el acceso de actions de terceros a secrets y auditar el origen de los maintainers.

Tras aprender del incidente tj-actions, mi enfoque es: todas las actions fijadas al SHA, todos los despliegues en la nube con OIDC y una auditoría mensual de secrets. Montar este flujo llevó unas dos semanas, pero después el costo de mantenimiento es bajo.

Si aún no has hecho estas comprobaciones, te recomiendo repasar hoy la lista de este artículo. Prevenir cuesta mucho menos que remediar después.

Configurar despliegue sin claves con OIDC en GitHub Actions

Configura autenticación OIDC para servicios en la nube AWS y logra un despliegue seguro sin almacenar credenciales estáticas

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Crear IAM Identity Provider

    Crea un proveedor de identidad en la consola de AWS IAM:

    • Provider URL: https://token.actions.githubusercontent.com
    • Audience: sts.amazonaws.com
    • Anota el Provider ARN generado
  2. 2

    Step 2: Crear IAM Role y configurar política de confianza

    Crea un IAM Role con política de confianza limitada a tu repositorio de GitHub:

    ```json
    {
    "Version": "2012-10-17",
    "Statement": [{
    "Effect": "Allow",
    "Principal": {
    "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
    "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
    "token.actions.githubusercontent.com:sub": "repo:OWNER/REPO:ref:refs/heads/main"
    }
    }
    }]
    }
    ```

    • Sustituye ACCOUNT_ID por tu ID de cuenta AWS
    • Sustituye OWNER/REPO por tu repositorio de GitHub
    • Puedes quitar :ref:refs/heads/main para permitir todas las ramas
  3. 3

    Step 3: Configurar el workflow para usar OIDC

    Solicita el token OIDC en el workflow de GitHub Actions:

    ```yaml
    jobs:
    deploy:
    runs-on: ubuntu-latest
    permissions:
    id-token: write # Obligatorio: solicitar token OIDC
    contents: read
    steps:
    - uses: aws-actions/configure-aws-credentials@v4
    with:
    role-to-assume: arn:aws:iam::ACCOUNT_ID:role/GitHubActionsRole
    aws-region: us-east-1
    - run: aws s3 sync ./dist s3://my-bucket
    ```

    • id-token: write es la declaración de permiso obligatoria
    • No hace falta configurar secrets.AWS_ACCESS_KEY_ID
  4. 4

    Step 4: Probar y verificar

    Ejecuta el workflow para verificar la configuración OIDC:

    • Revisa los logs del workflow y confirma autenticación correcta
    • Verifica que role-to-assume sea correcto
    • Confirma acceso a recursos AWS sin credenciales estáticas
    • Prueba operaciones objetivo (sincronización S3, push a ECR, etc.)

FAQ

¿Cuál es la diferencia entre Repository Secrets y Environment Secrets?
Repository Secrets se almacenan a nivel de repositorio y todos los workflows pueden acceder. Environment Secrets se aislian por entorno: solo los jobs que referencian ese entorno acceden a los secrets correspondientes, y también admiten flujos de aprobación. Para despliegues multi-entorno se recomiendan Environment Secrets.
¿Cómo usar secrets de forma segura en un workflow?
Sigue tres principios:

• Paso explícito: inyecta con el campo env, no uses en línea ${{ secrets.XXX }}
• Mínimo privilegio: pasa solo a los steps que lo necesiten; con GITHUB_TOKEN usa permissions: contents: read
• Rotación periódica: rota cada 30-90 días; en despliegues en la nube OIDC permite omitir la rotación por completo
¿Qué es OIDC? ¿Por qué es más seguro que los secrets tradicionales?
OIDC (OpenID Connect) permite que GitHub actúe como proveedor de identidad y demuestre la identidad del workflow a las plataformas en la nube, que emiten tokens JWT de corta duración. Ventajas: sin credenciales estáticas, sin rotación y sin riesgo de filtración. La latencia de autenticación es un 87% menor que con secrets tradicionales.
¿Cómo prevenir ataques a la cadena de suministro (como el incidente tj-actions)?
Medidas de protección clave:

• Fija las Actions al SHA del commit, no uses etiquetas de versión
• Limita los secrets solo a actions de confianza
• Audita el origen del maintainer de actions de terceros
• Ejecuta Dependabot o Renovate con regularidad para revisar actualizaciones
¿Pueden filtrarse los secrets en los logs?
GitHub sustituye automáticamente ${{ secrets.XXX }} por *** en los logs. Pero si usas variables de entorno directamente (como echo ${MY_TOKEN}), los logs mostrarán el valor real. Conviene probar los logs del workflow y confirmar que no haya exposición accidental.
¿Para qué escenarios sirven los Organization Secrets?
Para equipos con varios repositorios y credenciales compartidas. Configuras una vez a nivel de organización y todos los repos pueden usarlas. Puedes controlar el alcance: todos los repositorios o una lista concreta. Por ejemplo, cuando varios proyectos comparten AWS_ACCESS_KEY, Organization Secrets evita repetir la configuración en cada repo.

10 min de lectura · Publicado el: 18 abr 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog