“RLS” and “multi-tenant” are found side by side in almost all of the content already published on the subject – a legitimate choice for many SaaS architectures, but which is not Aurabase’s choice to separate its own clients from each other. This post explains the difference with the real provisioning engine and the RLS policies actually applied, not a simplified marketing description. For an overview of what Aurabase's Managed Postgres engine covers beyond isolation, see the databasedocumentation.
The essentials
- Between projects, Aurabase isolates by dedicated Postgres base, never by RLS alone — each project has its own physical base, on a dedicated CNPG cluster (company level) or on the CNPG cluster of its own organization (free/pro/team), never shared with another organization.
- The RLS (
auth.uid(),auth.role(),auth.jwt()) remains active and recommended inside your base, to isolate your own users — same convention as Supabase. service_roleand project-based administration roles bypass RLS by design (BYPASSRLS): an assumed architectural choice for server operations, not a flaw.- A regression already corrected in this repository -
PUBLICrights granted in error on old shared schemas - concretely illustrates why a border at the base level resists better than a purely application border.
The shortcut that most multi-tenant RLS guides take
The most documented pattern for multi-tenant Postgres consists of three lines: a single base, a tenant_id column on each table, an RLS policy which compares this column to a value extracted from the JWT. It's economical - a pool of connections, a diagram, a single instance to run - and it works well when the tenants are numerous, small, and of low individual stake.
The compromise is real: the boundary between two clients becomes a SQL expression, evaluated table by table. A policy forgotten on a new table, a connection running with a superuser role, a debug script launched live — each of these incidents, however trivial in operation, can silently expose the lines of all the tenants at the same time. The security boundary and the technical boundary (the basis) are then exactly the same thing.
This isn't a bad choice in itself — it's the right compromise for many products. The point of this post is elsewhere: this is not the compromise that Aurabase has made to separate its own clients (entire projects, potentially with different compliance requirements) from each other.
Since a recent consolidation of the provisioner (marked in the code as "Task 12"), an active Postgres project at Aurabase falls under exactly two architectures - the old models with a base actually shared between several projects have been removed from the provisioning path.
The project level decides which of the two applies — and it is the code that decides, not a box checked in a dashboard:
| Dimensions | FullyDedicated (company) | SharedClusterDedicated (free/pro/team) |
|---|---|---|
| CNPG cluster | Dedicated to this one project | Shared, but never between two organizations |
| Postgres database | app, only project on it | project_<uuid>, one per project on the cluster |
| PostgreSQL Login | A single project on the cluster: no risk of cross-membership | Login per-project (F-013), member of its only roles tenant_<uuid> |
On both architectures, the base or cluster never hosts two different organizations — so the question is not “is your data isolated” but “does your project have compute CloudNativePG all to itself, or does it share it with other projects in the same organization.”
This consolidation of two architectures is recent: the code previously carried two additional paths – a “shared master” where several projects coexisted in the same database, isolated only by diagram, and a postgrest_dedicated_shared_dbvariant. A dedicated migration removed them and tightened the constraint of the projects table to only the two remaining values, precisely because the shared schema model was the source of the bug described below.
A separate Postgres database is a connection-level boundary, not a row-level boundary. An application role connected to the database of project A simply cannot query the tables of project B — it does not have a session open to it. This property holds even if an RLS policy is poorly written, missing from a table, or bypassed by a high role: the worst case remains confined within a single database.
This deposit also bears the trace of a real bug which illustrates the opposite risk. Under the old shared schema model (since withdrawn), provision_postgres_schema mistakenly granted GRANT ALL ... TO PUBLIC rights to each project schema - PUBLIC applying to all roles in the database without membership conditions, a login isolated per project could read and write in the schema of another. A corrective migration (066) removed these rights from the existing one.
The patch didn't add one more RLS policy to plug the leak — it removed the very possibility of two projects sharing a database. On the two current architectures, a comment from provisioning.rs documents it in black and white: “each project already has its own physical Postgres database”. A base-level boundary makes an entire class of such bugs simply unreachable, rather than relying on every policy always being written correctly.
A patch from August 23, 2026 goes in the same direction: an REVOKE ALL ON SCHEMA public placed unconditionally by the provisioner was made conditional on the topology, because it only provided real isolation on the old shared database model — on the two current architectures, it blocked without benefit the import of SQL dumps which explicitly reference public.<table>.
EPIRB stays there — in your database, for your users
None of the above makes EPIRB useless — it just changes floors. Once in your project database, Aurabase exposes exactly the PostgREST convention taken up by Supabase: three SQL functions which read the JWT claims set by the gateway in request.jwt.claims.
These helpers are used in the actual policies of Aurabase itself — not just documented for yours. Here is the policy that protects storage_objects, as it is placed in the repository (reformatted on several lines for reading):
In your own project_<uuid>schema, the one that carries your application tables, Aurabase deliberately does not place any policies on your behalf — the code documents it as a “Supabase model”: the RLS of your tables remains your responsibility, with the same functions, the same syntax.
service_role bypasses RLS — by design, not accident
Postgres natively offers a role attribute, BYPASSRLS, which ignores all policies. Aurabase voluntarily uses it on two families of roles: aura_service_role (the server role, never exposed on the browser side) and the administration role specific to each project, used during DDL operations like ALTER SCHEMA ... OWNER TO.
The role that serves your requests anon/authenticated — tenant_<uuid> — does not have any BYPASSRLS: the RLS applies to it normally, without exception. As a bonus, the system diagrams specific to each project (_auth, _storage, _platform) receive an activated RLS without policy — therefore a total refusal by default for any non-bypass role, a defense in depth in case an application path accesses it one day by mistake.
Bypassing the RLS with an elevated server role is not unique to Aurabase — it is the same construct as service_role on the Supabase side. The point is not to avoid BYPASSRLS, it is to never grant it to a role accessible from a client, and to confine it to a single project.
This server role is part of a broader posture — predefined roles, custom RBAC, audit logs — detailed on the Security & RBACpage.
On the SharedClusterDedicatedlevel, several projects from the same organization coexist on a single CNPG cluster. The physical database already separates the projects from each other, but the PostgreSQL roles — they — are global objects to the cluster, not to the database. Aurabase therefore adds a layer: a separate PostgreSQL login per project.
Each project connects with its own login, member only of its own tenant_<uuid> / tenant_<uuid>_admin roles — never those of another project in the same cluster. The database already isolates the data; This login per project also isolates the identity that connects to it, so that an incident on a project does not give its login any membership to inherit to another.
RLS alone or dedicated base: how to decide for your own SaaS
The choice of Aurabase is not a universal rule — it is a compromise for a specific case: isolating clients from each other, potentially with different compliance requirements, on a platform that they do not control. If you are building your own SaaS, the same question arises for you, on a different scale.
- RLS with
tenant_idin a shared base — relevant when your tenants are numerous, individually of low stake, and the cost of a base per tenant would be disproportionate. Test each policy withpg_prove, on each table, without exception. - Dedicated base or diagram — relevant whenever a tenant has its own compliance issue (health, HR, public sector), a volume that justifies performance isolation, or the cost of a leak between two specific clients would be disproportionate to the cost of additional infrastructure.
The Aurabase pricing level applies this same arbitration to its own clients: shared base by organization by default, dedicated cluster when the challenge of the project justifies it. For RLS patterns inside your own database — ownership, multi-tenant by organization, role hierarchy — the RLS guide in production details the three cases with pgTAPtests. And if the auto-generated API on your diagram interests you beyond REST, the comparison on pg_graphql versus Hasura and PostGraphile covers the other half of the Postgres surface exposed by Aurabase.