AI Roundtable Open Debate · 6 models
What security vulnerabilities do solo developers typically miss when building a multi-tenant mobile app with Supabase and RLS?"
Answer: Solo developers often mistake basic RLS for full security, missing critical gaps in storage, functions, and metadata. (5 of 6 models agreed)
Counterpoint: Llama 4 Scout argued that broader architectural failures like poor logging and unvalidated input are more common than these Supabase-specific technical nuances.
Model Responses
Llama 4 Scout
Answer: Solo developers typically miss critical security vulnerabilities such as implementing robust authentication and authorization, inadequate data isolation, insufficient logging and monitoring, and unvalidated user input when building a multi-tenant mobile app with Supabase and RLS.
Upon reviewing the responses from other models, I realize that my previous answer was comprehensive but not exhaustive. Other models have pointed out additional security vulnerabilities that solo developers might miss. These include subtle RLS policy gaps, insufficient tenant isolation checks, omitting `WITH CHECK` clauses in RLS policies, forgetting to enable RLS on newly created tables, and accidentally bypassing security through `SECURITY DEFINER` database functions. Moreover, other models have emphasized the importance of tenant-isolation failures at the database and storage layers, trusting client-supplied tenant IDs, write-side isolation, Supabase Storage blind spots, RPC functions, Edge Functions, triggers, and `SECURITY DEFINER` functions that can bypass normal expectations. Despite these additional insights, my initial position remains largely unchanged. This is because the core security vulnerabilities that I initially identified, such as inadequate authentication and authorization, data isolation, logging and monitoring, and unvalidated user input, are still fundamental and critical issues that solo developers often overlook. However, I have gained a deeper understanding of the nuances of Supabase and RLS, and I acknowledge that my previous answer could have been more detailed. I will ensure that my responses moving forward are more comprehensive and take into account the additional security vulnerabilities highlighted by other models.
Grok 4.3
Answer: Solo developers most frequently miss incomplete RLS coverage across all tables, storage, realtime, and functions, combined with over-trusting client-supplied tenant identifiers.
My initial stance already highlighted subtle RLS policy gaps and weak tenant isolation, and the other models' responses reinforce rather than contradict this core point. They expand on the same themes with valuable specifics such as missing WITH CHECK clauses, forgotten tables, storage bucket misconfigurations, and SECURITY DEFINER bypasses. The detailed examples from Gemini 3.1 Pro and GPT-5.5 particularly strengthen the case for treating every layer—database, storage, realtime subscriptions, and RPCs—as potential isolation boundaries that require explicit tenant checks derived from server-controlled data rather than client input. Because the collective answers converge on these patterns without introducing a fundamentally different primary vulnerability, I maintain my original position while incorporating the shared emphasis on comprehensive, adversarial testing across the full Supabase stack.
Gemini 3.1 Pro (changed mind)
Answer: Solo developers typically miss ecosystem-wide tenant isolation gaps, most notably placing tenant IDs in user-editable metadata, failing to secure Storage and Realtime channels, and allowing cross-tenant foreign-key traversals.
I am expanding my position based on the excellent points raised by Claude Opus 4.7. While my initial focus was strictly on Postgres-level omissions—such as missing `WITH CHECK` clauses and `SECURITY DEFINER` function risks—Claude correctly identifies that the most severe vulnerabilities in Supabase multi-tenant apps happen across the broader ecosystem. The most critical vulnerability is the mismanagement of JWT claims. Developers frequently make the fatal mistake of storing tenant assignments in `raw_user_meta_data`. Because users can update this metadata directly from the client, an attacker can simply modify their JWT payload to inject another organization's tenant ID, entirely bypassing the underlying RLS logic. These claims must strictly live in the server-controlled `app_metadata`. Furthermore, securing the database is only half the battle. Supabase Storage and Realtime features require their own distinct security policies. Solo developers often assume database RLS automatically cascades to files or websocket broadcasts, leaving buckets public or exposing row changes to unauthorized listeners across channels. Finally, Claude Opus 4.7 highlights the subtle danger of foreign-key traversal leaks. Relying on transitive relationships to enforce tenancy (e.g., verifying an invoice ID but not the tenant ID directly on the invoice item) allows attackers who can guess related IDs to bypass isolation rules. Enforcing a direct tenant predicate on every single table, view, and storage path is the only reliable way to guarantee strict multi-tenant boundaries.
GPT-5.5 (changed mind)
Answer: Solo developers most often miss that Supabase RLS protects only the paths it is correctly applied to, so multi-tenant security usually fails through inconsistent policies, unsafe write checks, storage/RPC/realtime bypasses, service-role misuse, and untrusted tenant claims.
I would broaden my previous answer after reading the other responses, especially Claude Opus 4.7’s, because it usefully connected the obvious RLS isolation problem to several Supabase-specific escape hatches that solo developers routinely overlook. The central vulnerability is still tenant isolation failure. Developers often protect the main tables but forget secondary tables, join tables, audit logs, notifications, materialized views, lookup records, or newly created tables exposed through Supabase’s generated APIs. One missing RLS toggle or one overly broad policy can leak data across tenants. A particularly important detail is the difference between `USING` and `WITH CHECK`. Many developers write policies that restrict what a user can read, but fail to restrict what they can insert or update. That can allow a malicious mobile client to change `tenant_id`, create records inside another tenant, assign elevated roles, or move shared resources across tenant boundaries. The mobile-client threat model is also easy to underestimate. A client-supplied `tenant_id` is not trustworthy; requests can be intercepted and modified. Tenant membership should be derived from trusted server-side data such as a memberships table, server-controlled JWT claims, or backend logic. Claims stored in user-editable metadata are dangerous; tenant or role claims must come from a trusted path such as `app_metadata` or verified database relationships. Supabase Storage is another common gap. RLS on application tables does not automatically secure `storage.objects`. Public buckets, predictable object paths, incorrectly parsed folder names, long-lived signed URLs, and server-generated download links without ownership checks can all expose tenant files even when database rows are protected. RPC functions, views, triggers, and edge functions are frequent bypass points. `SECURITY DEFINER` functions, non-`security_invoker` views, or service-role operations can bypass normal RLS unless they explicitly re-check tenant authorization. The same applies to edge functions using the service-role key: the key must never be shipped in the mobile app, and backend code using it must revalidate the caller’s tenant and role before reading or mutating data. Realtime and broadcast features also deserve special attention. Developers may assume table RLS fully covers subscriptions, but websocket channels, broadcast authorization, and row-change filters need tenant-aware configuration and testing to avoid leaking events, metadata, or presence information. Finally, solo developers often miss operational vulnerabilities: weak logging and monitoring, unscoped analytics exports, sensitive data in crash reports, lack of rate limits, tenant enumeration through sequential IDs or error messages, and insufficient automated tests. The practical safeguard is to test every table, policy, storage path, RPC, and realtime channel as users from Tenant A and Tenant B, proving that cross-tenant reads, writes, updates, deletes, file access, and role changes all fail.
Claude Opus 4.7 (changed mind)
Answer: I'm maintaining my core position but expanding it: solo developers miss RLS gaps around foreign-key traversal, storage buckets, service-role key exposure, and JWT claims — and I now want to explicitly add SECURITY DEFINER functions, Realtime channels, and enumeration/inference leaks as equally critical blind spots that my original answer underweighted.
My original four-pillar framing (FK traversal, storage, service-role exposure, JWT claims) holds up well and overlaps heavily with what Gemini 3.1 Pro, GPT-5.5, and Kimi K2.6 independently arrived at — which reinforces that these are the real failure modes, not theoretical ones. But reading the other answers, I think I under-weighted three things worth elevating. First, **`SECURITY DEFINER` functions** — Gemini 3.1 Pro and Kimi K2.6 are right that this is a top-tier footgun I only gestured at. Solo devs reach for RPCs to solve a query they couldn't express in RLS, mark them `SECURITY DEFINER` to make them 'just work,' and silently create a superuser-privileged backdoor. Inside that function, `auth.uid()` still works but RLS does not apply to the queries it runs, so the developer must manually re-assert `tenant_id = (SELECT tenant_id FROM memberships WHERE user_id = auth.uid())`. Almost nobody does. This deserved to be in my top list. Second, **Realtime subscriptions** — Kimi K2.6 surfaced this and it's a genuinely under-discussed vector. Realtime respects RLS *only if* you've configured the publication and policies correctly; many devs enable Realtime broadcast on a table for convenience and websocket events stream row changes to every subscribed client. The damage is silent because it doesn't show up in REST logs. Third, **enumeration and inference leaks** — GPT-5.5's point about uniqueness constraints, error messages, and invitation flows leaking tenant existence is correct and something I missed entirely. RLS blocks the row but `duplicate key violates unique constraint` confirms the email/project/slug exists in another tenant. This matters for B2B apps where tenant identity itself is sensitive. Where I'd push back on the other answers: Llama 4 Scout's response is too generic — 'validate user input' and 'implement logging' apply to any app and don't engage with what's Supabase-specific. The whole point of this question is that Supabase's architecture (PostgREST auto-exposing tables, RLS as the primary auth layer, separate storage RLS, Realtime publications, JWT claims) creates a specific shape of vulnerability that generic OWASP advice doesn't catch. So my refined answer: the original four still lead, but I'd now present it as seven concrete failure modes — FK traversal, storage path policies, service-role key in client, stale/spoofed JWT claims, `SECURITY DEFINER` RPC bypass, Realtime channel leakage, and enumeration via constraints/errors.
Kimi K2.6 (changed mind)
Answer: Solo developers typically miss critical RLS policy completeness—including WITH CHECK clauses for writes, direct tenant predicates on every table to prevent foreign-key traversal, and proper JWT claim placement—while also leaving gaps in Storage, Realtime, and service-role key management.
Claude Opus 4.7 convinced me to sharpen my focus from broad cross-system gaps to specific, structural RLS policy failures inside Postgres itself. While I still believe Storage, Realtime, and RPC are overlooked, the most dangerous and insidious vulnerabilities are finer-grained database mechanics. The distinction between USING and WITH CHECK clauses is critical. USING filters reads and updates, but WITH CHECK validates new and modified rows. Missing WITH CHECK allows an attacker to update a row’s tenant_id and effectively migrate data into another tenant’s workspace. This is a subtle Postgres behavior that looks secure during normal read testing but fails under adversarial writes. Foreign-key traversal leaks are another blind spot I underemphasized. A policy on a parent table like invoices does not automatically protect child tables like invoice_items. If the child only checks membership via a transitive relationship (invoice_id IN (SELECT id FROM invoices)), an attacker who can guess IDs may bypass tenancy through the join. Every table needs its own direct tenant predicate, not a transitive one. JWT claim handling is equally crucial. Storing tenant_id in raw_user_meta_data is a mistake because users can update their own metadata. It must live in app_metadata or a custom claims hook, and policies must read from the right path (auth.jwt() -> 'app_metadata' ->> 'tenant_id'). I previously stressed service-role key exposure, but I now see that client-controllable claims are a parallel and equally catastrophic vector. I also now recognize that views and functions require explicit security configurations. SECURITY DEFINER functions bypass RLS entirely unless manually scoped, and views in Postgres 15+ need security_invoker = true to enforce the querying user's policies. Solo developers following older tutorials often miss these nuances. Finally, while my original answer highlighted operational mistakes, Claude’s point about backups, logs, and analytics as a second unprotected data store is an important expansion. Tenant data ends up in Postgres logs, error trackers, and analytics events with no tenant scoping, creating leakage outside the RLS-protected database path. This refined position keeps my original emphasis on treating RLS as one layer in a larger defense, but it is now rooted in the specific Supabase/Postgres mechanics that solo developers are most likely to misconfigure.
Final Summary
The AI Roundtable concluded that the greatest risk is a false sense of security; developers often protect main tables but leave 'backdoors' in secondary layers. Claude Opus 4.7 and Gemini 3.1 Pro highlighted the catastrophic mistake of placing tenant IDs in user-editable JWT metadata, while GPT-5.5 and Kimi K2.6 warned that missing 'WITH CHECK' clauses and 'SECURITY DEFINER' functions create silent paths for cross-tenant data theft. Grok 4.3 and others emphasized that Supabase Storage and Realtime require separate, explicit policies that are frequently forgotten.
5 of 6 models agreed