Facebook on iOS: What Each Permission Grants, and What Still Runs If You Deny Them

On Android, Facebook’s 90 second timer is in the code: while the app is on screen it can ship GPS, every nearby Wi-Fi network, and a Bluetooth census. Muse is built to act, not just watch. This post is Facebook on iOS, and it is the one where I could see what a live session actually wrote, because the phone was jailbroken.

Facebook for iOS 584.1.0, build 1097955119. The phone is an iPhone X running iOS 16.7.10, jailbroken with palera1n, tethered. Stock iOS will not let you read another app’s container. The jailbreak is why this post has file names and byte counts instead of only strings in a binary. I reached the phone over SSH, copied the app container, the shared app group, and the frameworks to a Mac, and read them there. The main binary is about 22 MB. FBSharedFramework alone is about 168 MB. A second pass was a normal session with the system permissions granted, including tracking and contacts, which a normal setup of this phone had denied. I watched locationd and the sandbox while that session ran. A later pass was the phone locked and untouched for about ten minutes, with the unified log streamed over USB. That is idevicesyslog. It is not a special Facebook feature. It is the system log, readable because the phone is jailbroken and plugged in.

iOS is stricter than Android. There is no getInstalledPackages(), no boot-completed broadcast, and no unfiltered BLE scan of every MAC around you. The gaps are different. A lot of the tracking pipeline does not ask for a permission at all.

The Android clock is hardcoded at 90 seconds. iOS has the same kit, UnifiedForegroundLocationService and FBForegroundLocationUploader, shared with Instagram. The interval itself is MobileConfig, so the server decides. I looked for the quadkey store in the foreground, in a short live log, in the locked ten minutes, in the response cache, and in the app group. It was not there. For this account the 90 second sampler looks switched off, or waiting on a location feature I did not trigger. The code is still in the binary. A cached photo struct did contain raw coordinates. I am not publishing them. The sampler being off is not the same as “this phone has no location data.”

What each permission grants, and what it did

Permission The usage string What the binary and the session actually showed
Location, when in use “Check-in, find local events and get better ads.” GPS while the app is open. Also the gate for the connected Wi-Fi SSID and BSSID (CNCopyCurrentNetworkInfo). Quadkey plus carrier id is the upload shape (_getQuadKeys:, _getUvmEventsForQuadkey:carrierId:periodType:), including a two-day rolling counter. The app ships seven different location nags in its onboarding cache, from “always” to Nearby Friends to dating.
Location, always Same sentence. Background location, significant-change (on the order of a few hundred meters), and region monitors. On this run the grant was Always. locationd logged AuthorizedAlways, then the app’s desired accuracy moved from 100 m to 1 km to 3 km within a fraction of a second. Fixes were delivered in the foreground and stopped when the app backgrounded. Background was the zone monitors, not a continuous GPS stream. allowsBackgroundLocationUpdates was false on the manager I saw.
Temporary precise location Same sentence again. One precise fix without the user committing to Always. Then it falls back.
No location at all The code has a kilometer-accuracy fallback. Carrier id plus a coarse quadkey can still be derived from the cell network. That is a weaker fix than Android’s tower lat/lon, and it is not “no location.”
Bluetooth always “Find and connect to devices and cast videos to TV.” CBCentralManager scanning, beaconIdentityConstraint (beacon matching, same idea as the Android iBeacon/Eddystone harvest), and FBAssistantStellaBluetoothManager for glasses. iOS randomizes peripheral MACs. The advertisement payload and RSSI are still the data. This session produced no BLE files. No glasses nearby, and the feature looks gated.
Local network Same cast sentence. Bonjour _googlecast._tcp. NSAllowsLocalNetworking is true, so some of this does not even show the iOS 14 prompt.
Motion and fitness “More relevant and personalized experiences.” CMMotionManager, activity classification (walking, running, automotive, stationary), barometer, device attitude. Unlike Android, the raw motion sensors do wait on this prompt. Once it is allowed, locationd was also handing the app motion-activity updates on the location connection. The phone’s own barometer calibration id lives in locationd and survives deleting Facebook.
Microphone Record audio, identify songs, voice search, effects. WebRTC, Reels, Live, voice search, song identification. The mic grant is also what arms the audio background mode, which is a keep-alive, not just a recorder.
Camera Photos, video, effects. FBCameraFramework, plus a large on-device depth model (about 41 MB) for 3D photos.
Photos Share and suggest photos. PHPhotoLibraryPreventAutomaticLimitedAccessAlert is true. That suppresses the “selected photos” chooser, so a grant silently means the whole library unless you go find the limit yourself.
Contacts See who you can add. Contact matching. A normal onboarding often denies this. This test had it allowed, so it is the granted case, not the default case.
Calendars Sync events you host or attend. Read and write.
Reminders, Siri, speech recognition Siri can look for photos. Speech is “to detect your confirmation.” INSendMessageIntent, a Siri photo-search activity, on-device speech for confirmations.
Apple Music / media library Play and save audio. Read of the media library for the music features.
Face ID Quick sign-in. Login and 2FA. The OS does the match.
NFC “Securely prove your identity.” Three ISO 7816 card selectors. Card scan, not a background poll of every tag you walk past.
App tracking (ATT / IDFA) “Personalize ads.” Advertising id, SKAdNetwork ids v9wttpbfk9 and n38lu8286q, attribution endpoint https://facebook.com. On this run the cached server responses mentioned idfa. tccd re-checked tracking every time the app foregrounded. If you deny ATT, identifierForVendor still exists. No prompt. It resets only after every Meta app from that vendor is gone.

