Wie diese Prüfung funktioniert
Scanaris fordert zwei Dateien im Root deiner Domain an: /package.json, das Manifest eines Node-Projekts, und /composer.json, das eines PHP-Projekts. Für jede bestätigt es den Befund nur, wenn sie mit 200 antwortet, der Body kein HTML ist, es gültiges JSON ist und die für das Manifest typischen Schlüssel enthält: dependencies oder devDependencies bei der ersten, require oder require-dev bei der zweiten. So meldet es weder eine Single-Page-App, die für jeden Pfad ihr HTML zurückgibt, noch irgendein JSON, das zufällig dort liegt. Jede gefundene Datei öffnet einen Befund mit niedrigem Schweregrad.
Warum das wichtig ist
Diese Dateien enthalten keine Geheimnisse, aber etwas fast ebenso Nützliches für einen Angreifer: die genaue Liste der Bibliotheken, die du nutzt, mit ihren Versionen. Damit muss er nichts raten: Er gleicht deine Liste mit der Datenbank bekannter Schwachstellen ab und weiß, welchen Exploit er gegen was probiert. Sie verraten außerdem den internen Projektnamen, die Build- und Deploy-Skripte, manchmal die URL des privaten Repositorys und die Namen interner Pakete deiner Organisation, die der Ausgangspunkt von Dependency-Confusion-Angriffen sind. Es ist niedriger Schweregrad, weil es Information ist, kein Zugang; aber Information, die du grundlos verschenkst.
Warum das passiert
Weil der Webserver den ganzen Projektordner statt des Ausgabeordners ausliefert. Ein sauber deploytes Vite, Nuxt oder Next.js veröffentlicht nur dist/, .output/public/ oder .next/static/, und dort gibt es keine package.json. Es taucht auf, wenn das komplette Repository per FTP nach public_html hochgeladen wird, wenn das Root-Verzeichnis eines statischen Hosters auf das Projekt-Root zeigt, oder in PHP-Projekten, in denen composer.json im selben Verzeichnis wie index.php liegt, was bei WordPress mit Composer und handgemachten Installationen üblich ist.
So behebst du es
- Liefere nur den öffentlichen Ordner aus. Auf Vercel und Netlify prüfst du das „output directory" bzw. „publish directory" des Projekts; es muss der Build-Ordner sein, nicht
.. In nginx und Apache zeigtrootbzw.DocumentRootaufpublic/oderdist/. - In PHP verschiebst du
composer.json,composer.lockundvendor/eine Ebene über das öffentliche Verzeichnis, wenn dein Framework das erlaubt (Laravel und Symfony tun es bereits). Kannst du sie nicht verschieben, sperre sie:
location ~* /(package|composer)(-lock)?\.json$ { return 404; }
location ~* /(yarn|pnpm-lock)\.(lock|yaml)$ { return 404; }
- In Apache ein
<FilesMatch "^(package|composer)(-lock)?\.json$"> Require all denied </FilesMatch>in der Konfiguration oder in der.htaccess. - Prüf, was sonst noch hochgeladen wurde. Wo eine öffentliche
package.jsonliegt, liegt meist eine.env, ein.git/oder einecomposer.lockdaneben; die Prüfung sensible Dateien sucht die schweren Fälle.
Prüf, ob es behoben ist
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://deinedomain.de/$p"
done
Erwartet: 404 oder 403. Ein 200 mit text/html ist das generische HTML einer Single-Page-App und kein Problem; ein 200 mit application/json ist die echte Datei.
Häufige Fragen
Mein Repository ist ohnehin öffentlich auf GitHub.
Dann ist das Manifest bereits öffentlich und dieser Befund fügt keine neue Exposition hinzu; du kannst ihn als akzeptiert markieren. Trotzdem verhindert das Korrigieren des Server-Roots, dass die nächste Datei, die im Projektordner landet, versehentlich veröffentlicht wird.
Und die Lockfiles?
Diese Prüfung schaut auf die beiden Manifeste, aber package-lock.json, yarn.lock und composer.lock sind schlimmer: Sie listen jede transitive Abhängigkeit mit exakter Version. Ist das Manifest exponiert, ist es das Lockfile fast sicher auch; die Sperrregel oben deckt sie ab.
Wenn ich das Manifest nicht ausliefere, kann dann niemand wissen, welche Versionen ich nutze?
Man kann es auf anderen, langsameren Wegen ableiten: Header, Bundle-Pfade, Verhalten. Was veraltete Bibliotheken in deinem JavaScript erkennt, sieht zum Beispiel jeder. Das Manifest zu verbergen nimmt die Abkürzung weg; die Abhängigkeiten zu aktualisieren nimmt das Problem weg.