All checks
Securitymediumsupabase-phone-signup-openchecked on every page we scan

Anyone can make your project send an SMS

Your Supabase project has phone (SMS) signup enabled while registration is open, so an unauthenticated request can ask it to text a verification code to any number in the world.

0% · n=37 · as of 16 September 2026 of sites UseShipReady scanned had this, counted across sites where this check completed.

Why it matters

Your Supabase project has phone (SMS) signup enabled while registration is open, so an unauthenticated request can ask it to text a verification code to any number in the world. Every one of those messages is billed to you. Automated abuse of exactly this endpoint has a name — SMS pumping: an attacker loops signups against premium-rate numbers they earn revenue from, and the owner discovers it on the invoice. This scan did not send a message, and it cannot see whether you have CAPTCHA or rate limits in place, because your project does not publish either; if phone sign-in is not a feature you use, the safest configuration is to turn the provider off.

How UseShipReady detects it

Tests whether a Supabase project is readable by anyone. The project URL and anon key are read from the page's own JavaScript — both are published there by design. The anon key is then used exactly as a browser would use it. ──────────────────────────────────────────────────────────────────────────── WHAT THIS USED TO ASK, AND WHY IT MISSED THE APPS IT EXISTS FOR ──────────────────────────────────────────────────────────────────────────── The probe list was twelve hardcoded names — users, profiles, orders, posts. A bakery app whose tables are `bakery_orders` and `pickup_slots` was therefore invisible, however wide open it was, and the report said nothing. That is the whole finding class this check exists for: CVE-2025-48757 documents Lovable+Supabase apps shipping tables with RLS off or set to USING (true), readable by anyone holding the anon key the front end publishes. Resources are now DISCOVERED rather than guessed, from three sources, and each finding records which one found it: client-code `.from("bakery_orders")`, `.rpc("claim_slot")` and `/rest/v1/...` paths in the site's own bundles — the strongest signal, because the application itself names the resource it depends on. postgrest-schema the project's own OpenAPI document at /rest/v1/, which PostgREST publishes to any holder of the anon key. This is schema METADATA — path names — not data. common-name the original twelve, kept as a supplement so a project whose bundle is unreadable is no worse off than before. ──────────────────────────────────────────────────────────────────────────── NO ROW IS EVER RETRIEVED ──────────────────────────────────────────────────────────────────────────── The old probe was `GET ...?select=*&limit=1` and it stored the returned row in `evidence.observed_response`. It proved the point by holding a copy of the customer's data — an email address, an address, whatever the first row held — in our logs and our database. The probe is now `HEAD` with `Prefer: count=exact`. A HEAD response carries no body at all; PostgREST answers with `Content-Range: 0-0/1523`, which is the count and nothing else. So the finding says "1,523 rows are readable with a key published in your own JavaScript" and the scanner has not read one of them. Returning zero rows is still not a finding. A table an anon key can reach but whose rows are all filtered is what correctly configured RLS looks like, and it is indistinguishable from an empty table — so neither is reported. ──────────────────────────────────────────────────────────────────────────── WHAT A READABLE TABLE DOES AND DOES NOT PROVE ──────────────────────────────────────────────────────────────────────────── A non-zero count proves one thing: the public anon key can read rows of that table. It does not prove RLS is disabled — a table with RLS enabled and a deliberate `for select to anon` policy answers the same probe identically, and the anon key cannot read pg_class or pg_policies to tell the two apart. Nor does it prove the access is unintended: a public leaderboard, a product catalogue or published content is meant to be read this way. So the finding says what was observed — the table is publicly readable — and is MEDIUM rather than critical. It used to be titled "Missing RLS Policy" at critical, which asserted a cause the scan never saw and scored a public leaderboard like a leaked customer table. A table name is not evidence either way, so severity is not raised for tables that merely sound sensitive. The guidance covers both cases: restrict the table if it is not meant to be public, and if it is, check every column, because RLS filters rows and does not hide columns. WRITE ACCESS IS NOT TESTED, deliberately. Establishing it means attempting an insert against a stranger's production database, and a table whose columns are all nullable would take the row. There is no non-destructive proof available from outside, so the finding states that read access was tested and write access was not, rather than implying both. The probe sequence is memoised on the discovered project, so a project referenced by ten pages is tested once rather than ten times.

Detection is deterministic. UseShipReady reports this only when it observes the condition directly, and prefers to miss a real problem over inventing one. Rule version 1.5.0.

How to fix it

This is the prompt UseShipReady puts in your report — written to be pasted straight into Cursor, Claude Code, or whichever assistant built the app.

Your Supabase project has phone (SMS) signup enabled with public signups open, so anyone can ask your project to send a verification SMS to any number. Each of those messages is billed to you, and automated abuse of exactly this endpoint — SMS pumping, where an attacker cycles premium-rate numbers they earn revenue from — is a well-known way to run up a four-figure bill overnight. If you do not use phone authentication, turn the phone provider off in Authentication → Providers. If you do: enable CAPTCHA protection under Authentication → Settings (Supabase supports hCaptcha and Cloudflare Turnstile), lower the SMS rate limits under Authentication → Rate Limits, restrict the countries your SMS provider will deliver to, and set a spend cap with that provider.

Frequently asked questions

What does "Anyone can make your project send an SMS" mean?
Your Supabase project has phone (SMS) signup enabled while registration is open, so an unauthenticated request can ask it to text a verification code to any number in the world.
How serious is it?
UseShipReady rates this medium. Fix soon. Meaningfully weakens a defence or degrades how the site works.
How common is this?
Across the sites UseShipReady has scanned where this check completed, 0% · n=37 · as of 16 September 2026.
How do I fix it?
Paste the fix prompt on this page into Cursor, Claude Code or your AI editor. It is the same prompt UseShipReady puts in your report.
Can I check my own site?
Yes — UseShipReady scans up to ten pages of any public site for free and reports this alongside every other check. The free report lists every issue it finds and shows full evidence and a fix prompt for the critical and high-severity ones; medium and low findings are counted and unlock on Pro.

Related checks

Run this check on your site

UseShipReady checks this and 196 other things across up to ten pages of your site, with an AI-ready fix for each. Free, no signup.