Módulo 3: Secrets Management
7. Manos a la obra: escaneando el repositorio por secretos filtrados
Descripción
Todo lo anterior en este módulo asumió que tú, quien escribes el HCL, sigues la disciplina de nunca escribir un valor literal donde corresponde una variable. Esta lección construye la red de seguridad para cuando esa disciplina falla —porque falla, en cualquier equipo, tarde o temprano—: Trivy, corriendo su escáner de secretos (--scanners secret) contra un repositorio real, con una credencial de prueba insertada a propósito en un commit, detectada con una salida que no vas a tener que imaginar — la vas a ver, literal, porque corrió de verdad para escribir esta lección.
Trivy corre 100% real en esta lección, sin cuenta, sin token, sin ninguna de las limitaciones de LocalStack que marcaron representativas las lecciones anteriores de este módulo. Es la primera herramienta de esta guía —junto con conftest en el Módulo 4— que no depende, en ningún punto, de si LocalStack está arriba o no.
Conexión con el módulo
Las lecciones 2 y 3 explicaron por qué un secreto en texto plano es un problema, incluso gitignoreado. Las lecciones 4 y 5 construyeron la alternativa correcta. Esta lección construye la herramienta que confirma, de forma automatizada y repetible, que la alternativa correcta es la que realmente se está usando — no una promesa, una verificación ejecutada. La lección 8 integra exactamente este escaneo como el paso final del proyecto que cierra el módulo.
Paso 1 — Confirmando la versión de Trivy
trivy --version
Qué esperar (literal — ejecutado para escribir esta lección):
Version: 0.74.0
Versión confirmada: Trivy 0.74.0, publicada el 14 de agosto de 2026 — la misma versión que fija el diseño completo de esta guía, y la que corrió cada comando de esta lección.
Paso 2 — Un repositorio desechable, igual que hizo cicd-and-gitops-on-aws-guide
cicd-and-gitops-on-aws-guide, Módulo 4, lección 2, ya estableció el patrón correcto para experimentar con una fuga de credenciales: nunca en andes-cargo-infra/, siempre en un repositorio aparte, desechable, creado para el experimento y descartado después. Esta lección sigue exactamente ese mismo patrón — vas a reproducir, en miniatura, el tipo de archivo que sí forma parte del proyecto real: terraform.tfvars, el archivo que terraform-and-iac-guide, Módulo 3, ya dejó existente en andes-cargo-infra/, listado en *.tfvars dentro de .gitignore desde el Módulo 1 de esa misma guía.
mkdir andes-cargo-infra-secret-scan-demo && cd andes-cargo-infra-secret-scan-demo
git init -b main
Un commit inicial limpio, sin nada sensible:
cat > README.md <<'EOF'
# andes-cargo-infra (demo for secret scanning)
EOF
git add README.md
git commit -m "Initial commit"
Ahora, el error que esta lección va a detectar: alguien, apurado por probar algo rápido contra la API de aduanas, escribe las credenciales directamente en terraform.tfvars y fuerza el add, ignorando el .gitignore que ya las debería haber excluido:
cat > terraform.tfvars <<'EOF'
# customs_api_credentials.tfvars -- accidentally hardcoded instead of read from Secrets Manager
customs_api_username = "andes-cargo-test-user"
customs_api_password = "test-password-do-not-use-in-prod"
# DO NOT DO THIS: a real-shaped AWS credential, hardcoded directly in a committed file
aws_access_key_id = "AKIAZ3TESTFAKEKEY001"
aws_secret_access_key = "wJalrXUtnFEMI/K7MDENGbPxRfiCYzTESTKEY99"
EOF
git add -f terraform.tfvars
git commit -m "Add terraform.tfvars with customs API credentials"
Los dos valores del bloque # DO NOT DO THIS son inventados, con la forma exacta de una clave de acceso de AWS, pero nunca una credencial real — ni de LocalStack (que ni siquiera las valida) ni de ninguna cuenta AWS de verdad. Están ahí a propósito, con la forma correcta, para que el escáner de la siguiente sección tenga algo real que encontrar. git add -f (forzado) es, a propósito, el error exacto que un .gitignore bien configurado no puede evitar por sí solo — la lección 2 de este módulo ya lo advirtió: gitignorar protege contra un add normal, no contra uno forzado explícitamente.
Paso 3 — trivy fs --scanners secret, la salida real
trivy fs --scanners secret .
Qué esperar (literal — ejecutado para escribir esta lección, con Trivy 0.74.0):
2026-08-14T10:03:49-06:00 INFO [secret] Secret scanning is enabled
2026-08-14T10:03:49-06:00 INFO [secret] Please see https://trivy.dev/docs/v0.74/guide/scanner/secret#recommendation for faster secret detection
2026-08-14T10:03:49-06:00 INFO Number of language-specific files num=0
Report Summary
┌──────────────────┬──────┬─────────┐
│ Target │ Type │ Secrets │
├──────────────────┼──────┼─────────┤
│ terraform.tfvars │ text │ 1 │
└──────────────────┴──────┴─────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)
terraform.tfvars (secrets)
==========================
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1)
CRITICAL: AWS (aws-access-key-id)
════════════════════════════════════════
AWS Access Key ID
────────────────────────────────────────
terraform.tfvars:6 (offset: 314 bytes)
────────────────────────────────────────
4
5 # DO NOT DO THIS: a real-shaped AWS credential, hardcoded directly in a committed file
6 [ aws_access_key_id = "********************"
7 aws_secret_access_key = "wJalrXUtnFEMI/K7MDENGbPxRfiCYzTESTKEY99"
────────────────────────────────────────
Léelo con cuidado, línea por línea. Un solo hallazgo, no dos. Trivy detectó el aws_access_key_id (regla aws-access-key-id, severidad CRITICAL) pero no el aws_secret_access_key de la línea siguiente — no es un bug del escáner, es una decisión deliberada de diseño: una clave de acceso de AWS (AKIA seguido de 16 caracteres alfanuméricos en mayúscula) tiene un formato lo bastante específico como para que la regla tenga confianza alta de que es una credencial real; una cadena base64 genérica de 40 caracteres, como una clave secreta de AWS, es indistinguible de cualquier otro hash o token aleatorio sin contexto adicional — marcarla siempre produciría demasiados falsos positivos. El valor detectado aparece redactado ("********************") directamente en la salida de la terminal — Trivy nunca imprime el secreto real que encontró, ni siquiera en el momento de reportarlo.
Fíjate también en el offset exacto (314 bytes) y el número de línea (terraform.tfvars:6) — no es "en algún lugar de este archivo", es la ubicación precisa, la misma información que necesitarías para ir directamente a corregirlo sin tener que leer el archivo entero buscándolo.
Paso 4 — Confirmando que el HCL correcto no dispara ningún hallazgo
Vuelve a andes-cargo-infra/, sobre el secrets.tf y variables.tf reales de las lecciones 4 y 5 — los que usan var.customs_api_username, nunca un valor literal — y corre el mismo comando:
trivy fs --scanners secret .
Qué esperar (literal — ejecutado para escribir esta lección, sobre el secrets.tf/variables.tf de las lecciones 4 y 5):
2026-08-14T10:04:06-06:00 INFO [secret] Secret scanning is enabled
2026-08-14T10:04:06-06:00 INFO [secret] Please see https://trivy.dev/docs/v0.74/guide/scanner/secret#recommendation for faster secret detection
2026-08-14T10:04:06-06:00 INFO Number of language-specific files num=0
2026-08-14T10:04:06-06:00 INFO [report] No issues detected with scanner(s). scanners=[secret]
Report Summary
┌────────┬──────┬─────────┐
│ Target │ Type │ Secrets │
├────────┼──────┼─────────┤
│ - │ - │ - │
└────────┴──────┴─────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)
Cero hallazgos — no porque Trivy no haya mirado (num=0 en "language-specific files" es sobre dependencias, no sobre el escáner de secretos), sino porque no hay nada que encontrar: cada valor sensible de secrets.tf es una referencia var.*, nunca un literal. Este es, exactamente, el contraste que hace que el Paso 3 sea convincente: la misma herramienta, el mismo comando, dos resultados completamente distintos según si la disciplina de las lecciones 4 y 5 se siguió o no.
Por qué el ejemplo de AWS docs (AKIAIOSFODNN7EXAMPLE) no dispara ningún hallazgo
Un detalle real, descubierto corriendo Trivy —no documentado de forma obvia—, vale la pena señalarlo explícitamente: si hubieras usado el valor de ejemplo estándar que la propia documentación de AWS usa en todos sus manuales (AKIAIOSFODNN7EXAMPLE), Trivy no lo habría marcado como hallazgo. Las reglas de secretos de la comunidad —Trivy incluida— mantienen una lista de valores conocidos de documentación oficial (identificables por el sufijo EXAMPLE, entre otros patrones) precisamente para evitar generar una alerta cada vez que alguien copia un fragmento de la documentación oficial de AWS a un archivo de prueba. Es la razón exacta por la que esta lección usó AKIAZ3TESTFAKEKEY001 —con la forma estructural correcta, pero sin el sufijo que activaría el filtro de falsos positivos— para producir un hallazgo real y verificable.
Errores comunes
Correr trivy fs sin --scanners secret y esperar que igual detecte el hallazgo (de flag faltante). Qué pasa: alguien corre trivy fs . a secas y ve una salida completamente distinta —o ninguna mención de secretos—, y concluye que Trivy no funciona. Cómo detectarlo: si tu salida no menciona [secret] Secret scanning is enabled en las primeras líneas. Cómo corregirlo: por defecto, trivy fs puede incluir otros escáneres (vulnerabilidades de dependencias, configuración de IaC —el tema del Módulo 5 de esta guía—) según el contexto; --scanners secret acota explícitamente el análisis al escaneo de secretos, el único que necesita esta lección.
Asumir que Trivy detecta cualquier cadena que "parezca" un secreto (de expectativa sobre el motor). Qué pasa: alguien inserta una contraseña genérica como password = "hunter2" y espera que Trivy la marque, igual que marcó la clave de AWS. Cómo detectarlo: si tu prueba de que Trivy "funciona" usa un valor sin ningún formato reconocible. Cómo corregirlo: Trivy detecta patrones específicos de proveedores y servicios conocidos —claves de AWS, tokens de GitHub, tokens de Slack, y decenas más, cada uno con su propia regla y formato reconocible—; una contraseña genérica sin ningún formato característico no dispara ninguna regla, no porque Trivy "falle", sino porque no hay ningún patrón estructural que la distinga de cualquier otra cadena de texto.
Confiar en el escaneo de Trivy como la única defensa, sin la disciplina de las lecciones 4 y 5 (de capas). Qué pasa: alguien razona que, si Trivy detecta secretos filtrados, ya no hace falta ser cuidadoso al escribir HCL —"si algo se cuela, Trivy lo va a encontrar"—. Cómo detectarlo: si tu plan de seguridad de secretos depende de un solo control, corrido en un solo momento. Cómo corregirlo: Trivy es una red de seguridad, no un sustituto de la disciplina — encuentra lo que ya pasó, después de que pasó (un guardrail más cercano a detectivo que a preventivo, la distinción que el Módulo 7 de esta guía desarrolla a fondo). La combinación correcta es la que este módulo completo enseña: variables en vez de literales primero (lecciones 4 y 5), Trivy como confirmación después (esta lección) — nunca uno en lugar del otro.
Ejercicios
Ejercicio 1 — Reproduce el escaneo con un segundo tipo de secreto. Repite el Paso 2 y 3 de esta lección, pero en vez de una clave de AWS, inserta un token con forma de GitHub Personal Access Token (ghp_ seguido de 36 caracteres alfanuméricos, por ejemplo ghp_1234567890abcdefghijklmnopqrstuvwxyz1234) en un archivo nuevo, notes.txt. Corre trivy fs --scanners secret . y confirma qué regla lo detecta.
Ver solución
echo 'GITHUB_TOKEN=ghp_1234567890abcdefghijklmnopqrstuvwxyz1234' > notes.txt
trivy fs --scanners secret .
Trivy debería detectarlo bajo una regla del tipo github-pat (GitHub Personal Access Token), con severidad alta —el mismo mecanismo de reglas específicas por proveedor que detectó la clave de AWS en esta lección, aplicado ahora a un formato distinto. El patrón es idéntico sin importar el proveedor: Trivy reconoce el prefijo y la longitud característicos de cada tipo de credencial conocida.
Ejercicio 2 — Explica por qué aws_secret_access_key no generó un hallazgo, a un compañero que esperaba dos. Un colega, viendo la salida del Paso 3, pregunta por qué Trivy solo marcó una de las dos líneas de credenciales, si ambas están igual de expuestas en el archivo. Responde usando lo que aprendiste sobre confianza de patrones.
Ver solución
Una respuesta completa suena, más o menos, así: "Ambas líneas están igual de expuestas, pero no todas las credenciales tienen la misma 'huella' reconocible. Una clave de acceso de AWS empieza siempre con AKIA seguido de una estructura fija — eso le da a la regla una confianza alta de que no es un falso positivo. Una clave secreta de AWS es, en cambio, una cadena base64 genérica de 40 caracteres: estructuralmente indistinguible de un hash, un token aleatorio, o cualquier otro dato codificado. Marcarla siempre generaría demasiados falsos positivos, así que el escáner prioriza no hacerlo — el hallazgo de la clave de acceso ya es, en la práctica, la señal suficiente de que ese archivo tiene un problema."
Ejercicio 3 — Diseña la corrección exacta que resolvería el hallazgo del Paso 3. Sin correr ningún comando, escribe los pasos concretos —no solo "no lo hagas"— que llevarían a andes-cargo-infra-secret-scan-demo de tener un hallazgo CRITICAL a tener cero hallazgos, incluyendo qué hacer con el commit que ya tiene la credencial filtrada.
Ver solución
Los pasos correctos, en orden: (1) reemplazar el contenido de terraform.tfvars para que no tenga ningún valor literal de credencial —o, mejor, eliminar el archivo del control de versiones por completo con git rm --cached terraform.tfvars, ya que *.tfvars está en .gitignore precisamente para que nunca debería haberse forzado su inclusión—. (2) Como la clave de esta lección es inventada y nunca fue una credencial real de ninguna cuenta AWS, no hace falta revocar nada —pero si hubiera sido una credencial real, el paso obligatorio, según cicd-and-gitops-on-aws-guide Módulo 4 lección 2, sería revocarla en IAM, sin importar si se reescribe el historial o no—. (3) Volver a correr trivy fs --scanners secret . para confirmar cero hallazgos, exactamente como el Paso 4 de esta lección confirmó sobre el secrets.tf real. El escaneo nunca es el paso final por sí solo — es la verificación de que la corrección funcionó.
Resumen y siguiente paso
En esta lección corriste Trivy 0.74.0 de verdad, con --scanners secret, sobre un repositorio desechable con una credencial de prueba insertada a propósito —una clave de AWS con formato estructural válido pero inventada— y viste la salida literal completa: un hallazgo CRITICAL, regla aws-access-key-id, con línea y offset exactos, el valor redactado en la propia terminal. Confirmaste, con el mismo comando, que el secrets.tf/variables.tf reales de las lecciones 4 y 5 —que nunca usan un literal, solo referencias a variables— no disparan ningún hallazgo. Y entendiste por qué el ejemplo estándar de la documentación de AWS (AKIAIOSFODNN7EXAMPLE) queda deliberadamente excluido de las reglas de detección.
Antes de avanzar deberías poder: correr trivy fs --scanners secret de memoria; explicar por qué una clave de acceso de AWS se detecta y una clave secreta no, en el mismo archivo; y explicar por qué el escaneo es una red de seguridad, no un sustituto de escribir HCL correctamente desde el principio.
La lección 8, el proyecto que cierra este módulo, integra exactamente este escaneo como el paso final de verificación: secrets.tf completo, migrado desde el patrón de .secrets, con el escaneo de esta lección confirmando cero secretos en texto plano en todo el repositorio.
Recursos
- trivy.dev — Secret Scanning — documentación oficial del escáner de secretos, incluida la lista completa de reglas integradas y su lógica de confianza.
- GitHub — aquasecurity/trivy — repositorio oficial, con el código fuente de cada regla de detección (
aws-access-key-identre ellas). cicd-and-gitops-on-aws-guide, Módulo 4, lección 2 (02-why-credentials-never-belong-in-the-repo.md) — el patrón del repositorio desechable que esta lección reutiliza, y la razón por la que revocar (no reescribir el historial) es la respuesta correcta ante una fuga real.terraform-and-iac-guide, Módulo 1, lección 8 (08-project-bootstrapping-andes-cargos-terraform-project.md) — el origen de*.tfvarsen.gitignore, el archivo que esta lección usó como vector del hallazgo.