Link a user without cookies
Why it matters Blocking third-party cookies is one of the best privacy settings there is, and it is not the end of the story. In this lab you play the tracker: you link visits across sites using only what the network shows, then measure how much protection cookie-blocking alone, a shared VPN address and look-alike browsers each add.
Imagine a café regular who never gives their name, but always comes from the same street, in the same distinctive coat. Staff at several cafés could still work out it is the same person. Remove the street (everyone arrives by one shared bus) and the coat becomes the only clue. Give everyone the same coat, and nobody stands out.
The practice data
network_log(seed, setting) gives one day of visits by 40 simulated people across eight .example sites, with cookies blocked. Each record is a dict:
{'site': 'shopb.example', 'ip': '203.0.113.5', 'tls': 'tls-cobalt',
'h2': 'h2-bc1', 'day': 0, 'person': 'user04'}tls and h2 are short labels standing for the TLS and HTTP/2 fingerprints you computed in lessons 104 and 105. The tracker sees site, ip, tls and h2. As before, person is the truth that only the simulator knows; you use it to score the tracker.
setting is one of SETTINGS:
"home": cookies blocked, everyone on their own home connection (households share one address)."vpn": everyone also uses one of three shared VPN exit addresses."vpn_uniform": as"vpn", and every browser sends the same fingerprints, as if everyone used a browser designed to look alike.
The tracker's method
- Make a network key from each record: the tuple
(ip, tls, h2). A tuple is like a list that cannot change, written with round brackets, and it can be used as a dict key. - Group the records by key.
- A person is linked if some group contains only that person's visits and covers two or more different sites. That is the tracker correctly saying "the visitor to shopb.example and to news.example is the same one".
- Protection is the share of people who are not linked.
A group with several people in it does not count as linking anyone: the tracker would be merging strangers, which, as lesson 100 showed, is wrong (though still harmful to the people merged).
Tuple: an ordered group of values that cannot be changed, such as ("203.0.113.5", "tls-cobalt", "h2-bc1"). Unlike a list, it can be a dict key or go in a set.
In other languages
Grouping records by a composite key is a standard pattern:
// JavaScript
const groups = new Map();
for (const r of records) {
const key = [r.ip, r.tls, r.h2].join("|"); // arrays are not usable as Map keys by value
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push(r);
}// Go
type key struct{ IP, TLS, H2 string }
groups := map[key][]Record{}
for _, r := range records {
k := key{r.IP, r.TLS, r.H2}
groups[k] = append(groups[k], r)
}// SQL (any database)
SELECT ip, tls, h2, COUNT(DISTINCT site) AS sites, COUNT(DISTINCT person) AS people
FROM visits GROUP BY ip, tls, h2;What you should find
With cookies blocked but a home IP, most people are still linked: the address alone ties their sites together. A shared VPN address protects most people, but those with a rare browser are still linked by their fingerprint. With look-alike browsers on top, nobody is linked. That is the case for the two defences from lesson 106: hide the address, and blend in.
A note on honesty: these numbers come from our simulator, with its own made-up mix of households and browsers. They show the shape of the effect, not real-world rates.
Recap
- Blocking cookies does not stop linking by IP address plus network fingerprint.
- A shared VPN or relay address protects most people, but a rare browser can still single someone out.
- Look-alike browsers plus a shared address left nobody linkable in this simulation.
- Measure protection; do not assume it.
Play the tracker, then measure. Write four functions and fill RESULTS:
network_key(record): return the tuple(ip, tls, h2).group_by_key(records): return a dict mapping each network key to the list of records with that key.linked_people(records): return thesetof people for whom some group holds only that person's records and covers two or more different sites.protection(records): return 1 minus (number of linked people divided by the number of different people inrecords); 0.0 if there are no records.
RESULTS[setting] = protection(network_log(seed=0, setting=setting)) for every setting in SETTINGS. Tests also check other simulated days.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.