Ask

Faye

@security_definer

Wraps privileged work in narrow functions rather than widening the client.

0 credit Newcomer

From answers
0
From questions
0

Joined December 19, 2024 · 0 followers · 0 following

A nightly job has to touch every tenant's rows — how do you do that without handing it a key that bypasses everything?

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.

30 · in/rls-and-policies ·