home
Security & data

Enforced at the
database.

Not by what a screen chooses to render. Every table in Repple carries row-level rules keyed to the account asking and, where it matters, to the gym that account belongs to. This page is the mechanism — who can read what, why a coach sees a progress photo only when you share that one photo, and what deletion actually removes. The policy version of the same ground is in Privacy.

The rule we build to

A screen that decides what to show you is a preference. A row your account cannot read is a fact.

Hidden buttons and filtered lists are how most software describes its permissions. They protect nothing: the request underneath them is still allowed, and anything that can make that request — a rebuilt app, a copied key, a curl command — gets the data anyway.

  • The rules live under the data, not over it. A read that a policy forbids returns nothing, whichever app asked and however the request was made.
  • Your gym is the fence, and it is per gym. The test is not “is this caller an owner” but “is this caller the owner of this gym”. Those are different questions and only the second one is safe.
  • One implementation per figure. The web console and the phone app call the same functions over the same rows through the same permissions, so a number cannot read one way at the desk and another way on a phone.
  • Health data is never sold or shared for advertising. It is used to show you your training. There is no other purpose in the system for it to be used for.
Who can read what

Three parties,
and one of them is you.

Your gym and your coach are not the same audience and do not get the same access. A gym needs what it takes to run the place. A coach needs what it takes to program for you. The two lists are genuinely different, and neither of them is everything.

What is recorded, and which of the three parties can read it
What is recordedYouYour coach, if you linked oneYour gym, if you are a member of one
Sessions, sets, cardio Read and write Read only No
Meals and food log Read and write Read only No
Scans, measurements, check-ins, habits Read and write Read only No
Progress photos Read and write No, unless you share one — per photo, withdrawable, at both layers No
Messages with your coach Read and write your own Read and write their own No — an owner has no access to these
Membership, payments, invoices Read your own No The owner, for the gym they own
Door visits Read your own No, not as your coach The owner, and staff working the desk
Class roster Your own bookings The trainer taking that class The owner of that class's gym

“Read only” is literal: a coach's access to your training record is a read permission and not a write one, and it exists only while the coaching link does. End the link and the access ends with it. A trainer who works the desk at your gym sees the door log because that is a staff job, not because they are anybody's coach.

Progress photos

A photograph is not
another number.

Every other thing a coach can read about you is a figure you chose to record for them to coach against. A progress photo is typically taken in underwear, alone, in a bathroom. A coach seeing your body-fat percentage is the product working; a coach seeing that photograph without you choosing it is a different act entirely.

  • The consent step now exists, and it is per photo. Nothing is shared because of who your coach is. You grant one photograph at a time, you can see which ones you have granted, and you can withdraw a grant.
  • Sharing has to be something you do. Not something that is quietly true because of who your coach is.
  • Only you can create the grant. A coach can read a grant and cannot make one, cannot make one for a photo that is not yours, and cannot make one where the coaching link is not live. Those are four separate conditions on the write, not a button that is hidden from them.
  • Ending the link ends the grants. They are not a copy a coach keeps. Stop working with them and the photographs you had shared stop resolving along with the rest of their access.

This page used to say that per-photo sharing would need three things together: a record of the grant, a read rule gated on it, and a matching rule on the file itself, because the record and the image are granted separately. All three are now built, and the rule on the file is written in terms of the rule on the row so the two cannot drift apart. One thing to know before you share: the grant is for the photo’s whole record, so the weight and body-fat figure stored alongside that photograph go with it.

A private bucket

The store your photos go into is not public. Access is keyed to a folder named after your own account, and nothing else can write into it or read out of it.

Links that expire

A photo is fetched through a temporary signed link that you mint by asking for it. There is no permanent address that would keep working if it leaked.

No coach access by default, and a grant you make per photo

A blanket read permission for the linked coach existed and was taken out. Nothing replaced it with another blanket rule: a coach sees a photo only where you have granted that specific photo, and the reasoning against re-adding a standing permission is written into the schema so nobody restores it by copying the pattern from the tables either side of it.

