# 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.

```mermaid
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í:

```json
{
  "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 `CAMBIAR` en `commerce/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_ID`
  en `frontend.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):

```toml
[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.
