Syllabus Lesson 111 of 156 · Apps and devices
Apps and devices

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.
Your turn

Write three functions.

  1. owner_of(host, owners): look up host in the dict owners; 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".
  2. sharing_graph(entries, owners): return a dict mapping each owner contacted (use the host from urlsplit(url).hostname) to a sorted list with no repeats of the kinds of data it received. Use fields_of and DATA_KINDS; skip names not in DATA_KINDS, and skip identifier fields whose value is ZERO_AD_ID or empty. Owners that received nothing personal still appear, with [].
  3. sharing_score(graph, app_owner): for every owner except app_owner, add 1 plus KIND_WEIGHTS[kind] for each of its kinds. Return the total.
Then set graph = sharing_graph(fitness_app_log(), SDK_OWNERS), score = sharing_score(graph, "FitFox"), and print both.

Spotted a problem in this lesson? Report it

Code · runs in your browser
Output