Skip to content

Find My on macOS 14.4+ via cache decryption (+ LAN URL multi-IP picker)#810

Open
PnutCN wants to merge 2 commits into
BlueBubblesApp:masterfrom
PnutCN:feat/findmy-decrypt
Open

Find My on macOS 14.4+ via cache decryption (+ LAN URL multi-IP picker)#810
PnutCN wants to merge 2 commits into
BlueBubblesApp:masterfrom
PnutCN:feat/findmy-decrypt

Conversation

@PnutCN

@PnutCN PnutCN commented Jun 9, 2026

Copy link
Copy Markdown

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

  • macOS 14.4 began encrypting the FMIP/FMF caches (ChaCha20-Poly1305) and the friend database LocalStorage.db (a custom AES-256 keystream-XOR SQLite codec).
  • The Private API helper that previously refreshed Find My friends no longer functions on 14.4+.
  • Reading the cache + decrypting it requires no code injection and no running of the Find My app, so it works on locked-down systems where the helper can't be used.

What's changed

Find My decryption (macOS 14.4+)

  • api/lib/findmy/decrypt/
    • cache.ts — decrypts FMIP/FMF .data caches (ChaCha20-Poly1305) for Devices, Items, and friend display names. Normalizes Apple's "$null" placeholders back to real null.
    • localStorage.ts — decrypts LocalStorage.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 joins secureLocations (coordinates) with friends (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

  • New "Find My Decryption Keys" card (shown on macOS 14.4+) with a folder picker that imports and validates the three keys (LocalStorage.key, FMIPDataManager.bplist, FMFDataManager.bplist) produced by findmy-key-extractor. Keys are stable across reboots, so this is a one-time setup.
  • IPC: get-findmy-keys-status, import-findmy-keys.

Version awareness

  • New isMinSonoma14_4 env flag (14.4 is where encryption was introduced / the helper broke).
  • GET /findmy/friends no 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)

  • When selecting the LAN URL proxy on a host with multiple local IPs (Wi-Fi / Ethernet / virtual adapters), a dialog now lets you pick/copy the reachable address instead of silently using the first one.

API compatibility

Verified against the app's models (findmy_device.dart, findmy_friend.dart) and the Find My page (findmy_page.dart):

  • Friend payload matches the existing shape — is_locating_in_progress is a boolean (the app field is bool and crashes on 0), last_updated is ms epoch, status is one of legacy/shallow/live, coordinates are [lat, lng].
  • Address fields for friends are null (the cache only stores coordinates; the Find My app reverse-geocodes at display time).
  • The encrypted cache uses Apple's "$null" placeholder for empty values (including a bare "$null" for address). These are normalized to real null, otherwise the app calls Address.fromJson("$null") (a String) and crashes the whole device list.
  • Duplicate-key workaround: the app keys each list row by ValueKey(address.uniqueValue) where uniqueValue = 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

macOS Devices / Items Friends
11.0 – 13.x plaintext cache (unchanged) Private API (unchanged)
14.0 – 14.3 plaintext cache Private API
14.4+ / 15 / 26 decrypt FMIP cache decrypt LocalStorage.db + FMF cache

Requirements / notes

  • The user must extract the three decryption keys once with findmy-key-extractor and import them via the new UI. BlueBubbles needs Full Disk Access to read the caches.
  • This bundles the earlier groundwork commit that adds the @noble/ciphers and bplist-parser dependencies (required by the decryption code).

Testing

  • Verified end-to-end on real Find My data (macOS 14.4+): friend names/coordinates/timestamps join correctly; devices (incl. addresses) decrypt and display.
  • Page-level decryption cross-checked byte-for-byte against the reference implementation; verified inside the packaged Electron runtime (better-sqlite3 ABI).

Screenshots

Find My key import card:
image

Friends & devices on the app:
IMG_20260609_215657
IMG_20260609_215818

LAN URL picker:
image

PnutCN added 2 commits July 17, 2025 13:04
…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
@PnutCN PnutCN mentioned this pull request Jun 9, 2026
munkledata added a commit to munkledata/gator that referenced this pull request Jul 1, 2026
… 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>
@qayshp qayshp mentioned this pull request Jul 4, 2026
@zlshames

Copy link
Copy Markdown
Member

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.

@PnutCN

PnutCN commented Jul 18, 2026

Copy link
Copy Markdown
Author

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.
PAPI path: when it works, it can push real-time location deltas into Server().findMyCache (the same in-memory store the decryption path writes to).
Both paths already converge on Server().findMyCache.addAll(), so they'd share the same dedup logic naturally. The decryption path could be the fallback when PAPI is unavailable or hasn't connected yet, and PAPI takes over for live updates once it's active.

munkledata added a commit to munkledata/gator that referenced this pull request Jul 19, 2026
… 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants