StackPractices
intermediate Por Mathias Paulenko

Seguridad en Pipelines CI/CD: Prevén Ataques a la Cadena de Suministro

Guía práctica para proteger pipelines CI/CD: gestión de secretos, runners de mínimo privilegio, firma de artefactos y defensa de la cadena de suministro.

Descripción general

Los pipelines CI/CD son objetivos de alto valor para un atacante. Contienen código fuente, secretos, acceso a producción y claves de firma: todo lo necesario para llegar a tus usuarios sin tocar tu aplicación. Ya no es teórico: en 2020 el sistema de build de SolarWinds fue envenenado y distribuyó actualizaciones troyanizadas a 18.000 organizaciones; en 2021 unos atacantes modificaron el bash uploader de Codecov dentro de su proceso de CI y exfiltraron variables de entorno de miles de pipelines; y a principios de 2025 una versión comprometida de tj-actions/changed-files filtró secretos en los logs de GitHub Actions de más de veinte mil repositorios.

Los tres incidentes comparten el mismo patrón: se confiaba más en el pipeline que en lo que este producía. Esta guía recorre los controles que rompen esa suposición — qué proteger en cada capa, en qué orden y cómo saber que está funcionando.

Cuándo usarla

  • Gestionas pipelines CI/CD que despliegan a producción
  • Quieres reducir el radio de impacto de un sistema de build comprometido
  • Necesitas cumplir marcos de compliance (SOC 2, ISO 27001, FedRAMP)
  • Has sufrido, o quieres prevenir, un ataque a la cadena de suministro
  • Estás migrando entre runners auto-hospedados y CI/CD nativo en la nube

Para una versión en formato checklist de estos controles, la plantilla de seguridad de pipeline CI/CD mapea cada control a un método de verificación. Para alternativas, consulta la guía completa de gestión de secretos.

Conceptos clave

ConceptoDescripción
Ataque a la cadena de suministroInyección de código malicioso vía dependencias, actions o herramientas de build comprometidas
Runner de mínimo privilegioAgentes de build con acceso mínimo a secretos e infraestructura
Firma de artefactosPrueba criptográfica de que un artefacto proviene de tu pipeline
Escaneo de dependenciasDetección automática de vulnerabilidades conocidas en librerías
Pipeline como códigoDefiniciones de CI/CD versionadas que hacen cumplir la política de seguridad
SBOMInventario de todos los componentes que integran una release
Federación OIDCTokens de identidad de corta duración en lugar de credenciales cloud almacenadas
SLSAMarco que clasifica la integridad de la cadena de suministro de SLSA 0 a SLSA 4

Dos conceptos merecen otra mirada. Los niveles SLSA te dan un vocabulario para la madurez del pipeline: SLSA 1 es un proceso de build documentado, SLSA 2 un servicio de build hospedado, SLSA 3 builders endurecidos, y SLSA 4 revisión por dos personas más reproducibilidad. La mayoría de los equipos está en 1-2; firma más provenance te acerca a 3. La federación OIDC es el control con más palanca de toda la guía: cambia secretos cloud de larga duración por tokens que caducan en minutos y no pueden filtrarse desde un fork.

Endurecimiento paso a paso de CI/CD

Cada etapa de abajo corresponde a un patrón de incidente real. Trabaja de arriba hacia abajo; cada capa asume que la anterior se sostiene.

flowchart diagram: Código fuente

1. Configuración segura del pipeline

Trata las definiciones del pipeline como código de producción: revísalas, versiónalas y asume que alguien las modificará sin tu permiso.

# Ejemplo: endurecimiento de GitHub Actions
name: Secure Build
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# Permisos mínimos por defecto
permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # Fija las actions al hash del commit
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2

      # Usa OIDC para autenticación cloud en lugar de secretos persistentes
      - name: Authenticate to AWS
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/CICD-Build-Role
          aws-region: us-east-1

      # Verifica dependencias antes de instalar
      - name: Install dependencies
        run: |
          npm ci --ignore-scripts  # Omite scripts post-instalación
          npm audit --audit-level=moderate

