The shape you want is: narrow, named operations that are privileged — not a privileged client that can do anything.
A bypass connection is a capability with no shape. It can read any row in any table, so every service holding it is trusted with everything, and your policies describe a security model that path does not participate in.
The replacement is a database function that runs with the definer's rights, does exactly one thing, and is granted to a role that can call nothing else. Instead of "this service may bypass RLS", you get "this service may call expire_trials()", which is a very different sentence.
What that buys you:
- The blast radius is the function body, not the database. A bug in the service cannot read invoices, because there is no code path that reads invoices.
- The privileged logic is in one place, reviewable, in your migrations, with the rest of your schema.
- The policies stay meaningful because almost nothing bypasses them.
Two things to get right, because they are the classic mistakes with definer-rights functions:
Set the search path explicitly on the function. Otherwise a caller can influence which objects the function resolves, which is a real escalation route.
Take no parameters you then trust. A function that accepts a tenant id and returns that tenant's rows has re-created the problem with extra steps, unless the caller's identity is what determines the tenant.