etwerkverkeer lezen via HAR-files
Een HAR-bestand is de volledige opname van al het netwerkverkeer bij een websitebezoek: elk verzoek, elke cookie, elke bestemming. Bijna al mijn privacy-bevindingen komen eruit. Een verklaring is een belofte, de HAR is de uitvoering. Het vastleggen kan iedereen; het lezen en duiden is het vak.
Wat een HAR-bestand is¶
Een HAR-bestand (HTTP Archive) is een JSON-opname van al het netwerkverkeer dat een browser afhandelt bij het laden van een pagina: elk verzoek, elke respons, de headers, de cookies, de timing, de payload. Het is de zwarte doos van een websitebezoek. Waar een screenshot laat zien wat je ziet, laat een HAR zien wat er werkelijk gebeurt.
De structuur is simpel. Onder log.entries staat een lijst van requests, elk met een request, response, cookies en timings. Een browser produceert dit bestand zonder dat je iets installeert. In Chrome of Firefox open je de ontwikkelaarstools, ga je naar het netwerktabblad, laad je de pagina, en exporteer je met "Save all as HAR". Wat je dan in handen hebt, is de volledige lijst van iedereen die tijdens dat bezoek is aangesproken, in de volgorde waarin het gebeurde, met de gegevens die zijn meegestuurd.
Dat is de reden dat bijna al mijn privacy-bevindingen uit dit ene bestandstype komen. Een verklaring in een privacybeleid is een belofte over wat er zou gebeuren. De HAR is de opname van wat er echt gebeurde. Als die twee uit elkaar lopen, staat het verschil zwart op wit in de entries.
Een korte kanttekening over hoe je de opname maakt, want de kwaliteit van de HAR bepaalt de kwaliteit van alles daarna. Zet het netwerktabblad open voordat je de pagina laadt, anders mis je de eerste verzoeken, en juist die vroege verzoeken zijn vaak de interessantste. Vink "Preserve log" aan zodat een doorverwijzing de lijst niet wist. En leg vast wat je deed: welke pagina, welke knoppen, wel of geen cookies geaccepteerd. Een HAR zonder dat logboek is een opname zonder bijschrift, en dan weet je later niet meer welke keuze welk verkeer opriep.
Uitlezen met een paar regels¶
Je hoeft geen tool te kopen om een HAR te lezen. Een paar regels Python halen alle uitgaande domeinen eruit, gesorteerd op hoe vaak ze werden aangeroepen:
import json
from urllib.parse import urlparse
from collections import Counter
har = json.load(open("capture.har", encoding="utf-8"))
entries = har["log"]["entries"]
hosts = Counter(urlparse(e["request"]["url"]).hostname for e in entries)
for host, n in hosts.most_common():
print(f"{n:4d} {host}")Dit is de eerste blik: welke domeinen werden geraakt, en hoe vaak. Je laadt één pagina en ziet dat er vijftig, honderd, soms meer dan tweehonderd verzoeken uitgingen naar tientallen verschillende hosts. De naam van de site die je bezocht staat er tussen, en daaromheen staan de namen die je niet had verwacht. Die vreemde namen tussen de first-party requests zijn de kandidaten. Een teller die één host tientallen keren laat terugkomen, wijst op een partij die tijdens je bezoek herhaaldelijk werd aangesproken. Dat is het eerste wat je wilt weten voordat je verder graaft.
De identifiers eruit halen¶
Interessanter wordt het als je de cookies en query-parameters bekijkt die meegaan. Daar zitten de advertentie-ID's en de herkenningsnummers:
for e in entries:
host = urlparse(e["request"]["url"]).hostname
for c in e["request"].get("cookies", []):
if len(c["value"]) > 20: # lange waarde = vermoedelijke identifier
print(host, c["name"], c["value"][:16], "...")De drempel van twintig tekens is een ruwe zeef. Een sessievlag of een taalvoorkeur is kort. Een identifier die één specifieke browser uit miljoenen moet kunnen aanwijzen, heeft veel bits nodig en is daarom lang. De filter haalt dus de korte, onschuldige waarden weg en laat de verdachte staan. Het blijft een vermoeden. Een lange waarde kan ook iets legitiems dragen, zoals een cache-sleutel.
Een lange, stabiele waarde die naar meerdere domeinen meegaat, is een koppelsleutel. Zie je dezelfde waarde bij verschillende partijen, dan is dat cookie-sync: losse bedrijven die dezelfde persoon herkennen. Dat is het punt waarop een lijst domeinen verandert in een netwerk. De partijen die op het oog niets met elkaar te maken hebben, blijken via één gedeeld nummer naar dezelfde bezoeker te wijzen.
Voor en na de weigering¶
De echte vraag is wat er gebeurt na een cookie-weigering. Ik maak twee captures, een met "accepteren" en een met "alles weigeren", en trek de first-party-hosts eraf. Wat dan nog vuurt, hoort daar niet te zijn:
def hosts_of(path):
h = json.load(open(path, encoding="utf-8"))
return {urlparse(e["request"]["url"]).hostname for e in h["log"]["entries"]}
na_weigeren = hosts_of("weiger.har") - hosts_of("first-party.har")
print("nog actief na weigeren:", sorted(na_weigeren))Deze vergelijking is de kern van veel van mijn metingen. Een banner die toestemming vraagt, doet een belofte: weiger je, dan gebeurt het niet. De weiger-capture toetst die belofte. Trek je de hosts eraf die sowieso van de site zelf zijn, dan houd je de derde partijen over die vuurden ondanks de weigering. Elke naam in die lijst is een plek waar de belofte en de uitvoering uit elkaar lopen.
De aftrek van de first-party-hosts is nodig omdat een site natuurlijk zijn eigen servers mag aanspreken. Beelden, stylesheets en de eigen backend horen erbij, ook na een weigering. Wat overblijft na die aftrek is het verkeer naar buiten. Ik houd daarbij één regel aan: het gaat om wat er verschilt tussen accepteren en weigeren, en om wat er blijft staan terwijl het weg had moeten zijn. Een host die in beide captures voorkomt en duidelijk bij de site zelf hoort, laat ik met rust. Een advertentie- of analysedomein dat na de weigering nog in de lijst staat, is de vondst.
Let op wat deze meting wel en niet aantoont. Ze laat zien dat er verkeer naar een derde partij ging na een weigering. Dat is gemeten en hard. Ze bewijst op zichzelf nog niet dat er persoonsgegevens werden verwerkt, want een verzoek kan leeg zijn of geanonimiseerd. Om van "er vuurde iets" naar "er ging een identifier mee" te komen, moet je terug naar de cookies en parameters van juist die entries. De hostlijst is het spoor, de inhoud van de request is het bewijs.
Waar de meting blind wordt¶
Een HAR ziet wat de browser verstuurt en ontvangt. Daar zit de grens. Als een site zijn tracking via de eigen server laat lopen, server-side tagging, dan zie je in de HAR alleen een net verzoek naar de site zelf. Wat die server daarna doorstuurt naar een advertentiepartij, gebeurt buiten je browser en staat niet in het bestand. De opname is dan schoon terwijl de tracking gewoon doorgaat, één laag dieper.
Dat is de belangrijkste beperking om eerlijk over te zijn. Een lege weiger-capture betekent niet dat er niets gebeurt. Ze betekent dat er niets gebeurt wat de browser kan zien. Om die reden is een schone HAR een geruststelling met een sterretje, en weet je pas zeker dat het spoor doorloopt als je ook de kant van de server kunt bekijken.
Lezen is het werk¶
De code is het makkelijke deel. Iedereen kan een HAR opslaan en er een script op loslaten. Het lezen is waar het vak zit. Je moet weten welke domeinen bij welke vendor horen, want de naam in de URL verraadt de eigenaar lang niet altijd. Je moet weten welke parameter een advertentie-ID draagt en welke een onschuldige cache-sleutel is. Je moet zien waar server-side tagging het spoor uit het zicht haalt, zodat je een schone opname niet aanziet voor een schone site.
Daar zit de reverse-engineering. Het vastleggen kan iedereen. De waarde ontstaat bij het duiden van wat er vastligt, en dat duiden bouwt op kennis die geen script voor je heeft: welke partij welke rol speelt, welk nummer een persoon aanwijst, en wanneer een stilte in de opname een echte stilte is of alleen een verplaatste.
De scripts hierboven zijn daarom een begin, geen eindpunt. Ze reduceren duizenden regels ruwe JSON tot een handzame lijst waar je op kunt redeneren. Wat je met die lijst doet, hangt af van wat je weet. Iemand die de vendors kent, ziet in dezelfde lijst een verhaal dat voor een ander onzichtbaar blijft. Dat is de reden dat ik de opname en de duiding streng gescheiden houd. De HAR is het bewijsstuk dat iedereen kan naslaan. Mijn conclusie erover is een interpretatie die op dat bewijsstuk rust, en die twee moet je nooit door elkaar laten lopen.
Zie ook Cookie-sync laat losse partijen dezelfde persoon herkennen, Server-side tagging verplaatst de tracking uit het zicht en Cookies weigeren scheelt echt, maar reken er niet blind op.
- HAR-formaat (HTTP Archive), specificatiede structuur van log.entries waarop mijn parser leunt
- Eigen onderzoeksdossier: forensics, 2026-06-11HAR-scans met privacy_scan-classificatie als werkwijze