security
the boundary lives in the database.
A coaching platform holds body readings, diets, and training histories: some of the most personal data a person shares. Trainbase isolates it with Postgres row-level security as the single enforcement point, never an app-code filter, and then proves the boundary holds by trying to break it.
one boundary, deny by default
Row-level security is enabled and forced on every table, including against the table owner. A table with no matching policy returns zero rows. Missing evidence of permission is a hard rejection, never a default-allow.
Application reads always run under the signed-in user's own session key, against which the policies are the real boundary. The administrative key that bypasses them is never used to serve application data.
who sees what
Three roles, three scopes, written once as database policy. Every food, exercise, and template also carries an owner: company items are shared across the install, trainer items are private to the coach who created them.
| a client | a coach | the company admin | |
|---|---|---|---|
| body readings, diet, training, notes | their own rows only | assigned clients only | every client in the company |
| another coach's clients | never | never | within the company |
| another coach's private foods, exercises, templates | never | never | within the company |
| writes to program and goal structure | none; logging and goal acceptance only | for assigned clients | within the company |
proven by exclusion, not by demo
A happy-path demo cannot catch a leak: if everything on screen belongs to you, nothing was ever excluded. So our proofs seed real foreign data, sign in as the user who must not see it, and assert it is unreachable. One example, from the library-ownership proof: a second coach is created with a private food and exercise, then the first coach's session runs these probes.
| probe, as the excluded coach | required result |
|---|---|
| search for the foreign private food by name | 0 rows |
| fetch the foreign food and exercise by exact id | 0 rows |
| attempt to edit the foreign items | 0 rows affected |
| attempt to delete the foreign items | 0 rows affected |
| fetch a known company-owned food (positive control) | 1 row, visible |
| foreign rows re-checked after the attempts | intact, untouched |
The same discipline covers the client and coach lines: further proofs seed a coach assigned to no clients and a foreign client, then assert that training logs, strength records, diets, and check-in histories return zero rows across every boundary they must not cross, with assigned-coach and own-data positive controls. New surfaces that read a new table get a new proof before launch.
the details that usually leak
Most isolation failures are not in the obvious reads. These are the quieter boundaries Trainbase closes deliberately.
reads never grant writes
A client reads their whole assigned program tree but writes none of its structure. Their write surface is logging sets, reporting compliance, and accepting a proposed goal. Write policies are separate from read policies on purpose.
references are checked, not just rows
A foreign-key constraint proves a row exists, not that you may see it. When a save accepts an id, the id is checked for visibility under the caller's own scope before it is stored, so a hidden row can never be smuggled into someone else's data.
goal acceptance is one narrow door
A client's only write on goals is a single database function that can flip only their own proposed goal to active. It cannot touch values, dates, or anyone else's rows, because no code path exists that would let it.
absence looks like absence
Fetching a row you may not see returns zero rows, exactly as if it did not exist. A guessed id confirms nothing, which means enumeration attempts learn nothing.
what we do not claim
Trainbase does not hold a SOC 2 report, an ISO certification, or a third-party penetration test. We would rather tell you that plainly than imply otherwise with a wall of badges. What we can show today is the thing certifications are meant to approximate: the boundary itself, written as reviewable policy in versioned migrations, and the proofs that attack it. If your practice needs a formal attestation, tell us; demand is what will schedule one.
Found something we missed? Write to hello@trainbase.io and it goes straight to the people who wrote the policies.
the honest answers
Can another coach see my clients or my private library?
No. A coach reads only the clients assigned to them and only their own trainer-owned foods, exercises, and templates. The database policy filters the rows out before they leave Postgres, so to another coach your private data is indistinguishable from data that does not exist.
What can my clients see?
Exactly their own profile: their body readings, their diet, their training history, and the notes their coach left them. A client who guesses at another client's id gets zero rows, not an error page that confirms the id exists.
Is the isolation enforced in application code?
No, and that is the point. Application-level filters are a promise every new screen has to remember to keep. Trainbase puts the boundary in the database itself, so every query from every surface passes through the same policies. A screen we ship tomorrow inherits the same isolation as the ones we shipped first.
How do you know the boundary actually holds?
We prove it by exclusion. Scripts in the repository seed real foreign data, sign in as the user who must not see it, and assert zero rows across search, direct lookup, edit, and delete, with a positive control to prove the test itself works. The proofs tear their seed data down to zero residue.
Where does the data live?
In a Supabase-hosted Postgres database in Sydney, Australia, reached over encrypted connections. The schema and every security policy are versioned migration files, so the entire boundary has a reviewable history.
isolation you can interrogate.
The privacy skeleton describes what we store and who can see it, and the platform pages show what all that protected data does.