Match SDKs to their owners
Why it matters Hosts are not companies. One company can use many hosts, and a host that looks like the app's own can belong to someone else. Grouping a capture by owner, and adding up what each owner received, turns a list of requests into an answer to the question you actually care about: who learned what about me?
Addresses and landlords
A host name is like a street address; the owner is the landlord. events.metrics.example and cdn.metrics.example are two addresses with the same landlord. And partners.fitfox.example sits on FitFox's street, yet a different landlord (AdCo) rents it. This is the app version of the CNAME cloaking you met in Leg 1.
Domain map: a list that says which company runs which host or site. Real ones are built by researchers and tracker-blocking projects; ours is SDK_OWNERS, a plain dict in the practice world.
from watched.leg5b_apps import SDK_OWNERS
print(SDK_OWNERS["adco.example"]) # AdCo
print(SDK_OWNERS["partners.fitfox.example"]) # AdCo
print(SDK_OWNERS["fitfox.example"]) # FitFox
Most specific first
To find the owner of a.b.adco.example, try the full name, then drop the leftmost label (a label is one dot-separated piece) and try again: b.adco.example, then adco.example. Stop at the first match. Stop before you are down to a single label like example, which nobody owns. If nothing matches, the owner is "Unknown", and an unknown owner deserves extra attention, not less.
labels = "a.b.adco.example".split(".") # ['a', 'b', 'adco', 'example']
print(".".join(labels[1:])) # b.adco.example
Going most specific first is what catches partners.fitfox.example: its full name is in the map as AdCo, so you never fall back to FitFox.
A data-sharing graph
Data-sharing graph: a map from each company to the kinds of data it received. Here we store it as a dict, owner -> sorted list of kinds.
The practice world gives you fields_of(entry) (the same as your all_fields from the last lesson) and DATA_KINDS, which says what kind of data a field name carries:
from watched.leg5b_apps import DATA_KINDS
print(DATA_KINDS["heart_rate"]) # health
print(DATA_KINDS.get("slot")) # None (not personal, ignore it)
The kinds are identifier, location, health, contact and viewing. As before, an identifier field whose value is ZERO_AD_ID (or empty) does not count. Every owner the app contacted appears in the graph, even with an empty list, because a connection alone hands over your IP address and the time.
A score you can compare
A single number lets you compare two apps at a glance. Ours is simple on purpose: for every owner other than the app's own company, add 1 for the contact, plus a weight for each kind it received, from KIND_WEIGHTS (identifier 2, location 3, health 3, contact 3, viewing 2). FitFox scores 26.
Real researchers weigh things differently, and no score captures everything. A score is a conversation starter: it is more useful for "this app shares twice as much as that one" than as a verdict.
A common misconception
"More hosts means more sharing." An app might use ten hosts all run by itself and one host run by a data broker. Counting owners, and what they received, is closer to the truth than counting hosts.
In other languages
JavaScript
function ownerOf(host, owners) {
const labels = host.toLowerCase().split(".");
for (let i = 0; i < labels.length - 1; i++) {
const name = labels.slice(i).join(".");
if (name in owners) return owners[name];
}
return "Unknown";
}
Rust
fn owner_of(host: &str, owners: &HashMap<&str, &str>) -> String {
let labels: Vec<&str> = host.split('.').collect();
for i in 0..labels.len().saturating_sub(1) {
if let Some(o) = owners.get(labels[i..].join(".").as_str()) {
return o.to_string();
}
}
"Unknown".to_string()
}
Recap
- Group by owner, not host: one company has many hosts, and a familiar-looking host can belong to someone else.
- Look up the most specific name first, then drop labels one by one; no match means "Unknown".
- A data-sharing graph lists, for each owner, the kinds of data it received.
- A simple score helps compare apps, but it is a starting point, not a verdict.
Write three functions.
owner_of(host, owners): look uphostin the dictowners; if it is missing, drop the leftmost label and try again, stopping before a single label is left. Return the first owner found, or"Unknown".sharing_graph(entries, owners): return a dict mapping each owner contacted (use the host fromurlsplit(url).hostname) to a sorted list with no repeats of the kinds of data it received. Usefields_ofandDATA_KINDS; skip names not inDATA_KINDS, and skip identifier fields whose value isZERO_AD_IDor empty. Owners that received nothing personal still appear, with[].sharing_score(graph, app_owner): for every owner exceptapp_owner, add 1 plusKIND_WEIGHTS[kind]for each of its kinds. Return the total.
graph = sharing_graph(fitness_app_log(), SDK_OWNERS), score = sharing_score(graph, "FitFox"), and print both.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.