Leg 2 project: the Tracker Lab report
Why it matters This is where Leg 2 comes together. You run the full loop, tracker, then partition, then blocker, then measurement, on a practice world you have not seen before, and you report it the way a privacy researcher would: as numbers anyone can check.
The brief
A practice newsroom has asked you: "How much does our readers' browser choice protect them on these three shops?" You will answer with a JSON report produced by your own code. JSON is just a dict written as text, so another program (or a grader) can read it.
Lab world: build_lab_world(seed) from watched.leg2b_lab: the usual three shops, AdCo and Metrics, plus one extra tracker whose name changes with the seed, and a harmless reviews widget on every shop, there to tempt a careless blocker.
Because the extra tracker changes with the seed, a hard-coded list will not pass. Your detector from lesson 48 has to find it. lab_truth(world) gives you the answer key for scoring only: in real research, building it is slow handwork.
Three runs
Each run browses the same list of pages from lab_urls(world, n, seed), with a fresh copy of AdCo's practice tracker installed (server, profiles = practice_tracker(world) from watched.leg2e_inside, the same ready-made tracker you studied in Module 7), so no run sees another's profiles. You do not write the tracker; you measure it.
- Plain browser. How many shop sites does AdCo's tracker join to one ID? Also run
find_trackerson this run's log. - Partitioned browser (
Browser(world, partitioned=True)). Same question. Each shop gets its own AdCo cookie jar, so the answer should drop to 1. - Your blocker (
Browser(world, blocker=make_blocker(found))). Same question, plus precision and recall againstlab_truth(world).
A tidy way to avoid repeating yourself: write one helper, for example tracked_run(world, urls, **options), that removes old trackers, installs a fresh one, browses, and returns the browser and the linked count. **options passes keyword settings like partitioned=True straight on to Browser.
The report
| Key | Value |
|---|---|
pages | number of URLs browsed |
trackers_found | sorted list of sites your detector flagged in run 1 |
linked_sites, linked_sites_partitioned, linked_sites_blocked | the most shop sites joined to one AdCo ID in runs 1, 2 and 3 (0 if no profile) |
requests_total | number of records in run 3's log |
requests_blocked | how many of them were blocked |
precision, recall | for run 3 against lab_truth(world), rounded to 3 places |
Finally set report_json = json.dumps(report, indent=2, sort_keys=True) for build_lab_world(seed=7) with lab_urls(world, 30, seed=7), and print it.
Checklist before you submit
- Each run installs a fresh tracker and removes it afterwards (
world.remove_trackers()). find_trackersdoes not flagwidgets.exampleorcdn.example.- Every value in the report is a plain number, string or list, so
json.dumpsworks.
Reading your own results
You should see the story of the whole leg in a few numbers: one ID across every shop, then 1 with partitioning, then 0 with your blocker. Partitioning alone stops the cross-site join, but AdCo still sees each visit. Blocking stops AdCo hearing anything, at the risk of false positives, which your precision shows. Neither touches what a shop's own server shares. That gap is where Legs 3 and 4 pick up.
Try it When you pass, make a careless copy of your detector that flags every third-party site, ignoring the clues, and rerun the blocker with it. Watch precision fall: which harmless sites got caught, and what would break on a real shop?
In other languages
JavaScript:
// The same report, written out as JSON
const report = {
pages: urls.length,
trackers_found: [...found].sort(),
linked_sites: linked,
precision: Math.round(precision * 1000) / 1000,
};
console.log(JSON.stringify(report, null, 2));Go:
type Report struct {
Pages int `json:"pages"`
TrackersFound []string `json:"trackers_found"`
LinkedSites int `json:"linked_sites"`
Precision float64 `json:"precision"`
}
out, _ := json.MarshalIndent(report, "", " ")
fmt.Println(string(out))Recap
- The full loop: track, partition, block, measure, on a world you had not seen.
- Partitioning stops cross-site joins; blocking stops the tracker hearing at all.
- Precision and recall keep your blocker honest about breakage and leaks.
- A report of checkable numbers is how privacy claims should be made.
Write find_trackers(log) (from lesson 48, with its helpers), score(log, trackers) (from lesson 51), make_blocker(trackers) returning a blocker that blocks every request whose site is in trackers, and run_lab(world, urls) returning a report dict with exactly the keys pages, trackers_found, linked_sites, linked_sites_partitioned, linked_sites_blocked, requests_total, requests_blocked, precision and recall, as described in the brief. Then set and print report_json for build_lab_world(seed=7) and lab_urls(world, 30, seed=7). AdCo's tracker is ready-made: use server, profiles = practice_tracker(world) from watched.leg2e_inside in each run.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.