TIPS 24 · PORTA

Does your database answer everyone?

The two-minute test to find out whether your app's database also answers people who haven't logged in, and the prompt that locks it. Only on your own app.

What it is and why it matters

Many apps built with Claude Code use a ready-made backend, like Supabase or Firebase. It's convenient: the app talks directly to the database, from the browser or the phone, with no server of your own in between.

To do that, the app carries two things with it: the project address and a public key. Anyone who opens your app can see them, just by looking at the requests in the browser. And that's fine: that key is meant to be there. It isn't a password, it only says “this request comes from my app.”

The point is what's behind the door. What decides who can read what isn't the key: it's the rules on the database. On Supabase they're called Row Level Security, on Firebase Security Rules. In plain words: for each table you write who can read, add, change and delete which rows. For example: “each user sees only their own orders.”

If those rules are missing, or written too loosely, the public key opens everything. Whoever copies the address and the key from the browser can read the tables: emails, names, messages, everything you put in there. Sometimes change them too. You don't need to be a hacker: one line in the terminal is enough.

Claude creates the tables and the app works, because the data reaches you while you're logged in. Whether it also reaches people with no account at all, you won't see by using the app. You only see it if you test it.

First of all: only on your own app

You run this test on your own project, with your own data or with test users. Never on other people's apps or databases, not even “just to see”: getting into a system without permission can be a crime, in Italy as in many other countries. If you think someone else's app is open, don't try it: write to whoever runs it.

1. The test, in two minutes

Three parts: the first finds the key, the second looks at what someone who hasn't logged in can see, the third at whether one user can read another's data. I describe them on Supabase; for Firebase, look further down.

a. Find the key

  1. Open your app in the browser, without logging in.
  2. Right-click > Inspect > Network tab, then reload the page.
  3. Look for the requests to the database. On Supabase they're addresses ending in .supabase.co.
  4. Click a request: there you see the project address and the public key (publishable or anon).

If you don't find any requests to the database, go to a page that loads data and look again.

b. Try to read without logging in

Without logging in, open a terminal and run this line. Put in your project, your public key and the name of one of your tables:

The line
curl "https://YOUR-PROJECT.supabase.co/rest/v1/TABLE_NAME?select=*" -H "apikey: PUBLIC_KEY"

If user rows come back, the door is open. If you get an empty list, [], or a permission error, that table doesn't answer passers-by. Two things to watch: check that the table actually has data in it, otherwise an empty list tells you nothing. And repeat it for every table, not just one.

c. Test as user A

Create two test users, A and B, and have each of them save something: a profile, a note, an order. Then log in as A and copy their access token: you'll find it in the Network tab, in the Authorization header of the requests made after login. Then run:

The line
curl "https://YOUR-PROJECT.supabase.co/rest/v1/TABLE_NAME?select=*" -H "apikey: PUBLIC_KEY" -H "Authorization: Bearer A_TOKEN"

The two headers do different jobs. The public key always goes in apikey. Authorization: Bearer carries only the access token of a user who has logged in, never the public key and never the secret one. That's why the first line has only apikey: there's no user there.

If B's rows are among them, A reads B. It's the easiest hole to miss: the door is closed to people who haven't logged in, but once inside, everyone sees everything. Treat A's token like a password: with it, you are A. Use it only for the test and don't paste it anywhere.

If you don't want to do it by hand

Ask Claude, inside your project. It makes the requests for you:

The prompt
Run the test yourself: with the public key and without logging in, try to read every table; then with two test users check whether A can see B's data. Tell me table by table what's open. Don't change anything.

If Claude Code asks your permission to run a command, read what it's about to do before saying yes. “Don't change anything” is there for exactly this: the test reads, it doesn't write.

2. The prompt that fixes it

Once you know what's open, close it. This is the prompt I use:

The prompt
Each user sees only their own rows. Turn on Row Level Security on every table and write the policies: read, insert, update and delete only on the logged-in user's rows. Tables that are truly public (e.g. a catalog) read-only. Also check the views, the RPC functions and the Storage buckets. List everything with the policy you set, then run the test again and show me that A can no longer see B.

The words are chosen on purpose. “Read, insert, update and delete” because on Supabase the policies are written per operation: if you only lock reading, someone can still write or delete. “The views, the RPC functions and the Storage buckets” because data doesn't only come out of tables. On Supabase a view, by default, skips the policies of the tables it reads; a function can be written so that it skips them; uploaded files have policies of their own. And “run the test again” because you check a policy by trying it, not by reading it.

3. Two things to know

Better a closed door

On Supabase, with Row Level Security on and no policies written, nothing gets through with the public key: better a closed door than an open one. Then you open only what's needed. If a screen stops loading data after the fix, don't turn security off: a policy is missing. Ask Claude to add that one, and nothing more.

The two keys

  • The public key (publishable, or anon in older projects) can live in the app and in the browser. On its own it opens only what the policies allow.
  • The secret key (secret or service_role) skips every policy: only on the server, never in the app, never on GitHub.

If the secret key ended up in the app or in a repository, consider it burned. Create a new one in the Supabase dashboard, put it in place of the old one wherever it's needed, and only then delete or deactivate the old one. Removing it from the code isn't enough: it stays in the history and with anyone who has already seen it.

The prompt
Search the app's code, and everything that ends up in the browser or in the published app, for the Supabase secret key (secret or service_role). Check the git history too. Tell me where you find it. Don't change anything.

If you use Firebase

Same idea, there they're called Security Rules. On Firebase too, the app's key isn't a secret: it identifies the project. What protects the data is the rules.

The rule to look for is the one that lets everyone through, like allow read, write: if true. Firebase's documentation says never to use it in production. In its place you need rules that check who's logged in and whose document it is.

The prompt
Read the Firebase Security Rules in my project. Tell me which collections someone who hasn't logged in can read or write, and whether one user can read another user's documents. Then propose rules where each user sees and changes only their own data. Don't publish anything until I tell you.

Common mistakes

  • Testing only one table. Every table has its own rules: test them all.
  • Locking only reading. Write the rules for insert, update and delete as well.
  • Locking the tables and forgetting views, functions and files. Check those too, with the same test.
  • A rule that lets anyone who's logged in read everything. The right check is on who owns the row, not on being logged in.
  • Turning security off when a screen stops working. Add the missing rule instead of opening everything again.
  • Putting the secret key in the app or in the repository. Use it only on the server and, if it got out, replace it.

Checklist

  • I ran the test without logging in on every table.
  • With two test users, A can't see B's data.
  • Every table has rules for read, insert, update and delete.
  • I also checked views, RPC functions and Storage buckets.
  • Public tables are read-only.
  • The secret key lives only on the server.
  • I ran the test again after the fix.

Frequently asked questions

Can I run this test on someone else's app, out of curiosity?

No. Only on your own app. Reading data from a system without permission can be a crime, even if the door is open. If you suspect a problem, tell whoever runs the app.

After the fix the app no longer loads some data. What did I do wrong?

Probably nothing: the door is closed now and a rule for that case is missing. Tell Claude which screen doesn't load and ask for the tightest rule that does the job, then run the test with A and B again.

Do I need to run it again from time to time?

Every time you add a table or change who can see what. Rules apply table by table, and a new table can be created without any.

← All guides