seguridad·14 min de lectura

Secrets Expuestos en GitHub: Como Detectar y Proteger tu Proyecto

guía práctica para detectar API keys, tokens y credenciales expuestas en tu repositorio de GitHub. Pre-commit hooks, .gitignore, git history cleanup y herramientas de escaneo automático.

Secrets Expuestos en GitHub: Como Detectar y Proteger tu Proyecto

Cada semana hay historias de desarrolladores que descubren que sus API keys de AWS, Stripe o Firebase estuvieron expuestas en GitHub durante meses. Algunos se enteran por una factura inesperada de miles de dólares. Otros porque un bot ya uso sus credenciales para minar crypto.

El problema no es que sean descuidados. El problema es que un solo git add . descuidado en un momento de prisa es suficiente para exponer credenciales que deberian ser privadas. Y una vez que estan en el historial de git, borrar el archivo no sirve de nada.

Por qué pasa esto

La mayoria de las filtraciones de secrets no son por ignorancia. Son por descuido en situaciones especificas:

1. El .env que se commiteo por error

El caso más común. Creas un proyecto, configuras tus variables de entorno, y en algún momento haces git add . sin verificar. El .env se sube, y aunque lo borres después, queda en el historial.

bash
# Esto pasa más de lo que crees
git add .
git commit -m "initial setup"
git push origin main
# Tu .env con STRIPE_SECRET_KEY ya esta en GitHub

2. Credenciales hardcodeadas "temporalmente"

Estas probando algo rápido, pones la API key directamente en el código "solo para probar", y se te olvida quitarla antes del commit.

code
// "Solo por ahora, después lo muevo a .env"
const stripe = new Stripe("sk_live_abc123xyz789")
 
// 6 meses después: sigue ahi

3. Archivos de configuración con secrets

Firebase configs, archivos .pem, serviceAccount.json, archivos de Docker Compose con passwords en texto plano.

yaml
# docker-compose.yml que se commiteo
services:
  db:
    environment:
      POSTGRES_PASSWORD: miPasswordReal123
      POSTGRES_USER: admin

4. Logs y outputs en el repo

Archivos de debug, respuestas de API guardadas como fixture, o notebooks de Jupyter con tokens en las celdas de output.

Que puede pasar

No es exageración. Hay bots que escanean los eventos publicos de GitHub en tiempo real buscando patrones de API keys.

AWS keys expuestas: En menos de 5 minutos, un bot puede detectar una AKIA... key y levantar instancias EC2 para minar criptomonedas. Desarrolladores han reportado facturas de miles de dólares de la noche a la mañana.

Stripe keys: Con una sk_live_ key, un atacante puede hacer refunds, ver datos de clientes, o manipular tu cuenta.

Database URLs: Una connection string expuesta da acceso directo a tu base de datos. Datos de usuarios, transacciones, todo.

Tokens de OAuth: Si tu CLIENT_SECRET de Google o GitHub se filtra, cualquiera puede hacer requests a nombre de tu aplicación.

Como detectar secrets en tu repositorio

búsqueda manual con grep

Puedes buscar patrones conocidos de API keys en tu repositorio:

bash
# Buscar patrones comunes de API keys
grep -rn "sk_live_\|sk_test_\|AKIA\|AIza\|ghp_\|gho_\|Bearer " --include="*.ts" --include="*.tsx" --include="*.js" --include="*.json" .
 
# Buscar en archivos que no deberian estar trackeados
git ls-files | grep -i "\.env\|\.pem\|secret\|credential\|serviceAccount"

Esto funciona para lo obvio, pero tiene limitaciones: no revisa el historial de git, no detecta patrones menos comunes, y depende de que recuerdes hacerlo.

Buscar en el historial de git

El problema real no es lo que esta en el código ahora, sino lo que estuvo en algún commit anterior:

bash
# Buscar una key específica en todo el historial
git log -p --all -S 'sk_live_'
 
# Buscar archivos .env en cualquier commit
git log --all --diff-filter=A -- "*.env" ".env.*"
 
# Ver todos los archivos que alguna vez existieron
git log --all --name-only --diff-filter=A | grep -i "env\|secret\|key\|credential" | sort -u

Si alguno de estos comandos devuelve resultados, tienes un problema.

Pre-commit hooks para prevenir

La mejor defensa es prevenir que los secrets lleguen al commit. Puedes usar un hook de pre-commit que detecte patrones de credenciales:

bash
# .git/hooks/pre-commit
#!/bin/sh
 
# Patrones de API keys conocidas
PATTERNS="sk_live_|sk_test_|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|ghp_[0-9a-zA-Z]{36}|password\s*=\s*['\"][^'\"]+['\"]"
 
# Revisar solo los archivos que se van a commitear
FILES=$(git diff --cached --name-only --diff-filter=ACM)
 
if echo "$FILES" | xargs grep -lE "$PATTERNS" 2>/dev/null; then
  echo "Se detectaron posibles secrets en los archivos staged."
  echo "Revisa los archivos listados arriba antes de commitear."
  exit 1
