← all articles

How to audit what your phone apps are actually sharing

Every app store listing carries a privacy label now, and the developer fills it in. Apple’s App Store labels and Google Play’s Data safety section are both declarations, and both stores say the developer is responsible for keeping them accurate. That makes a label a promise, not a measurement. I wouldn’t bet much on every promise matching what the app does.

This is for anyone who would rather check than trust. You don’t need to be a developer, but you do need to install a program on a laptop and change one Wi-Fi setting on your phone. I’m assuming the phone and the accounts on it are yours.

At the end you’ll have a list of every server each app talks to and a rough read on what’s inside the requests. Then you decide per app: keep, restrict or delete. The first app takes about an hour, later ones ten minutes. Everything here is free.

what you need

  • a phone you own: an iPhone on iOS 15.2 or later (Apple added App Privacy Report in that release, December 2021) or Android 12 or later. A spare handset beats your daily one
  • a laptop or desktop on the same Wi-Fi as the phone
  • mitmproxy from mitmproxy.org, free and open source, which shows you the requests passing through it
  • jq, if you want to query the iPhone report from a terminal (optional)
  • 5 to 10 apps: the ones you open daily plus one you barely touch
  • a router without client isolation, or the phone can’t reach the laptop
  • an hour and no money

Only test your own device on your own network. Decrypting your own traffic is ordinary security testing, but some apps’ terms forbid reverse engineering and the rules differ by country. This is not legal advice.

step by step

Step 1: write down what each app claims

Open each app’s store listing and note the data types it declares: the “App Privacy” card on Apple, the “Data safety” section on Google Play. Screenshot them. You want a written claim to compare against later.

Expected output: one short note per app, like “declares usage data and identifiers, no location”.

If it breaks: a listing that says “no data collected” is still a claim. Write it down. It’s the best one to test.

Step 2: turn on the built-in reports

iPhone: Settings, Privacy & Security, App Privacy Report, Turn On. It keeps seven days of history: which apps touched location, camera, microphone or contacts, and which domains they contacted. Android 12 and later: Settings, Privacy, Privacy dashboard shows a timeline of permission use, but not network destinations, so it covers half the picture.

Use the phone normally for a few days. On iPhone, scroll to the bottom and tap Save App Privacy Report. You get a file with one JSON record per line. Field names can shift between iOS versions, so look before you query:

head -n 3 App_Privacy_Report_v4_*.ndjson
jq -r 'select(.type == "networkActivity") | [.bundleID, .domain] | @tsv' App_Privacy_Report_v4_*.ndjson | sort | uniq -c | sort -rn | head -40

Expected output: a ranked list of app and domain pairs. An app contacting a dozen domains for a job that needs one stands out quickly.

If it breaks: an empty report means the feature was off, since it has no history before you enable it. Android menu names vary by maker, so search “privacy dashboard” in Settings.

Step 3: check for tracker SDKs with Exodus Privacy

Exodus Privacy is a non-profit that scans Android apps and lists the tracker SDKs compiled into them, such as Firebase Analytics or AppsFlyer. Search an app by package name (the text after id= in its Play Store URL). For an iPhone app, check its Android twin. The same company usually ships the same SDKs, which is a good guess and not proof.

Expected output: a named tracker list per app. A tracker being present doesn’t mean data was sent. That’s what the proxy is for.

If it breaks: no report, or an old one, means skip ahead. The proxy doesn’t care what Exodus has scanned.

Step 4: install mitmproxy and start it

Download it from mitmproxy.org (brew install mitmproxy on macOS), then start the web interface and find your laptop’s IP address:

mitmweb --listen-port 8080 --web-port 8081
ipconfig

On macOS use ipconfig getifaddr en0. Note the IPv4 address, something like 192.168.1.x.

Expected output: a browser tab with an empty flow list. Windows will ask about network access, so allow private networks only.

If it breaks: if the phone can’t connect later, add an inbound Windows Defender Firewall rule for port 8080.

Step 5: point the phone at the proxy and trust the certificate

iPhone: Settings, Wi-Fi, the (i) next to your network, Configure Proxy, Manual, then your laptop’s IP and port 8080. Open Safari, go to mitm.it, download the Apple profile, install it under Settings, General, VPN & Device Management, and switch it on under General, About, Certificate Trust Settings. Skip that last toggle and nothing decrypts. mitmproxy’s certificate docs cover every platform.

Android: set the same Wi-Fi proxy, visit mitm.it and install the certificate as a CA certificate. Here’s the catch. Apps targeting Android 7 or later ignore user-installed certificates unless the developer opted in, as Android’s network security configuration docs explain. Expect Chrome to decrypt and most other apps not to, and there’s no clean way around that without rooting, which I won’t recommend here. On Android the hostnames plus Exodus are your main evidence. PCAPdroid, a free open source app, logs which hosts each app connects to without needing a laptop.

Expected output: browse any HTTPS site on the phone and it appears in mitmweb with the full URL and contents.

