Synchrotron is designed for honest clients and relies on PostgreSQL Row Level Security (RLS) for data access control. The server is authoritative for the base tables, but it never runs application action code.
Every server request that reads/writes synced tables must:
- use a DB role that does not bypass RLS, and
- set the request principal in the transaction.
Synchrotron sets both:
synchrotron.user_id(library convention)request.jwt.claim.sub(Supabaseauth.uid()compatibility)
select set_config('synchrotron.user_id', '<user_id>', true);
select set_config('request.jwt.claim.sub', '<user_id>', true);RLS policies (both app tables and sync tables) can reference either:
current_setting('synchrotron.user_id', true)
current_setting('request.jwt.claim.sub', true)The demo RPC server requires:
Authorization: Bearer <jwt>(server verifies and derivesuser_idfromsub, Supabase-compatible defaults).
If you use the built-in HS256 verifier, configure:
SYNC_JWT_SECRET(orGOTRUE_JWT_SECRET)- optional:
SYNC_JWT_AUD/GOTRUE_JWT_AUD,SYNC_JWT_ISSUER,SYNC_JWT_USER_ID_CLAIM,SYNC_JWT_ALGORITHMS
If you use a JWKS (RS256) verifier, configure:
SYNC_JWT_JWKS_URL- optional:
SYNC_JWT_AUD/GOTRUE_JWT_AUD,SYNC_JWT_ISSUER,SYNC_JWT_USER_ID_CLAIM,SYNC_JWT_ALGORITHMS
Apps can replace the server auth service if they need a different mechanism (cookies, opaque tokens, JWKS, etc).
Recommended pattern (works for owner-only and shared rows): scope sync-log visibility by
action_modified_rows.audience_key membership (see docs/shared-rows.md). Owner-only apps can
model this as a user:<user_id> audience.
- Patch apply must run under the originating principal (
action_records.user_id), not the request principal. Synchrotron does this by settingsynchrotron.user_id(andrequest.jwt.claim.sub) per action/AMR during replay. - Sync-table RLS is for client visibility. The server materializer must be able to read the full canonical sync log to do rollback+replay, even if the current request user can’t see those rows (e.g. after membership revocation).
- Recommended escape hatch for sync-table
SELECTpolicies:current_setting('synchrotron.internal_materializer', true) = 'true'
- The server sets
synchrotron.internal_materializer=trueonly in server code, and only transaction-locally (set_config(..., true)). - Guardrail (recommended): additionally require the server DB role, so this bypass can’t activate for other roles:
current_user = 'synchrotron_app'- Example:
using ( (current_setting('synchrotron.internal_materializer', true) = 'true' and current_user = 'synchrotron_app') or <normal client visibility predicate> )
- Recommended escape hatch for sync-table
- This bypass is only for reading the sync log tables. It must not bypass base-table RLS; base-table RLS remains the enforcement boundary during patch apply.
- If your base-table RLS depends on membership/ACL tables, those tables must be replayable as part of canonical history (don’t mutate them out-of-band if you want late-arrival correctness across membership churn).
action_records.args are replicated verbatim to any user who can read that action_records row (no field-level redaction). Keep args non-sensitive; store secrets in normal RLS-protected tables and pass ids/opaque references in args.
In shared-row setups, action_records visibility is commonly derived from visible action_modified_rows rows. If an action touches multiple audiences, anyone who can see any touched audience can see the action record + args. Keep args safe for that union (or avoid multi-audience actions).