- What: Research on Pangle SDK's data collection practices
- Impact: Users may be tracked across apps despite privacy settings
This is Part 2 of my Pangle SDK research. Part 1 covered how I broke the encryption . This post covers what I found when I started comparing the decrypted data across apps. In Part 1, I decrypted 694 Pangle SDK payloads and documented what ByteDance collects: battery level, storage capacity, IP address, boot timestamps, and detailed hardware profiles from 40+ apps. The encryption turned out to be the cryptographic equivalent of taping the key to the doorframe. But decrypting the data was only half the story. The real question was: what happens when you compare the same user's data across different apps? The Setup Apple's App Tracking Transparency (ATT) framework gives users a simple choice. When an app asks "Allow this app to track your activity across other companies' apps and websites?", you can say no. When you do, the app gets a zeroed-out IDFA (Apple's advertising identifier). The app is not supposed to be able to link your activity across other apps. That is the entire point of ATT. I wanted to test whether the Pangle SDK respects this. Using my decrypted dataset of 874 Pangle events across 26 apps and 32 devices, I looked for cases where the same device appeared in two or more unrelated apps with ATT denied. What ATT Denial Looks Like in Pangle's Data When a user denies tracking, Pangle's request payload shows the right signals on the surface: { "atts": 2, "idfa": "00000000-0000-0000-0000-000000000000", "idfv": "[redacted]", "lmt": 1 } atts: 2 means "denied." The IDFA is zeroed. Limit Ad Tracking is on. The IDFV is a per-developer identifier that changes across apps from different developers. So far, Apple's rules appear to be followed. But scroll down in the same payload. The Fingerprint That Survives ATT Every Pangle request, regardless of ATT status, also includes: { "device": { "device_model": "iPhone15,4", "device_type": "D37AP", "total_mem": "6108520448", "total_space": "128241598464", "cpu_num": 6, "screen_height": 852, "screen_width": 393, "boot": "1770482937261", "power_on_time": "84291553", "battery_remaining_pct": 50, "volume": 100, "low_power_mode": 0, "darkmode": 0, "font_size": 17, "sys_build_version": "23C71", "ip": "192.168.x.x", "carrier_name": "--" } } That total_space value, 128241598464 bytes, is not a round number. It is the exact byte count reported by the device filesystem. It varies based on OS version, provisioning, and device history. total_mem is similarly exact. boot is the precise millisecond the device last started up. None of these fields are needed to serve an ad. Cross-App Matching I found three devices that appeared in two or more unrelated apps with ATT denied. In every case, the hardware fingerprint fields were identical across apps while the IDFV (which is supposed to be the only cross-referenceable identifier) was different. Device 1: Triple Master and MealOrbit Weekly Two completely unrelated apps. Different developers. Different IDFVs. ATT denied in both. Triple Master MealOrbit Weekly ATT Denied Denied IDFA zeroed zeroed IDFV F7C2A91E... 3B8D6F42... Model iPhone15,4 iPhone15,4 RAM 6108520448 6108520448 Storage 128241598464 128241598464 Screen 393x852 393x852 CPU 6 cores 6 cores Font size 17 17 Build 23C71 23C71 Device type D37AP D37AP Every hardware field matches. ByteDance receives two separate requests from two separate apps with two separate IDFVs, but the device fingerprint is identical. A server-side join on (total_space, total_mem, model, build_version) links them instantly. Device 2: QR Reader and ESM Timer Same pattern. Two Japanese utility apps. Different developers. Different IDFVs. ATT denied in both. QR Reader ESM Timer ATT Denied Denied IDFA zeroed zeroed IDFV A1D47B3E... 82E6C5F1... Model iPhone18,1 iPhone18,1 RAM 12418932736 12418932736 Storage 256109264896 256109264896 Screen 402x874 402x874 Font size 17 17 Build 23D127 23D127 Device type V53AP V53AP Identical fingerprint. Different IDFVs. The user said no to tracking in both apps. ByteDance has what it needs to link them anyway. Device 3: The Worst Case This one used three apps with Pangle. ATT was denied in two of them. But in the third app, the user authorized tracking, giving Pangle the real IDFA. Water Sort Puzzle Trivia Spin Find Differences ATT Denied Denied Authorized IDFA zeroed zeroed [real IDFA] IDFV C4A83D16... 5B2E7A93... D91C48E7... RAM (empty) 6042918912 6042918912 Storage (empty) 256387244032 256387244032 Model (empty) iPhone15,2 iPhone15,2 Build (empty) 23C55 23C55 This is the most damaging case. ByteDance receives the real IDFA from Find Differences alongside the hardware fingerprint. It receives the same hardware fingerprint from Trivia Spin, where ATT was denied. A trivial server-side join connects the denied user to their real advertising ID. The user explicitly denied tracking in Trivia Spin. ByteDance has the data to ignore that decision. How Unique Is the Fingerprint? The total_space field alone is highly distinctive. It is not "256 GB" rounded. It is 256387244032 bytes ...