Checklist de seguridad para las definiciones del pipeline:

  • Fija todas las actions de terceros al SHA del commit, no a la etiqueta de versión — las etiquetas se mueven, los SHA no. El incidente de tj-actions ocurrió porque una etiqueta se repuntó a un commit malicioso.
  • Usa bloques permissions: con el mínimo alcance necesario; un contents: read explícito a nivel de workflow es la mejora más barata de toda esta guía.
  • Nunca ejecutes CI sobre pull_request_target con checkout de código no confiable: ejecuta el código de la PR con tokens de escritura y secretos.
  • Pasa --ignore-scripts (o equivalente) al instalar dependencias; los hooks post-instalación ejecutan código arbitrario con los privilegios del pipeline.
  • Aplica branch protection con revisiones obligatorias para que nadie pueda saltarse la propia definición del pipeline.

2. Gestión de secretos

Los secretos en CI/CD son el vector de ataque más común porque son el botín más fácil: un AWS_SECRET_ACCESS_KEY filtrado en un log o en un build de un repo con fork y el atacante ya está dentro.

# MAL: secretos hardcodeados en los archivos del pipeline
# AWS_SECRET_ACCESS_KEY=AKIA...  # NUNCA HAGAS ESTO

# BIEN: almacenes de secretos nativos
# GitHub Actions: secrets de repositorio/entorno
# GitLab CI: variables CI/CD enmascaradas y protegidas
# Azure DevOps: grupos de variables tipo secreto

# MEJOR: OIDC elimina por completo las credenciales cloud almacenadas
# AWS: configure-aws-credentials con role-to-assume
# GCP: Workload Identity Federation
# Azure: managed identity + federated credentials

Secretos: lo que funciona

  • Rota lo que quede almacenado cada 30-90 días — y tras cualquier incidente, rota todo a la vez, no de a uno.
  • Prefiere tokens de corta duración (TTL de 1 hora); un token caducado no se puede reutilizar.
  • Acota los secretos a jobs y entornos, nunca a todo el pipeline.
  • Audita los logs de acceso a secretos; la mayoría de plataformas registra quién usó qué y cuándo.
  • Nunca hagas echo de secretos. Los sistemas CI enmascaran los secretos conocidos, pero los valores derivados — tokens minteados a partir de secretos — suelen salir sin enmascarar.

3. Endurecimiento de runners

Tus agentes de build son tan críticos como los servidores de producción — a menudo más, porque manejan credenciales que producción nunca ve.

EstrategiaDescripciónImplementación
Runners efímerosVM o pod nuevo por buildGitHub-hosted, GitLab SaaS, jobs de K8s
Aislamiento de redEgress restringido del runnerVPC, subredes privadas, proxy de salida
Mínimo privilegioIAM mínimo por tipo de jobRoles separados por pipeline/proyecto
Imágenes inmutablesImágenes de runner endurecidas y escaneadasPacker, golden AMI, firma de imágenes
Nada de secretos en discoCredenciales solo en memoriaMontajes tmpfs, inyección en runtime
# Checklist de endurecimiento de runners auto-hospedados
# 1. Aíslalos en una VPC sin egress directo a internet
# 2. Pools de runners separados por equipo o nivel de confianza
# 3. Sin sudo/root para el usuario del runner
# 4. Monta /tmp como noexec, nodev, nosuid
# 5. Corre builds de contenedores sin root (podman/docker rootless)
# 6. Borra el workspace entre jobs — nunca reutilices directorios
# 7. Escanea las imágenes de runner cada semana y reconstrúyelas a menudo

Un apunte práctico: los runners auto-hospedados detrás de tu firewall suelen tener más acceso de red que los servicios de producción. Si no puedes aislarlos bien, los runners efímeros hospedados en la nube son la opción segura por defecto.

4. Seguridad de dependencias y artefactos

Tus dependencias son tu eslabón más débil: heredas cada vulnerabilidad y cada cuenta de maintainer comprometida de tu árbol de dependencias.

