# Escaping the Matrix: Client-Side Deanonymization Attacks on Privacy Sandbox APIs

Privacy Sandbox debug reports and a storage-limit oracle leak top-level site and browsing history, and a cross-origin Criteo worklet exposes Shared Storage values.

- Payout: Research
- Severity: medium
- Program: Google
- Write-up by: Eugene Lim
- Disclosed: 2025-08-17
- Write-up quality: 8/10
- Original: https://spaceraccoon.dev/client-side-deanonymization-attacks-privacy-sandbox-apis/
- Library page: https://bugbountylibrary.com/r/blog%3Aspaceraccoon-dev-client-side-deanonymization-attacks-privacy-sandbox-apis
- Tags: area:os domain:browser domain:web impact:data-leak kind:research platform:research sev:medium tech:chromium technique:recon technique:source-review vuln:info-disclosure vuln:misconfiguration

**Target:** Chrome Attribution Reporting API and Shared Storage API
**Root cause:** Attribution debug reports are sent outside the top-level browsing context, bypassing Referrer-Policy/CSP and leaking source_site; storage limits on event-level reports create a countable oracle. Cross-origin worklets with attacker-controlled URLs leak Shared Storage values.
**Steps:**
1. Enroll a Privacy Sandbox origin and host an ad page with `<img src="https://attacker/register-source.php" attributionsrc>` under `Referrer-Policy: no-referrer`.
2. Observe the debug report POST to `/.well-known/attribution-reporting/debug/verbose` containing `source_site` despite no-referrer.
3. For the oracle: find a source registration with unowned debug destinations (e.g. `debugconversiondomain2.com`), purchase the domain.
4. Register triggers on the purchased domain to match the victim's source, generating event-level reports to the ad platform.
5. Repeatedly register sources/triggers with your own destination until 1000 stored reports; count reports received (999 vs 1000) and `trigger-event-storage-limit` debug errors to infer prior publisher visit.
6. For Shared Storage: load `https://fledge.criteo.com/interest-group/abt/worklet` via `sharedStorage.createWorklet` with `dataOrigin: script-origin`, call `selectURL('select-abt-url', [attacker URLs ending #0-#7])`, and read which fenced frame loaded to recover `chrome_abt_pop`.
**How found:** Browsed MDN Web APIs page to discover Attribution Reporting API; read GitHub docs noting a TODO about debug reports and cross-site leak; inspected chrome://attribution-internals to spot non-existent debug destinations in real DoubleClick/Google Ads source registrations; deobfuscated Criteo's cross-origin worklet code.
**Exploitation:** Attacker purchases dangling debug destinations injected by ad platforms, registers triggers to fabricate conversions and uses the 1000-report storage limit as a counting oracle to reconstruct whether a user visited a publisher site, defeating random report delays. Separately, any origin loads Criteo's cross-origin-allowed worklet with attacker URLs to read Shared Storage values via fenced frame selection.
**Escalation:** Combines destination hijacking with the storage-limit oracle to bypass rate limits and random report delay, enabling cross-site browsing history reconstruction.
**Insight:** Ad platforms inject placeholder debug destinations that are unregistered and purchasable — check attribution source registrations in chrome://attribution-internals for dangling domains, and treat storage/rate limits as countable oracles that defeat randomized delays.