If it breaks: nothing showing usually means the devices are on different networks (guest Wi-Fi often isolates clients) or a VPN app on the phone is carrying traffic around your proxy. vpn vs proxy explains why those two behave differently. Your router already sees hostnames for everything on the network, as your router sees every site on the network shows. The proxy adds the decrypted content.

Step 6: use the app and read the flows

Force-quit the app, launch it fresh with the proxy running, and do what you normally do: log in, scroll, tap around, close it. The first seconds after launch matter most, because that’s when SDKs initialise and most apps fire a burst of requests.

Look in URLs, headers and bodies for an advertising ID (parameter names like idfa, gaid or advertising_id), device model, OS version, carrier, timezone, your email or phone number and GPS coordinates. The mitmweb search box takes filters: ~m POST for uploads, ~bq idfa for request bodies containing that string, ~u analytics for URLs.

Expected output: a launch burst to domains you won’t recognise. In end-to-end encrypted chat apps the message bodies stay unreadable because encryption happens inside the app, so you’ll see metadata only. encrypted messaging compared covers who does that properly.

If it breaks: the app says it’s offline or nothing appears. That’s usually certificate pinning, where the app trusts only its own certificate. The event log still names the host in the handshake failure, which is useful. Getting past pinning means rooting, jailbreaking or instrumenting the app, and I’m not covering it here.

Step 7: count the hosts

Scrolling works for one app. To compare apps, count hosts. Stop mitmweb and save this as hosts.py:

from collections import Counter

seen = Counter()

def response(flow):
    seen[flow.request.pretty_host] += 1

def done():
    with open("hosts.txt", "w") as f:
        for host, n in seen.most_common():
            f.write(f"{n:5d}  {host}\n")
mitmdump --listen-port 8080 -s hosts.py -w app1.flow

Use the app for five minutes, press Ctrl+C once and wait for hosts.txt. Sort the hosts into the app maker’s own domains, delivery infrastructure like CDNs, and third parties. The third bucket is your audit. app-measurement.com is Google’s Firebase analytics, graph.facebook.com is Facebook’s SDK, api.mixpanel.com is Mixpanel. Search any host you don’t know.

Expected output: a count and hostname per line, busiest first. It only counts requests that got decrypted, so pinned apps under-report.

If it breaks: an empty file usually means a second Ctrl+C killed it before the file was written. Port 8080 in use means mitmweb is still running.

Step 8: act on it and re-run

For each third party carrying something you didn’t agree to, you have options. Revoke the permission it doesn’t need. Turn off tracking: on iPhone, Settings, Privacy & Security, Tracking, off; on Android 12 and later, Settings, Privacy, Ads, delete the advertising ID. Look for an analytics opt-out inside the app. Or swap the app for its website in a private browser, though sites leak in their own way through browser fingerprinting, which antidetectreview.org covers from the operator side. My own rule, and you may disagree: a utility app that talks to more third parties than first parties gets deleted, not tuned.

Then repeat steps 6 and 7 and compare the two hosts files with fc on Windows or diff elsewhere.

Expected output: a shorter list.

If it breaks: the same hosts after you opted out means the SDK ignores the toggle or hard-codes the call. Delete the app.

common pitfalls

  • treating a hostname as a leak. A request to app-measurement.com might carry a screen name or your email. Read the payload before you decide
  • testing one launch. Apps behave differently on first run, after login, after a consent banner and while idle. Leave the app backgrounded on the proxy for half an hour
  • using your daily phone and real accounts. Anything decrypted, passwords included, shows up in mitmweb and in saved .flow files. Use throwaway accounts, delete the flow files and never share them. If the spare phone changes hands afterwards, wipe it properly
  • leaving the certificate installed. The CA key lives in the .mitmproxy folder in your home directory, and anyone holding it can intercept a phone that still trusts it. Remove the profile and clear the Wi-Fi proxy when you finish
  • reading silence as clean. A pinned app that shows nothing hasn’t been cleared, only not inspected

scaling this

Ten apps is everything above, by hand, in an afternoon.

At a hundred, clicking through mitmweb stops working. Run mitmdump with hosts.py per app, keep one flow file and one hosts file per app per version, and diff hosts between updates, since a new host after an update is often a new SDK. Use Exodus as the first pass and proxy only the apps that trip it. A cheap dedicated test phone you factory reset between batches saves arguments later.

At a thousand it’s a program, not a tutorial. I haven’t tested this at that size and I’d distrust anyone who says it’s easy. You’d want emulators or a rack of test devices, scripted taps (adb shell monkey sends random input to an Android app), and a maintained map from host to owning company, since nobody researches thousands of domains by hand. Coverage never reaches 100%, because pinning and login walls stop some apps, so treat it as sampling.

where to go next

The full list of guides is on the blog index.

Written by Xavier Fok

disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-09-26.

from the team
Want a real mobile IP, not a datacenter VPN endpoint?

Shared VPN exit nodes get flagged and blocked. Singapore Mobile Proxy runs real 4G/5G mobile IPs that give you a residential-grade address carriers still trust.

see how it works →
read on
More from The Privacy Wire

VPN and tool reviews, realistic opsec guides, and privacy news for people who want to protect their data.

browse all articles →