Archivos .env Expuestos: Cómo Verificar si tu Sitio Filtra Secretos
Guía para detectar si tu sitio web expone archivos .env, .git y configuraciones sensibles. Verificación manual, protección en Next.js y Vercel, y remediación.
Archivos .env Expuestos: Como Verificar si tu Sitio Filtra Secretos
Un archivo .env expuesto en producción significa que cualquier persona con un navegador puede ver tus API keys, passwords de base de datos, tokens de acceso y todo lo que pusiste ahí pensando que era privado. No necesita herramientas especiales. No necesita ser hacker. Solo tiene que visitar tu-dominio.com/.env.
Pasa más seguido de lo que crees. Y verificarlo toma menos de 30 segundos.
Por qué se exponen archivos .env
La mayoría de las filtraciones de archivos .env no ocurren porque alguien los suba a propósito. Ocurren por configuraciones que parecen correctas hasta que alguien revisa las rutas públicas.
1. El servidor web sirve todo sin restricciones
La configuración por defecto de Apache y Nginx sirve cualquier archivo que exista en el directorio público. Si tu .env está ahí -- o en un directorio padre accesible -- el servidor lo entrega sin preguntar.
# Estructura típica de un proyecto
/var/www/mi-sitio/
.env # Archivo con secretos
.git/ # Repositorio completo
public/ # Directorio público
index.html
assets/Si el document root apunta a /var/www/mi-sitio/ en vez de /var/www/mi-sitio/public/, todo el directorio del proyecto queda accesible. Incluyendo .env y .git/.
2. Deploy de la carpeta completa como static files
Muchos hostings de sitios estáticos (Netlify, GitHub Pages, hosting compartido) sirven todo lo que esté en el directorio de deploy. Si tu proceso de build copia el proyecto completo -- incluyendo .env -- al directorio de salida, queda expuesto.
# Esto es un error común en scripts de deploy
cp -r ./proyecto/* /var/www/html/
# Copia TODO, incluyendo .env, .git/, config files3. Framework mal configurado
Algunos frameworks tienen directorios que se sirven como estáticos automáticamente. Si por error mueves o copias tu .env dentro de ese directorio, el framework lo sirve sin problema.
# En un proyecto Next.js, esto es un desastre
cp .env public/.env
# Ahora tu-sitio.com/.env es público4. Docker con volúmenes expuestos
En contenedores Docker, montar el directorio completo del proyecto como volumen puede exponer archivos que no deberían ser accesibles:
# docker-compose.yml mal configurado
services:
web:
volumes:
- ./:/app # Monta TODO, incluyendo .env
ports:
- "80:80"Si el servidor dentro del contenedor sirve archivos desde /app, tu .env queda accesible.
Las rutas más peligrosas
Estos son los archivos y rutas que los atacantes (y los bots automatizados) verifican constantemente en cualquier sitio público:
Archivos de entorno
/.env
/.env.local
/.env.production
/.env.development
/.env.staging
/.env.backup
/.env.old
/.env.save
/.env.example # A veces contiene valores reales por errorRepositorio git
/.git/HEAD # Confirma que existe un repo git expuesto
/.git/config # Puede contener tokens de acceso y URLs privadas
/.git/logs/HEAD # Historial de commits con mensajesUn .git/ expuesto equivale a entregar tu código fuente completo
Si /.git/HEAD devuelve un 200, un atacante puede reconstruir tu repositorio completo usando herramientas como git-dumper. Eso incluye todo el historial de commits, branches, y cualquier secret que haya estado en el código en algún momento.
Configuraciones de servicios
/config.php
/wp-config.php
/configuration.php
/database.yml
/application.properties
/application.yml
/docker-compose.yml
/firebase.json
/serviceAccountKey.json
/credentials.jsonBackups y dumps
/backup.sql
/dump.sql
/database.sql
/db.sql
/site.sql
/.sql
/backup.zip
/backup.tar.gzCada uno de estos archivos contiene información que un atacante puede usar para comprometer tu aplicación, tu base de datos, o tu infraestructura completa.
Como verificar manualmente
Con curl desde la terminal
La forma más directa. curl te da el código de respuesta HTTP sin descargar el contenido:
# Verificar archivo .env
curl -s -o /dev/null -w "%{http_code}" https://tu-sitio.com/.envEl resultado te dice todo:
- 404 -- No encontrado. Bien.
- 403 -- Acceso denegado. Bien, está bloqueado.
- 200 -- Encontrado y accesible. Tu
.envestá expuesto. - 301/302 -- Redirect. Sigue la redirección para verificar el destino.
Para ver el contenido real (si sospechas que está expuesto):
# Ver las primeras líneas del archivo
curl -s https://tu-sitio.com/.env | head -5Verificar el repositorio git
# Si esto devuelve algo como "ref: refs/heads/main", tu repo está expuesto
curl -s https://tu-sitio.com/.git/HEADScript para verificar múltiples rutas
Este script revisa las rutas más comunes de una sola vez:
#!/bin/bash
# verificar-rutas.sh
# Uso: ./verificar-rutas.sh tu-sitio.com
DOMINIO=$1
if [ -z "$DOMINIO" ]; then
echo "Uso: $0 dominio.com"
exit 1
fi
RUTAS=(
"/.env"
"/.env.local"
"/.env.production"
"/.env.backup"
"/.git/HEAD"
"/.git/config"
"/config.php"
"/wp-config.php"
"/database.yml"
"/application.properties"
"/docker-compose.yml"
"/firebase.json"
"/serviceAccountKey.json"
"/backup.sql"
"/dump.sql"
)
echo "Verificando rutas sensibles en $DOMINIO..."
echo "-------------------------------------------"
EXPUESTOS=0
for ruta in "${RUTAS[@]}"; do
status=$(curl -s -o /dev/null -w "%{http_code}" "https://$DOMINIO$ruta")
if [ "$status" = "200" ]; then
echo "[EXPUESTO] $ruta -> $status"
EXPUESTOS=$((EXPUESTOS + 1))
else
echo "[OK] $ruta -> $status"
fi
done
echo "-------------------------------------------"
if [ "$EXPUESTOS" -gt 0 ]; then
echo "Se encontraron $EXPUESTOS rutas expuestas. Accion inmediata requerida."
else
echo "No se encontraron rutas expuestas."
fiGuarda el script, dale permisos de ejecución, y córrelo contra tu dominio:
chmod +x verificar-rutas.sh
./verificar-rutas.sh tu-sitio.comDesde el navegador
Si no tienes terminal a la mano, abre tu navegador y visita directamente:
https://tu-sitio.com/.env
https://tu-sitio.com/.git/HEADSi ves el contenido del archivo en vez de una página de error, está expuesto. Así de simple.
Verificación automática
Si no quieres correr scripts manualmente, hay herramientas online que revisan las rutas más comunes automáticamente. Es útil para una verificación rápida, especialmente si manejas varios dominios.
Escanea tu sitio por archivos expuestos
Escáner de archivos sensibles gratuito -- Ingresa tu URL y revisa si /.env, /.git/HEAD, /.npmrc y otras rutas sensibles son accesibles públicamente.
Como proteger tu sitio
Next.js y Vercel
Next.js tiene una ventaja por diseño: solo sirve archivos que están dentro de la carpeta public/. Tu archivo .env en la raíz del proyecto nunca se expone como ruta pública, porque el servidor de Next.js no lo sirve.
En Vercel, la protección es aún más estricta. Los archivos del proyecto no son accesibles como static files. Las variables de entorno se configuran desde el dashboard y se inyectan en el runtime, sin archivos físicos en el servidor.
Pero hay casos donde las cosas se complican:
# Si tu next.config.ts tiene un rewrite que expone rutas del sistema de archivos
# O si usas un middleware que sirve archivos estáticos de forma incorrecta
# O si copias .env a public/ por error en tu script de buildCuidado con el prefijo NEXT_PUBLIC_
Las variables con prefijo NEXT_PUBLIC_ se incluyen en el bundle de JavaScript del cliente. Cualquier usuario puede verlas en el código fuente del navegador. Nunca pongas secrets (API keys privadas, passwords, tokens) con ese prefijo. Para más detalles, revisa la guía de variables de entorno en Next.js.
Validar las variables de entorno al arranque es una capa adicional de protección. Si falta una variable o tiene un formato incorrecto, la app falla inmediatamente con un mensaje claro en vez de funcionar de forma parcial:
// lib/env.ts
import { z } from "zod"
const serverEnvSchema = z.object({
DATABASE_URL: z.string().url(),
STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
NEXTAUTH_SECRET: z.string().min(32),
})
const clientEnvSchema = z.object({
NEXT_PUBLIC_SITE_URL: z.string().url(),
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: z.string().startsWith("pk_"),
})
// Validar al importar -- si falla, la app no arranca
export const serverEnv = serverEnvSchema.parse(process.env)
export const clientEnv = clientEnvSchema.parse({
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY,
})Apache (.htaccess)
Si tu sitio corre en Apache, agrega estas reglas al .htaccess en la raíz del directorio público:
# Bloquear archivos que empiezan con punto (.env, .git, .htpasswd)
<FilesMatch "^\.">
Require all denied
</FilesMatch>
# Bloquear archivos de configuración específicos
<FilesMatch "(config\.php|database\.yml|docker-compose\.yml|serviceAccountKey\.json)$">
Require all denied
</FilesMatch>
# Bloquear acceso al directorio .git completo
RedirectMatch 404 /\.gitVerifica que funcione:
# Debería devolver 403 o 404
curl -s -o /dev/null -w "%{http_code}" https://tu-sitio.com/.env
curl -s -o /dev/null -w "%{http_code}" https://tu-sitio.com/.git/HEADNginx
En la configuración de tu servidor Nginx (generalmente en /etc/nginx/sites-available/tu-sitio):
server {
# ... tu configuración existente ...
# Bloquear archivos y directorios que empiezan con punto
location ~ /\. {
deny all;
return 404;
}
# Bloquear archivos de configuración específicos
location ~* (config\.php|database\.yml|docker-compose\.yml|\.sql)$ {
deny all;
return 404;
}
# Bloquear backups
location ~* \.(sql|bak|backup|old|save)$ {
deny all;
return 404;
}
}Después de editar la configuración, verifica y recarga:
# Verificar que la configuración es válida
sudo nginx -t
# Recargar Nginx
sudo systemctl reload nginxDocker
Para Docker, el problema se previene en dos niveles.
Primero, un .dockerignore correcto para que los archivos sensibles no se copien a la imagen:
# .dockerignore
.env
.env.*
.git
.gitignore
*.pem
*.key
serviceAccountKey.json
docker-compose*.ymlSegundo, si usas volúmenes, monta solo lo necesario:
# docker-compose.yml -- correcto
services:
web:
build: .
volumes:
- ./public:/app/public # Solo el directorio público
ports:
- "80:3000"
env_file:
- .env # Inyecta variables sin montar el archivoLa diferencia entre env_file y montar el archivo como volumen es importante. env_file inyecta las variables en el entorno del proceso. Montar el archivo como volumen lo hace accesible en el sistema de archivos del contenedor.
Que hacer si tu .env está expuesto
Si acabas de descubrir que tu archivo .env es accesible públicamente, este es el orden de acción. No lo alteres.
Rota credenciales PRIMERO
No pierdas tiempo configurando el servidor antes de invalidar las keys expuestas. Cada minuto que pasa con credenciales válidas expuestas es un minuto que un atacante puede usarlas. Primero rotas, después proteges.
Paso 1: Rotar TODAS las credenciales
Ve al dashboard de cada servicio y regenera las keys. No solo las que "crees" que estaban en el archivo. Todas.
# Servicios comunes que debes verificar
# - AWS: IAM -> Security Credentials -> Rotate Access Keys
# - Stripe: Dashboard -> Developers -> API Keys -> Roll Key
# - Supabase: Settings -> API -> Generate New Keys
# - Firebase: Project Settings -> Service Accounts -> New Key
# - OpenAI: API Keys -> Revoke y Create New
# - Database: Cambiar password del usuario de la base de datos
# - OAuth: Regenerar client secrets (Google, GitHub, etc.)Paso 2: Bloquear el acceso inmediatamente
Aplica la configuración del servidor que corresponda (Apache, Nginx, o el que uses). Si no puedes editar la configuración del servidor, como medida temporal:
# Eliminar el archivo expuesto
rm /var/www/tu-sitio/.env
# O moverlo fuera del directorio web
mv /var/www/tu-sitio/.env /home/tu-usuario/.env-backupPaso 3: Verificar logs de acceso
Revisa quién accedió a esos archivos. Si alguien más los descargó, las credenciales están comprometidas (por eso las rotaste primero).
# Apache
grep "\.env" /var/log/apache2/access.log
# Nginx
grep "\.env" /var/log/nginx/access.log
# Buscar accesos al repositorio git también
grep "\.git" /var/log/nginx/access.logSi hay IPs desconocidas que accedieron a /.env con código 200, asume que tus credenciales fueron comprometidas. Ya las rotaste en el paso 1, pero verifica que no haya actividad sospechosa en los servicios afectados.
Paso 4: Configurar protección permanente
Aplica las reglas de servidor correspondientes (Apache, Nginx) que cubrimos en la sección anterior. Verifica que funcionen.
Paso 5: Escanear el historial de git
Si tu .env fue commiteado en algún momento, los valores siguen en el historial de git aunque lo hayas borrado después:
# Verificar si .env fue commiteado alguna vez
git log --all --diff-filter=A -- ".env" ".env.*"
# Si aparecen resultados, necesitas limpiar el historial
# Usa BFG Repo-Cleaner
bfg --delete-files .env
bfg --delete-files ".env.*"
# Limpiar y forzar push
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --forcePara una guía más detallada sobre como detectar y limpiar secrets del historial de git, revisa Secrets expuestos en GitHub: como detectar y proteger tu proyecto.
Prevención
La remediación es importante, pero la prevención evita que tengas que hacer todo lo anterior.
.gitignore correcto desde el primer commit
# Variables de entorno
.env
.env.*
!.env.example
# Keys y certificados
*.pem
*.key
*.p12
# Service accounts
serviceAccount*.json
firebase-adminsdk*.json
credentials.json
# Backups de base de datos
*.sql
*.dumpEl .env.example sí debe ir en git
Crea un archivo .env.example con las variables necesarias pero sin valores reales. Esto sirve como documentación para otros desarrolladores y no expone ningún secret.
# .env.example -- este sí se commitea
DATABASE_URL=postgresql://user:password@localhost:5432/nombre_db
STRIPE_SECRET_KEY=sk_test_xxx
NEXTAUTH_SECRET=genera-un-string-aleatorio-de-32-chars
NEXT_PUBLIC_SITE_URL=http://localhost:3000Validación de env al arranque con Zod
No confíes en que las variables van a estar. Valídalas al arrancar la aplicación:
// lib/env.ts
import { z } from "zod"
const envSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
DATABASE_URL: z.string().url(),
STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
NEXTAUTH_SECRET: z.string().min(32),
NEXT_PUBLIC_SITE_URL: z.string().url(),
})
export const env = envSchema.parse(process.env)Si alguna variable falta o tiene un formato incorrecto, la app falla al arrancar con un mensaje claro que dice exactamente qué falta. Mejor fallar inmediatamente que funcionar con credenciales incorrectas o faltantes.
Nunca poner .env en el directorio público
Parece obvio, pero ocurre. Scripts de build que copian archivos de más, volúmenes de Docker mal configurados, o simplemente un cp en el lugar equivocado.
# NUNCA hagas esto
cp .env public/
cp .env dist/
cp .env build/
# Tu script de deploy debería copiar SOLO lo necesario
# O mejor: usa un .dockerignore / .vercelignore adecuadoVerificación como parte de CI/CD
Agrega un paso en tu pipeline de CI/CD que verifique que no hay archivos sensibles en el build output:
# .github/workflows/security-check.yml
name: Security Check
on:
push:
branches: [main]
pull_request:
jobs:
check-secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Verificar que .env no está en el repositorio
run: |
if git ls-files --error-unmatch .env 2>/dev/null; then
echo "ERROR: .env está trackeado en git"
exit 1
fi
- name: Verificar que no hay secrets hardcodeados
run: |
PATTERNS="sk_live_|sk_test_|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}"
if grep -rE "$PATTERNS" --include="*.ts" --include="*.tsx" --include="*.js" .; then
echo "ERROR: Se detectaron posibles secrets en el código"
exit 1
fi
- name: Verificar .gitignore incluye .env
run: |
if ! grep -q "^\.env" .gitignore; then
echo "ERROR: .gitignore no incluye .env"
exit 1
fiEscaneo automático continuo
Los checks manuales y los scripts de CI cubren lo básico, pero tienen brechas. Un escaneo automático continuo revisa cada commit, branch, y el historial completo. datahogo conecta con tu repositorio de GitHub y detecta credenciales expuestas automáticamente, cubriendo patrones de cientos de servicios que los grep manuales no abarcan.
Preguntas frecuentes
¿Como sé si mi sitio expone el archivo .env?
Abre tu navegador y visita tu-dominio.com/.env. Si ves el contenido del archivo con tus variables de entorno, está expuesto. También puedes usar curl desde la terminal:
curl -s -o /dev/null -w "%{http_code}" https://tu-dominio.com/.envSi devuelve un código 200, tienes un problema. Un 404 o 403 significa que está protegido.
¿Por qué mi archivo .env termina expuesto en producción?
Las causas más comunes son: deployar una carpeta completa que incluye .env como static files, no configurar el servidor web para bloquear archivos con punto al inicio, o usar un hosting que sirve todos los archivos del directorio público sin restricciones.
Revisa la sección de protección por servidor para la configuración específica de tu setup.
¿Qué archivos además de .env pueden estar expuestos?
Los más comunes son:
/.git/HEAD-- expone el repositorio git completo/.git/config-- puede contener tokens de acceso/wp-config.php-- en sitios WordPress, contiene credenciales de la base de datos/config.php-- configuraciones generales de frameworks PHP/database.yml-- en proyectos Ruby on Rails/application.properties-- en proyectos Java/Spring/docker-compose.yml-- puede contener passwords en texto plano/serviceAccountKey.json-- credenciales de Firebase o GCP
Cualquier archivo de configuración en el directorio público es un riesgo.
¿Agregar .env a .gitignore soluciona el problema?
Solo parcialmente. .gitignore evita que el archivo se suba a git, pero no impide que se sirva como archivo estático si está en tu directorio público. Son dos problemas diferentes:
- Git:
.gitignoreevita que se commitee - Servidor web: La configuración del servidor evita que se sirva como HTTP
Necesitas ambos. Un .gitignore correcto Y reglas en tu servidor web para bloquear archivos sensibles.
¿Qué hago si descubro que mi .env está expuesto?
Sigue este orden exacto:
- Rotar TODAS las credenciales -- Ve al dashboard de cada servicio y regenera keys, passwords y tokens
- Bloquear el acceso -- Configura el servidor web para denegar acceso a archivos sensibles
- Verificar logs -- Revisa quién accedió a esos archivos
- Protección permanente -- Implementa las reglas de servidor y las verificaciones de CI/CD
- Limpiar historial de git -- Si el archivo fue commiteado, limpia el historial
La prioridad absoluta es rotar credenciales. Un servidor configurado con keys comprometidas sigue siendo vulnerable.
Verificar si tu sitio expone archivos sensibles toma menos de un minuto. Configurar la protección toma menos de diez. No hay excusa para dejar un .env accesible públicamente.
Si manejas múltiples sitios o quieres una verificación rápida sin escribir scripts, el escáner de .env de datahogo cubre las rutas más comunes automáticamente.
Para el siguiente paso, revisa la guía de variables de entorno en Next.js y Vercel para configurar tus secrets correctamente desde el inicio, y el checklist de seguridad para deploy para cubrir todos los puntos antes de poner tu aplicación en producción.
Preguntas frecuentes
¿Cómo sé si mi sitio expone el archivo .env?
Abre tu navegador y visita tu-dominio.com/.env. Si ves el contenido del archivo con tus variables de entorno, está expuesto. También puedes usar curl -s https://tu-dominio.com/.env desde la terminal. Si devuelve un código 200, tienes un problema. Un 404 o 403 significa que está protegido.
¿Por qué mi archivo .env termina expuesto en producción?
Las causas más comunes son: deployar una carpeta completa que incluye .env como static files, no configurar el servidor web para bloquear archivos con punto al inicio, o usar un hosting que sirve todos los archivos del directorio público sin restricciones.
¿Qué archivos además de .env pueden estar expuestos?
Los más comunes son /.git/HEAD (expone el repositorio git completo), /.git/config (puede contener tokens), /wp-config.php (en sitios WordPress), /config.php, /database.yml, /application.properties y /docker-compose.yml. Cualquier archivo de configuración en el directorio público es un riesgo.
¿Agregar .env a .gitignore soluciona el problema?
Solo parcialmente. .gitignore evita que el archivo se suba a git, pero no impide que se sirva como archivo estático si está en tu directorio público. Necesitas configurar tu servidor web para bloquear el acceso a archivos con punto al inicio y a rutas sensibles.
¿Qué hago si descubro que mi .env está expuesto?
Primero: rota TODAS las credenciales inmediatamente (API keys, passwords, tokens). Después: configura tu servidor para bloquear el acceso a esos archivos. Por último: verifica que los nuevos valores no estén en ningún log o historial de git.
Articulos relacionados
Row Level Security en Supabase: Errores Comunes que Dejan tu Base de Datos Abierta
Los 5 errores más comunes de Row Level Security en Supabase que dejan tu base de datos expuesta. USING(true), tablas sin RLS, service_role en el cliente y cómo corregirlos.
OWASP Top 10: Guía Práctica para Desarrolladores Web
Guía práctica del OWASP Top 10 en español. Las 10 vulnerabilidades más críticas en aplicaciones web con ejemplos de código, prevención en Next.js y Node.js, y checklist de seguridad.
Security Headers: Cómo Verificar y Configurar los Headers de Seguridad de tu Sitio
Guía práctica para verificar y configurar security headers en tu sitio web. HSTS, CSP, X-Frame-Options y más con ejemplos para Next.js y Vercel.