Secretos en repositorios: cómo se filtran y cómo evitarlo
Una clave de API subida a GitHub se explota en minutos y borrarla del commit no basta. Cómo se filtran, cómo frenarlos antes del push y qué hacer después.
- secretos
- git
- github
- devsecops
- gitleaks
- credenciales
Un secreto (clave de API, token, contraseña de base de datos) subido a un repositorio público hay que darlo por comprometido desde el primer segundo. Hay bots que leen el flujo de commits públicos de GitHub en tiempo real buscando patrones de claves. Y borrar la línea en el commit siguiente no arregla nada: la clave sigue en el historial. Lo único que funciona es revocarla y rotarla.
GitGuardian, en su informe State of Secrets Sprawl de 2025, contó más de 23 millones de secretos nuevos expuestos en commits públicos de GitHub durante 2024. Y no es solo cosa de proyectos personales: muchos salen de repositorios de empresas.
Por dónde se escapan
- El
.envque no estaba en.gitignore, o que se añadió al.gitignoredespués de haberlo commiteado una vez. - La clave "temporal" en el código para probar algo rápido, que se queda.
- Ficheros de configuración de ejemplo con valores reales en vez de marcadores.
- Logs y salidas de CI que imprimen variables de entorno.
- Un repositorio privado que se hace público años después, con todo su historial.
Detectarlos antes del push
El mejor momento para encontrar un secreto es antes de que salga de tu máquina. Un hook de pre-commit con gitleaks revisa cada commit y lo bloquea si encuentra algo:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.4
hooks:
- id: gitleaks
En el propio GitHub, push protection del secret scanning rechaza el push si detecta un patrón de un proveedor conocido, y está activo por defecto en los repositorios públicos desde 2024. En este mismo portfolio hay un test de PHPUnit (NoSecretsTest) que recorre los ficheros versionados y falla si encuentra una clave de API. Un hook local se puede saltar con --no-verify; un test que corre en CI, no.
Revisar el historial que ya existe
Para repositorios con años de historia, gitleaks y TruffleHog escanean todos los commits. TruffleHog además puede verificar si una clave encontrada sigue siendo válida, lo que ayuda a priorizar.
gitleaks detect --source . --log-opts="--all"
Si ya se ha filtrado
- Revocar y rotar primero. Antes que limpiar el historial. Cada minuto cuenta.
- Revisar los logs del servicio afectado para ver si alguien la ha usado desde que se subió. En una clave de AWS, CloudTrail filtrando por ese access key ID.
- Limpiar el historial con
git filter-reposi el repositorio es tuyo, sabiendo que cualquier fork o clon anterior conserva la clave. Por eso el paso 1 va primero. - Buscar la causa para que no se repita: ¿faltaba el
.gitignore? ¿no había hook?
La solución de fondo: que no haya secretos que filtrar
Cuantos menos secretos estáticos existan, menos hay que proteger. Un gestor de secretos (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault) en lugar de ficheros, y en CI/CD federación OIDC en vez de claves: GitHub Actions puede pedir credenciales temporales a AWS o Azure sin guardar ninguna clave en el repositorio. Y la opción más segura sigue siendo no necesitar credenciales: kev-digest solo lee un catálogo público, así que corre en Actions sin ningún secreto de terceros que proteger.
Preguntas frecuentes
¿Basta con hacer el repositorio privado?
No. Cualquiera con acceso al repositorio, o cualquier integración con permisos de lectura, ve el secreto. Y si alguien lo clonó mientras era público, ya lo tiene.
¿Borrar el commit elimina la clave?
No de forma fiable. El commit puede seguir accesible por su hash, en forks, en cachés y en clones. Hay que revocar la clave siempre.
¿Qué herramienta uso para empezar?
gitleaks como hook de pre-commit y en CI, más push protection en GitHub. Las dos son gratuitas.