Three cross-tenant data leaks closed in the Mike backend
WilliamACLove's PR patches a trio of authorization gaps that let one tenant read another's documents, chats, and LLM-extracted content. The most blunt problem: only the `user_profiles` table had Row Level Security enabled, leaving every other data table reachable through PostgREST with the anon key shipped in the JS bundle.
The RLS gap is the broadest of the three. Projects, documents, chats, tabular reviews and their cells, workflows, and folders were all accessible to anonymous HTTP clients. Because the Express backend talks to Supabase via the service-role key, turning RLS on without writing policies is enough to block anon and authenticated front-channel reads while leaving server paths intact. The PR updates both the one-shot schema and a standalone incremental migration for existing deployments.
The tabular review fix is more surgical. The four endpoints that create reviews, update them, regenerate individual cells, and run generation all accepted arbitrary document IDs without checking ownership. An attacker who knew a document UUID - plausible through shared links, exported chats, or screenshots - could attach it to a review they owned and read the extracted LLM content back through the normal GET. WilliamACLove introduces a shared accessible-document filter and threads it through each entry point so foreign documents are rejected before any model work starts.
The folder fix scopes move operations to the current project. The folder-move and document-move handlers were following parent links without confirming those folder IDs belonged to the project in the URL. A project member could shift structure across project boundaries. Both handlers now reject IDs outside the target project.
The PR explicitly leaves several things for follow-up: replacing broad auth.admin.listUsers sweeps with targeted lookups, adding expiry to download tokens, removing the dev-secret fallback, and normalizing legacy uppercase entries in shared_with arrays to stop silently blocking case-sensitive membership checks.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?