# Ejemplo: pipeline de escaneo de dependencias en GitHub Actions
jobs:
  security-scan:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v4

      # SAST (análisis estático de seguridad)
      - name: Run CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: javascript, python
      - uses: github/codeql-action/analyze@v3

      # Escaneo de vulnerabilidades en dependencias
      # Alertas y actualizaciones de Dependabot en la config del repo,
      # o Snyk / OWASP Dependency-Check

      # Escaneo de imagen de contenedor
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      - name: Scan image with Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'myapp:${{ github.sha }}'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

Prácticas de seguridad de artefactos:

  • Firma cada artefacto — la firma keyless de cosign vía OIDC elimina la necesidad de gestionar una clave privada.
  • Genera y publica SBOMs en cada release; sin inventario no puedes responder al próximo Log4Shell.
  • Escanea imágenes antes de subirlas al registry y bloquea con HIGH/CRITICAL.
  • Verifica las firmas en el despliegue — firmar sin verificar es decoración.
  • Usa registries inmutables con políticas de retención para que el artefacto de ayer no pueda ser sustituido en silencio.
  • Defiéndete del dependency confusion: registries privados con reserva de namespace, lockfiles con hashes de integridad y verificación de firmas en paquetes internos.

5. Seguridad del despliegue

La última etapa debe estar tan endurecida como la primera: un paso de despliegue comprometido anula todo lo anterior.

# Ejemplo: script de despliegue seguro
#!/bin/bash
set -euo pipefail

# 1. Verifica la firma del artefacto
cosign verify --key cosign.pub artifact.sig myapp:${DEPLOY_VERSION}

# 2. Confirma que el SBOM coincide con los componentes esperados
sbom-diff expected.sbom generated.sbom

# 3. Smoke tests antes de mover tráfico
./smoke-tests.sh --target staging

# 4. Despliega con rollback automático en caso de fallo
./deploy.sh --version ${DEPLOY_VERSION} --rollback-on-failure

# 5. Verifica la salud del despliegue
./health-check.sh --target production

Reglas de seguridad del despliegue:

  • Exige aprobación manual para producción — los environments con reviewers obligatorios te dan traza de auditoría gratis.
  • Rollback automático ante fallo de health check; blue-green o canary limitan el radio de impacto cuando se cuela un artefacto malo.
  • Separa credenciales de staging y producción — el rol de despliegue de staging no debe servir para nada en producción.
  • Registra cada evento de despliegue en una traza de auditoría de solo-append fuera de la propia plataforma CI.

Archivos complementarios: el repo companion de ci-cd-security-guide tiene un workflow endurecido de GitHub Actions que implementa estos pasos (secure-workflow.yml) más el checklist de endurecimiento de runners auto-hospedados.

Lo que funciona

  • Asume que el pipeline será comprometido. Diseña para la contención — radio de impacto, detección, recuperación — no solo para la prevención.
  • La infraestructura efímera gana a la endurecida. Un runner nuevo no puede alojar el malware de ayer.
  • Verifica todo lo que pasa por el pipeline: firmas, SBOMs, checksums. La confianza es lo que explotan los atacantes.
  • Permisos mínimos por job. Si un job solo lee, que no escriba; si no necesita secretos, que no los tenga.
  • Observa el comportamiento del pipeline, no solo sus resultados: alerta sobre egress inesperado, uso de credenciales fuera de jobs de despliegue o builds que de repente tardan el triple.
  • Practica la respuesta a incidentes: si un pipeline se ve comprometido, ¿puedes rotar todos sus secretos en una hora? Si no lo sabes, ensáyalo.

Errores comunes

  • Secretos de larga duración en CI: OIDC o tokens de corta duración allá donde la plataforma lo permita.
  • Runners auto-hospedados sin endurecer: suelen tener más acceso de red que producción y persisten entre jobs.
  • Confiar en pull_request_target: ejecuta código no confiable con tokens de escritura — un patrón documentado y explotado repetidamente.
  • Saltarse el escaneo de dependencias: los CVE conocidos en dependencias siguen siendo el vector más explotado de la cadena.
  • Confiar en las etiquetas de versión de las actions: las etiquetas se repuntan; fija SHAs.
  • Ignorar los CVE de la imagen base: parte de imágenes mínimas y mantenidas (distroless, Alpine cuando proceda) y reconstruye con regularidad.
  • Enmascarar solo el secreto crudo: los tokens derivados y las variantes en base64 se filtran por los logs sin enmascarar.

