How `OR` in a Postgres RLS policy leaked every flagged row to every user
A frontend QA pass on a brand-new account opened the library sidebar and saw two notes I had never written. They were public seed entries from a different user. Same UUIDs across every fresh account I tested.
This is a post-mortem of how multiple Pos...
divenrastdus.hashnode.dev7 min read
This catches almost everyone the first time. The mental model "each policy is a check" is wrong — Postgres treats multiple PERMISSIVE policies as OR (any policy granting access = access granted).
For cross-cutting constraints that must always hold, RESTRICTIVE policies are the right tool because they're AND'd with everything else:
CREATE POLICY user_isolation ON journal_entries AS RESTRICTIVE FOR ALL USING (auth.uid() = user_id);That lives alongside your existing permissive policies and short-circuits the OR-leak case. If a public-features schema ships before the feature is launched, the RESTRICTIVE policy still enforces ownership.
The app-layer filter you added is the right belt-and-braces fix. Combining both — explicit filters + RESTRICTIVE policy — gives two failure surfaces that both have to fail before data leaks. RESTRICTIVE in particular is underused because the docs mention it almost as a footnote.