Checklist
Vibe-coded app security checklist
Fifteen checks for an app you shipped with an AI builder. The list follows what Deploy Safely looks for on a public URL, plus the dashboard steps a scan cannot do for you. Every item is a check on your own app.
Free check
Check a URL against this list
Paste the public address. You get a private report with a fix prompt for each issue the check can actually see.
Critical
A stranger who opens the page can copy these. Rotate the key after you remove it.
- Critical1 of 15
No service role key in the browser
A Supabase service role key skips row-level security. Anyone who loads the page can copy it and read or change every row.
Check it yourself
Open the live app, view source, and search for service_role. Search the JavaScript files from your domain too. The anon key is supposed to be there.
The free check looks for a service role token in the page and its scripts, and it does not use the key.
- Critical2 of 15
No provider secret in the page or the scripts
OpenAI, Anthropic, Stripe secret keys, AWS, GitHub, Slack, SendGrid, Resend, and a PEM private key can all spend money or open an account that is yours.
Check it yourself
Search the live HTML and JavaScript for sk-, sk_live, rk_, AKIA, ghp_, xoxb-, and -----BEGIN. A Stripe publishable key starts with pk_ and is a different item, lower on this list.
The free check searches the page and up to a handful of scripts for these patterns, and stores only a short masked hint.
- Critical3 of 15
No database URL with a password in the client
A postgres, MySQL, or MongoDB address that includes a password opens the database directly, past the app.
Check it yourself
Search the published files for postgres://, mysql://, and mongodb://. The browser should not receive a connection string.
The free check flags a database URL that contains a password. It does not save the password or connect with it.
- Critical4 of 15
The .env files are not public URLs
A host that serves /.env is handing out the names and values you set aside for the server.
Check it yourself
Visit /.env, /.env.local, and /.env.production on the live domain. You want a not-found page, not a file.
The free check requests those three paths and reports them only when the body looks like an env file.
- Critical5 of 15
The .git folder is not public
A readable /.git directory can let someone rebuild the source, including a key you deleted in a later commit.
Check it yourself
Open /.git/HEAD and /.git/config on the live domain. Both should be missing.
The free check requests those two paths and looks for a Git HEAD or a config section.
High
Private data or a powerful token is one request away. Fix these before you invite users.
- High6 of 15
Supabase tables do not answer a signed-out visitor
The anon key is public by design. A table with row-level security off, or a policy of true, returns real rows to that key.
Check it yourself
In the Supabase dashboard, turn on row-level security for every table in the public schema. Read each policy. Private rows should require a signed-in user.
The free check does this only when the page already includes a supabase.co address and the anon key. It asks for one row per table and does not keep the row.
- High7 of 15
Firebase is not still in test mode
Test-mode rules allow reads and writes without a login. The web API key in the page is normal. The rules are the lock.
Check it yourself
In Firebase, Realtime Database rules should not set .read or .write to true. Firestore rules should require request.auth, then a tighter match on the user.
The free check calls Firebase only when the public config is already in the page. It does not sign in, and it does not save records.
- High8 of 15
No .npmrc token on the website
An npm auth token in /.npmrc can publish packages or download private ones.
Check it yourself
Open /.npmrc on the live domain. A not-found page is what you want. If a token was exposed, revoke it at the registry.
The free check reports /.npmrc only when the file contains an _authToken setting. The token value is not saved.
- High9 of 15
The Stripe webhook secret stays on the server
A whsec_ value proves that a webhook came from Stripe. If it is public, someone can send fake payment events.
Check it yourself
Search the live JavaScript for whsec_. It should appear only in the server route that verifies the webhook.
The free check flags a webhook secret in the page or a script.
Medium
These make the rest of the app easier to read. Clean them up in the same pass if you can.
- Medium10 of 15
Production source maps do not include your original code
A public map makes leftover keys, hidden routes, and comments much easier to find.
Check it yourself
In developer tools, open a script from your domain and look for sourceMappingURL. If the map includes original files, turn maps off and delete the .map files.
The free check fetches a map only when a script already points at one, and only reports it when original source is included.
- Medium11 of 15
No signed-in user token baked into the files
A Supabase user JWT should be created when someone signs in. Shipped inside the bundle, it can impersonate that user until it expires.
Check it yourself
Search the repo and the live bundle for a long-lived session token. Sign that user out if the token was real, and remove it from source.
The free check notes a JWT whose role is authenticated when it appears in the page or a script.
Low
Worth doing after the leaks above are closed. They are not the same kind of emergency.
- Low12 of 15
The main page sends basic browser headers
Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, a frame rule, and Referrer-Policy tell the browser how to treat the site. They do not replace a lock on the database.
Check it yourself
Open the live page response headers. Add the missing ones at the host. Start with a simple policy so you do not block your own app.
The free check mentions headers when two or more of those are missing. One missing header stays quiet.
- Low13 of 15
A Google API key in the page is restricted
Firebase and Maps apps often include a Google key in the browser. An unrestricted key can be copied and used from another site.
Check it yourself
In Google Cloud, restrict the key to your domain and to the APIs you use. If the key was meant for a server, move it out of client code and rotate it.
The free check notes a Google API key in the page as a low finding, not as a leaked secret key.
Usually fine
These show up in a healthy app. Leave them in place.
- Usually fine14 of 15
The Supabase anon key is allowed in the page
That key identifies your project to the client. Hiding it does not secure the data. Row-level security does.
Check it yourself
Leave the anon key in the browser. Confirm you did not replace it with the service role key, and that the tables are locked.
The free check explains an anon key when it sees one. It does not treat that key as a leak.
- Usually fine15 of 15
A Stripe publishable key is allowed in the page
Keys that start with pk_ are designed for the browser. They cannot issue refunds or see secret customer details on their own.
Check it yourself
Keep pk_ in the client. Confirm sk_ and rk_ are absent from the same files.
The free check labels a publishable key as expected.
What a clear check still leaves out
The scan reads the public page, the scripts that page loads, and a short list of paths (.env, .git, .npmrc). It asks Supabase or Firebase for data only when that public config is already in the page. It does not log in, guess passwords, or use a key it finds. A quiet result is an early warning that came back clean, not a promise that the whole app is safe.