Find My on macOS 14.4+ via cache decryption (+ LAN URL multi-IP picker)#810
Find My on macOS 14.4+ via cache decryption (+ LAN URL multi-IP picker)#810PnutCN wants to merge 2 commits into
Conversation
…he FindMyInterface class to support plist file parsing and data decryption
Apple started encrypting the Find My location cache (and the Private API helper stopped working) in macOS 14.4. Read locations by decrypting the cache instead of relying on Private API injection. - decrypt FMIP/FMF caches (ChaCha20-Poly1305) and LocalStorage.db (AES-256 keystream XOR + WAL) to get device & friend locations - add FindMyKeyManager + a key-import UI (folder picker) for the three keys - version-aware via new isMinSonoma14_4 flag: 14.4+ uses decryption, 11-14.3 keep the existing Private API path; GET /findmy/friends no longer forces the helper on 14.4+ - friend payload matches the legacy shape (is_locating_in_progress is a boolean); address fields are null (not persisted in the cache) - ui: LAN URL picker so hosts with multiple local IPs can choose/copy the reachable address
… PR BlueBubblesApp#810) Find My broke on macOS 14.4+ (incl. 26): the FMIP cache is ChaCha20-Poly1305 encrypted and the Private API helper no longer drives Find My. This adds the version-aware decryption path: - decrypt/cache.ts — ChaCha20-Poly1305 of the .data caches (@noble/ciphers) - decrypt/localStorage* — AES-256 page-codec decrypt of LocalStorage.db (+WAL) → temp sqlite (0600, deleted in finally) → better-sqlite3 join secureLocations×friends - decrypt/fmfReader.ts — friend display names from the FMF cache - decrypt/plistUtils.ts — bplist/XML parse + symmetric-key extraction - FindMyKeyManager.ts — load/validate/import the 3 keys (status + importFromDir for the UI) - macosVersion.ts — isMinSonoma14_4 gate; paths.ts — cache + keys locations FindMyDevicesReader.read() and FindMyService.refreshFriends() are now version-aware (gate is an injectable ctor arg for tests): >=14.4 decrypt; <14.4 the existing plaintext/Private-API paths. Wire shape unchanged for devices; friends emit title + last_updated for the app's normalizer. better-sqlite3 is lazy-required so importing the module doesn't load the native addon. Deps: @noble/ciphers, bplist-parser, plist (+@types/plist; bplist shim). Typechecks, tests 4/4, bundles. Next: Phase 2 (items/devices split), Phase 3 (Gator key-import UI). Needs the user to extract the 3 keys (findmy-key-extractor) before it returns data end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
For the FindMy part, it fetches and reads from the DB using the decryption keys, but does it also poll for changes? We are trying to fix the findmy implementation in the private API plugin, and i wonder if this can be the baseline, while the papi helper does the live updates. Trying to figure out how to incorporate it. |
No, the current implementation doesn't poll for changes — it's purely on-demand read-from-disk: every time GET /findmy/friends or refreshFriends() is called, it re-decrypts the cache files (LocalStorage.db + WAL) and re-reads the FMIP .data files. There's no periodic watcher or polling loop. The approach is "the OS writes the cache, we just read it at request time." Your idea of using the decryption as baseline + PAPI for live updates makes total sense. They're actually complementary: Decryption path (my code): provides the last-known locations even when PAPI is broken/busy, with zero code injection. Handles macOS 14.4+ where the PAPI helper hook no longer fires for Find My. |
… PR BlueBubblesApp#810) Find My broke on macOS 14.4+ (incl. 26): the FMIP cache is ChaCha20-Poly1305 encrypted and the Private API helper no longer drives Find My. This adds the version-aware decryption path: - decrypt/cache.ts — ChaCha20-Poly1305 of the .data caches (@noble/ciphers) - decrypt/localStorage* — AES-256 page-codec decrypt of LocalStorage.db (+WAL) → temp sqlite (0600, deleted in finally) → better-sqlite3 join secureLocations×friends - decrypt/fmfReader.ts — friend display names from the FMF cache - decrypt/plistUtils.ts — bplist/XML parse + symmetric-key extraction - FindMyKeyManager.ts — load/validate/import the 3 keys (status + importFromDir for the UI) - macosVersion.ts — isMinSonoma14_4 gate; paths.ts — cache + keys locations FindMyDevicesReader.read() and FindMyService.refreshFriends() are now version-aware (gate is an injectable ctor arg for tests): >=14.4 decrypt; <14.4 the existing plaintext/Private-API paths. Wire shape unchanged for devices; friends emit title + last_updated for the app's normalizer. better-sqlite3 is lazy-required so importing the module doesn't load the native addon. Deps: @noble/ciphers, bplist-parser, plist (+@types/plist; bplist shim). Typechecks, tests 4/4, bundles. Next: Phase 2 (items/devices split), Phase 3 (Gator key-import UI). Needs the user to extract the 3 keys (findmy-key-extractor) before it returns data end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Find My on macOS 14.4+ via cache decryption (+ LAN URL multi-IP picker)
Summary
On macOS 14.4+, Apple encrypts the Find My location cache and the Private API helper no longer works for Find My. As a result, the Find My Devices and Friends endpoints stopped returning data on recent macOS versions.
This PR makes Find My work again on macOS 14.4 / 15 (Sequoia) / 26 by reading and decrypting the local Find My cache directly, instead of relying on Private API injection. It is fully version-aware: older macOS (11–14.3) keeps the existing Private API path unchanged.
It also includes a small, unrelated UX fix for the LAN URL proxy option on machines with multiple local IPs.
Motivation
LocalStorage.db(a custom AES-256 keystream-XOR SQLite codec).What's changed
Find My decryption (macOS 14.4+)
api/lib/findmy/decrypt/cache.ts— decrypts FMIP/FMF.datacaches (ChaCha20-Poly1305) for Devices, Items, and friend display names. Normalizes Apple's"$null"placeholders back to realnull.localStorage.ts— decryptsLocalStorage.db(+ WAL) using Apple's per-page AES-256 keystream-XOR codec to obtain friend coordinates.localStorageReader.ts— opens the decrypted DB (better-sqlite3) and joinssecureLocations(coordinates) withfriends(handle).fmfReader.ts— pulls friend display names from the FMF cache.FindMyKeyManager.ts— loads/validates/imports the three decryption keys.findMyInterface.ts— version-aware strategy and friend payload assembly; output shape unchanged.Key management UI
LocalStorage.key,FMIPDataManager.bplist,FMFDataManager.bplist) produced by findmy-key-extractor. Keys are stable across reboots, so this is a one-time setup.get-findmy-keys-status,import-findmy-keys.Version awareness
isMinSonoma14_4env flag (14.4 is where encryption was introduced / the helper broke).GET /findmy/friendsno longer forces the Private API helper to be connected on 14.4+ (it reads the decrypted cache instead). 11–14.3 still require the helper as before.LAN URL multi-IP picker (UX)
API compatibility
Verified against the app's models (
findmy_device.dart,findmy_friend.dart) and the Find My page (findmy_page.dart):is_locating_in_progressis a boolean (the app field isbooland crashes on0),last_updatedis ms epoch,statusis one oflegacy/shallow/live, coordinates are[lat, lng].null(the cache only stores coordinates; the Find My app reverse-geocodes at display time)."$null"placeholder for empty values (including a bare"$null"foraddress). These are normalized to realnull, otherwise the app callsAddress.fromJson("$null")(a String) and crashes the whole device list.ValueKey(address.uniqueValue)whereuniqueValue = label ?? mapItemFullAddress. Co-located devices (e.g. several devices at the same home address) produce identical keys, which crashes/blanks the device list on rebuild. As a server-side workaround, duplicate address keys within a list group are made unique by appending zero-width spaces (U+200B) — unique key, visually identical. The proper fix is app-side (ValueKey(item.id)).Version / behavior matrix
Requirements / notes
@noble/ciphersandbplist-parserdependencies (required by the decryption code).Testing
Screenshots
Find My key import card:

Friends & devices on the app:


LAN URL picker:
