Leg 6 project: harden the engine
Why it matters This is where the module comes together. You assemble your own engine from the pieces you built, partitioning, Storage Access, link cleaning, farbling and a bounce purge, and run it against a tracker suite whose host names you cannot know in advance. Passing means you understand not just each defence, but why an engine needs all of them at once.
Think of it as securing a house. A lock on the front door is useless if the back window is open. Each tracker in the suite tries a different way in, and the hidden version moves the windows around. Only a house secured by how doors work, not by a list of which doors exist, keeps everyone out.
Defence in depth: several independent defences, so a tracker that slips past one is stopped by another.
Hidden suite: the same five tracking techniques as in the Breakage lab, but on other seeds. The three unlisted trackers get different host names each time, and the features move to different sites.
The brief
Write an engine that, on the 20 practice sites, leaves no tracking technique linked and keeps the breakage rate within a budget of 0.10 (at most two broken sites in 20), on seed 0 and on two hidden seeds.
Your engine is two functions the suite calls:
make_browser(world): returns a browser. UseAccessBrowser(world, request_fn=..., access_fn=..., blocker=..., trait_filter=...)fromwatched.leg6b_access. It is the practice browser with partitioning on and the Storage Access hooks.after_navigation(browser, visit, interacted): called after every page load.visit.recordslists every request; navigation records havekind"document" or "form", all but the last of those are redirect hops, andrecord.storedlists the cookies a response managed to save.interactedis True when Sam really used the page he landed on.
Run it with result = run_suite(make_browser, after_navigation) from watched.leg6b_harden. It returns linked, broken and breakage, just like the Breakage lab.
Checklist
- Storage Access rules:
request_accessandhas_access, as in the Storage Access lab, so the comments widget works after a click. - Blocker: navigations (
kind"document" or "form") get cleaned with yourclean_url, never blocked; subresources whose site is onTRACKER_LISTare blocked; everything else is allowed. Do not block every third party: that breaks thecdn.examplescript on every site. - Link cleaning:
clean_url(url)with the strip list and the allowlist fromwatched.leg6b_links, as in the link cleaning lab. - Farbling:
farble(values, top_site, session_key)as in the Farbling lab, used by atrait_filter(traits, page)that farblestraits["canvas_pixels"]andtraits["audio_samples"]withpage.top_siteand aSESSION_KEYyou choose. - Bounce purge: in
after_navigation, remember the sites Sam interacted with, and clear the cookies and storage of any redirect hop that stored a cookie and is not one of them. - Measure: print
result, then fix whatever is still linked or broken.
Two tips
Keep state on the browser, not in a global variable. The tests run your engine on three seeds, and a global set of "used sites" would carry over from one run to the next. In make_browser, write browser.used_sites = set() and read it back in after_navigation as browser.used_sites.
When something is still linked, ask which of your defences was meant to stop that technique, then print what that defence saw. The Breakage lab's table is a good map: each technique has one defence that stops it without a host list.
| Technique | Stopped by |
|---|---|
| third-party cookie | the blocklist, or partitioning |
| frame storage (unlisted host) | partitioning |
| fingerprint (unlisted host) | farbling |
| link decoration | cleaning every navigation |
| bounce (unlisted host) | the bounce purge |
The tests forbid watched.leg6b_presets and the link module's reference_clean: the point is that the engine is yours.
An honest note
Passing this suite does not make a real engine safe. Real trackers adapt, real sites are messier than twenty practice ones, and real engines spend years on exactly this loop: measure, defend, measure breakage, adjust. What you have now is the method, and that is what engine teams use every day.
In other languages
A real engine makes the blocker decision in fast native code. A sketch in Rust of the same rule your blocker follows:
use std::collections::HashSet;
enum Decision { Allow, Block, Rewrite(String) }
fn decide(kind: &str, url: &str, site: &str, tracker_list: &HashSet<String>) -> Decision {
if kind == "document" || kind == "form" {
let cleaned = clean_url(url); // your clean_url, in Rust; never block a navigation
return if cleaned != url { Decision::Rewrite(cleaned) } else { Decision::Allow };
}
if tracker_list.contains(site) { Decision::Block } else { Decision::Allow }
}And the bounce purge in JavaScript, as a browser extension might write it:
function afterNavigation(hops, landed, interacted, usedSites) {
if (interacted) usedSites.add(landed);
for (const hop of hops) {
if (hop.storedCookies.length && !usedSites.has(hop.site)) clearSiteData(hop.site);
}
}Recap
- No single defence stops every technique; an engine needs them layered.
- Defences that work by behaviour (partitioning, farbling, purging) beat trackers whose names you do not know.
- Clean navigations instead of blocking them, and keep allowlists for what sites need.
- Ship only what you have measured on both numbers: tracking left and breakage caused.
Build your engine. Define request_access, has_access, clean_url, blocker, farble, trait_filter, make_browser(world) and after_navigation(browser, visit, interacted) as described in the checklist, then set result = run_suite(make_browser, after_navigation). The tests run your engine on seed 0 and two hidden seeds; each run must leave result["linked"] empty with result["breakage"] at most 0.10. Do not import watched.leg6b_presets or reference_clean.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.