Parte: 11 — DevSecOps y seguridad del SDLC · Fuente: Securing DevOps (Julien Vehent) y OWASP Top 10 CI/CD Security Risks ⏱️ Duración estimada: 120 min · Nivel: Avanzado
Endurecer el pipeline de CI/CD, que se ha convertido en un objetivo de primer nivel: quien controla el pipeline controla lo que llega a producción. Estudiaremos los riesgos del OWASP Top 10 CI/CD, aplicaremos mínimo privilegio a los tokens, fijaremos (pinning) acciones y dependencias, aislaremos los runners, y protegeremos secretos y aprobaciones. Usaremos GitHub Actions como ejemplo con la herramienta de análisis zizmor.
Al finalizar, el alumno podrá:
GITHUB_TOKEN y a los secretos por job.| # | Tema | Por qué importa |
|---|---|---|
| 1 | El pipeline como objetivo | SolarWinds, Codecov: comprometer el build compromete todo |
| 2 | OWASP Top 10 CI/CD | Taxonomía de riesgos específicos del pipeline |
| 3 | pull_request_target y forks |
Vector clásico de robo de secretos |
| 4 | Mínimo privilegio de tokens | permissions: restrictivo por defecto |
| 5 | Pinning de acciones a SHA | Evita que un tag mutable inyecte código |
| 6 | Aislamiento de runners | Efímeros, sin credenciales persistentes |
| 7 | OIDC en vez de secretos long-lived | Credenciales cortas federadas a la nube |
GITHUB_TOKEN: token efímero del workflow. Característica: por defecto puede tener permisos amplios; restríngelo a read y sube solo lo necesario.@v3 es mutable y puede ser reescrito por un atacante que controle el repo de la acción.pull_request_target: evento que corre con secretos del repo base sobre código del PR. Característica: peligroso con forks; puede exfiltrar secretos.Instalación de zizmor:
pip install zizmor
zizmor .github/workflows/
🧪 Laboratorio ejecutable del programa:
devsecops-pipeline— es la capa 6 del lab, sobre un workflow conpull_request_target, inyección de expresiones y permisos totales.
zizmor .github/workflows/ci.yml
actionlint
Anota hallazgos: permisos amplios, acciones sin pin, uso de pull_request_target.
2. Restringe permisos. Aplica mínimo privilegio a nivel de workflow y sube por job solo lo necesario:
permissions:
contents: read # por defecto de solo lectura para todo
jobs:
build:
permissions:
contents: read
packages: write # solo este job puede publicar
# Mal: uses: actions/checkout@v4 (tag mutable)
# Bien: uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v4.1.1
pull_request_target con checkout del código del PR; si lo necesitas, no expongas secretos y separa el job que corre código no confiable del que usa credenciales.step-security/harden-runner para bloquear egress no esperado y detectar exfiltración.Nota ética: practica el hardening sobre repositorios y organizaciones propios. Explotar pipelines ajenos (aunque sea "para demostrar") requiere autorización explícita por escrito.
pull_request_target puede filtrar secretos y cómo evitarlo.Endurece un pipeline de CI/CD real aplicando los controles clave y demuéstralo con una auditoría.
Criterio de aceptación: (a) todos los workflows tienen permissions de mínimo privilegio;
(b) todas las acciones de terceros están pinneadas a SHA; (c) no hay uso inseguro de
pull_request_target con secretos expuestos a código de forks; (d) el despliegue usa OIDC o,
si no es posible, secretos con scope mínimo; y (e) zizmor no reporta hallazgos de severidad alta.
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
| Un PR de fork robó un secreto | pull_request_target con checkout del PR. Separa el código no confiable de los jobs con secretos. |
| Una acción "de confianza" inyectó código | Estaba pinneada a un tag mutable reescrito. Pin a SHA siempre. |
GITHUB_TOKEN con permisos de escritura por defecto |
No se restringió. Define permissions: contents: read a nivel global. |
| Secretos long-lived rotados a mano cada 90 días | Deuda operativa. Migra a OIDC con credenciales efímeras. |
| El runner descarga y ejecuta scripts de internet | Egress sin control. Usa harden-runner para restringir red. |
❓ ¿Por qué pinnear a SHA si confío en actions/checkout?
Confías en el código actual, no en cualquier código futuro que un tag mutable pueda apuntar si la cuenta de la acción se compromete. El SHA es inmutable.
❓ ¿Cuál es la diferencia entre pull_request y pull_request_target?
pull_request corre sin secretos del repo base (seguro para forks); pull_request_target corre con ellos y en el contexto del base, por eso es peligroso combinarlo con checkout del código del PR.
❓ ¿OIDC elimina todos los secretos? Elimina los de larga vida hacia proveedores que soportan federación (nube, registries). Puede quedar algún secreto, pero reduces drásticamente la superficie.
❓ ¿Basta con auditar una vez? No. Los workflows cambian y aparecen nuevas técnicas. Corre zizmor/actionlint en CI como gate continuo.
Clase 241 — Secretos en el código y pre-commit hooks