Extensión del laboratorio devsecops-pipeline para la ruta
Analista DevSecOps. No sustituye al recorrido guiado de las
ocho capas: lo continúa. El recorrido base termina cuando los escáneres han hablado; el trabajo
del analista empieza exactamente ahí.
⚠️ Todo lo que hay en
repo-vulnerable/es inseguro a propósito y sus credenciales son falsas. No lo despliegues ni reutilices su código. Practica solo aquí o en repositorios tuyos o con autorización explícita (Clase 025).
1. Recibir la salida de varios escáneres → formatos distintos, mismo problema
2. Normalizar y deduplicar → un hallazgo es un hallazgo, no tres
3. Separar los falsos positivos → con argumento y por escrito
4. Priorizar → KEV, EPSS, CVSS, exposición y criticidad
5. Crear tickets y acordar SLA → con dueño, fecha y criterio de verificación
6. Documentar una excepción → responsable, vencimiento y compensación
7. Verificar la corrección → ¿se arregló o se dejó de mirar?
8. Reportar riesgo y métricas → dos vistas: desarrollo y dirección
| Paso | Clases del programa |
|---|---|
| Escáneres y sus límites | 238, 239, 240, 241 |
| Priorización y escala | 245, 318 |
| Inventario y respuesta | 246 |
| Riesgo y excepciones | 277, 282, 284 |
| Reporte y cultura | 321, 248 |
Ejecuta el recorrido base completo para tener materia prima real:
cd labs/devsecops-pipeline
docker compose build && docker compose up -d
docker compose exec auditor ./auditar.sh # las ocho capas -> salida/
Trabaja sobre lo que quede en salida/. Si alguna capa aparece como NO EJECUTADA, anótalo:
esa es la primera línea de tu informe de cobertura, y distinguirlo de "sin hallazgos" es la
diferencia entre un analista y un generador de PDF.
Vas a tener, como mínimo, cinco fuentes con cinco vocabularios distintos:
| Fuente | Qué reporta | Identificador que usa | Su punto ciego |
|---|---|---|---|
| SCA (dependencias) | CVE en paquetes de terceros | CVE + paquete + versión | No sabe si tu código llama a esa función |
| SAST (código propio) | Patrones peligrosos en tu código | Regla + archivo + línea | No sabe si la entrada es controlable |
| Secretos | Credenciales en código e historial | Tipo de secreto + commit | No sabe si el secreto sigue siendo válido |
| Dockerfile / imagen | Malas prácticas y CVE del sistema base | Regla o CVE + capa | No ve lo que hace el proceso al arrancar |
| Workflows CI/CD | Permisos, acciones sin fijar, inyección | Regla + archivo | No ve la configuración del repositorio |
Primera decisión profesional: no mezcles todavía. Un hallazgo de SAST y una CVE de dependencia se priorizan con criterios distintos aunque acaben en el mismo backlog.
El objetivo es un registro único por problema real, no por línea de salida.
Modelo mínimo de normalización —te sirve una hoja de cálculo o un JSON como el de
hallazgos-ejemplo.json:
{
"id_interno": "DS-2026-0042",
"fuentes": ["trivy-fs", "bandit"],
"tipo": "dependencia | codigo | secreto | imagen | iac | ci",
"cve": "CVE-2021-44228",
"componente": "log4j-core 2.14.1",
"ubicacion": "repo-vulnerable/requirements.txt",
"cvss": 10.0,
"exposicion": "publica",
"criticidad_activo": "alta",
"estado": "nuevo | triado | ticket | excepcion | cerrado | verificado",
"decision": "",
"motivo": ""
}
Reglas de deduplicación que debes escribir y poder defender:
📉 En este laboratorio la deduplicación reduce poco, porque el repositorio es pequeño. En un entorno real es donde desaparece la mayor parte del "volumen aterrador" del primer informe.
Tres categorías, no dos. La tercera es la que ahorra más tiempo y la que peor se documenta:
| Categoría | Definición | Qué haces |
|---|---|---|
| Real | Existe y es alcanzable en este contexto | Priorizar |
| Falso positivo | La herramienta se equivocó | Descartar con motivo escrito y suprimir la regla en ese punto |
| Real pero no aplicable | Existe, pero aquí no es explotable | Documentar por qué, con fecha de revisión |
Ejemplos del propio laboratorio para practicar el argumento:
requirements.txt pero cuyo módulo vulnerable nunca se
importa: real, no aplicable. El argumento tiene que apoyarse en el código, no en una intuición.repo-vulnerable/config.py: son falsas a propósito en el laboratorio,
pero el hallazgo es correcto. Si fuera un repositorio real, la acción sería rotar primero y
purgar después.Cada descarte necesita cuatro datos: quién, cuándo, por qué y cuándo se revisa. Un descarte sin registro se vuelve a triar el mes que viene: el registro es el producto.
Convierte tus hallazgos normalizados al formato de hallazgos-ejemplo.json,
declara la exposición real de cada uno y pásalos por el priorizador:
python priorizar.py --hallazgos salida/mis-hallazgos.json --salida salida/plan.md
python priorizar.py --hallazgos salida/mis-hallazgos.json --sin-red # compara
El orden es KEV → EPSS → CVSS, y sobre él actúa el factor de exposición que ninguna herramienta puede calcular por ti:
exposicion |
Factor | Significado |
|---|---|---|
publica |
1.0 | Alcanzable desde internet |
interna |
0.6 | Solo desde la red corporativa |
no-alcanzable |
0.2 | El código afectado no se ejecuta |
desconocida |
0.8 | Sin analizar — no se asume el mejor caso |
Añade tú la quinta señal, la que el script no conoce: la criticidad de negocio del servicio. Documenta la fórmula final que uses. Da igual que sea simple; lo que se evalúa es que sea explícita y reproducible por otra persona.
Dos comprobaciones honestas antes de dar la lista por buena:
--sin-red y compara. Si cambia el orden, tu plan sin KEV ni EPSS es provisional y
debe entregarse marcado como tal, no presentarse como definitivo.exposicion: desconocida. Si son muchos, tu prioridad
número uno no es parchear: es averiguar la exposición.Un ticket que desarrollo puede ejecutar sin volver a preguntarte:
DS-2026-0042 · Actualizar log4j-core 2.14.1 -> 2.17.1
Servicio: facturacion-api Dueño: Equipo Pagos
Por qué ahora: explotación activa confirmada (KEV) y el servicio está expuesto a internet
Acción: fijar 2.17.1 en requirements.txt y regenerar el lockfile
Verificación: el SCA deja de reportar la CVE **y** el arranque del servicio no cambia
de comportamiento en las pruebas de integración
SLA: 7 días naturales (severidad crítica)
Notas: se elige 2.17.1 (mínima corregida), no la última: menos riesgo de romper
Propuesta de SLA para acordar con desarrollo, no para imponer:
| Severidad efectiva | Remediación | Verificación |
|---|---|---|
| Crítica (KEV o expuesto con EPSS alto) | 7 días | 3 días tras el cierre |
| Alta | 30 días | 7 días |
| Media | 90 días | 30 días |
| Baja | Siguiente ciclo de mantenimiento | — |
Un SLA que desarrollo no firmó no es un SLA: es una expectativa tuya que se incumplirá en silencio.
Cuando la corrección no es posible en plazo, la respuesta profesional no es bajar la severidad: es registrar la decisión.
Excepción EXC-2026-007
Hallazgo: DS-2026-0051 · parser-ejemplo 0.9.0, CVE-2023-88888, sin versión corregida
Riesgo: ejecución de código al procesar entrada no confiable (CVSS 8.1, exposición interna)
Solicita: Equipo Pagos Aprueba: Jefatura de Ingeniería
Motivo: no existe versión corregida publicada; sustituir la biblioteca requiere
reescribir el módulo de importación (estimado: 3 semanas)
Compensación: validación estricta de entrada en el borde + el endpoint queda restringido
a la red interna + alerta específica en el SIEM
Vence: 2026-11-30
Al vencer: se revisa obligatoriamente; sin renovación explícita, vuelve a ser bloqueante
Revisión: mensual, responsable del seguimiento: analista DevSecOps
Los cuatro elementos innegociables: responsable, aprobador con autoridad, control compensatorio verificable y fecha de vencimiento. Falta uno y no es una excepción: es un riesgo aceptado en la sombra.
La pregunta que separa el oficio del trámite: ¿el hallazgo desapareció porque se arregló, o porque se dejó de mirar?
Cuatro razones habituales por las que un hallazgo desaparece sin haberse arreglado:
Verificación mínima aceptable para cada cierre:
# 1. Reejecuta la capa concreta y guarda la evidencia
docker compose exec auditor ./auditar.sh deps
# 2. Comprueba que la versión instalada es la corregida (no solo el manifiesto)
docker compose exec auditor pip show <paquete>
# 3. Confirma que el alcance del análisis no cambió: mismo repo, misma rama,
# mismas exclusiones que en la ejecución anterior
Y la prueba que casi nadie hace: el control negativo. Reintroduce temporalmente el patrón vulnerable en una rama de prueba y confirma que el escáner vuelve a detectarlo. Si no lo detecta, tu verificación anterior no valía nada.
Dos vistas del mismo dato. No es duplicar trabajo: es que cada audiencia decida algo distinto.
Vista para desarrollo — qué te toca, en orden, con fecha:
facturacion-api 1 crítica (7 d) · 3 altas (30 d) · 2 excepciones vigentes
portal-web 0 críticas · 1 alta (30 d)
Vista para dirección — una página, con tendencia y una decisión pedida:
| Métrica | Este periodo | Anterior | Tendencia |
|---|---|---|---|
| Hallazgos críticos abiertos | |||
| Edad media de la deuda (días) | |||
| Cumplimiento de SLA | |||
| Falsos positivos entregados a desarrollo | |||
| Cobertura de escaneo (repos / tipos de análisis) | |||
| Excepciones vigentes / vencidas |
Cierra con la sección de cobertura, que es la que da credibilidad a todo lo anterior: qué capas
se ejecutaron, cuáles no y por qué, y qué no cubre este informe. Reutiliza
INFORME-PLANTILLA.md, que ya la incluye.
Por último, mapea tu evidencia contra un marco público —al menos cinco prácticas de NIST SP 800-218 (SSDF) o de OWASP SAMM—. Es lo que convierte tu trabajo técnico en algo que un auditor o un cliente puede aceptar.
--sin-red. Aceptación: explicas qué cambió y por qué el plan degradado se
entrega marcado como provisional.appsec-code ·
appsec-web · cloud-security ·
blue-team-soc