Cómo funciona esta comprobación
Scanaris pide dos archivos en la raíz de tu dominio: /package.json, el manifiesto de un proyecto Node, y /composer.json, el de un proyecto PHP. Para cada uno, da el hallazgo por confirmado solo si responde 200, el cuerpo no es HTML, es JSON válido y contiene las claves propias del manifiesto: dependencies o devDependencies en el primero, require o require-dev en el segundo. Así no marca una aplicación de una sola página que devuelve su HTML para cualquier ruta, ni un JSON cualquiera que casualmente viva ahí. Cada archivo encontrado abre un hallazgo de severidad baja.
Por qué importa
Estos archivos no contienen secretos, pero contienen algo casi tan útil para un atacante: la lista exacta de librerías que usas y sus versiones. Con eso no tiene que adivinar nada: cruza tu lista con la base de datos de vulnerabilidades conocidas y sabe qué exploit probar y contra qué. También revelan el nombre interno del proyecto, los scripts de build y despliegue, a veces la URL del repositorio privado y los nombres de paquetes internos de tu organización, que son el punto de partida de los ataques de confusión de dependencias. Es severidad baja porque es información, no acceso; pero es información que regalas sin motivo.
Por qué pasa
Porque el servidor web sirve la carpeta del proyecto entera en lugar de la carpeta de salida. Un Vite, Nuxt o Next.js bien desplegados solo publican dist/, .output/public/ o .next/static/, y ahí no hay package.json. Aparece cuando se sube el repositorio completo por FTP a public_html, cuando el directorio raíz de un hosting estático apunta a la raíz del proyecto, o en proyectos PHP donde composer.json está en el mismo directorio que index.php, que es lo habitual en WordPress con Composer y en instalaciones hechas a mano.
Cómo arreglarlo
- Sirve solo la carpeta pública. En Vercel y Netlify, revisa el "output directory" o "publish directory" del proyecto; debe ser la carpeta de build, no
.. En nginx y Apache, elrootoDocumentRootapunta apublic/odist/. - En PHP, mueve
composer.json,composer.lockyvendor/un nivel por encima del directorio público si tu framework lo permite (Laravel y Symfony ya lo hacen). Si no puedes moverlos, bloquéalos:
location ~* /(package|composer)(-lock)?\.json$ { return 404; }
location ~* /(yarn|pnpm-lock)\.(lock|yaml)$ { return 404; }
- En Apache, un
<FilesMatch "^(package|composer)(-lock)?\.json$"> Require all denied </FilesMatch>en la configuración o en el.htaccess. - Revisa qué más se subió. Donde hay un
package.jsonpúblico suele haber un.env, un.git/o uncomposer.lockal lado; la comprobación de archivos sensibles busca los graves.
Comprueba que está arreglado
for p in package.json composer.json package-lock.json composer.lock yarn.lock; do
printf "%-20s " "$p"; curl -s -o /dev/null -w "%{http_code} %{content_type}\n" "https://tudominio.com/$p"
done
Esperado: 404 o 403. Un 200 con text/html es el HTML genérico de una aplicación de una sola página y no es problema; un 200 con application/json es el archivo real.
Preguntas frecuentes
Mi repositorio es público en GitHub de todos modos.
Entonces el manifiesto ya es público y este hallazgo no añade exposición nueva; puedes darlo por aceptado. Aun así, corregir la raíz del servidor evita que el siguiente archivo que caiga en la carpeta del proyecto se publique sin querer.
¿Y los lockfiles?
Esta comprobación mira los dos manifiestos, pero package-lock.json, yarn.lock y composer.lock son peores: listan todas las dependencias transitivas con versión exacta. Si el manifiesto está expuesto, casi seguro que el lockfile también; la regla de bloqueo de arriba los cubre.
Si no sirvo el manifiesto, ¿nadie puede saber qué versiones uso?
Pueden deducirlo por otros medios, más lentos: cabeceras, rutas de los bundles, comportamiento. Lo que detecta librerías desactualizadas en tu JavaScript, por ejemplo, lo ve cualquiera. Ocultar el manifiesto quita el atajo; actualizar las dependencias quita el problema.