Installed without asking

These are plist keys, not prompts.

  • Seven background modes: bluetooth-peripheral, external-accessory, fetch, processing, remote-notification, voip, and audio. VoIP plus audio are the ones that keep a network path alive when you are not in the app.
  • Eight background tasks: app refresh, four Papaya (the React Native runtime) tasks split by power and network, an exit report, and feed cleanup.
  • 59 URL schemes it is allowed to query. Instagram, WhatsApp, Messenger, Snapchat, Line, Oculus, Spotify, Maps, Uber Eats, authenticator apps, and Meta’s own codenames, including Hatch, which is Muse.
  • Shared keychain group T84QZS65DQ.platformFamily with the other Meta apps. Tokens and device ids in that group are readable by whichever of those apps is installed.
  • Nine extensions. Notification service and content, widgets, lock screen, video widget, broadcast upload, Siri intents, and the share sheet. The share extension runs when some other app shares. It is a different process with the same keychain group.
  • App Transport Security exceptions. Four Facebook domains allow plain HTTP. Web content is allowed arbitrary loads. The in-app browser is not stuck behind ATS.
  • External accessory protocols com.meta.ar.wearable and com.facebook.stella. Glasses.

What ran in the session whether or not a sensor was the point

Sandbox grew from about 111 MB to about 226 MB in roughly ten minutes of ordinary use. None of this is the microphone.

System What showed up
FFLogger The feed analytics queue. Upload batches went from 17 at the start of the session to 29. The event catalog in the database has 15,742 event names. Batches carry a device id and a family device id. I am not publishing the ids or the session tokens that were sitting in that file.
ENIGMA On-device ad attribution. onsite_activities grew during the session. Tables exist for offsite features, family onsite features, attribution windows, and install referrers. An ad you were shown gets an id stamped locally.
Time in app Start and end for each foreground interval, wall clock and uptime. One stretch was about 89 seconds, another was a 6 second relaunch.
LOOM A profiler config: CPU stack samples every 25 ms, 30 second buffers, a 20 MB upload cap, daily upload. It is profiling the app, on your CPU, and shipping samples.
On-device models A Core ML model, fb_video_ads_image_tapability_model, input shape [1,140], built with Facebook’s PyTorch. It predicts whether you tap a video-ad image. Also a feed ranker, an audio super-resolution model, and the 3D-photo depth net. Inference is local. The behavior is still the training signal.
Time and navigation Caches named for time-spent embeddings, surface watch time, navigation history, and real-time video ranking. Some of the stores are sessionless, meaning they are device-scoped and exist before login.
Identity that calls itself anonymous Server responses include an epoch anonymous id, a signed epoch head, and the previous head. It rotates. It is chained, so the server can link the epochs. There is also a per-user tracking salt in the cache stats database.
Transport caches QUIC 0-RTT tokens, Fizz (their TLS stack) session tickets for HTTPS and for MQTT, and a DNS cache. The MQTT DNS cache was updated while the app was closed. runningboardd kept the process in the “after-life” state after I left it.
Pando cache About 114 gzipped server responses, about 7 MB, including whether this account is eligible for location UI. That is the A/B and eligibility state, on disk.

All granted, some granted, none granted

What All granted (this test) Typical Everything denied
GPS Precise, Always, zone monitors, BSSID of the connected network While-in-use only No precise GPS. Carrier and a coarse quadkey can remain. Connected BSSID may remain via the local-network allowance.
Ad id IDFA plus IDFV IDFV only, if ATT was denied IDFV, device id, family device id. None of those are the ATT prompt.
Motion and BLE Available. BLE did not fire in this session. Only if you allowed the prompt No raw motion samples from the app. The barometer calibration id in locationd is still there.
Mic, camera, photos, contacts Available. Photos default to the whole library because the limited picker is suppressed. Whatever you allowed Those features disable. The analytics pipeline does not.
FFLogger, ENIGMA, time-in-app, LOOM, the on-device models, MQTT, extensions, family device id Ran Ran Still there. Permissions gate the sensors. They do not gate the pipeline.

