Isolated by the database
Not by the interface. A bug in the app cannot expose one account to another, because the rules sit a layer below it.
A page about who you know is a sensitive thing to hand someone. Here is exactly how it's protected, in terms specific enough to verify rather than vague enough to sound good.
Not by the interface. A bug in the app cannot expose one account to another, because the rules sit a layer below it.
No API keys to any social network, no OAuth grants, no access tokens. There is no connected account to compromise.
One page, one database, no backend of our own to breach. Fewer moving parts is a security property, not just a simplicity one.
Most apps keep your data separate by writing code that only asks for your rows. That works right up until a bug asks for the wrong ones.
GrowthSeeded enforces separation in the database itself. Every read and write is checked against rules that compare the requesting user's identity to the path being accessed. If they don't match, the database refuses, regardless of what the app asked for.
Your identity is assigned by Firebase at sign-in and can't be forged from browser code. Team memberships are written only by our server, when someone accepts an invite. Anything not listed is denied by default; there is no catch-all rule.
// The rules protecting every account (simplified)
match /accounts/{accountId} {
// The owner, or a teammate the owner invited. No one else.
allow read: if isMemberOf(accountId);
// Plan and billing fields can never be changed from a browser.
allow update: if isMemberOf(accountId)
&& !changedKeys().hasAny(protectedKeys());
match /leads/{leadId} {
allow read, update, delete: if isMemberOf(accountId);
}
}Claims like the one above are easy to make and hard for a user to check. So the app ships with a test that checks it live.
In the Privacy and Security panel, a button attempts a real database read while signed out and reports whether the database refused. It isn't a simulation or a description; it exercises the actual rule against the actual database and shows you the result.
It deliberately lives inside the app rather than on the sign-in screen. A "prove your data is safe" button on a login page plants doubt in someone who arrived feeling fine.
If you view source, you'll find a Firebase configuration including something labelled apiKey. Anyone who looks for it will find it, and that is expected.
Firebase client keys identify a project, they do not authorize anything. They're closer to a postal address than a password. Possessing one lets you address a request to the right project; the security rules then decide whether that request is allowed, and for anyone who isn't signed in as you, the answer is no.
Mentioning this because a thoughtful person checking the source deserves an explanation rather than a scare.
Your plan and lead cap live in your own account document, which raises an obvious question: what stops someone opening dev tools and writing themselves onto the top tier?
Those specific fields are readable but not writable by the browser. They can only be changed by a server-side function that first verifies the cryptographic signature on a payment notification from Paddle. An unsigned or forged request is rejected before it reaches the database.
Authentication is handled entirely by Firebase. Passwords are never seen, transmitted to, or stored by us in any form, hashed or otherwise. Password resets run through Firebase's own flow.
Data is encrypted in transit and at rest by Google Cloud as standard.
Most data loss isn't an attacker. It's a misclick at the end of a long day. So destructive actions are treated as a security concern in their own right:
Listed because a security page that only lists strengths isn't a security page.
If you believe you've found a vulnerability, please report it privately before disclosing it publicly. You'll get a response within two business days and credit if you'd like it. There's no bug bounty budget, so this is an appeal to good faith rather than an offer.
No card. Nothing is ever sent for you.