Pipeline DevSecOps en Linux con GitHub Actions: Semgrep, Gitleaks y Trivy (2026)
Guía práctica para construir un pipeline DevSecOps en Linux con GitHub Actions. Integra Semgrep, Gitleaks y Trivy para blindar tu flujo CI/CD con herramientas open source y código listo para copiar.

Voy a ser directo: si tu pipeline CI/CD no tiene controles de seguridad integrados, estás jugando a la ruleta rusa con tu código en producción. Y no lo digo por dramatizar — el informe State of DevSecOps 2026 de Datadog lo confirma con números que asustan. El 87% de las organizaciones ejecutan al menos una vulnerabilidad explotable en producción, y la dependencia mediana lleva 278 días sin actualizar respecto a su última versión principal. Eso es casi un año entero.
Ah, y hay más. El 71% de los equipos nunca fija el hash de sus GitHub Actions. Básicamente, confían ciegamente en que nadie va a manipular el código detrás de un tag. Ya veremos más adelante por qué eso es una pésima idea.
La buena noticia es que puedes construir un pipeline DevSecOps robusto en Linux usando exclusivamente herramientas open source. En esta guía vamos a montar, paso a paso, un pipeline completo con GitHub Actions que integra análisis estático de código (SAST) con Semgrep, detección de secretos con Gitleaks y escaneo de vulnerabilidades con Trivy. Todo funcional, todo con código que puedes copiar hoy mismo a tu repositorio.
Arquitectura del pipeline: las cuatro puertas de seguridad
Antes de escribir una sola línea de YAML, necesitas tener clara la estructura. Un pipeline DevSecOps maduro implementa controles en cuatro puntos críticos:
- Pre-commit (local): hooks que bloquean secretos antes de que lleguen al repositorio
- SAST (análisis estático): escaneo del código fuente buscando patrones de vulnerabilidad
- Secret scanning: detección de credenciales, tokens y claves expuestas
- SCA + Container scanning: análisis de dependencias e imágenes Docker en busca de CVEs conocidos
Si cualquiera de estas puertas detecta un hallazgo crítico, el pipeline se detiene. Punto. El código no llega a producción.
Eso es enforcement real — la diferencia entre monitorear problemas y realmente impedirlos. He visto equipos que tienen dashboards de seguridad preciosos con docenas de alertas, pero ningún control que bloquee el merge. Eso no es DevSecOps, es decoración.
Requisitos previos
Para seguir esta guía necesitas:
- Un repositorio en GitHub (público o privado)
- Un proyecto con Dockerfile (usaremos un ejemplo con Node.js, pero funciona con cualquier stack)
- Linux como entorno de desarrollo (Ubuntu 22.04/24.04 o equivalente)
- Conocimientos básicos de Git y YAML
Nada más. No necesitas licencias ni productos comerciales.
Paso 1: Configurar Gitleaks como pre-commit hook
Gitleaks v8.30 (marzo 2026) detecta más de 160 tipos de secretos y ahora puede escanear recursivamente dentro de archivos comprimidos. Empezamos instalándolo localmente y configurando un hook que bloquee commits con credenciales antes de que toquen el historial de Git.
¿Por qué empezar aquí y no directamente en el CI? Porque cuanto antes atrapes un secreto, menos daño hace. Si un token llega al historial de Git (aunque lo borres después), está potencialmente comprometido.
Instalación en Linux
# Instalación con Homebrew (recomendado)
brew install gitleaks
# O descarga directa del binario
GITLEAKS_VERSION="8.30.1"
wget https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz
tar -xzf gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz
sudo mv gitleaks /usr/local/bin/
Configurar el pre-commit hook
# Crear el hook en tu repositorio
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "Escaneando secretos con Gitleaks..."
gitleaks git --pre-commit --staged --verbose
if [ $? -ne 0 ]; then
echo "BLOQUEADO: Se detectaron secretos. Elimínalos antes de hacer commit."
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
Personalizar reglas de detección
Crea un archivo .gitleaks.toml en la raíz del proyecto para excluir falsos positivos y añadir patrones personalizados. Esto es más importante de lo que parece — sin una buena configuración, vas a acabar ignorando las alertas por fatiga:
# .gitleaks.toml
[extend]
useDefault = true
[[rules]]
id = "internal-api-token"
description = "Token interno de API"
regex = '''INTERNAL_TOKEN_[A-Z0-9]{32}'''
tags = ["token", "internal"]
[allowlist]
paths = [
'''(.*?)test(.*?)''',
'''(.*?)\.example''',
"docs/",
]
Paso 2: Análisis estático con Semgrep
Semgrep es, en mi opinión, la herramienta SAST que más tracción ha ganado en 2026. Y no es casualidad. Su versión de abril incluye detección de vulnerabilidades con IA en beta, análisis de alcanzabilidad (reachability) para 12 lenguajes y reglas para los OWASP Top Ten actualizadas. Lo que lo hace especial es la velocidad — un escaneo típico tarda menos de 2 minutos — y la posibilidad de escribir reglas personalizadas sin volverte loco con la sintaxis.
Instalación y primer escaneo local
# Instalar Semgrep
pip install semgrep
# Ejecutar un escaneo rápido con reglas de seguridad
semgrep scan --config=p/security-audit --config=p/owasp-top-ten .
# Escaneo estricto que falla ante hallazgos críticos (enforcement)
semgrep scan --config=p/security-audit \
--error \
--severity ERROR \
--sarif --output=semgrep-results.sarif .
La diferencia clave está en el flag --error. Sin él, Semgrep reporta pero no bloquea. Con él, cualquier hallazgo de severidad ERROR hace que el proceso devuelva un código de salida distinto de cero — que es exactamente lo que necesitamos para que GitHub Actions marque el job como fallido. Parece un detalle menor, pero es lo que separa un escaneo informativo de un control real.
Reglas personalizadas para tu proyecto
Supongamos que tu equipo usa una función de logging interna que nunca debería recibir datos de usuario sin sanitizar. Puedes crear una regla Semgrep específica para eso:
# .semgrep/custom-rules.yml
rules:
- id: unsafe-log-user-input
patterns:
- pattern: logger.info($USER_INPUT)
- pattern-not: logger.info(sanitize($USER_INPUT))
message: "No registrar entrada de usuario sin sanitizar — riesgo de log injection"
languages: [python, javascript, typescript]
severity: WARNING
metadata:
category: security
cwe: "CWE-117: Improper Output Neutralization for Logs"
Este tipo de reglas personalizadas son las que realmente marcan la diferencia. Las reglas genéricas atrapan lo obvio, pero las tuyas atrapan los errores específicos de tu codebase.
Paso 3: Escaneo de vulnerabilidades con Trivy
Trivy v0.69.3 (la versión segura actual a abril de 2026) escanea imágenes de contenedor, sistemas de archivos, dependencias, configuración IaC y genera SBOMs. Es el escáner más adoptado del ecosistema open source, y honestamente, su integración con GitHub Actions es de las más sencillas que he configurado.
Escaneo local de imagen Docker
# Escanear una imagen Docker
trivy image --severity HIGH,CRITICAL mi-aplicacion:latest
# Escanear el sistema de archivos del proyecto (dependencias)
trivy fs --severity HIGH,CRITICAL .
# Escanear archivos de Infrastructure as Code
trivy config .
# Generar un SBOM en formato CycloneDX
trivy image --format cyclonedx --output sbom.json mi-aplicacion:latest
Escaneo de configuración de Dockerfile
Trivy no solo busca CVEs — también detecta malas prácticas en tus Dockerfiles. Ejecutar procesos como root, usar imágenes base sin fijar versión, exponer puertos innecesarios... Todo eso lo pilla:
# Escanear específicamente la configuración del Dockerfile
trivy config --file-patterns "Dockerfile" --severity HIGH,CRITICAL .
Paso 4: El workflow completo de GitHub Actions
Bueno, ahora viene la parte central: integrar todo en un workflow de GitHub Actions que se ejecute automáticamente en cada push y pull request. Este workflow implementa las cuatro puertas de seguridad con paralelización para que el tiempo total no se dispare.
# .github/workflows/devsecops-pipeline.yml
name: Pipeline DevSecOps
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Escaneo semanal los lunes a las 6:00 UTC
permissions:
contents: read
security-events: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Puerta 1: Detección de secretos (BLOQUEANTE)
secret-scan:
name: "Detección de Secretos"
runs-on: ubuntu-latest
steps:
- name: Checkout del código
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Ejecutar Gitleaks
uses: gitleaks/gitleaks-action@ff98106e6748f37d40c4bb5af02bdc9d4f63f40d # v2.3.9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Puerta 2: Análisis estático SAST (paralelo con puerta 1)
sast-scan:
name: "Análisis SAST con Semgrep"
runs-on: ubuntu-latest
steps:
- name: Checkout del código
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Ejecutar Semgrep
uses: returntocorp/semgrep-action@713efdd345f3035192eaa63f56867b654e8a9571 # v1
with:
config: >-
p/security-audit
p/owasp-top-ten
p/secrets
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
- name: Subir resultados SARIF
if: always()
uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3
with:
sarif_file: semgrep.sarif
# Puerta 3: Escaneo de dependencias y filesystem
dependency-scan:
name: "Escaneo de Dependencias con Trivy"
runs-on: ubuntu-latest
steps:
- name: Checkout del código
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Escanear filesystem con Trivy
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e7b4b01e007e821a # v0.29.0
with:
scan-type: fs
scan-ref: .
format: sarif
output: trivy-fs-results.sarif
severity: HIGH,CRITICAL
exit-code: 1
- name: Subir resultados de dependencias
if: always()
uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3
with:
sarif_file: trivy-fs-results.sarif
category: dependency-scan
# Puerta 4: Escaneo de imagen Docker (depende del build)
container-scan:
name: "Escaneo de Contenedor con Trivy"
runs-on: ubuntu-latest
needs: [secret-scan]
steps:
- name: Checkout del código
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Construir imagen Docker
run: docker build -t ${{ github.repository }}:${{ github.sha }} .
- name: Escanear imagen con Trivy
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e7b4b01e007e821a # v0.29.0
with:
image-ref: "${{ github.repository }}:${{ github.sha }}"
format: sarif
output: trivy-image-results.sarif
severity: HIGH,CRITICAL
exit-code: 1
- name: Subir resultados de imagen
if: always()
uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3
with:
sarif_file: trivy-image-results.sarif
category: container-scan
# Generar SBOM (Software Bill of Materials)
sbom:
name: "Generar SBOM"
runs-on: ubuntu-latest
needs: [container-scan]
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout del código
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Generar SBOM con Trivy
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e7b4b01e007e821a # v0.29.0
with:
scan-type: fs
format: cyclonedx
output: sbom-cyclonedx.json
- name: Subir SBOM como artefacto
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sbom
path: sbom-cyclonedx.json
retention-days: 90
Puntos clave del workflow
Hay varios detalles de seguridad en este workflow que merece la pena destacar (y que muchos tutoriales se saltan):
- Todas las Actions están fijadas por hash SHA, no por tag. Esto previene ataques de cadena de suministro como el incidente de tj-actions/changed-files de 2025, donde un atacante modificó el código detrás de un tag existente. Sí, es más verboso. Pero es la única forma segura.
- Los permisos del GITHUB_TOKEN son mínimos:
contents: readysecurity-events: write. Nada más. Principio de mínimo privilegio aplicado al pie de la letra. - La concurrencia con
cancel-in-progress: trueasegura que si haces múltiples pushes rápidos, solo el último se ejecuta — ahorrando minutos de cómputo y evitando resultados obsoletos. - Los jobs secret-scan, sast-scan y dependency-scan se ejecutan en paralelo. Solo container-scan espera a que secret-scan termine, porque no tiene sentido construir una imagen Docker si ya se detectaron secretos expuestos.
Paso 5: Hardening del propio pipeline
De nada sirve escanear tu código si el pipeline mismo es vulnerable. Esto es algo que mucha gente pasa por alto, y es un error grave. Tu CI/CD es código también, y necesita las mismas protecciones.
Usar OIDC en lugar de secretos estáticos para despliegues cloud
Si tu pipeline despliega a AWS, GCP o Azure, deja de guardar credenciales de larga duración como GitHub Secrets. En serio, deja de hacerlo. Usa OpenID Connect (OIDC) para obtener tokens temporales que expiran automáticamente:
# Ejemplo: Despliegue a AWS usando OIDC
permissions:
id-token: write
contents: read
steps:
- name: Configurar credenciales AWS con OIDC
uses: aws-actions/configure-aws-credentials@ececac1a45ab3e08a01984d8f4b668cc0a669e83 # v4.1.0
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: eu-west-1
# Sin access keys ni secret keys — el token se genera dinámicamente
Monitorizar el egress del runner
Aquí va un dato que sorprende a muchos: los runners de GitHub Actions tienen acceso completo a Internet por defecto. Eso significa que un paso comprometido puede exfiltrar secretos, código fuente o artefactos sin que te enteres. Usa step-security/harden-runner para restringir las conexiones salientes:
steps:
- name: Harden Runner
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
egress-policy: audit # Cambia a 'block' cuando tengas la lista completa
allowed-endpoints: >
api.github.com:443
github.com:443
registry.npmjs.org:443
ghcr.io:443
Mi consejo: empieza en modo audit para descubrir qué endpoints necesita tu pipeline realmente, y luego cambia a block para denegar todo lo que no esté en la lista blanca. No lo hagas al revés o vas a romper cosas.
Verificar la procedencia de las Actions de terceros
Usa OpenSSF Scorecards para evaluar automáticamente las prácticas de seguridad de las Actions que consumes:
# .github/workflows/scorecard.yml
name: Scorecard Supply-Chain Security
on:
schedule:
- cron: '0 3 * * 1'
push:
branches: [main]
permissions:
security-events: write
id-token: write
jobs:
analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ossf/scorecard-action@05b42c624433fc40578a4c7a9795be54ea2a4d78 # v2.4.1
with:
results_file: scorecard-results.sarif
results_format: sarif
- uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3
with:
sarif_file: scorecard-results.sarif
Interpretación de resultados y flujo de trabajo del equipo
Tener un pipeline que detecta problemas es solo el primer paso. Sin un proceso claro para actuar sobre los hallazgos, acabas con un montón de alertas que nadie mira. Créeme, lo he visto pasar.
Así es como debería funcionar:
- Hallazgos CRITICAL/HIGH: bloquean el merge automáticamente. El desarrollador debe corregir antes de que el PR pueda fusionarse. Sin excepciones.
- Hallazgos MEDIUM: aparecen como advertencias en el tab Security de GitHub. Se triagean en la reunión semanal de seguridad.
- Hallazgos LOW/INFO: se registran pero no bloquean. Útiles para mejorar la calidad del código gradualmente, sin generar fricción innecesaria.
Todos los resultados en formato SARIF se integran automáticamente con el tab de Security de GitHub, donde puedes asignar hallazgos a desarrolladores específicos, marcarlos como falsos positivos o crear issues de seguimiento. Es una integración que funciona sorprendentemente bien.
Métricas clave para medir la madurez del pipeline
Una vez que tu pipeline está funcionando, necesitas medir si realmente está haciendo su trabajo. Estas son las métricas que importan:
- Tiempo medio de remediación (MTTR): ¿cuántos días pasan desde que se detecta una vulnerabilidad hasta que se corrige? Si estás por encima de 7 días para hallazgos críticos, tienes un problema de proceso, no de herramientas.
- Tasa de falsos positivos: si supera el 20%, los desarrolladores van a empezar a ignorar las alertas. Es inevitable. Ajusta las reglas de Semgrep y los filtros de severidad antes de que eso pase.
- Cobertura de escaneo: porcentaje de repositorios que tienen el pipeline activo. Apunta al 100% de los repos en producción.
- Dependencias desactualizadas: la mediana del ecosistema está en 278 días (sí, ese número de antes). Si reduces eso a menos de 90, ya estás bastante por delante de la mayoría.
Preguntas frecuentes
¿Cuál es la diferencia entre SAST y SCA en un pipeline DevSecOps?
SAST (Static Application Security Testing) analiza el código fuente que tú escribes, buscando patrones de vulnerabilidad como SQL injection, XSS o log injection. SCA (Software Composition Analysis) analiza las dependencias de terceros — librerías, paquetes y frameworks — comparándolas contra bases de datos de CVEs conocidos. Necesitas ambos, porque el código vulnerable puede estar tanto en tu lógica como en las dependencias que importas. En nuestro pipeline, Semgrep cubre el SAST y Trivy cubre el SCA.
¿Cuánto tiempo añade un pipeline DevSecOps al CI/CD?
Con la configuración de esta guía, los tiempos típicos son: Gitleaks unos 30 segundos, Semgrep alrededor de 2 minutos, Trivy filesystem cerca de 1 minuto y Trivy image scan unos 3 minutos. Al ejecutar los tres primeros jobs en paralelo, el tiempo total añadido es de aproximadamente 3-4 minutos. Con la concurrencia configurada para cancelar ejecuciones obsoletas, el impacto real en tu flujo de trabajo es mínimo.
¿Es necesario usar Gitleaks si GitHub ya ofrece secret scanning?
Buena pregunta. GitHub Secret Scanning es útil pero tiene limitaciones: solo funciona en repositorios públicos de forma gratuita y cubre un conjunto específico de patrones de proveedores. Gitleaks te permite definir reglas personalizadas para secretos internos, escanear el historial completo del repositorio y funcionar como pre-commit hook local — deteniendo los secretos antes de que lleguen a GitHub. Lo ideal es usar ambos como capas complementarias de defensa.
¿Cómo evito que los falsos positivos saturen las alertas del equipo?
Tres estrategias concretas. Primero, usa el archivo .gitleaks.toml y las directivas nosemgrep para excluir falsos positivos conocidos y documentados. Segundo, configura filtros de severidad (--severity HIGH,CRITICAL) para que solo los hallazgos graves bloqueen el pipeline. Y tercero, revisa los falsos positivos semanalmente y actualiza las configuraciones para reducir el ruido de forma progresiva. No es un "configúralo y olvídate" — requiere mantenimiento constante.
¿Por qué es importante fijar las GitHub Actions por hash SHA en lugar de por tag?
Los tags de Git son mutables — un atacante que comprometa el repositorio de una Action puede modificar el código detrás de un tag existente (como v2 o v3) sin que nadie lo note. Esto es exactamente lo que ocurrió en el incidente de tj-actions/changed-files en 2025 y con la versión maliciosa de Trivy v0.69.4 en marzo de 2026. Al fijar por hash SHA, refieres un commit inmutable específico. Es más verboso, sí, pero es la única forma de garantizar que ejecutas exactamente el código que revisaste.