BloomraBloomra
Get the app
Legal

Privacy architecture

Last verified: September 2026 — against the Bloomra iOS app source.

Most privacy pages ask you to trust a promise. This page is written differently: each claim below names the exact mechanism behind it — the algorithm, the key storage, the verification step — so a security researcher can check it independently, rather than take our word for it. If a claim below is wrong or you find a gap, we want to hear about it. See the invitation at the end.

1

No accounts, no first-party server

Bloomra has no sign-up, no login, and no backend that receives your cycle data. The app's only network connections are to Apple's own services and two third-party SDKs that never see your health data.

How it works

  • •The app ships with no HTTP client of any kind — no package:http, no package:dio, no raw HttpClient, no SecurityContext or badCertificateCallback override, no WebSocket.
  • •The only outbound connections are: iCloud Drive (only when Delete All Data removes an iCloud copy left by an earlier version), RevenueCat (purchase and entitlement status only), TelemetryDeck (opt-in usage signals only), and system sheets for Safari, the App Store review prompt, and Share.
  • •Bundled fonts are used at runtime; there is no font-service fetch on startup.
Check it:Run the app under a network proxy (e.g. Proxyman or Charles) and watch the traffic: every request goes to Apple, RevenueCat or TelemetryDeck hosts. There is no Bloomra-owned API host. The only bloomra.app traffic is a web page that you open yourself from a link, in Safari.
2

Your cycle and journal data is encrypted on your phone

Every period, symptom, mood, and journal entry is encrypted at rest with AES-256 before it touches disk. The encryption key lives in the iOS Keychain, not in the database file, and it never leaves the device.

How it works

  • •Local storage uses Hive, encrypted with a 256-bit AES cipher (HiveAesCipher) applied to every box that holds health data.
  • •The key is generated once with a cryptographically secure random generator and stored in the iOS Keychain via flutter_secure_storage, under `first_unlock_this_device` accessibility — meaning it is bound to this device and requires the device to have been unlocked at least once since boot.
  • •Keychain reads and writes are retried. If the key cannot be stored, the app stops at launch instead of encrypting data with a key it could lose on the next restart.
Check it:Pull the app's container from a jailbroken device or the Simulator and open the Hive box files directly: the bytes are ciphertext, not JSON. The decryption key is not present anywhere in the app bundle, Info.plist, or SharedPreferences/UserDefaults.
3

Analytics: TelemetryDeck, opt-in, and blocked from ever carrying health data

Bloomra uses TelemetryDeck for anonymous product analytics. It is off by default — the user is asked to opt in during onboarding — and the code refuses to send any signal that contains a health-related word, throwing an exception instead.

How it works

  • •The default state is OFF. Analytics only starts after the user answers the analytics question on the last onboarding page.
  • •Every signal name and payload value is checked against a blocklist of health terms (period, cycle, flow, symptom, mood, pain, bleed, fertile, ovulation, discharge, spotting, cramp, luteal, follicular, pms, cervical, menstrual, pregnancy, pregnant) before it is allowed to leave the device. A match throws rather than silently stripping the field.
  • •Two 'funnel' signals fired before onboarding's consent question (that the onboarding flow started, and which page the user reached) are held in memory only, never written to disk, and discarded if the user declines. They are sent only after the user opts in.
  • •TelemetryDeck's own policy on what it collects and retains is published at telemetrydeck.com/privacy.
Check it:In Settings, check that analytics starts OFF on a fresh install. With a network proxy running, decline the analytics prompt and confirm zero TelemetryDeck requests fire; accept it and confirm every payload is a short, generic event name with no free text.
4

Purchases go through RevenueCat and carry no health data

Subscription purchases are processed by Apple's App Store and tracked by RevenueCat for entitlement status. Neither Apple nor RevenueCat ever receives a period date, symptom, mood, or journal entry — the purchase path and the health-data path never touch.

How it works

  • •The purchase code path only calls the RevenueCat SDK (`purchases_flutter`) — configuring the SDK, fetching offerings, and checking entitlement status. It has no reference to any journal, cycle, or symptom model.
  • •RevenueCat's own record for a user is a purchase/entitlement state, not a health record.
Check it:With a network proxy running, complete a test purchase (sandbox or TestFlight) and inspect the request bodies sent to RevenueCat: they contain product identifiers and transaction receipts, not app content.
5

“Delete All Data” verifies what it deletes — and deliberately leaves your subscription status alone

Delete All Data wipes the local database, the iCloud copy, OS-scheduled notifications, app preferences, and the app's per-feature Keychain keys — then reads each one back to confirm it is actually gone, not just reported gone. It deliberately keeps your subscription and free-trial status on the iPhone, so deleting your data can't be used to reset a free trial.

How it works

  • •The wipe runs in a fixed order: disarm background sync first, delete the iCloud copy second, then the local database, notifications, preferences, and Keychain keys — each with its own error handling, so one subsystem failing doesn't stop the rest from being attempted.
  • •Every step is followed by an independent read-back through a different code path than the one that did the deleting: Hive boxes are checked by file size on disk, SharedPreferences via `reload()` against the platform store, iCloud via a fresh file listing, the Keychain via `containsKey`, and notifications via the OS's own pending-request list.
  • •A terminal pass re-checks every surface again after all steps have run, to catch anything a still-running background process wrote back mid-wipe.
  • •As of the September 2026 change, the wipe also clears the RevenueCat offerings price cache, so the next paywall view re-fetches current prices rather than showing stale ones.
  • •Kept on purpose: the local database encryption key (the database it protects is wiped, so the key protects nothing afterwards), the date your Free Season trial started, three app flags that record when the upgrade and win-back prompts were last shown, and everything RevenueCat and Apple hold. The wipe does not call the RevenueCat SDK. Your subscription and trial eligibility are Apple and RevenueCat records, which Delete All Data cannot erase.
Check it:Log a cycle, run Delete All Data, then relaunch: the app should show a fresh-install state with no entries. A subscribed test account should still show as subscribed after the wipe — that persistence is the anti-abuse behavior described above, not a bug.
6

Backups are encrypted with a password-derived key, not a fixed one

An exported backup is only readable with the password you set when you created it. The key is derived from that password with 600,000 rounds of PBKDF2-HMAC-SHA256, the iteration count OWASP recommends for this function, and every backup is integrity-checked with an HMAC before it's decrypted.

How it works

  • •Current backups: AES-256-CBC encryption, key derived via PBKDF2-HMAC-SHA256 at 600,000 iterations, with a random 16-byte salt per backup and an HMAC-SHA256 integrity check computed over the IV and ciphertext.
  • •Backups created before app version 1.1 used 100,000 iterations. The restore path still accepts that older format so existing backups keep working, but every new backup always uses 600,000.
  • •A restore that replaces your current data first takes a snapshot of that data. If the restore fails partway through, the app rolls back to the snapshot, so a bad backup file can't leave the app half-restored.
Check it:Export a backup, open the file: it is an opaque, prefixed base64 blob, not JSON. Try restoring it with the wrong password — it is rejected outright rather than silently importing garbage.

Found something we got wrong?

If any claim on this page doesn't match what you observe in the app — a network request that shouldn't exist, a key that's stored somewhere it shouldn't be, a wipe that leaves something behind — write to hello@bloomra.app. We would rather hear about a gap from you than from an app review rejection or a user.

This page describes the Bloomra iOS app (bundle ID com.newlifefhs.lunaperiod). For the legal privacy policy, see Privacy Policy.

Questions about this policy: hello@bloomra.app