Every mistake we look for, in plain words.
Hover any card to see the fix. Switch on Dev mode in the menu to see rule IDs and severity codes.
A live secret is sitting in your codesecret-in-code
Anyone who reads your code or your site’s JavaScript can copy it and spend your money.
Rotate the key, then move it to a server-side environment variable.
Your .env file is committedenv-file-committed
Your .env file is in the repo, so every password in it is public.
Remove .env from git, add it to .gitignore, and rotate every value.
A server secret has a browser-visible namepublic-env-secret
A NEXT_PUBLIC_ or VITE_ name ships the secret to every visitor’s browser.
Drop the public prefix and move the call to a server route.
Your AI key runs in the browserai-key-in-browser
Strangers copy your key from the browser and use your OpenAI credits for free.
Call the AI from a server route that holds the key.
A database table has no row level securityrls-missing
Anyone with your public key can read every row in this table.
Turn on row level security and add an owner-only policy.
A database policy lets anyone writerls-open-write
The lock exists, but it’s set to “allow all”. Anyone can edit or delete.
Replace true with a real condition, like auth.uid() = user_id.
Your Stripe webhook accepts anythingstripe-webhook-unverified
Someone sends a fake “payment succeeded” and gets paid features free.
Verify every Stripe event with its signing secret.
User input may be pasted into a SQL querysql-string-concat
A crafted form entry becomes a database command.
Use a parameterised query. Never build SQL with template strings.
Your AI route has no spending brakeai-route-no-rate-limit
One script loops overnight and you wake up to a huge bill.
Add a per-user rate limit and a monthly spending cap.
Admin access is decided in the browserrole-in-local-storage
Users flip one browser setting and become admin.
Store roles in the database and check them on the server.
Raw HTML goes straight onto the pageunsanitized-html
A comment with a hidden script runs in every visitor’s browser.
Sanitise it with DOMPurify, or render it as plain text.
Any website can call your APIcors-wildcard
Other sites can talk to your app on your users’ behalf.
Replace the wildcard with your own domain.
A secret may be going to your logssecret-in-logs
Passwords end up in logs that more people can read than your database.
Log that it happened, never the value.
One crash takes down the whole screenno-error-boundary
One bad record gives your users a blank white page.
Add an error boundary (error.tsx in Next.js) with a retry.
You will hear about errors from your usersno-error-tracking
Your app breaks for a week before anyone tells you.
Add an error tracker such as Sentry on the client and the server.
Nothing checks that a change did not break loginno-tests
A Friday fix breaks signup until Monday.
One test each for sign up, log in and pay, run on every pull request.
Your deploys are not repeatableno-lockfile
A package updates itself on deploy and your live app breaks.
Commit your lockfile.