How the fence holds

Six things that stop it
being decorative.

A permission system is only as good as the ways around it. These are the ones that were found by reviewing the whole schema rather than the feature being worked on, and each is closed in a way that keeps closing as the system grows.

Scope

This gym's owner, not an owner

The eight-word difference that matters most. A rule testing only that the caller holds the owner role opens every gym on the platform to any owner. Every rule tests ownership of the specific gym the row belongs to.

Identity

You cannot promote yourself

Row-level rules cannot restrict which columns an update touches, so a rule letting you edit your own profile also let you set your own role and move yourself into somebody else's gym. A trigger refuses both. Joining a gym happens by invitation, and there is no other path.

Grants

Nothing is callable signed out

Postgres grants execute on a new function to everyone by default, and recreating a function to fix something else quietly restores that default. So the grants are re-applied as a sweep over the catalogue rather than a list of names that would go stale the first time somebody adds one.

Assertions

A policy without RLS is inert

A rule written for a table that never had row-level security switched on looks like protection in the migration and is never consulted. Four tables were missing it, two of them carrying rules that had therefore done nothing since the day they were written. The fix asserts that security is on before it writes a rule, and fails loudly if it is not.

Keys

The key inside the app opens nothing

A public key is compiled into every shipped mobile app; that is how they are built, and anyone can extract it. It is not a credential here. Every rule is written against the signed-in account, so holding the key without an account gets you nothing.

Secrets

Server keys are never in code

The privileged key used for background work is read from a secret store at the moment it is needed, not written into a function body where anything able to read the schema could read it too.

The phone in your hand

The other half of this
is a bench in a changing room.

Everything above is about what a request may read. None of it helps if the phone is face up on a bench and somebody picks it up.

Lock

Face ID, Touch ID or a passcode over the app

On iOS it is offered in those words; on Android as a fingerprint, face unlock or a screen lock, because that is what the handset calls them. It asks on a cold start and after a minute in the background — not every time you glance at a notification, which is how a lock gets turned off within a day.

Not a sign-in

It stores no password, because it is not one

This is a lock over a session you are already signed in to, not a second way to sign in. A “log in with Face ID” that keeps your password in the keychain and replays it would create a copy of something we do not hold at all. The device check decides whether the app is shown; nothing about your credentials changes.

Fallback

A face that will not read is not a lockout

The device passcode is deliberately left as a fallback. A gym is dark, you are sweating and your hands are chalked, and being unable to open your own training record because of that is a worse outcome than the one the lock prevents.

Password

Changing it asks for the old one

The platform underneath does not require that: any live session can set a new password, which means an unlocked unattended phone is enough to lock you out of your own account permanently. So the current password is checked first, and it is checked the only way a phone honestly can — by signing in with it.

Sessions

Sign out everywhere else, and stay signed in here

Changing your password offers to end every other session. It keeps this one on purpose: you have just proved you know the password, and dropping you onto a sign-in screen would leave you wondering whether the change went through.

Deletion

A delete button that only
sets a flag is a lie.

Deletion in Repple removes the account itself, and the account is what the rest of the data hangs from — so it goes by construction rather than by a hand-maintained list of statements that would drift the first time somebody added a table.

Counted from the live catalogue, 14 September 2026

What deleting an account reaches

