7 min de lectura

En el pipeline, no todo debe bloquear

Cómo introducir controles de seguridad en CI/CD sin frenar al equipo. Qué bloquear desde el primer día, qué medir antes de bloquear y cómo endurecer por etapas.

Hay una forma rápida de que un equipo deje de confiar en los controles de seguridad: activar todos los escáneres a la vez, en modo bloqueante, un lunes. El martes el pipeline está rojo por cuatrocientas alertas de dependencias, la mitad sin parche disponible y otras en código que no se ejecuta. El miércoles alguien agrega allow_failure: true y el control deja de existir, aunque siga corriendo.

La alternativa es decidir qué bloquea, cuándo y con qué criterio, y llegar ahí por etapas.

Por qué falla el “todo bloquea”

Cada herramienta tiene un perfil de ruido distinto:

  • Escaneo de secretos (gitleaks y similares): pocos falsos positivos una vez ajustado, y cada verdadero positivo es grave.
  • Dependencias y contenedores (SCA, Trivy): muchos resultados reales, pero una parte no tiene corrección disponible o afecta a código que la aplicación no usa.
  • SAST: útil, ruidoso al inicio y muy dependiente de las reglas elegidas para el lenguaje y el framework.
  • Infraestructura como código: mezcla de problemas reales y de recomendaciones que no aplican a su contexto.

Tratar todo igual hace que lo grave se pierda entre lo discutible. Y un control que el equipo percibe como arbitrario termina desactivado.

Etapa 1: lo que bloquea desde el primer día

Hay una sola categoría que no admite período de prueba: secretos nuevos en el código. Una clave que llega al repositorio debe rotarse aunque se borre al minuto siguiente.

# .gitlab-ci.yml
# Versión fija de ejemplo: actualícela con Renovate o Dependabot, nunca con :latest.
.gitleaks:
  stage: test
  image: { name: zricethezav/gitleaks:v8.21.2, entrypoint: [""] }
  variables:
    GIT_DEPTH: 0   # historial completo, para que exista el commit base

secretos-mr:
  extends: .gitleaks
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - gitleaks git --log-opts="$CI_MERGE_REQUEST_DIFF_BASE_SHA..$CI_COMMIT_SHA" --redact --exit-code 1

secretos-rama-principal:
  extends: .gitleaks
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - gitleaks git --log-opts="$CI_COMMIT_BEFORE_SHA..$CI_COMMIT_SHA" --redact --exit-code 1

Las rules importan: fuera de un merge request, CI_MERGE_REQUEST_DIFF_BASE_SHA está vacía, el rango no contiene commits y el control pasa en silencio. Por eso el trabajo de los merge requests solo corre en ellos, y la rama principal tiene su propio trabajo para lo que llegue por push directo. Escanear solo el rango de commits nuevos bloquea lo que se introduce ahora, sin castigar al equipo por lo que ya estaba. El historial completo se revisa una vez, aparte, y lo que aparezca se rota.

En la misma etapa conviene incluir lo que también es binario: pruebas automatizadas de autorización. Si un endpoint de administración responde 200 sin token, el cambio no debe pasar.

Etapa 2: medir antes de bloquear

Para dependencias, contenedores, SAST e IaC, el primer paso es correr en modo observación: el resultado se publica como artefacto o comentario, pero el pipeline no falla.

dependencias:
  stage: test
  image: { name: aquasec/trivy:0.57.1, entrypoint: [""] }   # versión fija de ejemplo
  script:
    - trivy fs --scanners vuln --severity CRITICAL,HIGH --format table --exit-code 0 .
  allow_failure: true

Durante una o dos semanas se revisa qué aparece y se ajusta con el equipo:

  • qué reglas de SAST no aplican al framework;
  • qué carpetas no se escanean (pruebas, fixtures, código generado);
  • qué hallazgos se aceptan temporalmente, con fecha de vencimiento y responsable.

Las excepciones van en un archivo versionado (.trivyignore, .gitleaksignore, la configuración del SAST), con un comentario que explique por qué. Una excepción sin motivo ni fecha es una vulnerabilidad sin dueño.

Etapa 3: bloquear por severidad y por novedad

Con el ruido ajustado, el criterio de bloqueo suele quedar así:

Control Bloquea Advierte
Secretos Siempre —
Pruebas de autorización Siempre —
Dependencias y contenedores Critical con corrección disponible High, y Critical sin corrección
SAST Reglas de alta confianza acordadas El resto
IaC Recursos públicos, cifrado desactivado Recomendaciones

Dos ideas hacen que funcione. La primera, bloquear lo nuevo y gestionar lo heredado: un cambio no debería fallar por una vulnerabilidad que ya estaba en la rama principal; esa se gestiona como deuda con su propio plan. La segunda, bloquear solo lo que se puede corregir: si no hay versión parcheada, fallar el pipeline no reduce el riesgo; hay que mitigarlo de otra forma y registrarlo.

Con Trivy, por ejemplo:

trivy image --severity CRITICAL --ignore-unfixed --exit-code 1 "$IMAGEN"

Ojo: este comando también falla por los Critical que ya traía la imagen base. Antes de activar el bloqueo, registre esos Critical heredados en .trivyignore, cada uno con su fecha de vencimiento y su responsable, para que el control bloquee solo lo nuevo.

Lo que tampoco puede faltar: volver atrás

Un pipeline más estricto reduce lo que llega a producción, pero no lo elimina. La otra mitad del trabajo es poder revertir rápido: despliegues blue-green o rolling con verificación de salud, y una reversión que no dependa de que alguien recuerde un comando a las once de la noche.

Operando CI/CD con despliegues blue-green, nuestro fundador midió 0 peticiones perdidas de 455 durante un despliegue de prueba. La cifra importa menos que el hábito: si desplegar y revertir es aburrido, el equipo despliega cambios pequeños, y los cambios pequeños se revisan mejor.

Cómo saber si funciona

Tres señales sencillas:

  • el equipo no pide desactivar controles, pide ajustarlos;
  • las excepciones tienen fecha y se revisan;
  • los hallazgos que aparecen en un pentest ya no son de las clases que el pipeline vigila.

Si la tercera no se cumple, el pipeline está midiendo lo que es fácil de medir y no lo que importa. Ahí es donde una prueba ofensiva periódica y un pipeline bien ajustado se complementan.

Solo sobre sistemas propios o autorizados.

Las técnicas de esta nota se aplican sobre sistemas de los que usted es titular o con permiso por escrito del titular. Probar sistemas ajenos sin permiso es un delito en Ecuador (COIP, art. 234) y en la mayoría de los países.

Más notas

¿Quiere recibir las próximas? Siga el RSS.

Siguiente paso

¿Quiere que lo revisemos en su sistema?

Esta nota es parte de lo que probamos en el servicio de DevSecOps. Empezamos con una llamada gratuita de 30 minutos.