fi

Hazlo ejecutable:

bash
chmod +x .git/hooks/pre-commit

El problema de los hooks locales es que cada desarrollador tiene que configurarlos manualmente. En un equipo, alguien siempre se lo va a saltar.

.gitignore correcto desde el inicio

Tu .gitignore debería incluir al menos esto:

gitignore
# Environment variables
.env
.env.*
.env.local
.env.production
.env.staging
 
# Keys y certificados
*.pem
*.key
*.p12
*.pfx
 
# Service accounts
serviceAccount*.json
firebase-adminsdk*.json
 
# IDE y OS (pueden contener paths con secrets)
.idea/
.vscode/settings.json
 
# Docker
docker-compose.override.yml
Importante

Si un archivo ya fue commiteado, agregarlo a .gitignore solo evita que se incluya en futuros commits. No lo elimina del historial. Necesitas limpiar el historial por separado.

Automatizar la detección

Hacer grep manual y configurar hooks locales funciona, pero tiene brechas. Los hooks se pueden saltear con --no-verify, los patrones manuales no cubren todos los servicios, y nadie revisa el historial de git manualmente cada semana.

La forma más confiable de cubrir esto es con escaneo automático continuo. Herramientas que revisan cada commit, cada branch, y todo el historial en busca de patrones de credenciales de cientos de servicios.

datahogo hace exactamente esto: conectas tu repositorio de GitHub y escanea todo automáticamente. Si detecta API keys, tokens o passwords expuestos, te alerta y genera un PR con la corrección. Funciona con repos publicos y privados, y cubre patrones de AWS, Stripe, Firebase, Supabase, OpenAI, y la mayoria de servicios que usamos como desarrolladores.

Lo interesante es que revisa también el historial de commits, no solo el estado actual del código. Eso es algo que la mayoría de herramientas de linting no hacen.

Verifica tu sitio en producción

Escáner de archivos expuestos gratuito -- Escanea tu URL y detecta si archivos como /.env, /.git/HEAD o configuraciones sensibles son accesibles públicamente.

Qué hacer si ya tienes secrets expuestos

Si ya confirmaste que tienes credenciales en tu repo, sigue este orden. La prioridad es clara:

Paso 1: Rotar la credencial inmediatamente

No pierdas tiempo limpiando git primero. Ve al dashboard del servicio afectado y regenera la key:

  • AWS: IAM -> Users -> Security Credentials -> Create Access Key (y desactiva la vieja)
  • Stripe: Dashboard -> Developers -> API Keys -> Roll Key
  • Firebase: Project Settings -> Service Accounts -> Generate New Key
  • Supabase: Settings -> API -> Generate New Key
  • OpenAI: API Keys -> Create New Key (y revoke la vieja)

Paso 2: Verificar que la key vieja esta desactivada

Haz un request de prueba con la credencial vieja para confirmar que ya no funciona:

bash
# Ejemplo con Stripe
curl https://api.stripe.com/v1/charges \
  -u sk_live_LAKEY_VIEJA:
 
# debería devolver un error de autenticación

Paso 3: Limpiar el historial de git

Usa BFG Repo-Cleaner (más rápido que git filter-branch):

bash
# Instalar BFG
brew install bfg
 
# Crear un archivo con los strings a eliminar
echo "sk_live_abc123xyz789" >> secrets-to-remove.txt
echo "AKIA1234567890ABCDEF" >> secrets-to-remove.txt
 
# Limpiar el historial
bfg --replace-text secrets-to-remove.txt
 
# Forzar garbage collection
git reflog expire --expire=now --all
git gc --prune=now --aggressive
 
# Push forzado (esto reescribe el historial remoto)
git push --force
Push forzado reescribe el historial

Si otros desarrolladores tienen clones del repo, necesitan hacer git fetch --all y git reset --hard origin/main. Coordinalo con tu equipo antes de hacer el push forzado.

Paso 4: Actualizar las credenciales nuevas

Configura las credenciales nuevas en variables de entorno, nunca en el código:

bash
# .env.local (nunca commitear)
STRIPE_SECRET_KEY=sk_live_NUEVA_KEY_AQUI
DATABASE_URL=postgresql://user:NUEVA_PASSWORD@host:5432/db

Paso 5: Prevenir que vuelva a pasar

  • Configura .gitignore correctamente
  • Agrega pre-commit hooks al proyecto
  • Conecta un escaner automático al repositorio
  • válida tus variables de entorno con Zod al arrancar la app
tsx
// lib/env.ts
import { z } from "zod"
 
const envSchema = z.object({
  STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
  DATABASE_URL: z.string().url(),
  NEXTAUTH_SECRET: z.string().min(32),
})
 
// Si falta alguna variable, la app no arranca
export const env = envSchema.parse(process.env)

Checklist de seguridad para repositorios

