Security you can actually read.
Most apps give you a one-line privacy promise. This page gives you the whole blueprint — every layer of how Businessily is built across iPhone, iPad, Mac, the web app and Android, where your data physically lives, and the reasoning behind each decision. Including the parts we don't claim yet.
Privacy by architecture Honest about limits
1 · The core idea: privacy by architecture, not policy
A privacy promise can be broken with one bad deploy. A privacy architecture can't hand over data it was never built to hold. Businessily is designed so the sensitive things are physically absent from places they could leak — not merely "policy says we won't look."
Businessily runs as two tiers, and which one you use depends on your devices:
On-device tier — Apple apps
On iPhone, iPad and Mac, your books live in a local database on the device. By default nothing is sent to any server — not ours, not anyone's. Sync is opt-in and runs through your iCloud.
Private-cloud tier — Web & Android
Where there's no on-device option, your data lives in a Postgres database where row-level security locks every row to your account. It's private to you and encrypted in transit and at rest — but, honestly, it is not end-to-end encrypted (more in §15).
The most private design — everything on-device — only works where there's a device to store it. The web runs in a browser with no durable local store, and Android isn't built yet. Rather than weaken the Apple apps to match the web, we keep each tier as private as its platform allows and tell you exactly which one you're using.
2 · Where your data lives, per device
The same app, four platforms, different storage models. Here's the honest matrix.
| iPhone & iPad | Mac | Web app | Android | |
|---|---|---|---|---|
| Status | ◐ Coming soon | ◐ Coming soon | ● Live now | ◐ Coming soon |
| Primary storage | On device | On device | Private cloud | Private cloud |
| Works fully offline | ● Yes | ● Yes | ✕ Needs connection | ◐ Planned |
| Default sync | None — local only | None — local only | Your account | Your account |
| Full SSN / contractor TIN | Encrypted, on device only | Encrypted, on device only | ● Never stored | ● Never stored |
| Payments | Your own Stripe | Your own Stripe | Your own Stripe | Your own Stripe |
| Analytics / ads | ● None | ● None | ● None | ● None |
"Never stored" = there is no database column for the full value (see §9). Tables scroll into cards on small screens.
3 · On-device storage (iPhone, iPad, Mac)
The Apple apps are built in SwiftUI with SwiftData (Apple's local persistence framework) as the single source of truth. Every record — clients, invoices, income, expenses, mileage, contractors, payroll, bookkeeping — is written to a local store on the device.
How it's set up
The store is created with cloudKitDatabase: .none. That one flag is deliberate: it tells SwiftData not to auto-mirror your database to iCloud. Storage stays purely local, and any sync is something we do explicitly and selectively (see §6–7). The app also self-heals — if a store ever fails to open or migrate, it's moved aside and rebuilt so the app never bricks on launch.
Automatic mirroring would copy your entire database — including financials — into iCloud and force every model to fit CloudKit's structural rules. By keeping storage local and syncing only a tiny allowlist by hand, your financial records have no automatic path off the device, and we keep full control of what can ever leave.
4 · Encryption at rest
On the Apple apps, the most sensitive values get a second lock on top of on-device storage, using Apple's CryptoKit.
- Algorithm: AES-GCM — authenticated encryption, so data is both unreadable and tamper-evident.
- Key: a 256-bit key generated on the device the first time it's needed.
- Key storage: the iOS/macOS Keychain, marked
WhenUnlockedThisDeviceOnly— it can't be read while the device is locked, and it never leaves the device or rides along in an iCloud backup. - What's encrypted: full Social Security Numbers and contractor TINs (see §9).
A device-only key means an encrypted SSN can't be decrypted anywhere but the device that created it — even a stolen backup is useless. AES-GCM detects tampering, not just hides data.
5 · Sign-in & app lock
The two tiers authenticate differently, on purpose.
Apple apps — biometric app lock, no account required
There's no login to use the Apple apps. Because your data is on-device, the meaningful question isn't "who are you" but "is this the device owner." An optional toggle enables a Face ID / Touch ID lock (with device-passcode fallback) that gates the whole app when you switch away from it.
An account only exists to authenticate you to a server. There is no server in the on-device tier, so requiring one would add a credential to steal and a database to breach for zero benefit. The lock is convenience security layered on top of the device's own protection — and it's optional, off until you turn it on.
Web app — a real account, scoped by token
The web app uses Supabase Auth. You can sign in by magic link (passwordless, recommended), set a password, or start as an anonymous guest. On first sign-in the app provisions your private workspace and your session token is stamped with the claims that scope every database query to you (see §8).
6 · Sync: CloudKit vs. Supabase
Sync is off by default. When you turn it on, Businessily uses a pluggable design — a single internal "sync provider" interface with interchangeable backends — so the app picks the most private option for your situation without the rest of the app caring how it works.
Local only
The default. Nothing syncs; everything stays on the device.
iCloud (CloudKit)
For all-Apple teams. Syncs through your own iCloud — no Businessily account.
Private cloud (Supabase)
For mixed fleets — Android, web, Windows alongside Apple.
You choose once during setup (a quick "what devices does your team use?" question), and it's stored on your business profile. SwiftData stays the source of truth, so you can switch backends later without losing local data. Conflicts use last-write-wins by timestamp, and deletions propagate via tombstone records so a delete on one device removes the row everywhere.
CloudKit is free, private, and account-free — perfect for solo and all-Apple users, because data rides your iCloud, not our server. But CloudKit is Apple-only, so cross-platform teams need a neutral backend; that's Supabase (Postgres + row-level security). Abstracting both behind one interface means switching is a configuration change, not a rewrite — and the app never hard-codes a single vendor.
7 · Apple sync, in detail (CloudKit)
When an all-Apple team turns on sync, Businessily uses CloudKit inside your own iCloud container. The design has two safety properties worth spelling out.
A strict allowlist — financials can't sync, by construction
Only a short, fixed list of record types is ever allowed to leave the device: employees, clock entries, appointments, and the bookkeeping plumbing that supports a shared team (pending changes, activity log, roles). Everything financial is on a hard blocklist — invoices, income, expenses, clients, contractors, mileage, job costs and more. The block is enforced at the point of writing to the cloud (an attempt throws an error), again when pushing, and again on a worker's device when receiving — three layers of "no."
Allowed to sync
A short, fixed allowlist
- Employees
- Clock entries
- Appointments
- Roles
- Pending changes
- Activity log
Blocked — all financials
No data-mapper exists for these
- Invoices
- Income
- Expenses
- Clients
- Contractors
- Mileage
- Job costs
Beyond the explicit block, financial records simply have no code that knows how to turn them into a cloud record — so there's no path to sync them even by accident. The privacy guarantee is structural: the capability doesn't exist.
Per-worker zones — a worker sees only their own data
The business owner's full dataset sits in a private iCloud zone that is never shared. Each worker you add gets their own separate zone, shared to just that one person. A worker's device can physically only receive their own time punches and their assigned schedule — not the roster, not pay rates, not anyone else's hours.
An iCloud share grants access to an entire zone. By giving each worker a dedicated zone, the boundary is the storage itself — there's no query to get wrong and no field to forget to strip. A worker literally has no zone that contains other people's data.
8 · The private cloud (Supabase + row-level security)
The web app — and, soon, Android — stores data in Supabase (managed PostgreSQL). The whole security model rests on one Postgres feature done rigorously: Row-Level Security (RLS).
How isolation works
- Default-deny on every table. RLS is enabled on all data tables, so the database returns nothing unless a policy explicitly allows a row.
- Your token carries your scope. When you sign in, your session token is stamped (by a server-side hook) with your workspace and role. Every query Postgres runs evaluates that token, row by row.
- Owner vs. worker. Owner policies allow a row only when it belongs to your workspace and you're the owner. A worker can read and write only their own clock entries and their own assigned shifts — and the rules pin their identity so they can't even clock in as someone else. Workers get zero rows from the roster, roles, pay rates and audit log.
- File attachments live in a private storage bucket, namespaced per workspace, reachable only through short-lived signed links (one-hour expiry).
The web app ships its database key in the browser — that's normal and safe, because that key grants no data access on its own. Postgres refuses every row that isn't yours, regardless of what the client asks for. The token is the scope, checked in the database on every single row — the cloud equivalent of CloudKit's per-worker zones. We back this with an automated adversarial test that asserts a worker sees their own one record, zero of everyone else's, and is rejected when trying to act as another person.
Android's local database — SQLCipher-encrypted
The Android app keeps a working copy of your data in a local Room database that is SQLCipher-encrypted with AES-256, using a key held in the Android Keystore. OS-level app backup is disabled, so the database isn't copied off the device by an automatic system backup. Cross-device sync still runs through the same Supabase private-cloud tier described above.
9 · Tax IDs: SSN, TIN & EIN
This is where "architecture beats policy" is most concrete.
Other people's SSNs and TINs — masked everywhere, full value never in the cloud
When you record a contractor's or employee's tax ID, Businessily keeps only the last four digits for display and for 1099/W-2 logic. On the Apple apps, the full nine-digit value is stored AES-GCM-encrypted on the device and is only ever decrypted to auto-fill a tax form — never shown on screen. In the cloud, there is no column for the full value at all.
A promise fails open: one careless insert, one mis-scoped query, one bug, and the secret is there to leak. An absent column fails closed: there is physically nothing to read, write, breach or subpoena. The database can't hand over a full SSN it was never built to hold.
The honest exception: your own EIN
Your own business's Employer Identification Number is stored in full. An EIN is a public-facing payer ID — it prints on the invoices and 1099s you issue and on the W-9s you hand to clients — so it isn't a secret in the way someone else's SSN is. We'd rather call this out plainly than let "we never store tax IDs" quietly overstate the truth: we never store a full SSN or a contractor's full TIN — only the masked last-4. Your own EIN is stored so your forms can print correctly.
10 · Bank data & statement import (no Plaid)
Businessily does not connect to your bank and uses no login aggregator like Plaid. Instead, you export a statement from your bank and import the file — and the work happens on your side.
- The file (CSV, OFX or QFX) is read and parsed in your browser / on your device — it isn't uploaded anywhere to be processed.
- Transactions are categorized on-device with a keyword model, income vs. expense detected from the sign of each amount.
- Likely duplicates are flagged by matching amount and date against what you've already recorded.
- Everything lands in a review queue you can edit. Only the rows you approve are ever saved — the raw statement never leaves your device until you say so.
Aggregators ask you to hand over your banking credentials to a third party and grant standing read access to your accounts. Importing a file you exported yourself means Businessily never sees your bank login and never holds a live connection to your money. It's a little more manual, and far more private.
11 · Payments (Stripe Connect)
Accepting card payments is optional and off by default. When you enable it, Businessily uses Stripe Connect to link your own Stripe account.
- Customers pay into your Stripe balance directly. Money never flows through a Businessily-owned account — we hold no funds.
- Card details are entered on Stripe-hosted checkout pages — card data never touches Businessily.
- Your Stripe secret keys are never in the app or the browser. The app only ever holds your connected-account ID and a "connected" flag; all Stripe API calls happen server-side (see §12).
Routing payments straight to your own Stripe means Businessily isn't a money transmitter, never custodies your funds, and never stores your card data or Stripe credentials. You keep the relationship with Stripe; we just generate the link.
12 · Secrets & the server surface
The web app is a static site that talks directly to your database. There's almost no "backend" — and what little server code exists is exactly the code that must hold a secret.
- In the browser: only a public publishable database key. It identifies the project and grants no data access on its own — RLS does the gating. We verified the shipped bundle contains no secret keys.
- On the server (edge functions): the handful of operations that need a secret — starting a Stripe Connect or Google Calendar OAuth flow, creating a hosted checkout, syncing calendars, accepting public booking/W-9 submissions. The Stripe secret key, the Google client secret and webhook secrets live in the server-side secret store, never shipped to clients.
- Everything else — all your CRUD, statement parsing, categorization, PDF generation, reports and payroll math — runs in your browser.
Anything reachable in client code is public by definition, so secrets can only live server-side — and the smaller the server, the smaller the attack surface and the less there is to patch, scale or breach. Server code exists only where a long-lived third-party credential makes it unavoidable.
13 · No tracking, no ads, no data sale
There is no analytics SDK, no ad network, no crash-tracking service, no third-party trackers anywhere in Businessily — on any platform. We checked the codebase for the usual suspects; the only matches are the lines in our own UI promising their absence.
- The Apple apps link only Apple's own frameworks plus the official Supabase client (used solely against your own workspace).
- On-device intelligence — receipt OCR, voice logging, categorization — uses Apple's Vision, Speech and on-device models, so even those never call out to a server.
- On-device Apple Intelligence (iOS / macOS 26). The optional AI features — "Ask your books", drafting text, smart categorization — run on Apple's on-device Foundation Models, entirely on the device. Nothing is sent to a cloud model to perform the AI, which is a real privacy difference from assistants that ship your data to a server. The web and Android apps don't include on-device AI rather than route your books to an outside model.
- The in-app "Your data" panel is wired to the same blocklist the sync engine enforces, with a build-time check that warns us if the marketing claim ever drifts from the code.
The product is free because it's cheap to run, not because you're the product. There's no ad or data business to feed, so there's no reason to collect anything — and a lot of reasons not to.
14 · App-level hardening
Beyond where your data lives, the apps add a layer of practical, device-side defenses. These are live today unless noted otherwise.
- Biometric app lock. An optional lock gates the whole app behind Face ID, Touch ID, an Android fingerprint, or the device passcode when you switch away and come back.
- Jailbreak / root detection. On iOS and Android, the app checks for common signs that the device has been jailbroken or rooted and warns you if it finds them. It's a heads-up, not a wall — it informs rather than blocks.
- Encrypted local backups. The "back up everything" export is encrypted with a key derived from your passphrase using PBKDF2 (600,000 iterations) and AES-GCM, so a backup file is useless without your passphrase.
- hCaptcha bot protection. Sign-up and sign-in are protected by hCaptcha to keep automated abuse off the auth surface.
- Security headers & CSP. The web app ships a Content-Security-Policy and a standard set of hardening response headers to reduce injection and clickjacking risk.
Face ID / Touch ID / fingerprint, or device passcode
Warns you if the device looks compromised
PBKDF2 (600,000 iterations) + AES-GCM, keyed by your passphrase
Keeps automated abuse off sign-up and sign-in
Reduces injection and clickjacking risk on the web
App Attest & Play Integrity — a scaffold, not yet active
15 · What we don't claim yet
A security page that only lists strengths is marketing. Here's the candid version — the things we want you to know before you trust us.
It's private to your account and encrypted in transit and at rest, but the service can process your records to sync them. For zero-knowledge, on-device storage, use the Apple apps.
We're early and bootstrapped. We don't have formal third-party security audits or certifications. If your business requires one today, an incumbent may suit you better — and that's okay.
On the Apple apps we field-encrypt full SSNs/TINs with a device-only Keychain key (not the Secure Enclave). Other records rely on on-device-only storage plus OS protection — strong, but not the same as encrypting every field.
It's a public payer ID that has to print on your forms — but if "no tax IDs are stored" is what you assumed, now you know the precise truth (see §9).
We'd rather you trust us because the architecture holds up to reading — not because we hid the edges. Questions about any of this? Email a real person.
16 · Reporting a vulnerability (responsible disclosure)
Found a security issue? We want to hear from you — and we'll work with you to fix it, not come after you.
How to report
Email honorius@neogy.dev with the affected surface (web, iOS, iPadOS, macOS or Android), a clear description, and reproduction steps. PGP available on request. Please give us a reasonable window to remediate before any public disclosure.
Safe harbor
If you make a good-faith effort to follow this policy during your research, we consider your testing authorized: we won't pursue or support legal action against you, and we'll work with you to understand and resolve the issue quickly. This does not authorize actions that harm users or access data that isn't yours.
Scope & ground rules
- In scope:
businessily.com,businessily.app, and the Businessily native apps. - Out of scope: third-party services (Supabase, Cloudflare, Stripe, hCaptcha) — report those to the respective vendor; denial-of-service / volumetric testing; social engineering; physical attacks.
- Test only against your own account and data — never access, modify or exfiltrate anyone else's. Stop and report as soon as you've confirmed an issue.
What to expect
We aim to acknowledge reports within a few business days and to keep you posted through remediation. We don't run a paid bug-bounty program yet, but we're genuinely grateful for responsible disclosure and happy to credit you.
Private by design — and free.
Start in your browser today, or read the rest of the story.