Fundamentos de seguridad
¿Tu archivo .env está expuesto? Las doce rutas a revisar
¿Tu archivo .env está expuesto en tu propio servidor web? Doce direcciones te lo dicen, y un acierto significa que todo lo que contiene ya es público.

En resumen
- Dos problemas distintos se llaman igual: un archivo .env expuesto. Este es el archivo en sí, puesto en tu servidor web y descargable por cualquiera que escriba la dirección.
- Un host que solo sirve tu frontend ya construido no puede hacer esto. Un servidor de verdad sí, y por eso siete de las ocho aplicaciones que encontramos eran de Replit.
- De 30.761 aplicaciones en vivo donde nuestra comprobación obtuvo respuesta, 8 estaban entregando un archivo privado. Doce direcciones te dicen si eres la novena.
Alguien te ha dicho que tu archivo .env está expuesto, y por la frase no puedes
saber si eso es grave. Cubre dos situaciones completamente distintas. Una de
ellas es la herramienta funcionando como debe. La otra significa que un archivo
que creías privado se puede descargar desde tu sitio en vivo, por cualquiera,
desde el día en que está ahí.
Esta es la parte que guía tras guía mezcla: que un valor de tu .env acabe
dentro de tu JavaScript no es el mismo suceso que tu servidor web sirviendo el
archivo .env. Lo primero es el build haciendo lo que le pediste cuando
llamaste a la variable VITE_ALGO. Lo segundo es un error de archivo, y es lo
que conviene mirar antes, porque puedes comprobarlo tú desde fuera en un minuto.
Piensa en tu sitio en vivo como el mostrador de una tienda. Todo lo que hay en el
mostrador está para llevarse: la página, las imágenes, el JavaScript, el
logotipo. La trastienda guarda lo que hace funcionar el negocio, y un visitante
no tiene camino hasta ahí. Un valor compilado dentro de tu JavaScript es una
línea impresa en el folleto del mostrador. El archivo .env que responde en una
dirección pública es la carpeta de la trastienda, dejada en el mostrador junto a
todo lo demás.
¿Mi archivo .env está expuesto?
Abre tu sitio en vivo en un navegador, añade /.env al final de la dirección y
pulsa enter.
Pueden volver tres cosas, y solo una de ellas es un problema.
Una página de tu aplicación. La mayoría de los frontends responden a cualquier dirección desconocida con su propia página índice, porque así enruta una aplicación de una sola página. Te sale tu portada, o tu pantalla de no encontrado. En esa ruta no se sirve nada.
Un error. Un 403 o un 404 significa que al servidor se le preguntó y declinó. Esa es la respuesta que quieres.
Texto plano, con líneas dentro. Algo como SUPABASE_URL=https://… y
SUPABASE_SERVICE_ROLE_KEY=eyJ…, en un muro monoespaciado sin estilo alguno. El
archivo se entrega a quien lo pida, y lleva haciéndolo desde el día en que se
sirvió por primera vez.
Dos cosas distintas se llaman archivo .env expuesto
Una va de tu bundle. La otra va de tu servidor.
| Qué ha pasado | Dónde está el valor | Qué cuesta arreglarlo |
|---|---|---|
Llamaste a una variable VITE_, NEXT_PUBLIC_ o EXPO_PUBLIC_ | Compilado en el JavaScript que descarga cada visitante | Mover el trabajo a un servidor y rotar la clave. El archivo nunca se sirvió. |
| Tu servidor web publica tu carpeta de proyecto | En el archivo, en https://tusitio/.env | Sacar el archivo de lo que publica el servidor y rotar todo lo que contenía. |
La primera fila es la común y tiene su propio artículo: el prefijo es una instrucción de publicar, y el build la obedeció. Todo lo de abajo es la segunda fila.
Por qué una aplicación de Lovable no puede y una de Replit sí
Porque los dos hosts publican cosas distintas.
Un builder que despliega un frontend estático le entrega a su host una carpeta de
archivos construidos: algo de HTML, algo de JavaScript, algunas imágenes. Tu
.env se leyó durante el build y no está en esa carpeta, así que en /.env no
hay nada que nadie pueda pedir. El host no podría servir el archivo ni
queriendo.
Una aplicación de Replit suele ejecutar su propio servidor, en su propia carpeta de proyecto, con tus archivos al lado de tu código. A un servidor al que se le dice que sirva su carpeta sirve todos los archivos que hay en ella, y no tiene manera de saber que uno de ellos guarda tus claves. Lo mismo vale para cualquier cosa que hayas desplegado en una máquina tuya.
Los números siguen a la arquitectura. De 30.761 aplicaciones en vivo donde esta comprobación obtuvo respuesta, 8 entregaban al menos un archivo privado. Siete de las ocho eran de Replit, y las de Replit eran 3.025 de las 30.761. La octava estaba en un dominio propio, que dice lo mismo de otra manera: alguien tenía su propio servidor.
Es el hallazgo más raro que imprimimos, y el único de los nueve sin explicación inocente. Si quieres la lectura amplia de lo que entregan de verdad las aplicaciones de Replit, escaneamos 3.042 de ellas.
Las doce rutas a revisar
Doce direcciones, en el orden que merece la pena. Pon cada una detrás de tu dominio.
| Dirección | Qué guarda | Si responde con texto |
|---|---|---|
/.env | Todas las claves con las que se construyó tu app | Trátalo todo como público y rota |
/.env.local | Lo mismo, de una ejecución local | Lo mismo |
/.env.production | Lo mismo, de tu despliegue en vivo | Lo mismo |
/.git/config | La dirección remota de tu repositorio | Todo el directorio .git suele ser legible |
/.git/HEAD | En qué rama estás | Lo mismo, y es la más silenciosa de las doce |
/.aws/credentials | Claves de Amazon de larga duración | Rota en Amazon y luego revisa la factura |
/database.sql | Esquema y filas | Todas las filas de todas las tablas son públicas |
/dump.sql | Lo mismo | Lo mismo |
/backup.sql | Lo mismo | Lo mismo |
/config.json | Lo que hayas puesto dentro | Léelo y mira; hay tokens ahí más a menudo de lo que se cree |
/docker-compose.yml | Definiciones de servicios, a menudo con contraseñas | Rota todo lo que esté escrito dentro |
/.npmrc | Un token de registro | Revoca el token |
Nuestro propio escaneo lee las doce desde fuera y te nombra las que respondieron. Tarda unos 20 segundos y no necesita cuenta: escanea tu aplicación.
Qué significa de verdad un acierto
Todo lo que hay en ese archivo es público ahora mismo, y lo es desde el día en que se sirvió por primera vez.
No vas a averiguar quién lo leyó. Un builder no te da un registro de archivos descargados que puedas ir a mirar, y la petición se ve igual que cualquier otra petición de cualquier otro archivo. De lo que sí puedes estar seguro es de que alguien lo intentó: hay crawlers automáticos recorriendo exactamente estas doce rutas por todo internet, sin parar, y no necesitan saber quién eres para dar con la tuya.
Cinco de las ocho aplicaciones que encontramos entregaban una de las cuatro caras
de inmediato: .env, un directorio .git, .aws/credentials o un dump .sql.
Las otras tres entregaban config.json, docker-compose.yml o .npmrc. Esas
tres son las que todo el mundo da por inofensivas, que es justo por lo que acaban
con tokens y contraseñas de base de datos escritos dentro.
Lo que esto no es, es un veredicto sobre tu aplicación. Una comprobación externa lee lo que tu sitio le entrega a un desconocido, y doce rutas que responden con nada son doce rutas que responden con nada. Qué más se escapa de una aplicación vibe-codeada es la lista larga.
La que arruina una semana: un directorio .git
Un directorio .git no es un archivo. Es todo tu historial.
Ahí está cada commit, incluido aquel en el que pegaste una clave y el posterior
en el que la quitaste. Esa es la parte que la gente entiende mal de una fuga de
.git: borrar un secreto del código que tienes hoy no hace nada con la versión
donde seguía estando, y esa versión está en el mismo directorio que tu servidor
publica.
La comprobación lee /.git/config y /.git/HEAD porque son pequeños, su
contenido es inconfundible y cualquiera de los dos respondiendo significa que el
directorio en sí se está sirviendo. A partir de ahí quien lee no necesita ninguna
herramienta especial. El formato está documentado y software corriente lo clona.
Si una clave estuvo alguna vez en un commit, rotarla es lo único que ayuda. Cuál va primero depende de si la copia ya está fuera, y ese orden merece la pena acertarlo.
El dump que alguien dejó en la carpeta
Tres de las doce rutas son volcados de base de datos: /database.sql,
/dump.sql y /backup.sql.
Un dump son todas las filas de todas las tablas en un archivo, con el esquema encima. Direcciones de correo, contraseñas con hash, pedidos, mensajes, lo que guarde tu aplicación. Responde en una dirección pública por el motivo más aburrido de todo este artículo: alguien hizo lo responsable, sacó una copia de su base de datos y la guardó en la carpeta de proyecto donde estaba.
Así que el archivo hecho para proteger los datos se convirtió en la vía más rápida de leerlos todos. Dónde vive una copia es tanta decisión como si la haces. Tres formas de respaldar una base de datos Supabase repasa las opciones, y Reeve Care guarda una copia de tu base de datos de Supabase fuera de tu propio servidor, verificada antes de contar como copia.
Cómo arreglarlo, y por qué importa el orden
Saca el archivo de lo que publica tu servidor. Después rota cada secreto que tenía dentro. En ese orden.
Rotar primero parece la mitad urgente, y es la mitad que desperdicia el trabajo. Tus claves nuevas van al mismo archivo, el archivo sigue respondiendo en la misma dirección, y has rotado directamente de vuelta a la fuga. Nada está más seguro que hace diez minutos.
Dónde ponerlo en su lugar, según lo que tengas montado:
- Un host con su propio almacén de secretos. Replit Secrets, las variables de entorno de una plataforma, tu panel de hosting. Tu servidor lee el valor en tiempo de ejecución y nunca queda en un archivo dentro de la carpeta publicada.
- Fuera de la carpeta servida. Si controlas la configuración del servidor, apúntalo a una carpeta de salida del build en lugar de a la raíz del proyecto. Entonces un archivo en la raíz no tiene dirección ninguna.
- Tampoco en el repositorio, que es una costumbre aparte y buena. Con el problema de hoy no hace nada.
Ese último punto conviene decirlo claro, porque .gitignore es la respuesta a la
que todo el mundo va. Mantiene el archivo fuera de tu repositorio. El archivo de
tu servidor llegó ahí porque el servidor está sentado en tu carpeta de proyecto,
y git no tiene opinión sobre eso.
Qué hacer ahora mismo
Qué hacer
- Escribe las doce direcciones detrás de tu propio dominio, o lanza el escaneo gratuito y que escriba él. Lee lo que vuelve, no el código de estado.
- Si una responde con tus propios ajustes, saca el archivo de la carpeta que publica tu servidor y vuelve a desplegar. Ese es el paso que lo corta.
- Después rota cada clave, contraseña y token que tenía el archivo, en cada proveedor. Rotar antes de mover el archivo devuelve los valores nuevos a donde estaban los viejos.
- Revisa facturación y registros del proveedor al terminar. Rotar corta lo que viene después y no hace nada con lo que ya pasó.
- Si un directorio
.gitera legible, rota todo lo que estuvo alguna vez en un commit, no solo lo que hay hoy en el código.
Si prefieres recorrer tu aplicación como una lista, la checklist de seguridad de 10 minutos cubre esto junto a las otras cosas que conviene cerrar en una aplicación recién lanzada.
Preguntas frecuentes
¿Cómo sé si mi archivo .env es público?
Abre tu sitio en vivo en un navegador, añade /.env al final de la dirección y pulsa enter. Si vuelve una página de tu aplicación, no se está sirviendo nada en esa ruta. Si vuelve texto plano, con líneas como SUPABASE_URL=https://abcdefghij.supabase.co, el archivo se entrega a quien lo pida. Haz lo mismo con /.env.local y /.env.production, porque un servidor que publica uno suele publicar los tres.
¿Un archivo .env en mi bundle es lo mismo?
No, y la solución es otra. Un valor que acaba dentro de tu JavaScript llegó ahí porque al build se le pidió, con una variable llamada VITE_ o NEXT_PUBLIC_ o EXPO_PUBLIC_. El archivo nunca salió de tu máquina; el valor sí. Ese es un problema aparte y tiene su propio artículo. Este va del archivo en sí, servido por tu servidor web, lo que deja público cada valor que contiene, lleve prefijo o no.
¿Por qué alguien puede descargar mi carpeta .git?
Porque tu servidor apunta a tu carpeta de proyecto y .git es un directorio dentro de ella como cualquier otro. Un servidor no sabe que uno de ellos es tu historial de versiones. Si /.git/config o /.git/HEAD responde con texto, todo el directorio suele ser legible, y eso incluye cada commit que hiciste, no solo el código que tienes hoy.
He encontrado un backup.sql en mi propio sitio, ¿y ahora?
Sácalo de la carpeta que publica tu servidor antes de hacer nada más, porque el archivo se está entregando mientras lees esto. Después trata como público cada contraseña, clave y dato personal que contenga: un dump lleva el esquema y todas las filas de todas las tablas. Y averigua cómo llegó ahí, que suele ser alguien que hizo una copia en la carpeta del proyecto y nunca la movió.
¿Roto las claves o borro el archivo primero?
Mueve el archivo primero y rota después. Rotar mientras el archivo se sigue sirviendo escribe los valores nuevos en algo que cualquiera puede descargar, así que gastas el trabajo y acabas donde estabas. Cuando ya no responda nada en esa ruta, rota cada secreto que tenía el archivo, en cada proveedor, y revisa facturación y registros al terminar.