The other three posts are apps. Facebook on Android watches on a 90 second clock. Muse is built to act as you. Facebook on iOS mostly wrote bookkeeping, not that clock, once I could see the files. This post is the phone those apps run on.
Same iPhone X, iOS 16.7.10, jailbroken with palera1n. A stock phone will not let you open locationd‘s caches, the per-app network database, or the analytics engine. Those are root-owned. The jailbreak is the only reason the tables below have row counts. I copied them over SSH and read them on a Mac. A locked, idle stretch of the unified log was streamed over USB with idevicesyslog. None of that is required for Apple to record the data. It is required for me to read it back.
The split is the same as the app posts. What gets written on the device, and what actually leaves.
One toggle sits in front of most of the uploads
Settings, Privacy, Analytics and Improvements. On this phone the upload queues behind that toggle were empty. analyticsd’s reservoirs were empty. geod had nothing in flight. So the batch upload looks off.
The local recording does not stop. The unified log, the location caches, the per-app network database, Duet’s training rows, and the App Store engagement backlog were all still growing.
| System | What it records | Leaves the phone? | The toggle |
|---|---|---|---|
Unified log (logd) |
Debug-level stream from every daemon: motion, barometer, Wi-Fi, baseband, location | Stays on device, rolling. A USB syslog or a sysdiagnose can read it. “Send Diagnostics” does not turn the capture off. | None for the local firehose |
locationd and geod |
Per-app location use, geofences, cell and Wi-Fi tiles, frequent places | Per-app cache is encrypted on device. Frequent places sync through iCloud. geod analytics upload when Analytics is on. | Location Services, Precise, Significant Locations. Partial. |
networkd / netusage.sqlite |
Per-app bytes, a time-of-day traffic model, tracker domains, every network you join | The database is local. The aggregate is what Analytics uploads. | No user switch for the database |
symptomsd |
“This app used a lot of network,” Wi-Fi quality, per-app domain tracking | Diagnostic records upload with Analytics | Analytics, plus a domain-tracking switch that was off here. The daemon still runs. |
analyticsd |
The rule engine that turns the rest into .ips batches |
Uploaded when the toggle is on | Analytics and Improvements. This is the master switch, and it was off. |
Duet (duetexpertd) |
Which apps you open, in what order, after idle, and whether the screen just lit up | Models stay on device. Differential-privacy aggregates can upload. | No single switch. Analytics gates the upload. The model runs anyway. |
| Apple Media Services | Today, App Store, News, Podcasts, TV: purchases, notification taps, page exits, which A/B cell you are in | Uploaded to the recommender | Personalized Recommendations and Apple’s ads. Partial. |
| Siri | Wake-word detection on device. The utterance goes to the cloud. Search feedback carries a device GUID. | Speech uploads. Feedback records ride Analytics. | Hey Siri stops the wake word. It does not delete the feedback GUID. |
| CrashReporter | Crashes, jetsam, low-battery logs, per-daemon CPU and disk, panics | Upload with Analytics | Analytics |
| MobileAsset | The ML models and configs Apple has pushed down: voices, translation, captioning, geofence polygons, experiment assets | This direction is inbound | None |
| TCC | Every per-app permission grant | On device | The permission prompts. That is the floor, not the ceiling. |
The log that never asks
On iOS 16 the unified log is Debug level, every process. In one rotated statistics file, about 28 MB, the kernel was 47 percent, CommCenter (the baseband) 19 percent, locationd 12 percent, mDNSResponder 8 percent, wifid 5 percent. That is an idle phone. The OS is logging cellular, location, and Wi-Fi the whole time.
A locked stretch of the live log showed the same thing in sentences: accelerometer samples, ambient light, a motion-state update (“am I being carried”), pedometer fields, and the push daemon noting the exact number of seconds the screen had been unlocked. Stock logs wrap a lot of payloads in <private>. The subsystem and the process name are still there. wifid logged a call that copies the current network’s password. The network name was redacted in that line. The fact of the copy was not.
This log is the raw feed the other systems sample. It stays on the device for days. Send Diagnostics is how a copy of it leaves. It is not how you stop it being written.
Location, the Apple side
locationd keeps the database behind Settings, Privacy, Location. Facebook on this phone was When In Use. The App Store was Always. On top of any app, about thirty system location bundles run anyway: suggestions, calendar, country, Do Not Disturb, Emergency SOS, exposure notification, Routine (frequent places), safety alerts, shortcuts, time zone, traffic, Wi-Fi calling, compass and motion calibration, navigation. You do not get a prompt for those.
The interesting names in the collection budget are Apple’s own: avengers_proactive_gps_monitor, avengers_proactive_wifi_monitor, avengers_geotagging_data_collection_monitor, avengers_cross_validation_monitor. Each has a reset window. That is the OS scheduling GPS and Wi-Fi so it has fresh assist data, not an app asking for a check-in.
Cell tiles, indoor tiles, a multi-megabyte sensor-state file, and barometer state sit next to that. userinfo also holds fitness fields derived from Health, including sex, resting heart rate, and a VO2 figure, used by the pedometer model. I am not publishing the values.
The per-app location caches that would show who consumed which fix are encrypted. The key is in the Secure Enclave. The schema is visible. The rows are not.
geod keeps a second counter: per-app usage rows, including the Maps widget and the location-permission prompt plugin. Those become analytics blobs. The inflight table was empty here, which matches Analytics being off. Frequent places are a different path. There is a CloudKit cache for locationd, so significant locations sync to iCloud even when the analytics blobs do not upload.
The network database you cannot see in Settings
netusage.sqlite is what feeds the per-app cellular and Wi-Fi totals, and it stores more than the totals.
- Bytes in and out, per process, per interface.
- A statistical model of each app’s traffic by hour and by day of week. Mean, variance, sample count. Low Data Mode uses this to guess what “normal” looks like.
- Domains the system has decided are trackers, tied back to the app.
- Every network attachment: hotspot or not, known-good, low quality, last attempt failed.
On this phone the attachment table had only the home access point, two radios, both marked known-good. I am not publishing the name or the BSSIDs. Lifetime Wi-Fi for a few processes, so the scale is obvious: the App Store had pulled about 713 MB, Facebook about 330 MB in, mobileassetd about 1.1 GB in (those are the model downloads below), mDNSResponder about 1.6 GB in, locationd about 143 MB in, geod about 99 MB in. There is no switch that stops this file.
Analytics is a rule engine, not a checkbox
analyticsd ships a 25 MB config database of transforms and budgeted metrics. That is why “Analytics” can describe almost anything. The live state tracks what has been computed on a daily bucket, a 90-day bucket, and a never bucket. The directories that hold records waiting to upload were empty.
CrashReporter still had 100 .ips files on disk. Not all of them are crashes:
- Siri search feedback, 25 of them. Country, session start, and a per-device
user_guidso Apple can join feedback across sessions. I am not publishing the GUID. - Jetsam: which process was killed for memory.
- Microstackshots: who was on the CPU.
- Low-battery logs: capacity, voltage, awake time, standby time, per process.
- Wi-Fi quality, software-update errors, reboot counts.
- Per-daemon CPU and disk records for iCloud, the intelligence platform, analyticsd itself, the engagement daemon, and search.
- Two full kernel panics.
symptomsd is the daemon that writes “this app used a lot of network” and “Wi-Fi was bad,” with the app name on the record. Its domain-tracking switch has been off on this phone since late 2024. The daemon was still in the idle log. The switch changes what it samples. It does not remove the daemon.
Duet already knows the next app
duetexpertd plus Biome is the on-device model behind App Library suggestions, Today, notification grouping, and Siri Suggestions. The store on this phone was 284 MB.
For each app it keeps install date, last launch, last Spotlight launch, how long you usually wait between launches, and a learned cluster id. It keeps the last launch sequence. On this phone that sequence was Messages, then Phone, then Books, then Tips. It keeps training rows for “the phone just went idle”: how often each candidate app was opened in the last 12 hours, 24 hours, 48 hours, 7 days, 14 days, and 28 days, plus a candidate location. That is the corpus for “after I set this down, what do I open.”
There is also a co-occurrence matrix, app against app. During the locked capture Duet was writing its notification-group stream, and contextstored fired on backlight changes. Screen on and screen off is a feature. It knows when you pick the phone up.
A plist holds a per-device salt used to hash location before a differential-privacy upload. The models arrive as a MobileAsset. Training is local. There is no Duet toggle. Analytics is what gates the aggregate leaving.
The Today page is an event log
amsengagementd is the recommender for Today, the App Store, News, Podcasts, TV, Home, Fitness, and Wallet. The journey database had about 1,900 engagement events queued, 158 content items, and a key-value store of A/B assignments per app: App Store, Fitness, Home, Wallet, Books, Photos, News, Podcasts, TV.
The id in that store is a local device id, not your Apple ID. A separate cloud identifier exists so the same engagement can be joined across devices. I am not publishing either id.
The allowed events include purchase, session start, notification displayed, notification tapped, page exit, and OS upgrade. That upload is the one gated by Personalized Recommendations and Apple’s ads, not by the Analytics switch.
The models Apple pushes down
/private/var/MobileAsset is the catalog of what Apple has already downloaded. Fifty-two manifests on this phone, fetched from Apple’s asset CDN, with the phone checking gdmf.apple.com for new ones. The set of assets is itself a fingerprint of which experiments you are in. Trial.Siri.* assets were present: dialog, dictation, experience, inferred helpfulness, speech understanding. Those are live A/B tests.
| Asset | What it is for | Uploads? |
|---|---|---|
| Hey Siri endpoint, voice trigger | Wake word and the short-press detector, on device | The detector stays. The utterance can go to the cloud. |
| On-device speech and translation | Dictation and live translation | No, for the model itself |
| Sound detection | Knock, alarm, dog, baby | No |
| Image captions, Magnifier OCR, lock-screen icon vision, back-tap | On-device vision and gestures | No |
| Siri and TTS voices, including custom voices | Speech out | The model is local. Voice data can sync. |
| Geo polygons and Core Location config | Cell and Wi-Fi geofence shapes for the location engine | No |
| Duet, ContextKit, proactive events, suggestions | The prediction stack above | Differential privacy, or engagement uploads for suggestions |
| Mail dynamic data | Suggested reply, smart folders | No |
| DeviceCheck | An opaque attestation that an app’s server can ask Apple for | Yes, per app, and you never see the token |
| HomeKit, CarPlay, Wallet, security experiments, subscription-timing models | Feature models and A/B config | Some of it is differential privacy |
mobileassetd was a gigabyte of download in the network database. That is this catalog arriving. You do not approve the models one by one.
Permissions are the floor
TCC is the database behind the permission prompts. It recorded what Facebook had been granted on this phone, and it records what every other app has been granted. Duet, the system location bundles, geod, networkd, the engagement daemon, and the downloaded models do not consult that table before they watch how you use the phone. Denying an app the microphone does not stop the OS logging motion, backlight, and which app you open next.
What I would actually change
- Leave Analytics and Improvements off if you do not want the
.ipsbatches, the jetsam and battery records, the geod counters, and the Duet aggregates to upload. On this phone that already looks off. The on-device copies remain. - Significant Locations off, if you do not want frequent places in iCloud.
- Precise Location per app, for the apps. It does not stop the system bundles.
- Personalized Recommendations and Apple’s ads off, if you do not want the Today and App Store event log uploaded.
- Hey Siri off, if you do not want a wake-word detector running. Search feedback already recorded is a separate record.
There is no switch for the unified log, netusage.sqlite, or Duet learning the next app. Those are the phone.
What this means if you do not live in the code
The apps are not the only thing keeping a record. On this phone, Analytics was off. The folders that hold records waiting to be sent to Apple were empty. The phone was still writing.
While it sat locked, the system log kept accelerometer samples, light, motion, step state, and the exact number of seconds the screen had been unlocked. A separate on-device model, Duet, stores which apps you open, how long you usually wait between opens, and what you open after the phone goes idle. It noticed the backlight. Pick the phone up, set it down, that is a feature in the model. The network database stores not just how many megabytes each app used, but a guess of what “normal” looks like by hour and by day, and which domains it thinks are trackers. Location is not only the pin an app asked for. About thirty system components have location whether or not you granted it to Facebook, and frequent places can sync to iCloud even when the analytics upload is off.
Put next to the app posts, the story is one trail with four authors.
- Facebook on Android takes a sample about every 90 seconds while it is on screen: where you are, every Wi-Fi network around you, every Bluetooth device around you. That is the app, and it needs the location and Bluetooth grants to do the full version.
- Muse is built to go past watching. Texts, every notification, calls, contacts, calendar, health, and a cloud browser that stays logged in as you. Those depend on the permissions. The device ID and the cloud browser do not.
- Facebook on iOS, on this account, was not running that 90 second sampler. It was writing analytics, an on-device ad model, and a process that stays up after you leave. I know that because the phone was jailbroken and I could open the folder. The app does not need the jailbreak to write the folder.
- iOS itself is already scoring motion, Wi-Fi, and which app comes next. When Facebook asks Core Location for a fix, it is asking a system that has been paying attention the whole time.
The risks, in one place:
- Denying an app’s microphone does not stop the diary. The mic findings in the Facebook posts are gated. The motion, backlight, app-launch, and network logs are not an app permission.
- Turning Analytics off stops the mail, not the writing. On this phone the upload queues were empty and the local stores were not.
- Frequent places are an iCloud problem, not an Analytics problem. Significant Locations is its own switch.
- The App Store and Today page are a separate upload, with its own id, gated by recommendations and ads rather than by Analytics.
- Jailbreak and root are the flashlight, not the camera. Android Facebook was read from the APK over ADB. That phone was not rooted, so its live databases stayed sealed and the claims there are about the code. This iPhone was jailbroken, so the iOS claims include files that actually changed. Neither fact is required for the collection. Both facts are why the evidence is this specific.