Cross-app and cross-device tracking

The identity a person uses on one app and the identity they use on another may feel entirely separate. They can nevertheless be linked. Three mechanisms produce those links without any single system holding the whole picture: identifiers a service is handed, accounts somebody signs into, and identifiers inferred from a device rather than supplied by it. What a tracker ends up with is an identity it has constructed, and that construction matches a real person only some of the time.

Recital 30 puts the mechanism in the Regulation’s own words: people “may be associated with online identifiers provided by their devices, applications, tools and protocols”, and those traces, “in particular when combined with unique identifiers and other information received by the servers, may be used to create profiles of the natural persons and identify them”. Combined is where the work happens.

Shared identifiers

When a user logs into an app with their email address, the app may hash that address and report it to a shared analytics service along with the user’s behaviour. A second app, appearing unrelated, can do the same. The same hash arrives at the same endpoint from two different sources, and the connection is made silently in the backend.

# App A: user logs in, reports behaviour
user_hash = sha256(user_email)
send_to_tracker(user_hash, source="app_A", event="opened_app")

# App B: unrelated in appearance, same analytics partner
send_to_tracker(user_hash, source="app_B", event="clicked_ad")

# Tracker backend:
profile[user_hash] = merge(app_A_events, app_B_events)

Hashing the address does not prevent the join, and the demonstration below measures what it does prevent.

The user never sees this connection. From their perspective, the two apps have nothing to do with each other. From the tracker’s perspective, both streams of behaviour belong to the same person.

One login, two devices

If a user signs into the same account on a phone and a laptop, the service observes both devices as belonging to one identity. Behaviour on the phone and purchases on the laptop are combined into a single record.

sessions_for_user = ["device_phone", "device_laptop"]

activity = {
    "device_phone":  ["browsed travel sites", "searched for flights"],
    "device_laptop": ["visited airline site", "completed purchase"]
}

combined_profile = merge all activity across sessions_for_user
→ infer: user researched and then booked a flight

Combining the two records shows a sequence neither device holds by itself: research on one, the purchase on the other. Neither record alone reveals that the two belong to the same sequence.

No identifier at all

The third mechanism requires no shared login and no supplied identifier. A fingerprinting script running on different websites can produce sufficiently similar fingerprints from the same device to link the visits, with no common login and no cookies. The behaviour is attached to a browser environment recognised by its observable properties, and the tracker works the continuity out for itself.

# Same device visits site_X and site_Y, both using the same fingerprinting service
fingerprint = generate(screen_resolution, fonts, GPU, browser_version, ...)

# site_X visit
tracker_database[fingerprint].append(site_X_behaviour)

# site_Y visit, different session, no cookies
tracker_database[fingerprint].append(site_Y_behaviour)

# Result: linked profile with no login required

265 identities for 400 people

greenhouse-attacks crossdevice.py generates 1,036 devices belonging to 400 people living in 160 households, gives each device the identifiers a tracker would see, and joins any two devices that share a value. A component is the group of devices those joins connect, and each component is one person as far as the tracker is concerned. The generator knows who owns each device, so every component can be scored against that ground truth.

Identifiers held

Components

People wholly in one

Components per person

Components mixing people

advertising ID

1,036

22%

2.59

0

+ household address

324

67%

1.38

121

advertising ID + hashed email

785

39%

1.96

0

all three

265

78%

1.24

121

The two failure modes run in opposite directions, and each identifier a tracker adds trades one for the other.

The advertising ID joins nothing, because it is issued per device and being per device is the point of it. Every device is its own component, so only the 22% of people who own a single device appear whole, and everyone else is scattered across 2.59 identities on average. One person read as several is the first failure.

Only 57% of the generated devices are signed in, so the hashed email can join only those. It never merges strangers, and it leaves three people in five still in pieces.

The household address produces the largest reduction, taking 1,036 devices down to 324, and it is the one that merges strangers. In 121 of those components the tracker joined correctly and then read a household identifier as a person identifier, because a shared connection cannot tell a spouse from a flatmate. Several people read as one is the second failure.

Holding all three, the tracker ends with 265 components for 400 people. 121 of them hold more than one person, and even among the rest somebody can appear in two: 78% are whole, so about one person in five is still split. What it holds is 265 things it believes are people, and the belief fails in both directions at once.

Hashed for your protection

Nothing.

Components

People wholly in one

Components mixing people

Identifiers as they are

265

78%

121

Every value SHA-256’d first

265

78%

121

Hashing is deterministic, so equal values stay equal, and equality is the entire mechanism. The hash travels instead of the address and joins exactly as well. Hashing hides the identifier’s original representation. It does not hide equality, and equality is all the join ever needed.

Sold as one person

Running the three mechanisms together defeats the usual assumption that separate apps and cleared cookies keep separate identities apart. Somebody using two apps owned by different brands under the same parent company, or two websites sharing an analytics provider, is tracked as one identity. What they did on each site can be combined into a single profile and used to pick which advertisements they see.

The failure runs the other way too, and it is not the one people expect. A tracker joining on a shared connection cannot distinguish a person’s own laptop from their flatmate’s, so the profile it assembles holds more than one person’s behaviour and is used as one person’s. The targeting that follows lands on somebody who was never on that site. The generator knows which of the 121 components are like this. The tracker has no such column, and nor does anybody whose behaviour lands in one of them.

The network has a say

Separate browsers for separate contexts reduce fingerprint correlation across activities. Avoiding cross-platform login, particularly using a major platform account to log into third-party services, closes the signed-in identifier. The device’s privacy settings list which apps can read the address book, and withdrawing that access shuts off a common route by which a hashed address reaches a tracker. Blocking fingerprinting scripts through a browser extension stops the third mechanism running at all, though the block needs maintenance as scripts adapt.

Each of those is a choice somebody makes about their own account, and the identifier that joins the most devices is not one of them. A shared connection is a property of the network, and everyone behind it can present the same outward-facing address whatever they do with their logins. That covers a household, an office, a university and a café alike. A VPN can replace that address with one shared by other users of the service. That cuts the household join and joins a different crowd instead.

Last reviewed: 2026-09-19.