Variantes

  • GitHub Actions: permisos por permissions, la diferencia pull_request / pull_request_target, OIDC vía id-token: write, Dependabot, environments con reviewers obligatorios.
  • GitLab CI: variables enmascaradas/protegidas, id_token OIDC por job, runner tags para aislamiento, escaneo de contenedores y secretos integrado. El equivalente OIDC se ve así:
# GitLab: token OIDC por job, aud acotado al job
deploy:
  id_tokens:
    AWS_ID_TOKEN:
      aud: sts.amazonaws.com
  script:
    - aws sts assume-role-with-web-identity
        --role-arn arn:aws:iam::123456789012:role/gitlab-deploy
        --role-session-name gitlab-$CI_JOB_ID
        --web-identity-token $AWS_ID_TOKEN
        --duration-seconds 3600

El token vive solo dentro del job, está ligado al proyecto y al ref, y muere con él — mismo trato que id-token: write en GitHub. Combínalo con variables protected para que los secretos solo lleguen a jobs de ramas protegidas.

  • Jenkins: aislamiento de agents (un job por agent o agents de K8s), scopes de credenciales, shared libraries como frontera de revisión, script security.
  • Builds nativos en la nube: AWS CodeBuild, Google Cloud Build y Azure DevOps te dan identidades integradas con IAM y builders aislados — menos endurecimiento de runners para ti, pero los permisos y la verificación de artefactos siguen siendo tuyos.

Respuesta a un pipeline comprometido

Todos los controles anteriores asumen el día en que fallan. Cuando — no si — un pipeline se ve comprometido, la respuesta tiene que ser más rápida que el siguiente movimiento del atacante. Este es el manual.

1. Aísla primero. Desactiva el workflow o el pool de runners afectado inmediatamente; no investigues un objetivo en vivo. Pausa los despliegues. Si el runner era auto-hospedado, mata la VM o el pod en lugar de limpiarlo: lo efímero gana a la forense sobre una máquina comprometida.

2. Da por perdido cada secreto que tocó. Rótalo todo, en orden de dependencia: primero las credenciales cloud (son las que abren todo lo demás), luego las claves de registry y despliegue, después los tokens de terceros y por último las claves de firma. Con OIDC este paso se reduce a revocar políticas de confianza — una razón más para migrar.

3. Audita lo que el pipeline produjo. Revisa qué artefactos se construyeron y desplegaron durante la ventana de exposición: digests de imágenes, attestations de provenance, logs de despliegue. Cualquier artefacto que no puedas probar limpio se redespliega desde un build verificado.

4. Audita lo que tocó. Busca egress inesperado, webhooks nuevos, archivos de workflow modificados, instalaciones de paquetes sospechosas y accesos a recursos que el job no debía necesitar. Los propios logs del pipeline son lo primero que un atacante borra: envíalos a un almacén externo de solo-append antes del incidente, no después.

5. Reconstruye, no parchees. Recrea los runners, re-emite los tokens, redespliega desde artefactos verificados. Un sistema comprometido y parcheado sigue siendo un sistema comprometido.

Mapeo de compliance

Si lo haces por un marco de cumplimiento, aquí encaja cada control:

Requisito del marcoControl del pipeline que lo satisface
SOC 2 CC6.1 (acceso lógico)OIDC, secretos acotados, permisos mínimos
SOC 2 CC7.1 (monitorización)Alertas de egress, envío de logs de auditoría
ISO 27001 A.8.9 (gestión de config)Pipeline como código, branch protection, revisión
ISO 27001 A.8.25 (desarrollo seguro)Gates SAST/SCA, escaneo Trivy, code review
NIST SSDF PO.5 / PW.6Firma de artefactos, SBOM, provenance
FedRAMP CM-7 / SI-7Runners de mínimo privilegio, verificación de firma en despliegue

