Skip to content
Robnu
Live

The database refuses cross-tenant reads. Always.

Tenancy is enforced primarily at the application layer (every Prisma query goes through withSellerScope). Postgres Row-Level Security on seller_id is defense in depth — even if a code path forgets to filter, the database refuses cross-tenant rows.

Free during early access · Forever free under 25 orders/day
app.robnu.com/platform/rls-defenseTenant isolation · defense in depthapp layer + Postgres RLS on seller_idSeller ASeller BSeller CRLS · app.current_seller_id = $Aenforcedcross-tenant blocked
TL;DR
  • Application layer: withSellerScope wraps every Prisma transaction; sets SET LOCAL ROLE robnu_tenant + app.current_seller_id.
  • Database layer: Postgres RLS policies on every seller-scoped table filter by seller_id.
  • Helper functions: app_current_seller_id() returns the current request's seller; app_is_admin_scope() controls admin-bypass.

What you get.

withSellerScope is the only path

There is no alternative way to read seller data. A new code path that bypasses withSellerScope hits the database under the wrong role and gets zero rows back.

Defense, not redundancy

Even if the application layer fails, the database is the gate. Schema-per-seller would have been impossibly complex at 5K sellers; RLS gets the same isolation guarantee with one connection pool.

Admin bypass is explicit

SuperAdmin reads use a separate role with explicit ImpersonationSession scoping. Every admin read is logged with the session ID, not just the user.

What's enforced

Tables under RLS.

  • Order, OrderItem — all order-scoped reads filtered.
  • Shipment, ShipmentItem, ShipmentEvent — same.
  • OrderReturn, OrderReturnItem, OrderReturnEvent, ReturnAwb — same.
  • MarketplacePayout, PayoutLineItem — same.
  • ReconciliationBatch, ReconciliationItem, Adjustment — same.
  • return_claims, return_claim_events, return_claim_evidences, return_scan_events — same.
  • ai_conversations, ai_messages, ai_tool_calls, ai_predictions, ai_document_extractions, ai_usage_ledger — same.
  • Catalog tables (CatalogProduct, CatalogListing, ListingAlias, Warehouse, Inventory*) — same.
FAQ

Practical answers.

Negligible when policies are simple equality filters and seller_id is well-indexed. Robnu seeds seller_id indexes alongside the primary key on every seller-scoped table.

Yes — they're SQL in the prisma migration directory. The full ADR is core/decisions/2026-04-26-postgres-rls-defense-in-depth.md in robnu-docs.

5K migrations and 5K connection pools at 5K sellers. Operationally infeasible. RLS gets the same isolation guarantee with one pool.

Try it inside your own dashboard.

Free during early access. No card. Forever free under 25 orders/day.

build 547000c1ac5d3ea9cb039864711ed788f9948b69 · 2026-06-12T02:03:58+05:30