The Advisor blind-spot
// why an external scan mattersThe Advisor analyses the schema from inside the dashboard, and only warns — in a panel most vibe-coders never open — about schema shape, not live exploitability. An entire class of critical, real-world-exploited issues lives in the shipped client code, the bundle, or the app's design, outside its detection scope by design. NewScan proves those from the public URL + anon key: the attacker's exact position, and exactly the evidence an auditor wants.
// THE ADVISOR SEES (SCHEMA)
RLS disabled on a table, a permissive USING (true) policy, a SECURITY DEFINER view, GraphQL exposure — all warned in-dashboard as schema shape.
// THE ADVISOR CAN'T SEE (NEWSCAN DOES)
A service_role key shipped in the browser bundle, a missing WITH CHECK clause, whether a table actually returns data to anon, or an RPC that runs without an auth guard.
service_role key in the client bundle
// tool: scan_supabase criticalThe single most catastrophic Supabase mistake. The service_role key carries Postgres BYPASSRLS — any holder reads, writes, and deletes every row in every table and reaches the auth schema (hashed passwords, recovery tokens → account takeover), regardless of RLS. It leaks silently: build, deploy, and app all succeed. NewScan fetches the deployed bundles, finds every JWT, and decodes the role claim.
// WHY THIS IS FALSE-POSITIVE-FREE
A finding is recorded only when a bundle JWT base64-decodes to role === "service_role" (or a new-format sb_secret_ key). The anon key is meant to be public and is never flagged — the role claim is unambiguous, so recording on decode alone can't false-positive.
Live data exposure via the Data API
// tool: enumerate_supabaseA table with RLS disabled, or a USING (true) / any-authenticated policy, is fully readable through auto-generated PostgREST at /rest/v1/<table> using only the anon key. NewScan confirms exposure by row count, never by exfiltration.
// COUNT-SAFE EXPOSURE PROBE
// THE LOAD-BEARING FP GUARD
Data or a non-zero count = exposed. An empty array is not a finding — RLS-enabled-with-no-policy legitimately returns [] and is the secure state. NewScan never flags an empty result, the biggest false-positive trap in naive Supabase scanners.
Missing WITH CHECK & anon RPCs
// non-destructive · no Advisor lintA policy with a USING clause but no WITH CHECK lets unauthorised rows be written — the Advisor has no lint for it, and developers routinely test only SELECT. And a database function at /rest/v1/rpc/<fn> is anon-callable unless it guards on auth.uid(). Both are proven without ever creating a row or running a destructive function.
// WRITE AUTHORISATION DIFFERENTIAL high
Only the 400 DB-constraint vs 401/403 differential records — a bare 400 does not. The write fails on validation, so no row is ever created.
// ANON-CALLABLE RPC high
A SECURITY DEFINER function callable by anon runs as the owner and bypasses RLS — unauthenticated privilege escalation.
What NewScan records
// class · method · severity| Finding | How NewScan finds it | Severity |
|---|---|---|
| service_role key leaked | A client-bundle JWT decodes to role=service_role (or an sb_secret_ key). | critical |
| Table live-exploitable | /rest/v1/<table> returns data or a non-zero count to the anon key. | high |
| Missing WITH CHECK | An anon INSERT is authorised (400 DB-constraint, not 401/403) — no row created. | high |
| Anon-callable RPC | A sensitive /rest/v1/rpc/<fn> exists (200/400, not 404) with the anon key. | high |
| Public storage bucket | An anonymous /storage/v1/object/list/<bucket> returns a non-empty listing. | medium |
Recent Supabase security issues
// why these checks matterThe vulnerability classes above, seen at scale in the wild — AI-generated apps shipping tables with no RLS, and real projects anonymously leaking user data.
CVE-2025-48757
170 apps exposed by missing RLS
A 2025 scan of AI-generated Supabase apps found 10.3% of projects had tables readable with the anon key — 303 exposed endpoints.
byteiota →
Black-box testing · 2025
22% anonymously leak user data
A practitioner running black-box Supabase tests reports the most common critical finding was a policy exposing every row to any authenticated user.
Precursor Security →
Supabase docs
Row-Level Security
The primary reference: a BaaS backend authorises purely by RLS, so every table, function, and bucket needs its own policy.
supabase.com →
External links; referenced for context. CVE severities and details are the publishers'.
How it's recorded
// audit-ready evidenceCATEGORY
The service_role leak maps to OWASP API2; table/bucket exposure to API1; missing WITH CHECK to API3; anon RPC to API5.
SEVERITY
One calibrated severity per finding that translates into each framework's risk rating and remediation SLA — no inflated numbers.
EVIDENCE
Every finding stores the exact request and the decoded role, the count, or the DB-constraint differential that proved it — never a row of your users' data.
Point it at your Supabase app.
NewScan is free and self-hosted — bring your own key. Scanning your own Supabase project is local to you, so it never depends on any hosted resource. The service_role, RLS-exposure, write, and RPC checks run in the deterministic floor with no model or provider key at all.