Nada de esto es ceremonial. Los auditores piden evidencia — y los logs de aprobación, los artefactos firmados y los SBOMs que producen estos controles son exactamente la evidencia que buscan.

Referencia rápida amenaza → control

AtaquePunto de entradaControl principal
PR maliciosapull_request_target + secretosSolo pull_request, sin secretos en forks
Action envenenadaEtiqueta de versión repuntadaPin de SHA + revisión
Exfiltración de secretosSalida de logs, builds de forksOIDC, enmascarado, secretos por job
Movimiento lateralAcceso a runner auto-hospedadoRunners efímeros y aislados
Dependency confusionPaquete público homónimoRegistry privado + namespace
Artefacto alteradoEscritura en el registrycosign sign + verify en el despliegue
Despliegue no autorizadoCredenciales de despliegue robadasOIDC + aprobación manual + auditoría

Sigue mejorando

La seguridad CI/CD es una práctica continua, no un proyecto de endurecimiento que termina. Cada action nueva, cada dependencia y cada tipo de runner suma superficie. Elige el control que tu próxima auditoría señalaría primero — para la mayoría de equipos es OIDC o la firma de artefactos — y mételo en este sprint, no en el backlog.

Lecturas recomendadas

Preguntas frecuentes

¿Cómo migro de secretos de larga duración a OIDC?

Configura la federación de identidad en tu proveedor cloud (proveedor OIDC de IAM en AWS, Workload Identity en GCP, federated credentials en Azure) y sustituye las credenciales estáticas por role-to-assume en el paso de autenticación. El token OIDC queda acotado al repositorio y la rama del job, así que un token filtrado vale casi nada.

¿Debería usar runners auto-hospedados u hospedados en la nube?

Los runners en la nube son efímeros y aislados por defecto — la base segura. Los auto-hospedados te dan velocidad de caché y proximidad de red, pero a costa del endurecimiento del runner, el riesgo de persistencia y el control del egress. Elígelos solo si puedes aislarlos correctamente.

¿Cómo prevengo los ataques de dependency confusion?

Reserva tu namespace interno en un registry privado, fija versiones exactas con lockfiles que incluyan hashes de integridad y prioriza la resolución privada sobre la pública, para que internal-lib nunca se resuelva desde un paquete público con el mismo nombre.

¿Cuál es el mínimo viable de seguridad CI/CD?

Branch protection, Dependabot o equivalente, OIDC para auth cloud, actions fijadas por SHA y escaneo de imágenes antes del despliegue. Ese conjunto bloquea la mayoría de los incidentes documentados y se monta en días, no en meses.

¿Cómo detiene la firma de artefactos a un atacante de verdad?

Cambia la pregunta de confianza en el despliegue: de "¿este artefacto vino de nuestro pipeline?" a "¿puede este artefacto demostrar que vino de nuestro pipeline?". Un atacante con acceso de escritura al registry puede cambiar una imagen — no puede falsificar una firma sin tus claves ni tu identidad.

¿Cómo mido si la seguridad del pipeline mejora?

Sigue el número de secretos eliminados gracias a OIDC, el porcentaje de artefactos firmados, el tiempo medio de parcheo de CVE críticos en dependencias y el progreso en niveles SLSA. Si esas cifras se mueven, la postura es real — no solo documentos de política.

¿Qué es lo primero que hay que hacer tras descubrir un pipeline comprometido?

Aíslo antes de investigar: desactiva el workflow, mata el runner, pausa los despliegues. Después rota todos los secretos a los que el pipeline podía acceder, en orden de dependencia — las credenciales cloud primero, porque abren todo lo demás. La auditoría puede esperar una hora; un pipeline abierto, no.

¿Necesito todos estos controles desde el primer día?

No. Empieza con el mínimo viable — branch protection, escaneo de dependencias, OIDC, pin de SHA, escaneo de imágenes — y añade el resto en el orden de lo que te costaría una brecha de verdad. La firma y el provenance importan sobre todo cuando consumidores externos dependen de tus artefactos, no antes.