TrackerControl for iOS is a web service and analyser pipeline for finding trackers in iOS apps. It is inspired by TrackerControl for Android and related research on privacy analysis for mobile apps.
The project has two parts:
- A Node.js/Express website where people can search App Store apps and view tracker reports.
- An analyser worker that downloads free App Store apps, analyses them on an actual iPhone, and uploads the results back to the website.
The website also includes jurisdiction analysis, showing which companies and countries control detected tracking infrastructure.
- Search free iOS apps from the App Store.
- Queue apps for analysis by popularity and staleness.
- Detect embedded tracker signatures and declared tracking domains.
- Store current and historical analysis results.
- Show tracker, permission, and jurisdiction summaries.
- Run the analyser from macOS or a Raspberry Pi host.
Only free App Store apps are queued for analysis. The queue prioritises apps with more stored App Store reviews, then rechecks stale analyses over time.
analyser/ App download, install, scan, and upload scripts
lib/ Shared website helpers, including jurisdiction analysis
migrations/ SQL migrations
models/ PostgreSQL access layer
routes/ Express routes and analyser API endpoints
scripts/ Maintenance scripts
views/ Pug templates
public/ Browser assets
static/ Static image assets
Website:
- Node.js
- npm
- PostgreSQL
DATABASE_URLpointing at an empty PostgreSQL database (the schema is created by runningnode scripts/migrate.js)
Analyser:
- An iPhone reachable over SSH
trackerscaninstalled on the iPhone- Matching
UPLOAD_PASSWORDon the website and analyser
Install dependencies:
npm installCreate a root .env file:
DATABASE_URL=postgres://user:password@host:5432/database
UPLOAD_PASSWORD=change-me
CURRENT_ANALYSIS_VERSION=4
BODY_LIMIT=25mb
PUBLIC_FORM_BODY_LIMIT=100kb
PORT=3000BODY_LIMIT applies to authenticated analyser JSON and text uploads.
PUBLIC_FORM_BODY_LIMIT is the smaller limit for the public search form.
Run migrations:
npm run migrateStart the website:
npm run watchFor production:
npm run startOpen http://localhost:3000 if PORT=3000 is set.
Copy the example config:
cp analyser/.env.example analyser/.envSet at least:
SERVER=https://your-server.example
UPLOAD_PASSWORD=change-me
ANALYSIS_VERSION=4
ANALYSIS_MODE=trackerscan
TRACKERSCAN_CMD="ssh iphone trackerscan"
TRACKERSCAN_SIGNATURES=/var/mobile/ios_signatures_v2.json
TRACKERSCAN_SIGNATURE_SET=ios-v2Log in to ipatool once on the analyser host:
ipatool auth loginRun the queue processor:
bash analyser/processQueue.shTo analyse one app immediately:
ONLY_APP_ID=com.spotify.client bash analyser/processQueue.shThe default analysis path uses trackerscan and uploads analysis version 4. The legacy Frida flow is still available with:
ANALYSIS_MODE=frida bash analyser/processQueue.shThe analyser has conservative download and retry limits by default. Tune these in analyser/.env only if the host, network, and storage can handle the extra load.
The analyser can run on Linux/arm64. A Raspberry Pi needs node, python3, unzip, zip, curl, openssh-client, libimobiledevice tools, ideviceinstaller, and a Linux arm64 ipatool.
Use the installer:
sudo bash scripts/setup-raspi-analyser.shSee raspberry-pi-analyser.md for the full setup, including systemd, SSH aliases, ipatool, and RAM-backed IPA storage.
The website exposes analyser endpoints:
GET /queuereturns the next app to process in the response body and its one-use assignment token in theX-Analysis-Claim-Tokenresponse header.GET /pingmarks the analyser online.POST /uploadAnalysisstores successful analysis results when sent with the assignment'sX-Analysis-Claim-Tokenheader.POST /reportAnalysisFailurestores failed analysis results when sent with the assignment'sX-Analysis-Claim-Tokenheader.
Analyzer requests authenticate with Authorization: Bearer $UPLOAD_PASSWORD.
Authentication and assignment tokens serve different purposes: the bearer token authorises the analyser, while the assignment token prevents an expired worker from overwriting a newer result.
The claim-token migration deliberately requeues every in-flight assignment that predates tokens. Use a coordinated deployment:
- Stop all analyser queue processors.
- Deploy the website and run
npm run migrate(or start it withnpm run start). - Deploy the matching
analyser/processQueue.shto every analyser host. - Restart the analysers.
The /queue response body remains a plain bundle ID, but older analyser scripts do not return the required claim header. Their completion requests will be rejected and must not remain running after the migration. Any interrupted work is safely available to the updated analysers because the migration requeues it.
Health checks:
GET /healthzreturns200when the website can reach PostgreSQL.GET /healthz/analyserreturns200when the analyser has pinged in the last hour.
Useful maintenance commands:
npm test
npm run queue-status
npm run priority-report -- --limit=20
npm run backup-db
npm run migrateReset one app for a fresh analysis:
npm run reset-app -- --appid=com.google.ios.youtube --applyWithout --apply, the reset command runs as a dry run.
Set CURRENT_ANALYSIS_VERSION=4 on the website when version 4 results should be treated as current.
The queue will reprocess apps when:
- They have never been analysed.
- Their analysis version is stale.
- Their analysis is older than
STALE_ANALYSIS_DAYS. - A previous processing marker has expired after
PROCESSING_TIMEOUT_MINUTES.
The default stale window is 180 days. The default processing timeout is 120 minutes.
This project is licensed under AGPLv3.