Owner isolation in saas-kit
How saas-kit isolates each user's data via owner-scoped repositories and AsyncLocalStorage context, with lint and integration guards against cross-user access.
saas-kit is a single-user B2C foundation. It uses a shared database with row-level
isolation by user_id and enforces that boundary at four layers.
Layer 1 — authenticated identity
BetterAuth resolves the signed-in user on the server. Owned resource routes never accept a client-supplied owner ID as authority.
Layer 2 — AsyncLocalStorage
@saas-kit/context wraps each request in withRequestContext. Repositories obtain
the caller's userId through requireUserId() instead of trusting request data.
Layer 3 — owner-scoped repositories
Owned repository operations bind reads and writes to the caller's user_id. Mutating
queries use atomic owner predicates so a resource cannot change owners between a
separate check and write.
Layer 4 — ESLint rule
@saas-kit/no-raw-owner-query rejects unsafe direct database access outside reviewed
modules. Sanctioned admin and system operations live in *.cross-user.ts files.
The cross-user integration suite creates two real users and proves that user A cannot read or mutate user B's rows. That executable check is the final defence against ownership regressions the lint rule cannot see.