Cómo funciona esta comprobación
Scanaris pide una lista de rutas donde suelen quedarse archivos que nunca deberían estar en un servidor público: .env, .env.local, .env.production, .git/config, .git/HEAD, wp-config.php, config.json, docker-compose.yml, backup.sql, phpinfo.php, .npmrc, Dockerfile, .gitlab-ci.yml, .aws/credentials, .ssh/id_rsa, web.config, adminer.php y .DS_Store. Un 200 no basta para marcar nada: muchas aplicaciones de una sola página responden 200 con su HTML a cualquier ruta. Por eso el escáner comprueba que el contenido parece real, por ejemplo que un .env tiene líneas CLAVE=valor y no es HTML, o que .git/HEAD empieza por ref:. La severidad depende del archivo: crítica para .env, copias de la base de datos y claves privadas; alta para .git y configuraciones de servidor; baja para .DS_Store o un Dockerfile.
Qué hace un atacante con esto
Lo mismo que el escáner, pero contra todos los dominios que existen. Un .env accesible entrega las credenciales de la base de datos, las claves de pago y los tokens de correo de golpe. Un directorio .git expuesto permite descargar el repositorio completo con una herramienta de línea de comandos, con todo el historial, incluidas las claves que se borraron en un commit posterior. Un backup.sql es tu base de datos entera, con usuarios y contraseñas cifradas. Es uno de los fallos más sencillos de explotar y más graves.
Por qué ocurre
Dos causas. La primera es desplegar la carpeta del proyecto entera en lugar de la carpeta de salida del build: todo lo que hay en el repositorio pasa a ser servible. La segunda es un servidor web (nginx, Apache, IIS) que sirve una carpeta donde también se desarrolla o se hacen copias de seguridad. Las plataformas como Vercel o Netlify solo publican la carpeta de salida, por eso allí este fallo suele reducirse a archivos copiados por error a public/.
Cómo arreglarlo
- Quita el archivo y rota todo lo que contenía. Si un
.envestuvo accesible, cada clave que hay dentro está comprometida aunque nadie te avisara. - Despliega solo la salida del build (
dist/,.output/,.next/), nunca la raíz del proyecto. En Express,express.staticdebe apuntar a esa carpeta. - Bloquea las rutas en el servidor como segunda barrera. En nginx:
location ~ /\.(?!well-known) { deny all; }bloquea todo lo que empieza por punto salvo.well-known; añade reglas para*.sql,*.ymly*.log. En Apache, lo equivalente conFilesMatchyRequire all denied. - Revisa la carpeta public/ de tu proyecto: ahí no debe haber nada que no sea una imagen, una fuente o un archivo pensado para descargarse.
- Mira los registros de acceso buscando peticiones a
/.envo/.gitcon respuesta 200 para saber si alguien llegó antes que tú.
Comprueba que está arreglado
for p in .env .env.local .git/config .git/HEAD config.json backup.sql docker-compose.yml .npmrc; do
printf "%-20s " "$p"; curl -s -o /dev/null -w "%{http_code}\n" "https://tudominio.com/$p"
done
Lo esperado es 404 o 403 en todos. Un 200 en una SPA puede ser el HTML de siempre: ábrelo y confirma que no es el archivo real.
Preguntas frecuentes
Mi web devuelve 200 en todas las rutas. ¿Me va a marcar falsos positivos?
No. El escáner valida el contenido antes de abrir un hallazgo; un HTML genérico no cuenta como .env.
¿Por qué .DS_Store es un hallazgo si no contiene secretos?
Porque lista los nombres de los archivos de la carpeta, incluidos los que no enlazas desde ninguna página. Es un mapa gratuito para quien busca copias de seguridad o archivos olvidados. Severidad baja, pero conviene quitarlo.
¿Y las rutas .well-known?
Esas deben ser accesibles: ahí viven security.txt, las verificaciones de dominio y los certificados. La regla de nginx del ejemplo las excluye del bloqueo.