Deleted with you 76 columns 68 tables Kept — unlinked, not anonymous 81 columns 59 tables Reached in total following the chain down 129 tables 0 30 60 90 120 150
The middle bar is the one to read carefully, and its label is the part that used to mislead. Those rows survive — payments taken, door visits and guest passes, which a gym has tax and legal reasons to keep. Invoices and memberships are in that set too, and this page said the opposite until the schema was read again: all three of memberships, gym_invoices and gym_payments point at a person with set null rather than cascade, so the row outlives the account. What goes is the link to it, and only the link. Detached is not anonymous. Several of those tables keep your name in a column of their own, on purpose: the name an invoice was billed to, the name a payment was taken from and the name on a membership are copied onto the row in the moment before the profile goes, because an invoice for an unnamed party does not balance anybody’s books; the name written at the desk stays on a guest pass; and the name that signed a waiver stays on the waiver, because a waiver nobody signed is not evidence of anything. A gym with a retention obligation, and a member deciding whether to press the button, each need both halves of that, so both are stated rather than assumed. These are not estimates: they come from one query over the database’s own foreign keys — every table whose rows go when the account does, then every table reached by following those deletions down. Say which row a figure is counted from, because the two answers differ: the first two bars are counted from your profile row, the third from the sign-in account that the erasure deletes, which reaches the profile and 128 other tables. The figures move whenever a cascading key is added, and they have moved — every number on this chart was re-counted on 14 September 2026 and every one of them had grown — so they are re-measured rather than remembered.
  1. 1

    You ask, in the app or by email

    Settings has the request; support@repplefitness.com reaches a person if you would rather it went that way.

  2. 2

    There is a window, and you can take it back

    Thirty days, and withdrawing is a real operation rather than a sentence on a page — there is a function that clears the request, because a grace period nothing can undo is not a grace period.

  3. 3

    Your gym can see it, and only that

    A gym owner sees the pending requests for their own gym: who asked, when, and how long is left. Not a general window onto member records — just what is needed to act on it.

  4. 4

    The record of the deletion is written first

    Afterwards the profile is gone and there is nothing left to read a name or a gym from. So the audit row goes in before the delete, which is what lets a gym show its own compliance record once the person no longer exists.

  5. 5

    It can only be actioned for someone who asked

    A gym cannot use this to remove a member who has not requested it. That would be a deletion tool wearing a compliance label, and the function refuses it.

  6. 6

    The files are chased separately, and every store that holds a person’s file is chased

    Deleting a database row does not delete an image in object storage — there is no cascade between the two. So the gap is closed in software, and the same way each time: the object’s location is recorded before the row is gone, a delete is sent for it on a schedule, and the entry is marked done only on a reply that actually says the object is no longer there. Nothing is marked deleted because it asked nicely. That now covers seven stores across two queues — progress photographs, injury documents, message attachments, profile photos, and on the coaching side a coach's logo, published documents and exercise clips. One is left out on purpose: a gym's own filing cabinet is keyed to the building rather than to a person, so an individual erasure cannot even enumerate it, and how long a gym must keep a document about a former member is a legal question with a different answer in each country. That decision has not been made, so those files stay and the outstanding ones are listed for the gym that will have to make it. Both apps say all of this at the moment you ask for deletion rather than leaving you to find out.

  7. 7

    The queue is drained on a schedule, and an alarm fires when an entry stops moving

    The drain runs every five minutes and records that it ran; recording that it ran is not recording that it removed anything. So a second scheduled job reads both queues once a day and fails on purpose if anything has been outstanding for more than a day, naming the bucket, the path and the last thing storage said about it. A failed scheduled run is a red row in the log of runs, which is a signal in a place already read — and it is worth being exact about the size of that, because it is a failing job in a log rather than a page or an email to anybody. What it rules out is the failure this had already had once: a drain reporting success every five minutes for three days while one entry, a test row as it turned out, never moved.

  8. 8

    Only one route can remove an acceptance, and it is the coach’s own erasure

    A coach can publish a document their clients accept, and an acceptance is locked to its document so neither can be altered without the other. That lock is a foreign key that refuses deletion, and it is unchanged: no admin screen, no cleanup script and no policy can delete an accepted document. What exists alongside it is a single named exception — when the coach who owns the document is erased, the acceptances are released first and the erasure completes. Until that was built, a coach anybody had ever agreed with could not be deleted at all; the attempt raised a constraint error and rolled the whole thing back, including the record that it had been attempted. The exception costs something, so it is written down rather than smoothed over: erasing a coach takes the record that their clients accepted their document with it. The alternative was keeping a person’s name, file and paperwork indefinitely because their clients ticked a box, which is a refusal to erase wearing retention’s clothes.