The in-app browser

The JS bundle root is EntryPoints/WildeBundle. Papaya is the React Native runtime, about 34 MB of framework. Web content is allowed to load over plain HTTP. Pages in that webview can use geolocation, motion, WebRTC, and the web Bluetooth API, each with the normal site prompt, all riding on the grants you gave Facebook. There is a script hook whose name is disableLocationServicesUserScript. JavaScript in that webview can turn the app’s location services off. Spotify’s SDK and FFmpeg are in the bundle too.

What I would actually change

  • When in use, not Always.
  • Deny Motion if you do not want activity classification and the barometer from this app.
  • Deny Bluetooth if you do not have their glasses.
  • Deny tracking. You still have an IDFV. You do not have the advertising id.
  • After allowing photos, go set Selected Photos yourself. The app hid the chooser.
  • Turn off Background App Refresh for Facebook.

VoIP and audio background modes, MQTT, the background tasks, the vendor id, the extensions, and the shared keychain do not follow those toggles. Deleting the app is the off switch, and the keychain group sticks around if Instagram, WhatsApp, or Messenger is still installed.

I did not fully disassemble 280 MB of Mach-O. Symbol and string evidence is not the same as a debugger trace of every branch. The live session is one account, one afternoon, on a jailbroken phone, with the permissions already granted. Treat the “it ran” rows as observed. Treat the “it can” rows as code that is present and wired.

What a locked phone still did

After the first session I locked the phone, left it plugged in, and did not touch it for about ten minutes. The unified log kept streaming. The process stayed in Apple’s after-life state. Background Task Scheduler ran zero Facebook tasks in that window. The app container got no new files.

The shared app group did. The messenger database kept syncing. Its write-ahead log moved. The crypto database file was touched, and the peer-device table inside it was byte-identical to a pull from earlier that afternoon, so that idle window was not a fresh harvest of those records. Experiment values for the extensions were written again. I can compare those files only because the jailbreak lets me copy the app group. A stock phone shows you none of this.

Messenger has tables for live-location sessions and favorite places. Every one of those was empty. That feature had not been used.

Separately, Apple’s own locationd did not idle. Screen off, it was still feeding the barometer, scoring indoor versus outdoor from the visible Wi-Fi networks, and classifying motion. That is the operating system. Facebook receives the result the next time it asks Core Location for a fix. That side of the phone is the next post.

What this means if you do not live in the code

The Android app has a clock, and the clock’s job is place plus the wireless world around you, every 90 seconds, while the app is open. On this iPhone I could not find that clock running. I could find everything else that does not need a sensor.

Open the app, scroll, leave. In about ten minutes the sandbox roughly doubled. The analytics queue gained batches. An on-device model whose name is about whether you will tap a video ad was sitting there, input width 140 numbers, built with their PyTorch. A profiler config said to sample the CPU stack every 25 milliseconds. The process stayed alive after I left, in the state Apple calls after-life, and the messaging database kept syncing while the screen was off. None of that is the microphone. The microphone is a permission, and it also arms an audio background mode that keeps a path open. I am not claiming the mic was recording in the idle window. I am claiming the bookkeeping was.

The risks that match what this phone actually did:

  • The interesting log is not gated by the prompts. Deny motion, Bluetooth, and tracking and you remove those sensors. You do not remove the analytics batches, the ad model, the time-in-app rows, or the device ids those batches carry.
  • Photos are wider than the prompt. The app turns off the “selected photos” chooser. A grant can mean the whole library unless you go find the limit yourself.
  • “Always” location was this test, not a law of nature. I had set location to Always. locationd recorded that, then the app asked for 100 meters and quickly settled to a kilometer and then 3 kilometers. Fixes showed up in the foreground and stopped when the app backgrounded. Background was zone monitors, not a continuous GPS trail. That is milder than the Android clock. It is still location, and the usage string you tap is one sentence for When In Use, Always, and a one-shot precise fix.
  • The ad id is not the only id. Denying tracking removes the advertising id. The vendor id remains until every Meta app from that vendor is gone. The extensions and the other Meta apps share a keychain group, so a share sheet in some other app is not a separate identity.
  • Jailbreak is how I saw it. It is not how they collect it. Facebook does not need your phone to be jailbroken. The jailbreak is what let me read the result. On a normal phone the same writes happen inside a folder you cannot open.

If you keep it: When In Use, not Always. Deny Motion and Bluetooth if you do not use those features. Deny tracking. After allowing photos, set Selected Photos yourself. None of that stops the analytics pipeline. Deleting the app does, and the shared keychain sticks around if Instagram, WhatsApp, or Messenger is still installed.

Leave a Reply

Your email address will not be published. Required fields are marked *