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.

DevSecOps Linux 2026: Pipeline GitHub Actions

Por qué necesitas un pipeline DevSecOps en Linux ahora mismo

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:

  1. Pre-commit (local): hooks que bloquean secretos antes de que lleguen al repositorio
  2. SAST (análisis estático): escaneo del código fuente buscando patrones de vulnerabilidad
  3. Secret scanning: detección de credenciales, tokens y claves expuestas
  4. 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: read y security-events: write. Nada más. Principio de mínimo privilegio aplicado al pie de la letra.
  • La concurrencia con cancel-in-progress: true asegura 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:

  1. Hallazgos CRITICAL/HIGH: bloquean el merge automáticamente. El desarrollador debe corregir antes de que el PR pueda fusionarse. Sin excepciones.
  2. Hallazgos MEDIUM: aparecen como advertencias en el tab Security de GitHub. Se triagean en la reunión semanal de seguridad.
  3. 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.

Sobre el Autor Editorial Team

Our team of expert writers and editors.