Antes de hacer tu proximo deploy, verifica:

  • .env y variantes estan en .gitignore
  • No hay archivos .pem, .key o serviceAccount.json trackeados
  • Las credenciales estan en variables de entorno, no hardcodeadas
  • grep -rn "sk_live_\|AKIA\|Bearer " . no devuelve resultados en el código
  • git log --all -- "*.env" no muestra archivos .env en el historial
  • Tienes un pre-commit hook o escaner automático configurado
  • Las variables de entorno se validan al arrancar la aplicación

Resumen

métodoQue cubreLimitaciones
grep manualcódigo actualNo revisa historial, patrones limitados
Pre-commit hooksCommits nuevosSe puede saltear con --no-verify
.gitignoreArchivos nuevosNo limpia archivos ya commiteados
Escaneo automáticoTodo: código, historial, PRsNecesita configuración inicial
BFG Repo-CleanerLimpieza de historialSolo remediation, no prevención

La combinación ideal: .gitignore desde el inicio + validación de env con Zod + escaneo automático continuo. así cubres prevención, detección y remediación.

Preguntas frecuentes

¿cómo se si tengo secrets expuestos en mi repositorio de GitHub?

Puedes buscar manualmente con git log -p --all -S 'sk_live_' o grep recursivo por patrones de API keys. Pero el método más confiable es usar herramientas de escaneo automático que revisan cada commit, archivo y branch en busca de patrones conocidos de credenciales como tokens de Stripe, AWS, Firebase y otros servicios.

¿Eliminar un archivo con secrets de mi repo lo soluciona?

No. Git guarda el historial completo de cada archivo. Aunque borres el archivo o hagas un commit nuevo sin el, las credenciales siguen accesibles en el historial de git. Necesitas limpiar el historial con git filter-branch o BFG Repo-Cleaner, y además rotar las credenciales porque cualquiera que haya clonado el repo ya tiene acceso.

¿Qué hago si ya subi una API key a GitHub?

Primero rota la credencial inmediatamente desde el dashboard del servicio afectado. No pierdas tiempo limpiando git primero, la prioridad es invalidar la key expuesta. después limpia el historial de git, genera nuevas credenciales y configura tu proyecto para prevenir que vuelva a pasar con .gitignore y pre-commit hooks.

¿Los repositorios privados también son vulnerables a secrets expuestos?

Si. Un repositorio privado puede volverse público por error, un colaborador puede filtrar acceso, o un atacante puede obtener credenciales de GitHub de alguno de tus colaboradores. Tratar un repo privado como seguro para almacenar secrets es un riesgo innecesario. Siempre usa variables de entorno.

¿Qué tan rápido se explotan las credenciales filtradas en GitHub?

Hay bots que escanean GitHub en tiempo real buscando patterns de API keys. Una credencial de AWS expuesta en un repositorio público puede ser explotada en menos de 5 minutos. Los atacantes usan estas keys para minar criptomonedas, enviar spam o acceder a datos sensibles. La velocidad de respuesta es crítica.

#seguridad#github#api-keys#secrets#git#devops

Preguntas frecuentes

¿Cómo se si tengo secrets expuestos en mi repositorio de GitHub?

Puedes buscar manualmente con git log -p --all -S 'sk_live' o grep recursivo por patrones de API keys. Pero el método más confiable es usar herramientas de escaneo automático que revisan cada commit, archivo y branch en busca de patrones conocidos de credenciales como tokens de Stripe, AWS, Firebase y otros servicios.

¿Eliminar un archivo con secrets de mi repo lo soluciona?

No. Git guarda el historial completo de cada archivo. Aunque borres el archivo o hagas un commit nuevo sin el, las credenciales siguen accesibles en el historial de git. Necesitas limpiar el historial con git filter-branch o BFG Repo-Cleaner, y además rotar las credenciales porque cualquiera que haya clonado el repo ya tiene acceso.

¿Qué hago si ya subi una API key a GitHub?

Primero rota la credencial inmediatamente desde el dashboard del servicio afectado. No pierdas tiempo limpiando git primero, la prioridad es invalidar la key expuesta. después limpia el historial de git, genera nuevas credenciales y configura tu proyecto para prevenir que vuelva a pasar con .gitignore y pre-commit hooks.

¿Los repositorios privados también son vulnerables a secrets expuestos?

Si. Un repositorio privado puede volverse público por error, un colaborador puede filtrar acceso, o un atacante puede obtener credenciales de GitHub de alguno de tus colaboradores. Tratar un repo privado como seguro para almacenar secrets es un riesgo innecesario. Siempre usa variables de entorno.

¿Qué tan rápido se explotan las credenciales filtradas en GitHub?

Hay bots que escanean GitHub en tiempo real buscando patterns de API keys. Una credencial de AWS expuesta en un repositorio público puede ser explotada en menos de 5 minutos. Los atacantes usan estas keys para minar criptomonedas, enviar spam o acceder a datos sensibles. La velocidad de respuesta es crítica.