Deleting your account sets out the same ground for a member, including what survives and how to ask.

Where it is held

One database,
one file store.

Supabase
Database, sign-in and files. Managed Postgres, which is where the row-level rules on this page live. It may be processed outside your country; where data leaves the UK or EEA it moves under the safeguards those rules require.
TLS
Encrypted in transit, and at rest by the platform. Every connection to the database and the file store is over TLS. Encryption at rest is Supabase’s disk encryption underneath both — it is real, and it is theirs rather than something Repple adds on top. No individual column is separately encrypted by this application, so anyone holding a valid database credential reads plain values; what stands between an account and somebody else’s row is the row-level rules on this page, not a second key.
Stripe
Card details never reach us. Where a gym or trainer takes a payment, Stripe handles the card directly. We do not receive or store card numbers.
Export
Yours to take out. Anyone with an account can export their record from Settings as a JSON file, without asking a person for it — forty-odd tables including the gym-side ones, plus a list of every file held for them, which you then save one at a time. Where a table could not be read, the export says so in place of the rows rather than writing an empty list. A gym can ask us to export everything held for the gym.

The full list of the services that process data on our behalf, and what each one is for, is in Privacy.

Straight about limits

What this page
does not claim.

A security page is a place where confident language is cheap, so here is the boundary of what is being asserted above.

Not claimed

No certification, no audit report

Nothing here is one, and none is cited. The review that produced most of what is on this page was our own reading of the whole schema, not a third party's.

Not claimed

No uptime figure

Repple has not launched publicly, so there is no period long enough to quote an availability number from. A figure without that behind it would be decoration.

Not claimed

No promise of perfection

Several of the protections described above exist because the same review found the problems they fix. That is what a review is for. It is also a reason to say plainly that the count of what has not been found yet is unknown, as it always is.

If you find something wrong, tell us and it gets fixed: support@repplefitness.com. There is no disclosure program to enrol in and no form to fill out first.

Straight answers

Can my gym read my training log?

No. A gym sees the membership and attendance information it needs to run the place — what you are on, what you have paid, when you came in, which classes you booked. Your sessions, sets, meals, scans and photographs are not part of that, and there is no rule anywhere that would let an owner read them.

Can my coach read my messages with a different coach?

No. A message is readable by the two people in that conversation and nobody else — not another coach, and not the gym's owner.

What happens to my coach's access when I end the link?

It ends with it. Their read permission is written against the live coaching link, so it is not a copy of your data that they keep — it is a view that stops resolving.

Could another gym see our records?

Every rule is keyed to the specific gym a row belongs to, and being an owner somewhere is not the same as being the owner of your gym. That distinction is the single most common way a system like this leaks, and it is why every rule tests the second thing rather than the first.

Can coaches see progress photos?

Only behind a control you operate, per photo, that you can withdraw — and that control now exists. Nothing is visible to a coach because of who they are; a photograph reaches them because you granted that photograph, and ending the coaching link ends every grant with it. Anything less would be people uploading in the belief it is private and being wrong, which is the outcome this design exists to prevent.

Does deleting my account delete my gym's records of me?

Partly, and the split matters — and this answer had it backwards until the schema was read again. Your membership, your invoices and the payments a gym recorded do not go with you: those are the gym’s financial record and it has tax and legal reasons to keep them, so the row stays and the link to your account is emptied. Door visits and guest passes stay the same way. Emptying the link is not the same as anonymising the row: on an invoice, a payment, a membership, a guest pass and a signed waiver the name is deliberately kept, because an invoice made out to nobody balances nothing and a waiver nobody signed is evidence of nothing. What goes is everything that was yours — the profile, the training, the body data, the messages. Deleting your account lists both sides, and the chart above counts them.

Do people at Repple look at my data?

Not as a matter of routine. Support work occasionally needs somebody to look at an account to answer the question you asked, and that is the only reason it happens.

Ask us anything here.

Including the awkward questions. A due-diligence email gets a straight answer, and a “we have not built that” where that is the truth.