One tenant's rows are fenced at the database
Every record belongs to exactly one studio, and the database itself enforces it: row-level security is FORCED on the tenant table, so a query that forgot to filter returns nothing rather than somebody else's data. The application cannot opt out of it, and neither can a mistake in the application.
Membership authorises, never the address
Knowing a studio's URL grants nothing. Access is resolved once per person, and holding no role means holding nothing — there is no fallback that quietly allows more. Nobody can grant a right they do not hold themselves, checked both where roles are assigned and where join requests are approved.
Passwords are hashed at cost 12, and re-hashed on the way in
bcrypt at a work factor of 12. When someone signs in with a password stored at an older, weaker factor, it is re-hashed on that login — so raising the factor protects the accounts that already exist rather than only the ones created afterwards.
Session tokens are stored as digests
What is kept is a SHA-256 digest of the session token, not the token. Anyone reading the store finds values that cannot be replayed as a session, and a single session can be signed out by its digest without touching the others.
Repeated credential guesses are slowed, then stopped
Failed sign-ins are counted three ways — by source, by account, and by the pair — and a source that keeps failing is locked out for progressively longer. Three counters rather than one on purpose: a single per-account limit would hand anybody a way to lock a named person out of their own account by typing their address wrong.
The console requires a second factor
The administrative console that can reach across studios is behind multi-factor authentication, separately from the product's own sign-in.
Every change is recorded, from one place
Each mutation records who made it, which kind of identity they were, which studio, what they did, which record, and what the system told them. It is written by the single wrapper every mutating request already passes through — so it is one place that can be got right rather than ninety-seven places somebody could forget.
Files are served only after the same check the screens use
Uploaded files are never handed to a browser by a public storage link. The request is checked for membership first, and the bytes are then streamed by the application — so the access decision stays in code rather than being delegated to a store that cannot express who may read what.
Browser-level protections are set at the edge
HSTS with a two-year max-age, framing refused outright, MIME sniffing disabled, and geolocation, camera, microphone, payment and USB switched off by policy. The site loads no third-party JavaScript at all — no tag manager, no chat widget, no analytics script — so there is no third party to trust with your visitors.