Is your data readable with the public key?
A backend the browser talks to directly is a backend anyone can talk to directly. The anon key is meant to be public; whether the rows behind it are is decided by policies nobody can see from the outside - unless something tries.
Sound familiar?
It was enabled on the tables you remember. The one added at midnight, with the policy still on the to-do list, answers every query.
It is in the bundle because it has to be. That makes it the credential every visitor holds, and the one worth testing with.
The key that bypasses every policy, pasted into a component "for the admin page". It is now in the bundle too.
The app works for logged-in users. Whether it also works for nobody is a question the app never asks itself.
A probe with your own key.
The browser pass records the requests your page makes to its data backend. The access-control probe replays a few of them - read-only, with the public key the page itself shipped - and reports whether the data came back without a session.
- Read-only, bounded, on your say-so
At most 6 replays, only of requests classified as safe reads, and only after you have verified the domain by DNS TXT, Well-known file, Meta tag. browser.network.data-readable-unauthenticated
- The backend the browser reaches
A data backend reachable from the page is recorded as a fact, and a public key that opens it is a finding of its own. browser.network.public-key-data-backend
- The key that should never reach a browser
A Supabase service-role token in public JavaScript or in a client file is found by its shape, and the invariant that private keys stay on the server is re-checked on every push. source.secrets.private-key-exposed-to-browser
- One finding, two proofs
When the repository is connected, the route that skips the check and the response that proves it are one finding, not two.
200 · 148 items · no auth header
The browser pass proves the data comes back without a session.
export async function GET(req) {
Taint analysis proves the route has no guard before the query.
One finding, two proofs: the line that is missing the guard, and the response that proves it is missing in production.
What the first week looks like
- 01Scan the deployed site
The report says which data backend the page talks to and whether a request came back without a session.
- 02Verify the domain
A TXT record, a well-known file or a meta tag. The probe and the rate-limit burst run only on a domain you have proven is yours.
- 03Connect the repository
The inventory finds the service-role key if it is there, and the routes that query without a tenant scope.
- 04Fix, then watch it stay fixed
The next scan re-runs the probe. The finding resolves when the data stops coming back.
The free scan runs the probe once on one page. Pro runs it across the crawl on every scan, and reads the repository for the key that should not be there.
- The free scan already asks the question, once
- Up to twenty pages per scan, each with its own requests
- The source rules for exposed keys and unguarded routes
- Fix with AI as a draft pull request
Start on Free, which does not expire. Upgrade when it pays for itself.
Ask the question the app never asks.
One URL. If your data answers to nobody, you will know today.