The migration review habit that stops most Supabase drift
Ask when a project's protection regressed and the answer is rarely a dramatic event. It is a Tuesday migration that created a table and meant to circle back to policies; a rename that unhinged a predicate; a rebuild that dropped policies with the old table. Drift enters through ordinary work done quickly.
This page proposes the smallest discipline with the largest yield: a pairing rule enforced at review time, a four-line reviewer checklist, and honest acknowledgement that review alone cannot catch everything — which is what scheduled scanning is for. The discipline costs minutes per migration; the page keeps to what is checkable.
RowShield does not detect this yet. This guide gives you the catalog queries to check it yourself. The nine rules that do ship are listed on the rules index.
The pairing rule
One rule does most of the work: a migration that creates or replaces a table must, in the same migration, enable and force row-level security on it and define its initial policies. Table without policies does not merge — not in a follow-up ticket, not next sprint, because follow-up tickets are where protection goes to expire.
The pairing extends naturally. Every INSERT or ALL policy carries WITH CHECK, not merely USING, or writes escape the constraint the read side enforces. Policies reference real identity columns rather than constant truths, and FORCE ROW LEVEL SECURITY appears wherever the owner is not a superuser — which, on managed Postgres, is effectively everywhere.
A short reviewer checklist
Checklists work when they fit in a glance, so this one has four lines. Does the diff create or alter any table, and if so does the same migration carry the full pairing? Does any touched policy still bind to a column that will exist after this migration runs?
Third, does any statement grant more than the ticket describes — a widened USING, a new role added to a policy, a RESTRICTIVE modifier flipped to permissive? Fourth, does the migration rebuild or recreate anything, since recreation drops policies silently along with the old object? Four questions, asked every time, intercept the majority of regressions this page's opening described.
Why regressions still happen
Review governs proposed changes, and reality admits others. Migrations interact: next month's rename can break this month's predicate in a file nobody is currently reading. Restores rewind schemas past fixes. Console hotfixes skip the pull request entirely, precisely because urgency is why they exist.
And reviewers tire. The checklist catches what attention catches; week forty of a project is not week four. None of this indicts anyone — it defines the boundary of what human review structurally delivers, which is a strong filter on declared change and silence about everything else.
The safety net underneath
Underneath the discipline sits detection. Scheduled scans read the live catalog, evaluate the nine rules — including disabled, absent and tautological policies, missing WITH CHECK, and public buckets — and diff against the previous run, so a policy that vanished surfaces as a drift event and a reopened hole as a regression, with alerts routed by per-destination severity thresholds.
The division of labour is then honest: review prevents what it sees, scanning detects what slipped past, and neither pretends to be the other. Run the free audit at rowshield.dev/audit to see what the safety net observes about your project today — a URL is the entire input.
Frequently asked
- What is the single highest-value migration review rule?
- Pairing: any migration creating or replacing a table must enable and force row-level security and define its policies in the same change. Deferring protection to a follow-up is the most common path a leak enters by.
- Does good review discipline remove the need for monitoring?
- No — review filters proposed changes, while restores, console hotfixes, cross-migration interactions and fatigue all operate outside it. Scheduled scans compare live state over time and catch what arrived by any other path.
- Why insist on WITH CHECK on insert policies?
- Because USING alone constrains which rows a statement may see, not which rows it may create. Without WITH CHECK, an authenticated caller can insert rows owned by anyone — the write half of the isolation guarantee silently absent.
Check your project in about ten seconds
Paste a URL. No signup, no writes, nothing stored.
Run the free audit