Corre en push y pull_request, antes del job build (needs: gitleaks). Si detecta un secreto, exit-code=1 detiene el pipeline antes de construir la imagen. Scan histórico completo del repo (249 commits) confirmado limpio, corrido aparte de forma manual. Documenta el step en docs/devsecops/gitleaks.md: qué es secret scanning, por qué corre antes del build, cómo leer un hallazgo y cómo manejar falsos positivos con allowlist.
5.7 KiB
Gitleaks — detección de secretos
!!! info "Qué problema resuelve"
Gitleaks busca patrones de credenciales (API keys, tokens, contraseñas,
llaves privadas) dentro del código fuente. No sabe si una credencial es
"real" — detecta formas que parecen credenciales (una API key de
AWS siempre empieza con AKIA, una llave privada siempre tiene el
encabezado -----BEGIN PRIVATE KEY-----, etc.) y también cadenas con
entropía alta (aleatoriedad), que suelen ser tokens generados.
Por qué esto importa
Un secreto commiteado a git nunca deja de estar ahí, aunque lo borres en el siguiente commit. Sigue existiendo en el historial, en cualquier fork, en cualquier clon local que alguien ya haya hecho. La única manera real de "revocar" un secreto filtrado es rotarlo (generar uno nuevo e invalidar el viejo) — borrar el commit no alcanza.
Por eso el objetivo de gitleaks no es "arreglar" el secreto después de que se filtró, sino evitar que el commit con el secreto llegue a existir en el repo remoto.
Por qué corre antes del build
En .gitea/workflows/build.yaml, el job gitleaks corre antes que el
job build (que compila la imagen Docker y la sube al registry). El job
build tiene needs: gitleaks — si el scan falla, build ni siquiera
arranca.
flowchart LR
A[push / pull_request] --> B[gitleaks]
B -- "sin hallazgos" --> C[build]
B -- "secreto detectado" --> X["❌ pipeline detenido<br/>build no corre"]
La lógica es simple: no tiene sentido gastar tiempo de build y minutos de runner compilando una imagen a partir de un commit que de todas formas hay que rechazar. Fallar rápido, fallar barato.
También corre en pull request, no solo en push a main — así un
secreto se detecta antes de que el PR se mergee, que es el punto donde
todavía es más fácil corregirlo (basta con un git commit --amend o un
nuevo commit en la misma rama, sin tocar main).
Alcance de este scan
El step de CI escanea el árbol de archivos ya checked out del commit
(gitleaks detect --no-git), no el historial completo — el checkout del
pipeline es superficial (fetch-depth: 1, solo el último commit), así que
no hay historial que recorrer en ese punto.
Antes de integrar esto al pipeline corrimos un scan histórico completo
del repo apps-registry (los 249 commits, con gitleaks detect en modo
git normal, sin --no-git) para confirmar que no había secretos ya
commiteados en el pasado. Resultado: sin hallazgos. Ese scan histórico
es una tarea puntual, no algo que corra en cada push — si alguna vez se
sospecha una filtración vieja, se repite manualmente.
Cómo leer un hallazgo
Un hallazgo de gitleaks (en el gitleaks-report.json que el pipeline
publica como artifact) se ve así:
{
"Description": "AWS Access Key",
"StartLine": 14,
"File": "workloads/ecommerce/lib/config.ts",
"Match": "REDACTED",
"Secret": "REDACTED",
"RuleID": "aws-access-token",
"Commit": "a1b2c3d"
}
Campos clave:
| Campo | Qué significa |
|---|---|
RuleID |
Qué tipo de secreto detectó (la regla que hizo match) |
File / StartLine |
Dónde está, exactamente |
Match / Secret |
El valor detectado — el pipeline usa --redact, así que en el reporte real aparece censurado, no en texto plano |
Commit |
En qué commit se introdujo (solo aplica al scan histórico, no al scan --no-git del pipeline) |
!!! danger "Si el hallazgo es real"
1. No lo borres del código y listo — el secreto sigue "filtrado"
aunque ya no esté en el archivo actual.
2. Rota la credencial primero en el sistema que la emitió (AWS,
Gitea, Medusa, lo que sea). Un secreto que ya se vio en un log de
CI o en un diff de PR se trata como comprometido.
3. Después de rotarla, sí, saca el valor viejo del código y usa una
variable de entorno / secret de Gitea Actions en su lugar.
4. Si el secreto llegó a estar en main (no solo en una rama de PR),
avisa antes de reescribir historial — reescribir historial en un
repo compartido tiene sus propios riesgos y hay que decidirlo con
calma, no como reacción automática del pipeline.
Falsos positivos: cómo hacer allowlist
Gitleaks detecta formas, no intención. Cosas que típicamente generan falsos positivos en este proyecto:
- Placeholders como
CAMBIARencommerce/secrets.template.yaml— no deberían disparar nada porque no tienen la forma de un secreto real (baja entropía, texto plano legible), pero si algún día se usa un placeholder con más pinta de secreto real (ej. un UUID de ejemplo), sí puede hacer match. - Hashes largos o IDs opacos que no son secretos (ej.
MEDUSA_REGION_IDenfrontend.yaml), si tienen entropía suficientemente alta.
Cuando gitleaks marca algo que no es un secreto real, se agrega una
regla de allowlist en un archivo .gitleaks.toml en la raíz del repo
(todavía no existe — se crea la primera vez que haga falta):
[allowlist]
description = "Falsos positivos conocidos del lab"
regexes = [
'''MEDUSA_REGION_ID''',
]
paths = [
'''workloads/ecommerce/commerce/secrets\.template\.yaml''',
]
!!! warning "No es una vía rápida para ignorar hallazgos reales" Cada entrada de allowlist debe quedar documentada (por qué es un falso positivo, no solo "molestaba") y revisada antes de mergear, porque una allowlist mal escrita (una regex demasiado amplia) puede silenciar un secreto real futuro sin que nadie se dé cuenta.
Dónde ver el resultado
El job gitleaks publica el reporte JSON como artifact del pipeline
(gitleaks-report) en cada ejecución, tenga o no hallazgos — así queda
disponible para inspección incluso cuando el scan pasa limpio.