v3.5.3 7 February 2026 Fix

RLS Coverage Gap Fix

Fixed

  • bug_reports table — RLS FORCE was set but ENABLE was not, meaning policies existed but were not being enforced. The table owner (accounting role) could bypass the four tenant isolation policies. ENABLE now applied — all operations enforced through RLS (bug_qfhk)
  • user_organisations table removed from RLS scope — Was incorrectly included in the v3.5.0 batch migration. This is a global lookup table used by tenant middleware to establish org context, so it fundamentally cannot require org context to read. RLS disabled, policies removed. Now correctly classified alongside users, organisations, and sessions as a global table (bug_qfhk)
  • rls-verify.sql script — Fixed three bugs: uses pg_class for FORCE column (was missing from pg_tables), nil UUID for no-context test (was empty string failing UUID cast), correct tl.amount column in P&L EXPLAIN (was nonexistent tl.entry_type)

Root Cause

Both table issues originated in the v3.5.0 RLS batch migration (rls-enable.sql). bug_reports only partially applied (FORCE without ENABLE). user_organisations was included in the tenant table list but should have been classified as global — it’s queried by the tenant middleware via the connection pool before any RLS context exists, causing 403 NO_ORGANISATION errors on all tenant-scoped routes.

Discovery

Found during post-RLS verification (step 1 of rls-verify.sql) which checks both relrowsecurity and relforcerowsecurity via pg_class.

Verification

  • 25 tenant-scoped tables show rls_on=t, forced=t
  • user_organisations correctly excluded from RLS (global table)
  • Full 8-step RLS verification suite passing
  • Dashboard and all tenant routes loading correctly
  • No application errors after fix

Files Changed

  • db/migrations/rls-verify.sql — Three bug fixes, org UUIDs populated