et risico zit in de optelsom, niet in het losse veld
Losse datapunten lijken onschuldig: een locatie, een zoekterm, een tijdstip. Het gevaar ontstaat als ze bij elkaar komen, tot een aanwezigheidskalender, een koopprofiel of een medisch beeld. De schade zit in de combinatie.
Los onschuldig, samen gevaarlijk¶
Losse datapunten lijken onschuldig. Een locatie. Een zoekterm. Een tijdstip. Elk voor zich zegt zo'n veld bijna niets, en dat is precies het argument waarmee de verzameling wordt goedgepraat. Het is toch maar een tijdstip. Het is toch maar een postcode.
Het gevaar ontstaat op het moment dat die velden bij elkaar komen. Een reeks locaties met tijdstippen wordt een aanwezigheidskalender van je huis. Zoektermen plus aankopen worden een koopprofiel. Een paar categorieën plus een frequentie worden een medisch beeld. De schade ontstaat in de combinatie. Een los veld richt in zijn eentje weinig aan. Zodra de velden gekoppeld worden, ontstaat er informatie die in geen enkel afzonderlijk veld zat, en die je zelf vaak niet eens kent.
Waarom de optelsom de meting stuurt¶
Dit heeft een direct gevolg voor hoe je risico beoordeelt. Wie alleen naar losse velden kijkt, onderschat het risico stelselmatig. Een tijdstip is niets. Een tijdstip plus een locatie plus een herhaling is een patroon. En een patroon voorspelt gedrag, onthult gewoontes en verraadt waar je woont en werkt.
De vraag die telt is daarom niet of één gegeven op zichzelf gevoelig is. De vraag die telt is of dat gegeven aan de rest gekoppeld kan worden. Een op het oog onschuldig veld wordt gevaarlijk zodra er een sleutel bestaat die het aan je andere velden verbindt. Zo'n sleutel is meestal een identifier: een reclame-id, een cookie, een apparaatkenmerk. Twee bedrijven die zo'n herkenningsnummer onderling uitwisselen, maken de optelsom mogelijk die elk van beide in zijn eentje niet kon maken. Vanaf dat moment heb je te maken met één gecombineerd profiel in plaats van twee losse verzamelingen.
De sleutel is het echte scharnier¶
Omdat de koppeling het risico maakt, ligt daar ook het scharnier van mijn onderzoek. Ik let minder op de inhoud van een enkel veld en meer op de vraag of er een gedeelde sleutel rondgaat. Een reclame-id lijkt onschuldig omdat je hem kunt resetten. In de praktijk blijft hij lang genoeg gelijk om profielen aan elkaar te knopen, en dat maakt hem alsnog een sleutel. Dat werk ik uit in Een resetbare identifier is alsnog een sleutel.
Nog vervelender is de uitwisseling tussen partijen. Als partij A jouw id kent en partij B ook, en ze matchen die ids onderling, dan kan A jouw gedrag bij B zien en omgekeerd. Deze cookie-sync is het mechanisme waarmee losse partijen samen één beeld opbouwen. Ik bekijk dat mechanisme apart in Cookie-sync laat losse partijen dezelfde persoon herkennen. En zelfs zonder opgeslagen id kan een combinatie van apparaatkenmerken je herkenbaar maken, wat fingerprinting heet en waar ik in Fingerprinting ontwijkt je weigering door niets op te slaan op inga.
Hoe ik de koppeling zichtbaar maak¶
De optelsom klinkt abstract, dus het helpt om te laten zien hoe je haar meet. Ik neem het netwerkverkeer van een app of site op als HAR-bestand. Daarin staat elke request met zijn bestemming en zijn parameters. De koppeling wordt zichtbaar zodra dezelfde identifier opduikt in requests naar verschillende hosts. Dat is het signaal dat losse partijen aan dezelfde persoon zitten te rekenen.
Een ruwe eerste zeef ziet er zo uit:
import json
from urllib.parse import urlparse, parse_qs
from collections import defaultdict
har = json.load(open("capture.har"))
per_id = defaultdict(set)
for entry in har["log"]["entries"]:
host = urlparse(entry["request"]["url"]).netloc
params = parse_qs(urlparse(entry["request"]["url"]).query)
for key, values in params.items():
for v in values:
if len(v) >= 16: # ruwe drempel voor id-achtige waarden
per_id[v].add(host)
# waarden die naar meerdere hosts gaan, zijn verdacht
for value, hosts in per_id.items():
if len(hosts) > 1:
print(len(hosts), sorted(hosts), value[:24])Dit is een grove zeef en meer niet. Een waarde die naar meerdere hosts gaat, is een aanwijzing en geen bewijs. Ik moet daarna handmatig nagaan of het echt een persoonsgebonden identifier is, of gewoon een gedeelde versienummer of een publieke sleutel. Bewijsniveau hierbij: de aanwezigheid van dezelfde waarde bij meerdere hosts is gemeten. Dat die waarde jou identificeert en dat de partijen hem bewust matchen, is afgeleid en vraagt aanvullende bevestiging.
Van aanwijzing naar bevinding¶
De zeef hierboven geeft me kandidaten. Wat volgt is het werk dat een kandidaat tot een bevinding maakt, en dat werk is waar de meeste haast sneuvelt. Een waarde die naar meerdere hosts gaat, kan van alles zijn. Ik loop daarom een vaste volgorde af voordat ik iets een gedeelde identifier durf te noemen.
- Is de waarde persoonsgebonden? Een sessie-id dat per app-start verandert, koppelt niets over de tijd. Een waarde die over meerdere sessies gelijk blijft, is verdachter, want die overleeft de sessie en kan dus als sleutel dienen.
- Gaat de waarde echt naar aparte partijen? Twee hosts kunnen bij hetzelfde bedrijf horen. Ik kijk naar de domeinen en naar wie ze bezit voordat ik van uitwisseling tussen partijen spreek.
- Zit de waarde in de request of alleen in een gedeeld script? Een publieke sleutel of een versienummer dat overal opduikt, is geen identifier. Ik moet uitsluiten dat ik naar zoiets kijk.
- Herhaalt het patroon zich? Eén samenval kan toeval zijn. Hetzelfde id dat structureel bij dezelfde combinatie hosts opduikt, is een patroon dat ik kan hardmaken.
Pas als een kandidaat deze stappen doorstaat, noteer ik hem als bevinding, en zelfs dan met het juiste label. De koppeling is dan aangetoond als technisch mogelijk en waarschijnlijk in gebruik. Dat een bepaalde partij op een bepaald moment een concreet profiel over jou samenstelde, blijft meestal een gevolgtrekking, tenzij ik het in het verkeer letterlijk zie gebeuren. Dat onderscheid tussen mogelijkheid en daad houd ik streng, want een naam in de code of een sleutel op de lijn toont aanleg. Een handeling toont het daarmee nog niet.
Wat dit voor mijn werk betekent¶
Dit standpunt bepaalt mijn oordeel over wat gevoelig is. Ik weiger een verzameling af te schrijven als onschuldig omdat de losse velden dat lijken. Ik vraag telkens of er een koppeling mogelijk is, en of er een partij is die van die koppeling profiteert. Een app die op zichzelf weinig verstuurt, kan alsnog schadelijk zijn als wat hij verstuurt via een gedeelde id aan een groter geheel wordt geknoopt.
Het betekent ook dat ik voorzichtig blijf met mijn eigen conclusies. De optelsom die ik meet, is een aanwijzing van wat mogelijk is. Of de koppeling in de praktijk ook gemaakt wordt, moet ik zo goed mogelijk hardmaken voordat ik het opschrijf. Een gedeelde sleutel bewijst de mogelijkheid. Het bewijst nog niet de daad. Dat onderscheid houd ik overeind, ook als het mijn verhaal minder scherp maakt. Een enkele fout in een gedeelde tenant kan overigens een hele sector raken, wat laat zien hoe ver zo'n koppeling kan reiken. Dat staat in Een gedeelde analytics-tenant maakt van een fout een sectorlek.
Zie ook Cookie-sync laat losse partijen dezelfde persoon herkennen, Een resetbare identifier is alsnog een sleutel en Fingerprinting ontwijkt je weigering door niets op te slaan.
- Eigen onderzoeksdossier: solar inverter, 2026-06-11uit het verbruikspatroon is af te leiden wanneer een huis leeg staat