Was der decivo Scan prüft
Regel-Katalog
Jede Prüfung des decivo Scans hat einen stabilen, versionierten Code (z. B. RSC-SEC-014) und eine eigene Seite: was geprüft wird, warum es zählt und was zu tun ist. Der Katalog ist die offene Referenz hinter jedem Verdikt.
Eigene App kostenlos prüfenWie das Verdikt entsteht
Bis August 2026 stand hier eine Rechnung: gewichtete Dimensionen, Strafpunkte je Befund, Schwellen. Sie ist ersatzlos entfallen. Eine Zahl aus frei gewählten Gewichten sah nach Messung aus, war aber eine Meinung mit Nachkommastelle — und sie konnte einen belegten Befund hinter einem guten Mittelwert verstecken. Stattdessen gelten drei nachlesbare Regeln über Schwere und Belastbarkeit. Die Werte auf dieser Seite kommen direkt aus dem laufenden Code, nicht aus einer Doku, die veralten kann.
Schweregrad: wie schlimm es wäre
Vier Stufen. Sie sagen, was der Befund anrichtet, wenn er zutrifft. Reine Hinweise („Supabase erkannt“) zählen intern als Info und gehen weder in die Liste noch in das Verdikt ein.
- Kritisch
- Hoch
- Mittel
- Niedrig
Belastbarkeit: wie sicher wir es wissen
Abgeleitet aus der Prüfstufe, die den Befund erzeugt hat. Ein fehlender Header ist von außen zweifelsfrei belegt; eine Vermutung aus einer halb rekonstruierten Source Map ist es nicht. Ohne diese zweite Angabe stünden Beobachtung und Vermutung gleichberechtigt nebeneinander:
- bestätigt
- hoch
- mittel
Die drei Regeln, je Dimension
- Stopp: mindestens ein Befund mit Schweregrad „kritisch“ UND Belastbarkeit „bestätigt“. Beides zusammen — „bestätigt“ gibt es nur aus einem Lauf mit Domain-Nachweis oder mit Repo-Zugang. Das heißt ausdrücklich: Ein rein anonymer Außen-Scan sagt nie „Stopp“. Er kennt keinen Befund, den er selbst ausgelöst oder im Quelltext gelesen hätte.
- Iterate: sonst — mindestens ein Befund ab „hoch“ aufwärts, oder mindestens 3 Befunde mit Schweregrad „mittel“ in derselben Dimension, oder ein einzelner mittlerer Befund in Sicherheit oder Datenschutz. Ein einzelner mittlerer Befund ist in einem MVP normal; 3 in einem Bereich sind ein Muster. In den zwei Bereichen, die Vertrauen tragen, genügt aber schon einer: Neben einer anonym lesbaren Tabelle oder Tracking ohne Einwilligung können wir nicht „tragfähig“ sagen.
- Go: sonst. Befunde mit Schweregrad „niedrig“ kippen das grüne Licht nie — acht fehlende HTTP-Header sind kein kaputtes Fundament. Und zwei mittlere Befunde sind davon ausgenommen: „Content-Security-Policy fehlt“ und „HSTS-Header fehlt“ beschreiben eine fehlende zusätzliche Schutzschicht, keinen Defekt. Nicht gehärtet ist nicht dasselbe wie kaputt.
Das Gesamturteil ist das schlechteste Verdikt über die sechs Dimensionen. Befunde werden dabei nicht über Bereiche hinweg zusammengezählt: Die Schwelle soll ein Muster in EINEM Bereich erkennen. Befunde, die ein verbundenes Repository widerlegt hat, zählen nicht mehr mit.
Was nicht geprüft werden konnte, wird als „begrenzt prüfbar“ ausgewiesen und zeigt „—“, nie eine Null und nie eine geschönte Bestnote. Daneben steht die Prüftiefe in Zahlen: wie viele Prüfungen in eurem Fall anwendbar waren und wie viele davon tatsächlich liefen. Prüfungen, die für euch gar nicht gelten, verlassen den Nenner — Prüfungen, die wir nicht ausführen konnten, bleiben darin.
Sicherheit
- Kein durchgängiges HTTPSPrüft, ob die finale Antwort verschlüsselt ausgeliefert wird oder auf http endet.
- HSTS-Header fehltPrüft, ob Strict-Transport-Security gesetzt ist und der erste Aufruf damit abgesichert wird.
- Keine Content-Security-PolicyPrüft Header und Meta-Tag auf eine Content-Security-Policy als Schutzschicht gegen XSS.
- MIME-Sniffing nicht unterbundenPrüft, ob `X-Content-Type-Options: nosniff` das Erraten von Inhaltstypen verhindert.
- Kein Clickjacking-SchutzPrüft X-Frame-Options bzw. CSP `frame-ancestors` gegen das Einbetten in fremde Seiten.
- CORS-Wildcard am öffentlichen DokumentErfasst `Access-Control-Allow-Origin: *` am HTML — allein keine Lücke, aber ein Prüf-Signal.
- Server-Technologie preisgegebenErfasst Version-Header wie `X-Powered-By`, die gezielte Exploit-Suche erleichtern.
- Cookies ohne Secure/HttpOnlyPrüft gesetzte Cookies auf die Flags Secure und HttpOnly.
- Supabase-Client-Konfiguration sichtbarErfasst Projekt-URL und anon-Key im Bundle — erwartbar, aber Ausgangspunkt der Zugriffsprüfung.
- Supabase service_role-Schlüssel im ClientDekodiert JWTs im Bundle und meldet den Rollen-Claim `service_role`, der RLS vollständig umgeht.
- Geheimschlüssel im ausgelieferten CodeSucht Muster echter Geheimnisse (Stripe, AWS, GitHub, PEM) im HTML und in den Bundles.
- Firebase-Backend erkanntErfasst Firebase-Konfiguration im Client — zulässig, entscheidend sind die Sicherheitsregeln.
- .env-Datei öffentlich abrufbarRuft bekannte Env-Pfade ab und bestätigt Treffer inhaltlich anhand von KEY=VALUE-Zeilen.
- .git-Verzeichnis öffentlichBestätigt ein ausgeliefertes `.git` über die Signaturen von `config` und `HEAD`.
- Datenbank-Tabellen der anon-Rolle zugeteiltFragt mit dem von der App selbst veröffentlichten Client-Schlüssel den Schema-Endpunkt ab und listet die Tabellen, die der anon-Rolle zugeteilt sind — ohne einen Datensatz oder eine Zeilenzahl abzurufen. Zugeteilt heißt noch nicht lesbar: ob Zeilen herauskommen, entscheidet Row Level Security.
- Datenbank-Tabellen anonym lesbar (nachgewiesen)Beantwortet die eigentliche Frage: Kommen mit dem öffentlich ausgelieferten Schlüssel tatsächlich Zeilen heraus? Gezählt wird ausschließlich über den `Content-Range`-Header einer HEAD-Anfrage — es wird keine einzige Zeile abgerufen.
- Google API-Schlüssel im ClientErfasst Schlüssel im Format `AIza…` im ausgelieferten Code — bei Maps und Firebase vorgesehen, riskant ohne Domain-Beschränkung.
- Datenbank-Zugangsdaten im Client sichtbarFindet Connection-Strings mit eingebettetem Benutzer und Passwort im HTML oder in den Bundles.
- Firestore-Collections anonym lesbarFragt die Collections, die die App selbst im Bundle nennt, mit dem öffentlichen Web-Schlüssel an — mit `mask.fieldPaths=__name__`, also nur Dokument-Namen, kein einziger Feldwert.
- Datenbank-Funktionen für anonyme Aufrufer freigegebenListet die der anon-Rolle zugeteilten RPC-Funktionen aus der Schema-Beschreibung — gelesen, nicht aufgerufen.
- Registrierung offen, Konten sofort bestätigtLiest die anonym abrufbare Auth-Konfiguration: Selbstregistrierung offen und E-Mail-Bestätigung deaktiviert.
- KI-API direkt aus dem Browser gerufen (statisch belegt)Findet Endpunkte von KI-Anbietern im ausgelieferten Code. Mit passendem Schlüssel-Muster im selben Bundle `high`, ohne `medium`.
- Browser schreibt direkt in die DatenbankFindet Schreibaufrufe (`insert`, `update`, `upsert`, `delete`, Storage-Upload) im ausgelieferten Code bzw. in `use client`-Dateien.
- Zugangsdaten fest im Browser-Code verglichenFindet Vergleiche wie `password === "…"` gegen ein festes Literal im ausgelieferten Code.
- Storage-Buckets öffentlich listbarPrüft, ob Datei-Buckets ihre Objektliste anonym herausgeben.
- Firebase-Datenbank anonym lesbarFragt den Realtime-Database-Endpunkt mit `shallow=true` ab: antwortet er mit Schlüsselnamen statt „permission denied“, ist die Datenbank offen.
- Geschützter Bereich nur im Browser abgesichertErkennt Admin-/Konto-Routen, die der Client-Router kennt, deren Schutz aber ausschließlich als Bedingung im Bundle steht — wer die URL kennt, ist drin.
- Rollenprüfung im Client-BundleFindet Rollenvergleiche wie `role === "admin"` im ausgelieferten JavaScript — im Browser beliebig manipulierbar.
- Zugangstoken in localStorageBeobachtet zur Laufzeit, ob ein JWT-artiger Wert in Web-Storage landet — per XSS auslesbar.
- KI-API direkt aus dem Browser gerufenBeobachtet Aufrufe an LLM-Anbieter aus dem Client — der dafür nötige Schlüssel liegt damit offen und jeder Besucher kann auf eure Rechnung generieren.
- Backup- oder Datenbank-Dump öffentlichPrüft typische Dump-/Backup-Pfade und bestätigt Treffer über SQL- bzw. Archiv-Signaturen.
- Keine Permissions-PolicyPrüft, ob Kamera, Mikrofon und Standort per Header explizit eingeschränkt werden.
- Keine Referrer-PolicyPrüft, ob ausgehende Links interne URLs samt Query an Dritte weitergeben.
- Unverschlüsselte Inhalte auf HTTPS-Seite (aktiv / passiv)Findet per `http://` eingebundene Ressourcen auf einer https-Seite und trennt aktive (Skripte, Frames, Stylesheets — vom Browser blockiert) von passiven (Bilder, Audio, Video — Warnung, kein Block).
- Anmeldung ohne erkennbare SperreSendet nach Ownership-Nachweis eine kleine Serie fehlerhafter Anmeldeversuche und prüft, ob überhaupt gebremst wird.
- Admin-Bereich anonym erreichbarRuft entdeckte Verwaltungs-Pfade anonym auf und meldet, wenn Inhalt statt Weiterleitung zur Anmeldung kommt.
- Kein SPF und kein DMARC — E-Mail-Fälschung ungeschütztPrüft, ob die Domain mindestens einen SPF- oder DMARC-Eintrag im DNS hat, mit dem Empfänger gefälschte Mails erkennen können.
- Kein DMARC-EintragPrüft, ob zu einem vorhandenen SPF-Eintrag auch ein DMARC-Eintrag existiert, der festlegt, was mit gefälschten Mails passiert.
- SPF-Eintrag mit `+all`Prüft, ob der SPF-Eintrag auf `+all` (oder ein nacktes `all`) endet und damit ausdrücklich jeden Server als Absender zulässt.
- DMARC mit `p=none`Prüft, ob der DMARC-Eintrag noch auf `p=none` steht und Empfänger damit gefälschte Mails weiterhin zustellen sollen.
- Kein DKIM-Schlüssel unter üblichen Selektoren gefundenFragt eine Handvoll gängiger DKIM-Selektoren ab und meldet, wenn unter keinem ein Schlüssel liegt — ein Hinweis, keine Feststellung.
- TLS-Zertifikat abgelaufenLiest das Zertifikat des Hosts aus und prüft, ob sein Gültigkeitsdatum überschritten ist.
- TLS-Zertifikat läuft in Kürze abLiest das Zertifikat des Hosts aus und meldet, wenn es in 14 Tagen oder weniger abläuft.
- Selbstsigniertes TLS-ZertifikatPrüft, ob das ausgelieferte Zertifikat von seinem eigenen Inhaber statt von einer öffentlichen Zertifizierungsstelle beglaubigt ist.
- Kein CAA-EintragPrüft, ob die Domain per CAA-Eintrag festlegt, welche Zertifizierungsstellen Zertifikate für sie ausstellen dürfen.
- Keine security.txt — keine Meldestelle für SicherheitslückenPrüft, ob unter `/.well-known/security.txt` eine Datei nach RFC 9116 mit Kontaktangabe liegt.
- security.txt ohne oder mit abgelaufenem ExpiresPrüft, ob die vorhandene security.txt die Pflichtzeile `Expires:` trägt und ob das Datum noch in der Zukunft liegt.
- package.json öffentlich abrufbarPrüft, ob unter `/package.json` die Projektdatei mit Name und Abhängigkeitsliste ausgeliefert wird.
- API-Beschreibung (OpenAPI/Swagger) öffentlich abrufbarPrüft die gängigen Pfade (`/openapi.json`, `/swagger.json`, `/api-docs`, `/docs` …) auf eine OpenAPI-/Swagger-Beschreibung oder eine Swagger-UI-/ReDoc-Seite.
- Fremde Skripte ohne Subresource IntegrityFindet `<script src>` und `<link rel=stylesheet>` von öffentlichen CDNs (cdnjs, jsDelivr, unpkg, jQuery, Bootstrap …) ohne `integrity`-Attribut.
- Datenbank steht offen (Verbund)Fasst zusammen, was einzeln erklärbar wäre: Zugangsschlüssel im ausgelieferten Code, fehlende oder durchlässige Row Level Security und — für die kritische Stufe — Schreibzugriffe direkt aus dem Browser.
- Mehrere Geheimnis-Familien im selben Stand (Verbund)Feuert, wenn mindestens zwei voneinander unabhängige Geheimnisse (Service-Role-Schlüssel, Datenbank-Verbindung, Zugangsdaten, Anbieter-Schlüssel, Git-Historie) im selben Lauf gefunden wurden.
- Phishing-fertig: Mail-Fälschung plus Login (Verbund)Verbindet eine Domain ohne SPF/DMARC mit einem Anmelde- oder Kontaktformular auf der Seite — die Zutaten einer glaubwürdigen Phishing-Mail im Namen der Domain.
- Sicherheitsrichtlinie erlaubt Skripte direkt im HTMLPrüft, ob die Content-Security-Policy in script-src (bzw. default-src) 'unsafe-inline' erlaubt, ohne Nonce oder Hash.
- Tabellen ohne Row Level SecuritySammelt alle `create table` und alle `enable row level security` über sämtliche Migrationen und meldet Tabellen, die nie geschützt werden.
- RLS-Policy erlaubt allen ZugriffFindet Policies mit `using (true)` — RLS ist aktiv, wirkt aber nicht.
- RLS-Policy prüft nur die Anmeldung, nicht den EigentümerFindet Policies mit `auth.role() = 'authenticated'` oder `auth.uid() is not null` ohne Bezug auf den Datensatz-Eigentümer.
- RLS-Policy erlaubt beliebiges SchreibenFindet `with check (true)` — die Lesebedingung kann streng sein, geschrieben werden darf trotzdem alles.
- Edge Function ohne JWT-PrüfungFindet `verify_jwt = false` in `supabase/config.toml` — die Serverfunktion ist ohne Anmeldung aufrufbar.
- Firebase-Regeln erlauben allen ZugriffFindet `allow …: if true` in `.rules`-Dateien — Firestore oder Storage stehen offen.
- Kostenpflichtige Serverfunktion ohne ZugangsschutzFindet Edge Functions, die ein KI-Modell, SMS oder Mailversand aufrufen, ohne den Aufrufer zu prüfen oder die Menge zu begrenzen — jeder kann damit Kosten auf eure Rechnung auslösen.
- service_role-Schlüssel im QuellcodeDekodiert JWTs in Quelldateien und meldet den Rollen-Claim `service_role`; auf dem Syntaxbaum zusätzlich Client-Dateien, die den Schlüssel aus der Umgebung lesen (Kommentare zählen nicht).
- Geheimschlüssel im QuellcodeSucht Muster echter Geheimnisse in Quelldateien (Stripe, AWS, OpenAI, Anthropic, Slack, SendGrid, PEM).
- Datenbank-Zugangsdaten im KlartextFindet Connection-Strings mit eingebettetem Benutzer und Passwort.
- .env-Datei versioniertErkennt eine `.env` mit gesetzten Werten im Quellstand (nicht `.env.example`).
- Supabase genutzt, Schema nicht versioniertMeldet, wenn Supabase im Code steckt, aber keine Migrationen mit Tabellen und RLS vorliegen — dann ist der Zugriffsschutz aus dem Code nicht verifizierbar.
- Geheimnis über öffentliches Env-Präfix exponiertFindet Variablen mit öffentlichem Bundler-Präfix (`NEXT_PUBLIC_`, `VITE_`, `REACT_APP_`, `PUBLIC_`, `EXPO_PUBLIC_` …), deren Name auf ein Geheimnis hindeutet — solche Werte landen fest im Client-Bundle.
- Env-Datei im ausgelieferten BundleErkennt eine `.env` mit gesetzten Werten unter den aus Source Maps rekonstruierten Dateien — der Build hat sie mit ausgeliefert.
- Server Action ohne ZugriffsprüfungWertet den Körper jeder `"use server"`-Funktion aus, die schreibend auf die Datenbank zugreift, und meldet fehlende Identitätsprüfung — Server Actions sind öffentliche Endpunkte.
- Öffentliche Schreib-Aktion mit MissbrauchsschutzFindet Server Actions und Route Handler, die ohne Anmeldung in die Datenbank schreiben, davor aber Aufrufe begrenzen und Eingaben prüfen — gewollt öffentliche Formulare.
- Systemprompt im ausgelieferten CodePrüft, ob die Steueranweisung eines KI-Assistenten im Browser-Code mitgeliefert wird.
- Ausgelieferte Bibliothek mit bekannter SchwachstelleErkennt Frontend-Bibliotheken samt Version im ausgelieferten Bundle und in rekonstruierten Source Maps (retire.js-Signaturen) und gleicht die Version gegen OSV bzw. die mitgelieferte retire.js-Datenbank ab. Meldet nur bei sicher erkannter Version.
- Abhängigkeiten mit bekannter SchwachstelleGleicht die Paketversionen des Repos gegen die öffentliche OSV-Datenbank ab und nennt die betroffenen Pakete mit Advisory-ID.
- Geheimschlüssel in der Git-HistoriePrüft die hinzugefügten Zeilen der letzten Commits gegen dieselbe Musterliste wie den aktuellen Stand — ein gelöschter Schlüssel bleibt lesbar.
- Env-Datei war im Repository und wurde entferntMeldet Umgebungsdateien, die in der geprüften Historie auftauchen, im aktuellen Stand aber fehlen — die alten Werte bleiben abrufbar.
- Angemeldete Fremde lesen fremde DatensätzeLegt nach Domain-Nachweis ein Wegwerf-Konto an und zählt, wie viele Zeilen dieses fremde Konto in personenbezogenen Tabellen sieht — ohne eine einzige Zeile abzurufen.
- Tabelle für jeden Angemeldeten vollständig lesbarMeldet Tabellen, die anonym verborgen sind, einem frisch angelegten Fremdkonto aber alle Zeilen zeigen — bei Katalogdaten gewollt, sonst derselbe Fehler wie bei personenbezogenen Tabellen.
- Mandantentrennung hält im TestBestätigt, dass ein frisch angelegtes Konto ohne eigene Daten in den geprüften Tabellen keine Zeilen sah.
- Fremdes Konto sieht eure hochgeladenen DateienPrüft mit einem Wegwerf-Konto, ob nicht-öffentliche Datei-Ablagen auflistbar sind.
- Datei-Ablage gegen fremde Konten abgeschottetDie positive Gegenaussage: Kein nicht-öffentlicher Bucket gab einem fremden Konto eine Dateiliste.
- Fremdes Konto darf in eure Tabellen schreibenLegt ein Wegwerf-Konto an und prüft je Tabelle, ob es schreiben darf.
- Schreibzugriff für fremde Konten abgewiesenDie positive Gegenaussage: Die geprüften Tabellen wiesen jeden Schreibversuch ab.
- Prüfung auf fremde Datensätze nicht durchführbarHält fest, dass kein Konto angelegt werden konnte (Registrierung zu oder Bestätigung nötig) — die Mandantentrennung bleibt damit ungeprüft.
- Zugriffsprüfung erst nach dem SchreibzugriffVergleicht die Position von Auth-Aufruf und Datenbank-Schreibvorgang im selben Funktionskörper und meldet, wenn geprüft wird, nachdem geschrieben wurde.
- Auth-Ergebnis wird nie ausgewertetMeldet Funktionen, die die Identität abfragen, das Ergebnis aber weder prüfen noch an den Schreibvorgang binden — der Aufruf bleibt wirkungslos.
- API-Route ohne ZugriffsprüfungFindet Route Handler mit schreibenden Methoden, in denen keine Authentifizierung vorkommt.
- Eingaben ungeprüft in die DatenbankVerfolgt Request-Bodies, die ohne Schema-Validierung in ein Insert oder Update fließen (Mass Assignment).
- Webhook ohne SignaturprüfungErkennt Zahlungs-Webhooks, die den Payload verarbeiten, ohne die Signatur zu verifizieren — jeder kann dann Zahlungen vortäuschen.
- Ungeprüftes HTML gerendertFindet `dangerouslySetInnerHTML` mit dynamischem Wert — der klassische XSS-Pfad.
Datenschutz (DSGVO)
- Google Fonts zur Laufzeit geladenFindet Einbindungen von fonts.googleapis.com/gstatic.com, bei denen die IP der Nutzer an Google geht.
- US-Tracker im ausgelieferten Code eingebundenErkennt Analytics- und Pixel-Skripte mit Drittland-Transfer (Google Analytics/Tag Manager, Meta Pixel, Hotjar, Segment) im ausgelieferten Markup. Ob sie erst nach der Einwilligung laden, prüft diese Regel nicht — das belegt die Browser-Prüfung (RSC-PRV-005).
- Pflichtangaben nicht sichtbarPrüft bei Zielen mit DE-, AT- oder CH-Bezug, ob Impressum und Datenschutzerklärung verlinkt sind.
- Datenschutz-Signale unauffälligStandardisierte Einordnung, wenn von außen keine Drittland-Transfers erkennbar sind.
- Tracker feuern vor der EinwilligungBeobachtet im Browser, welche Third-Party-Hosts schon beim ersten Aufruf kontaktiert werden, bevor irgendetwas zugestimmt wurde — der belegbare Verstoß, den statische Prüfungen nur vermuten können.
- Daten an Drittland-DiensteListet zur Laufzeit kontaktierte Fremd-Hosts, die typischerweise außerhalb der EU verarbeiten.
- Tracker ohne Consent-OberflächeMeldet einwilligungspflichtige Skripte, ohne dass im Markup irgendeine Consent-Mechanik erkennbar ist.
- Tracking-Cookies ohne Einwilligung gesetztBeobachtet im Browser, welche Cookies gesetzt werden, bevor irgendeine Einwilligung erteilt wurde.
- Fremd-Cookies ohne Einwilligung gesetztMeldet Cookies fremder Domains, die vor jeder Einwilligung gesetzt wurden.
- Einwilligung ohne gleichwertiges AblehnenPrüft im gerenderten Zustand, ob neben dem Zustimmen auch ein Ablehnen auf derselben Ebene steht.
- Impressum: Musterwerte aus der VorlagePrüft, ob im Impressum noch Beispielwerte stehen — Muster-Anschrift, Platzhalter-E-Mail, unersetzte Vorlagen-Felder.
- Kundenstimme mit erfundenem GesichtFindet Portraits aus Zufallsgeneratoren in Abschnitten, die Kundenstimmen oder Referenzen zeigen.
- Erzeugte Medien ohne sichtbaren HinweisPrüft bei Apps, die Bilder oder Töne erzeugen, ob irgendwo im sichtbaren Text steht, dass Inhalte von einer KI stammen.
- Kontaktdaten im ausgelieferten JavaScriptFindet E-Mail-Adressen und Telefonnummern im Klartext in den ausgelieferten JavaScript-Bundles — nicht im HTML, wo das Impressum sie verlangt.
- Liste von E-Mail-Adressen öffentlich im MarkupZählt verschiedene E-Mail-Adressen im ausgelieferten HTML der Startseite und der entdeckten Unterseiten und meldet ab zehn — das sieht nach einer öffentlich gerenderten Mitglieder- oder Kundenliste aus.
- CSP-Verstöße werden an einen Drittanbieter gemeldetLiest `report-uri` und `report-to` aus der Content-Security-Policy (Header oder Meta-Tag) und meldet, wenn die Berichte an einen fremden Host gehen.
- Ablehnen wirkt nicht — Tracker laden trotzdemKlickt im Browser das erkannte „Ablehnen“ des Consent-Banners und beobachtet, ob danach weiterhin Tracking-Dienste kontaktiert oder Tracking-Cookies gesetzt werden — der Differentialscan, der ein vorhandenes Banner erst belastbar macht.
- Ablehnen wirktPositivbefund des Differentialscans: Nach dem Klick auf „Ablehnen“ wurden keine Tracking-Dienste kontaktiert und keine Tracking-Cookies gesetzt.
- Bilder ohne Herkunfts-KennzeichnungPrüft bei Apps, die Bilder erzeugen, ob den ausgelieferten Bildern die unsichtbare Angabe „von einer KI erstellt“ noch anhängt.
- Dienst nicht in der Datenschutzerklärung genanntGleicht die Dienste, die eure App im Browser tatsächlich anspricht, gegen den Text eurer Datenschutzerklärung ab.
- KI-Interaktion ohne erkennbaren HinweisFindet KI-Endpunkte im Client, ohne dass im sichtbaren Text auf die KI-Interaktion hingewiesen wird.
- Tracking-Cookie mit sehr langer LaufzeitMisst die Laufzeit gesetzter Tracking-Cookies und meldet alles über einem Jahr.
- Einwilligung mit vorangekreuzten KästchenFindet bereits angekreuzte Auswahlkästchen in der Consent-Oberfläche.
- Impressum: Pflichtangaben nicht gefundenRuft die Impressumsseite ab und sucht Anschrift, Kontaktmöglichkeit und die Kennung des Rechtsraums (USt-ID/Handelsregister, Firmenbuch/UID, CHE-UID/Handelsregister).
- Datenschutzerklärung: Pflichtangaben nicht gefundenRuft die Datenschutzerklärung ab und sucht Verantwortlichen, Betroffenenrechte, Beschwerderecht und Speicherdauer.
- Formular ohne DatenschutzhinweisFindet Formulare mit E-Mail-Feld, in deren Umfeld kein Hinweis auf die Datenschutzerklärung steht.
- Consent-Theater (Verbund)Fasst zusammen, wenn ein Banner mit Ablehnen-Möglichkeit vorhanden ist und einwilligungspflichtige Dienste trotzdem laden — vor der Entscheidung oder nach dem Klick auf „Ablehnen“.
Skalierung & Kosten
- Abfragen ohne LimitFindet Datenbank-Abfragen, die ganze Tabellen ohne `limit`/`range` laden — funktioniert bei 10 Datensätzen, kippt bei 10.000.
- Sehr großes JavaScript-BundleMisst die ausgelieferten Skript-Bytes gegen ein Budget — jedes Megabyte kostet Ladezeit und mobile Abbrüche.
- Bilder ohne OptimierungZählt Bilder ohne Größenangabe oder moderne Formate — treibt Traffic-Kosten und Layout-Sprünge.
- Keine Cache-StrategiePrüft, ob Antworten Cache-Direktiven tragen — ohne sie zahlt jeder Aufruf den vollen Preis.
- Antworten unkomprimiertPrüft, ob Text-Antworten mit gzip/brotli ausgeliefert werden.
- Abfragen in der Schleife (N+1)Erkennt `await` innerhalb einer Schleife über Datensätze — pro Zeile ein Roundtrip.
- Schwerer erster SeitenaufrufMisst im Browser die tatsächlich übertragenen Bytes des ersten Aufrufs.
- Skalierungs-Signale unauffälligStandardisierte Einordnung, wenn Bundle, Caching und Bilder im Rahmen liegen.
- Der Server antwortet langsamMisst, wie lange der Server braucht, bevor eine Seite zu laden beginnt — Startseite und die ersten Unterseiten, die der Scan ohnehin lädt, ohne Antworten aus einem Cache.
Barrierefreiheit (BFSG)
- Keine SprachauszeichnungPrüft das `lang`-Attribut am `<html>`-Element — ohne es liest der Screenreader in der falschen Sprache vor.
- Bilder ohne AlternativtextZählt `<img>`-Elemente ohne `alt`-Attribut im ausgelieferten Markup.
- Formularfelder ohne LabelFindet Eingabefelder ohne zugeordnetes Label oder `aria-label` — mit Screenreader nicht bedienbar.
- Barrierefreiheits-Verstöße im gerenderten ZustandFührt axe-core gegen die fertig gerenderte Seite aus und meldet die Verstöße nach Schweregrad — die Prüfung, die statische Analyse nicht leisten kann.
- BFSG-Relevanz erkanntErfasst Hinweise auf Verkauf oder Dienstleistung an Verbraucher — dann greift das Barrierefreiheitsstärkungsgesetz für den digitalen Auftritt.
- Barrierefreiheits-Signale unauffälligStandardisierte Einordnung, wenn Sprache, Alternativtexte und Labels im Markup vorhanden sind.
Architektur & SEO
- Plattform erkanntErkennt die verwendete No-Code-/KI-Plattform und gewichtet deren typische Lücken.
- Framework-Stack erkanntErkennt den Framework-Stack bei framework-nativen Builds (Cursor, Claude Code, klassisch).
- Domain-Inhaberschaft nachgewiesenBestätigt einen erbrachten Nachweis (DNS-TXT oder Datei) und schaltet die aktiven Prüfungen frei.
- Wegwerf-Konto für die Prüfung angelegtBenennt das Konto, das der Scan für den Nachweis der Mandantentrennung über eure Selbstregistrierung angelegt hat — damit es löschbar bleibt.
- Consent-Anbieter erkanntBenennt die eingebundene Consent-Management-Plattform (Usercentrics, OneTrust, Cookiebot, Borlabs, …) — oder dass nur Googles Consent Mode ohne Banner vorhanden ist.
- Dienste und Routen der App erkanntLiest aus dem ausgelieferten JavaScript, womit die App spricht: eigene Routen, Backend-Dienste (Supabase, Firebase …), KI-Anbieter und weitere fremde Dienste — als Liste mit Methode und Fundstelle.
- Serverfunktionen erkannt, die Geld kosten könnenNennt die Serverfunktionen, die die App aufruft und deren Name nach KI, SMS oder Mailversand klingt — Kandidaten für Kosten-Missbrauch, deren Schutz erst der Quellcode zeigt.
- Testdaten konnten nicht entfernt werdenBenennt Datensätze, die die Schreibprüfung angelegt und nicht wieder gelöscht hat.
- Inhalt nur clientseitig gerendertErkennt leere Root-Container mit kaum Text im initialen HTML.
- Grundlegende Metadaten fehlenPrüft Seitentitel und Meta-Description im Dokument.
- Unveränderter Vorlagen-TitelErkennt Generator-Standardtitel wie „Vite + React“ — Hinweis auf ungeprüfte Tool-Defaults.
- Kein Canonical-LinkPrüft, ob die Seite eine kanonische URL benennt (Duplicate-Content-Schutz).
- Nicht existierende Pfade antworten mit 200Ruft einen zufälligen Pfad ab: liefert die App dort Inhalt mit Status 200, kann weder Browser noch Suchmaschine Fehler erkennen.
- Keine robots.txt und keine SitemapPrüft, ob das Ziel Crawling-Hinweise und eine Sitemap ausliefert.
- Platzhalter-Inhalte im AuslieferungsstandDurchsucht den sichtbaren Text der erreichten Seiten nach Blindtext, Muster-Angaben und unersetzten Vorlagen-Feldern — und die eingebundenen Bilder nach Platzhalter-Diensten wie picsum.photos oder placehold.co.
- Gemischte Ansprache auf einer SeiteZählt Du-, Ihr- und Sie-Form im sichtbaren Text einer Seite und meldet, wenn zwei davon nebeneinander stehen.
- Sehr großes Dokument — Auswertung teilweiseHält fest, dass das HTML-Dokument die Prüfgrenze überschritten hat und nur bis dorthin ausgewertet wurde. Kein Mangel, sondern eine Angabe zur Abdeckung.
- Service Worker erkanntErkennt einen registrierten oder unter `/sw.js` bzw. `/service-worker.js` liegenden Service Worker und wertet seinen Quelltext wie ein Bundle aus.
- Progressive Web App erkanntErkennt ein per `<link rel="manifest">` verlinktes Web-App-Manifest mit Name oder Start-URL.
- Entwicklungs-Adresse im ausgelieferten CodeFindet Aufrufe an localhost, 127.0.0.1, private Netzadressen oder Tunnel (ngrok) im ausgelieferten JavaScript — Reste aus der Entwicklung, die bei Nutzern ins Leere laufen.
- Formular ohne EmpfängerFindet Formulare mit Eingabefeldern, die im HTML keine Zieladresse haben, auf Seiten, deren vollständig gelesener Code ebenfalls keinen Empfänger nennt — keine Adresse zum Senden, kein Backend, keinen Formular-Dienst.
- Knopf führt ins LeerePrüft die Knöpfe der Startseite, deren Text eine Handlungsaufforderung ist („Jetzt starten“, „Termin buchen“, „Get started“): Führt ihr Ziel auf eine Seite, die es nicht gibt? Und auf Seiten ohne JavaScript: Haben sie überhaupt ein Ziel?
- Anfragen gehen an einen EntwicklungsrechnerFindet ausgelieferten Code, der seine Anfragen an localhost, eine private Netzadresse oder einen Tunnel schickt — bei Nutzern kommen diese Anfragen nie an.
- Beim Veröffentlichen fehlte eine EinstellungFindet Adressen im ausgelieferten Code, in denen statt Datenbank oder Server das Wort „undefined“ oder „null“ steht — die zugehörige Einstellung war beim Veröffentlichen nicht gesetzt.
- Die Datenbank der App antwortet nichtPrüft, ob das Supabase-Projekt hinter der App pausiert ist oder nicht mehr existiert — dann funktioniert nichts, was Daten braucht.
- Die Seite bleibt beim Laden leerÖffnet die App in einem echten Browser und prüft, ob nach dem Laden etwas zu sehen ist. Leer und dabei ein Fehler — das ist der „weiße Bildschirm“.
- Datenabfragen der App scheitern beim LadenBeobachtet im Browser, ob Abfragen an die App selbst oder ihre Datenbank beim Laden mit Serverfehler oder Netzfehler enden.
- Server verrät interne ArbeitsschrittePrüft, ob der Header Server-Timing eigene Schrittnamen der App nennt — nicht nur die, die ein CDN von sich aus setzt.
Code-Qualität
- Source Maps öffentlichErkennt ausgelieferte Source Maps, aus denen sich der Originalquellcode rekonstruieren lässt.
- Debug-Reste im AuslieferungsstandFindet `console.log`, `debugger` oder TODO-Marker im Produktions-Bundle.
- Code-Interna begrenzt sichtbarTransparenz-Finding: ohne Source Maps oder Repo-Zugang lässt sich Code-Qualität nicht seriös bewerten.
- Nicht alle Bundles ausgewertetHält fest, dass die Bundle-Auswertung an der Budgetgrenze endete — Funde bleiben gültig, das Fehlen von Funden sagt nichts.
- Quellcode aus Source Maps rekonstruiertTransparenz-Finding: benennt, wie viele Originaldateien aus öffentlichen Source Maps gelesen wurden.
- Abdeckung des Deep-ScansTransparenz-Finding: benennt die Anzahl read-only analysierter Repository-Dateien.
- Keine Dateien lesbarMeldet einen Deep-Scan ohne gelesene Quelldateien — das Ergebnis ist dann nicht aussagekräftig.
- Repository nur teilweise gelesenMeldet, dass die Dateiauswahl an der Budgetgrenze abgeschnitten wurde — Abwesenheits-Regeln schweigen dann, und ein GO ist ausgeschlossen.