Stand
Eine Website kann auf den ersten Blick vollkommen harmlos wirken. Keine Werbung, kein Google Analytics, keine eingebetteten YouTube-Videos und kein offensichtlich blinkender Cookie-Banner.
Damit ist aber noch lange nicht geklärt, was im Hintergrund tatsächlich passiert.
Werden beim Aufruf Cookies gesetzt? Schreibt JavaScript etwas in den LocalStorage? Wird eine Verbindung zu einem Drittanbieter hergestellt? Was passiert erst dann, wenn ein Besucher auf einen Button klickt, seinen Standort freigibt oder eine Route berechnen lässt?
Genau das wollte ich für meine Website einmal vernünftig wissen.
Was ursprünglich als kleiner Check begann, entwickelte sich zu einer ziemlich gründlichen Untersuchung, die letztlich einen großen Teil eines Tages beanspruchte. Einen wesentlichen Teil der Analyse, der Testskripte und der Fehlersuche habe ich dabei mit Unterstützung von ChatGPT durchgeführt.
Und schon vorweg: Es hat sich gelohnt.
Ein Cookie-Scanner war mir irgendwann nicht mehr genug
Mein erster Gedanke war naheliegend: Die Website einfach von einem der bekannten Datenschutz- oder Cookie-Scanner untersuchen lassen.
Solche Dienste sind durchaus nützlich. Sie zeigen beispielsweise Cookies, eingebundene Dienste oder auffällige Drittanbieter-Verbindungen.
Aber relativ schnell stellte sich eine entscheidende Frage:
Was ist mit Funktionen, die erst nach einer Benutzeraktion etwas tun?
Ein normaler Scanner öffnet eine Seite, wartet kurz und analysiert den Zustand. Er klickt aber nicht zwangsläufig auf „Standort verwenden“, berechnet keine Route, speichert keinen Datensatz und probiert nicht jede Funktion einer Webanwendung aus.
Gerade dort können jedoch die interessanten Dinge passieren.
Deshalb wollte ich nicht nur wissen, was eine Website beim Aufruf macht, sondern auch, was sie bei tatsächlicher Benutzung macht.
Das Werkzeug: Playwright
Dafür fiel die Wahl auf Playwright.
Playwright ist ein von Microsoft entwickeltes Open-Source-Framework zur Browser-Automatisierung. Ein Skript kann damit beispielsweise Chromium starten, eine Webseite besuchen, Buttons anklicken, Formulare ausfüllen und gleichzeitig beobachten, welche Netzwerkverbindungen und Browser-Speicher verwendet werden.
Das Entscheidende für meinen Zweck:
Der Test läuft mit einem richtigen Browser.
Damit lässt sich eine Website fast so bedienen, wie es ein echter Besucher tun würde.
Die Einrichtung unter Windows
Für die Tests wurde zunächst eine aktuelle Node.js-Version benötigt.
Nach der Installation ließ sich in PowerShell kontrollieren, ob Node und npm verfügbar sind:
node --version npm --version
Danach wurde ein separates Arbeitsverzeichnis für die Prüfung angelegt:
mkdir Website-Check cd Website-Check mkdir playwright-datenschutzcheck cd playwright-datenschutzcheck
Anschließend entstand ein kleines Node-Projekt:
npm init -y
Playwright wurde installiert mit:
npm install playwright
Danach noch Chromium für Playwright:
npx playwright install chromium
Ein erster einfacher Funktionstest war:
npx playwright screenshot https://example.com test.png
Wenn anschließend ein Screenshot im Verzeichnis liegt, funktioniert die Browser-Automatisierung grundsätzlich.
Bis hierhin war noch nichts Besonderes passiert.
Der interessante Teil begann erst danach.
Test 1: Was passiert beim normalen Seitenaufruf?
Im ersten eigenen Playwright-Skript wurden beim Laden der Website unter anderem folgende Dinge beobachtet:
- Cookies
- LocalStorage
- SessionStorage
- Netzwerkrequests
- externe Domains
- interne Links
Dabei war wichtig, zwischen der eigenen Domain und echten Drittanbietern zu unterscheiden.
Ein einfacher Test auf exakt denselben Hostnamen reicht dafür nicht unbedingt aus. example.de und www.example.de gehören schließlich zur selben Website.
Das Prinzip sah deshalb ungefähr so aus:
function sameSite(url) { const host = new URL(url).hostname.toLowerCase(); return host === 'example.de' || host.endsWith('.example.de'); }
Damit konnte der Crawler die öffentlich erreichbaren Unterseiten besuchen und protokollieren, ob beim normalen Aufruf etwas Auffälliges passiert.
Das erste erfreuliche Ergebnis:
Keine Tracking-Cookies, keine automatischen LocalStorage-Einträge und keine überraschenden Drittanbieter-Verbindungen auf den normalen Inhaltsseiten.
Aber das war nur der passive Test.
Test 2: Welche Bedienelemente gibt es überhaupt?
Der nächste Schritt war zunächst erstaunlich banal.
Bevor ein Skript Funktionen testen kann, muss man wissen, welche Funktionen vorhanden sind.
Deshalb ließ ich Playwright sämtliche relevanten Eingabe- und Bedienelemente bestimmter Werkzeugseiten auflisten.
Zum Beispiel:
const controls = await page.locator( 'button, input, select, textarea' ).evaluateAll(elements => elements.map(el => ({ tag: el.tagName, type: el.type, id: el.id, text: el.innerText || '', value: el.value || '' })) );
Das war nützlicher als erwartet.
Plötzlich war klar ersichtlich, welche Schalter tatsächlich getestet werden mussten: Standort verwenden, Preise abrufen, Route berechnen, Route speichern, Ergebnis speichern und lokale Daten löschen.
Test 3: Eine Standortfunktion ohne echten Standort testen
Für Standortfunktionen bietet Playwright einen sehr angenehmen Vorteil:
Man muss nicht den eigenen tatsächlichen Standort verwenden.
Dem Testbrowser lässt sich einfach ein künstlicher Standort geben.
Beispiel:
const context = await browser.newContext({ geolocation: { latitude: 51.0000, longitude: 7.0000 }, permissions: ['geolocation'] });
Die Werte sind reine Testkoordinaten.
Damit ließ sich kontrolliert untersuchen, was geschieht, wenn der Benutzer auf eine Standortfunktion klickt.
Das war auch ein wichtiger Punkt für die Veröffentlichung dieses Artikels: Für solche Tests müssen keine privaten Wohn- oder Standortdaten dokumentiert werden.
Browser-Verkehr oder Server-Verkehr?
Hier kam eine der wichtigsten Erkenntnisse des gesamten Tages.
Playwright kann hervorragend beobachten, welche Verbindungen der Browser aufbaut.
Nehmen wir an, der Browser ruft auf:
https://example.de/api.php?action=prices
Playwright sieht zunächst nur diese Verbindung.
Was anschließend PHP auf dem Server macht, ist eine andere Sache.
Der Webserver könnte seinerseits beispielsweise eine Preis-, Geocoding- oder Routing-API aufrufen.
Das nennt man vereinfacht:
Browser → eigene Website → externer Dienst
Der Besucher stellt dabei nicht zwangsläufig selbst eine direkte Verbindung zum externen Dienst her.
Datenschutztechnisch ist dieser Unterschied ziemlich wichtig.
Der überraschendste Fund: die Karte
Bei einem Routenrechner zeigte sich genau dieser Unterschied sehr schön.
Geocoding und Routenberechnung liefen über die eigene Server-Schnittstelle.
Dann wurde jedoch die fertige Karte angezeigt.
Und plötzlich erschienen Requests wie:
https://tile.openstreetmap.org/...
Das war eine direkte Verbindung des Browsers zu OpenStreetMap.
Damit wird beim Abruf technisch unter anderem die IP-Adresse des Besuchers an die Infrastruktur der OpenStreetMap Foundation übertragen. OSMF nennt in ihrer Datenschutzerklärung außerdem beispielsweise Browser-/Gerätetyp, Betriebssystem, Referrer sowie Datum, Uhrzeit und aufgerufene Dienste als mögliche automatisch verarbeitete Informationen. Kartenkacheln werden über eine verteilte Cache-Infrastruktur ausgeliefert.
Das ist kein heimliches Werbetracking.
Aber es gehört trotzdem in eine vernünftige Datenschutzerklärung.
Und genau solche Dinge findet ein oberflächlicher Startseiten-Scan unter Umständen nicht.
LocalStorage ist nicht automatisch Tracking
Ein weiterer Schwerpunkt waren die Speicherfunktionen.
Ein Fahrtkostenwerkzeug bot beispielsweise die Möglichkeit:
Route auf diesem Gerät speichern
Das haben wir tatsächlich ausprobiert.
Vor dem Klick:
LocalStorage: {} SessionStorage: {}
Nach dem Speichern entstand ein lokaler Datensatz.
Entscheidend war danach die Kontrolle des Netzwerkverkehrs:
KEINE Netzwerkrequests ausgelöst.
Die Daten waren also tatsächlich ausschließlich im Browser gespeichert worden.
Bei einem zusätzlich gespeicherten Berechnungsergebnis entstanden weitere lokale Einträge. Auch hier konnten wir genau nachvollziehen, welche Informationen enthalten waren.
Danach wurde die angebotene Funktion zum Löschen der lokalen Daten getestet.
Ergebnis:
LocalStorage: {} SessionStorage: {}
und wieder:
KEINE Netzwerkrequests ausgelöst.
Das war für mich wesentlich überzeugender als eine bloße Behauptung im Quelltext.
Wir hatten die Funktion tatsächlich benutzt.
Cookiebanner – ja oder nein?
Eine häufige Vereinfachung lautet:
LocalStorage vorhanden = Cookiebanner nötig.
So einfach ist es nicht.
§ 25 TDDDG betrifft nicht nur klassische Cookies, sondern allgemein das Speichern von Informationen auf einer Endeinrichtung und den Zugriff darauf. Grundsätzlich ist dafür eine Einwilligung vorgesehen. Das Gesetz enthält aber Ausnahmen – unter anderem dann, wenn das Speichern oder Auslesen unbedingt erforderlich ist, um einen vom Nutzer ausdrücklich gewünschten digitalen Dienst bereitzustellen.
Das heißt nicht, dass man jede beliebige Speicherung einfach als „notwendig“ deklarieren darf.
Bei einer Funktion wie:
Route auf diesem Gerät speichern
ist die Situation aber eine ganz andere als bei einem versteckten Werbe- oder Tracking-Identifier.
Der Nutzer fordert die Speicherfunktion selbst an.
Genau deshalb lohnt es sich, nicht nur nach der Existenz von LocalStorage zu fragen, sondern nach dessen Zweck und Auslöser.
DSGVO und TDDDG sind zwei verschiedene Baustellen
Ein Punkt wird bei Cookie-Diskussionen ebenfalls gerne vermischt.
Das TDDDG betrachtet unter anderem den Zugriff auf Informationen im Endgerät.
Die DSGVO regelt dagegen die Verarbeitung personenbezogener Daten.
Für personenbezogene Daten benötigt man eine Rechtsgrundlage nach Art. 6 DSGVO. Eine mögliche Grundlage ist beispielsweise ein berechtigtes Interesse nach Art. 6 Abs. 1 lit. f, sofern nicht die Interessen oder Grundrechte der betroffenen Person überwiegen.
Das bedeutet:
Ein technischer Vorgang muss gegebenenfalls unter beiden Gesichtspunkten betrachtet werden.
Deshalb halte ich Aussagen wie
„Wir haben keine Cookies, also brauchen wir keinen Datenschutz.“
für ziemlich gefährlich.
Auch die Datenschutzerklärung musste mit der Technik übereinstimmen
Nachdem die technischen Abläufe bekannt waren, kam der eigentlich logische zweite Teil:
Stimmt das, was die Datenschutzerklärung behauptet, mit dem überein, was die Website tatsächlich macht?
Unter anderem mussten dort nachvollziehbar beschrieben sein:
die lokale Speicherung ausdrücklich gespeicherter Daten, Standortfunktionen, serverseitige API-Anfragen, Geocoding, Routing und die direkte Kartenverbindung zu OpenStreetMap.
Dabei zeigte sich auch ein organisatorisches Problem, das nichts mit DSGVO zu tun hatte, aber trotzdem wichtig war:
Wenn eine Website Inhalte aus mehreren Datenquellen beziehen kann, sollten diese nicht unterschiedliche Fassungen der Datenschutzerklärung enthalten.
Eine veraltete Rückfallkopie ist schließlich keine besonders gute Rückfallkopie.
Deshalb wurden Hauptdatenquelle und Fallback abschließend auf denselben Stand gebracht.
Eine kleine Lektion über Tests
Nicht jedes Testskript funktionierte auf Anhieb.
Ein Beispiel war die Funktion „Lokale Daten löschen“.
Der entsprechende Button war im HTML vorhanden, aber nicht sichtbar.
Der erste Verdacht war, dass ein Menü geöffnet werden müsste.
Falsch.
Der zweite Verdacht war ein anderer Bearbeitungsbereich.
Auch falsch.
Also wurde nicht weiter geraten, sondern untersucht:
const info = await page.locator( '#clearLocalDataBtn' ).evaluate(el => { const result = []; let current = el; for (let i = 0; current && i < 8; i++) { const style = getComputedStyle(current); result.push({ tag: current.tagName, id: current.id, class: current.className, display: style.display, visibility: style.visibility }); current = current.parentElement; } return result; });
Das Ergebnis zeigte:
Der Button selbst war sichtbar definiert, aber sein übergeordneter Bereich hatte:
display: none
Warum?
Weil noch kein Ergebnis gespeichert worden war.
Erst:
Fahrtkosten berechnen → Ergebnis speichern → Verlauf sichtbar → Lokale Daten löschen
entsprach der tatsächlichen Bedienlogik.
Das ist für mich eine der wichtigsten Erkenntnisse dieses Tests:
Automatisiertes Testen ersetzt nicht das Verständnis der Anwendung.
Es hilft aber enorm dabei herauszufinden, was wirklich passiert.
Was am Ende herauskam
Nach dem kompletten Testtag sah das Ergebnis in meinem Fall erfreulich unspektakulär aus:
- keine Analyse- oder Werbe-Cookies auf den öffentlich untersuchten Seiten,
- kein verstecktes Tracking festgestellt,
- keine automatische Speicherung der getesteten Routen- und Ergebnisdaten,
- LocalStorage erst nach ausdrücklicher Speicheraktion,
- vollständiges lokales Löschen funktionierte,
- serverseitige API-Aufrufe statt unnötiger direkter Browser-Verbindungen,
- eine direkte Drittanbieter-Verbindung zur Karteninfrastruktur wurde identifiziert und anschließend transparent dokumentiert,
- Datenschutzerklärung und tatsächliches Verhalten wurden aufeinander abgestimmt.
Das ist allerdings kein allgemeines Prüfsiegel und keine Rechtsberatung.
Ein Browsertest sieht beispielsweise nicht automatisch sämtliche internen Serverprozesse, Logfiles, Verträge mit Dienstleistern oder organisatorischen Datenschutzmaßnahmen.
Er beantwortet dafür eine andere, sehr praktische Frage ziemlich gut:
Was tut die Website tatsächlich, wenn ein Besucher sie benutzt?
Die wichtigsten PowerShell-Befehle noch einmal
Für einen eigenen Einstieg reicht erstaunlich wenig:
node --version npm --version
Projekt anlegen:
mkdir playwright-datenschutzcheck cd playwright-datenschutzcheck npm init -y
Playwright installieren:
npm install playwright npx playwright install chromium
Funktion testen:
npx playwright screenshot https://example.com test.png
Ein eigenes Skript ausführen:
node scan-startseite.js
Weitere Skripte funktionieren nach demselben Schema:
node scan-komplett.js node scan-bedienelemente.js node scan-interaktion.js node scan-speicher.js
Die Dateinamen sind natürlich frei wählbar.
Was ich heute anders machen würde
Am Anfang dachte ich noch:
Ein Cookie-Scanner wird schon reichen.
Heute würde ich eine interaktive Website anders prüfen.
Zuerst einen passiven Scan.
Dann Netzwerk und Browser-Speicher beobachten.
Danach gezielt alle Funktionen testen, die Daten verarbeiten können.
Und erst anschließend die Datenschutzerklärung mit dem tatsächlichen technischen Verhalten vergleichen.
Gerade dieser letzte Schritt ist wichtig.
Eine Datenschutzerklärung kann sprachlich hervorragend sein und trotzdem die falsche Website beschreiben.
Fazit nach einem ziemlich langen Tag
Der Aufwand war erheblich größer als erwartet.
Dafür weiß ich jetzt aber deutlich genauer, was meine Website tut und was sie nicht tut.
Und genau darin liegt für mich der größte Wert der ganzen Aktion.
Datenschutz sollte nicht daraus bestehen, vorsichtshalber zehn Banner einzubauen und anschließend zu hoffen, dass es schon stimmen wird.
Viel sinnvoller ist:
erst verstehen, dann dokumentieren und nur dort Einwilligungen verlangen, wo sie tatsächlich erforderlich sind.
Ein Browser-Automatisierungswerkzeug wie Playwright ist dafür kein Datenschutzanwalt und auch kein fertiger Compliance-Scanner.
Aber es ist ein erstaunlich gutes Mikroskop.
Und nach einem Tag mit Cookies, Storage, Netzwerkrequests, unsichtbaren Buttons, Testkoordinaten und einigen falschen Fährten war das schönste Ergebnis eigentlich dieses:
Die Website war wesentlich datensparsamer, als der Aufwand der Prüfung vermuten ließ.
Kleiner rechtlicher Hinweis für den Beitrag
Ich würde am Ende noch diesen kurzen Kasten verwenden:
Hinweis: Dieser Beitrag beschreibt einen technischen Praxistest und meine dabei gewonnenen Erfahrungen. Er stellt keine Rechtsberatung dar. Ob eine konkrete Verarbeitung zulässig ist oder eine Einwilligung benötigt, hängt immer von der tatsächlichen technischen Umsetzung und den Umständen des jeweiligen Angebots ab.
Offizielle Grundlagen zum Weiterlesen
Für die rechtliche Einordnung sind vor allem § 25 TDDDG zum Zugriff auf Informationen in Endeinrichtungen sowie Art. 6 DSGVO zur Rechtmäßigkeit der Verarbeitung relevant.
§ 25 TDDDG – Gesetze im Internet
DSGVO – EUR-Lex
Datenschutzerklärung der OpenStreetMap Foundation
OSMF-Hinweise für Dienste- und Kartennutzer