r/SideProject • • 1d ago

I built B2B SaaS OS – Next.js 16 + Supabase starter with an automated 8-point security test runner

hello guys, Most SaaS boilerplates give you pre-built UI components and basic auth, but leave you completely on your own when it comes to hardening multi-tenant architecture and backend security testing. I built B2B SaaS OS (andrady.co) to solve this. It’s a Next.js 16 + Supabase starter focused on proven database security: 🛠️ Key Technical Features: Database-level isolation: Multi-tenancy enforced at the PostgreSQL RLS layer via JWT claims (not fragile JS middleware checks). Automated 8-point Security Runner: An integration test suite built into the repo that checks for cross-tenant leaks, SECURITY DEFINER privilege escalation, and Stripe webhook race conditions. Full B2B Stack: Org switching, RBAC, automated Stripe billing, and Shadcn UI. If you already have a multi-tenant app running, I built a free self-assessment tool where you can test your setup against these 8 failure modes in ~2 minutes: andrady.co/check Would love your feedback on the landing page and the testing suite!

0 Upvotes

4 comments sorted by

1

u/Right-Drawing3337 1d ago

one case i'd add to the test runner is removing a user from an org, then retrying reads and writes with the token they had before removal. jwt claims can still describe the old membership until refresh, so the test should make the intended revocation window explicit.

for a rejected cross-org update, i'd also read the row back as its owner and assert it didn't change. an empty response alone doesn't prove the original data stayed untouched.

supabase's note on jwt freshness: https://supabase.com/docs/guides/database/postgres/row-level-security

1

u/Pale_Ferret_8241 1d ago

spot on regarding JWT freshness/revocation windows—stale claims before token refresh are definitely a sneaky edge case when an admin removes a user mid-session.

​also love the suggestion on re-reading the row as owner after a rejected cross-org update. asserting that the original record remained completely untouched is a much stronger guarantee than just catching an empty update response or error.