May 6, 2026 · 1 min read

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.