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.

1enforcement pointthe database, not scattered app filters
denythe default postureno policy match means zero rows
everytable, rls forcedapplies even to the table owner
0app-code isolation filtersscreens inherit the boundary, they do not implement it

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 clienta coachthe company admin
body readings, diet, training, notestheir own rows onlyassigned clients onlyevery client in the company
another coach's clientsneverneverwithin the company
another coach's private foods, exercises, templatesneverneverwithin the company
writes to program and goal structurenone; logging and goal acceptance onlyfor assigned clientswithin 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.

the ownership exclusion proof
probe, as the excluded coachrequired result
search for the foreign private food by name0 rows
fetch the foreign food and exercise by exact id0 rows
attempt to edit the foreign items0 rows affected
attempt to delete the foreign items0 rows affected
fetch a known company-owned food (positive control)1 row, visible
foreign rows re-checked after the attemptsintact, untouched
From web/scripts/prove-ownership-exclusion.ts, run against the live database through the same key the app uses. The company-owned control row proves the probes themselves work; a boundary that returned nothing for everyone would falsely pass without it. The seeded coach and rows are torn down to zero residue.

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.