Skip to content
StackSiren

How to test your website security before you launch

By StackSiren · Published

You built the app with Lovable, Bolt, Replit, Cursor or v0, and it works. Before you share the link, spend about 20 minutes looking at it the way a stranger would: signed out, from the outside, with nothing but a browser and a terminal.

This guide is the how: seven tests you run yourself, what a good and a bad result look like, and where to go if something fails. If you want the list of what to cover before launch, including the parts that need your code and database, read the full checklist.

Before you start

  • Only test sites and Supabase projects you own. Everything here is read-only: normal page loads and GET requests, the same things any visitor's browser does.
  • Use your live production URL, not the editor preview. Replace yourapp.com below with your domain.
  • You need a browser (Chrome, Edge, Firefox or Safari) and a terminal with curl. curl comes with macOS, Windows 10 and later, and most Linux systems. On Windows, leave off the | head ... and | grep ... parts of each command, or run them in Git Bash or WSL.

Test 1: Ask for your .env file and .git folder

Your .env file holds your keys, and the .git folder can hold your whole project history. Neither should ever be served by your site.

In the browser, open these two addresses:

https://yourapp.com/.env
https://yourapp.com/.git/config

Or in the terminal:

curl -s https://yourapp.com/.env | head -5
curl -s https://yourapp.com/.git/config | head -5
You see Meaning
A 404 page, a 403, or your app's normal homepage Fine. Many single-page apps return the homepage for any unknown path.
Lines like SUPABASE_URL=... or STRIPE_SECRET_KEY=... Your env file is public. Treat every secret in it as leaked.
A block starting with [core] Your Git folder is public.

If either one fails, rotate the exposed keys first, then remove the files from your deploy. Full steps: Exposed .env file or .git folder.

Test 2: Search your page source and scripts for secret keys

Everything the browser downloads is public, including the JavaScript bundle your builder generated. Some keys are meant to be public: a Supabase publishable key (sb_publishable_...) or legacy anon key, a Stripe publishable key (pk_...) and Firebase web config. Secret keys are not.

  1. Open your live app and press Ctrl+U (Cmd+Option+U on a Mac) to view the page source. Press Ctrl+F (Cmd+F) and search for the terms below.
  2. That only covers the HTML. To search the JavaScript too, open DevTools (F12), then press Ctrl+Shift+F (Cmd+Option+F on a Mac). This searches every script the page loaded.

Search for each of these:

sk_live_   sk_test_   rk_live_   sk-proj-   sk-ant-   sb_secret_   service_role   AKIA   ghp_   github_pat_   xoxb-   SG.   PRIVATE KEY

A hit inside your own code (not a library's comment or a placeholder like sk_live_xxxx) means that key is compromised. Rotate it first, then move the code that uses it into a server function, such as a Supabase Edge Function or a Next.js route handler. A long eyJ... string next to service_role is a legacy Supabase key that bypasses Row Level Security, so treat it as critical. Full steps: Exposed API keys.

Test 3: Check whether your source maps are public

Source maps (.map files) turn your minified bundle back into readable source code. They're useful while you develop, but public ones hand that code to anyone.

  1. In the page source from Test 2, find a <script src="..."> tag pointing at your own domain, for example /assets/index-abc123.js. Open that address.
  2. Press Ctrl+End (Cmd+Down on a Mac) to jump to the bottom. Look for a last line like //# sourceMappingURL=index-abc123.js.map.
  3. If it's there, open the same address with .map on the end:
curl -s https://yourapp.com/assets/index-abc123.js.map | head -c 200

JSON that starts with {"version":3 and contains "sources" and "mappings" means your maps are public. A 404 or your homepage is fine. In Vite, build.sourcemap is off by default, and in Next.js productionBrowserSourceMaps is off by default, so a public map usually means someone switched one on. Fix: Exposed source maps.

Test 4: The signed-out Supabase test

If your app uses Supabase, this is the most important test here. The publishable (or anon) key in your front end is public by design, so the real question is: what can someone do with only that key, while signed out?

You need two things from your app, both of which a visitor can see too:

  • your project URL, https://YOUR-PROJECT-REF.supabase.co (search the page source or DevTools for supabase.co);
  • the publishable key (sb_publishable_...) or legacy anon key (a long eyJ... string).

Then pick a table name from Table Editor in your Supabase dashboard, ideally one holding user data such as profiles, orders or messages, and run a single read:

curl -s "https://YOUR-PROJECT-REF.supabase.co/rest/v1/profiles?select=*&limit=3" \
  -H "apikey: YOUR_PUBLISHABLE_OR_ANON_KEY"

Use only the publishable or anon key, never a secret or service_role key. A secret key bypasses Row Level Security, so testing with one tells you nothing.

Response Meaning
Rows of real data, such as other people's emails or orders Anyone on the internet can read this table. Fix it before launch.
[] (an empty list) Signed-out visitors see no rows. That's what you want for private tables, as long as the table actually has rows (check in Table Editor).
An error with "code":"42501" / "permission denied" The anon role has no grant on this table, so it's blocked before RLS is even checked. Fine for a private table.
An error saying the table can't be found Check the table name, or the table isn't exposed through the API.

Supabase's docs describe the difference: a missing grant returns a permission error, while a policy that matches no rows returns an empty result. Repeat the test for every table that holds user data. Data that's meant to be public, such as a list of blog posts, can legitimately come back.

If any private table returns rows, follow the Supabase RLS guide to turn on Row Level Security, add policies and set grants.

Test 5: Check HTTPS and your security headers with curl

One command shows the headers your site sends:

curl -sI https://yourapp.com

Look for these six headers in the output:

Header A typical good value
content-security-policy Any policy. Start in report-only mode if you're adding one.
strict-transport-security max-age=63072000; includeSubDomains
x-frame-options (or frame-ancestors in the CSP) DENY or SAMEORIGIN
x-content-type-options nosniff
referrer-policy strict-origin-when-cross-origin
permissions-policy e.g. camera=(), microphone=(), geolocation=()

While you're there, look at any set-cookie lines. Session cookies should include Secure, HttpOnly and SameSite.

Then check that plain HTTP redirects to HTTPS:

curl -sI http://yourapp.com | head -3

You want a 301, 307 or 308 with a location: https://... line. Check again after adding a custom domain. Missing headers aren't an emergency on their own, but they're quick to add. Per-host snippets: Security headers, and for Vercel specifically, Vercel security.

Test 6: Check how your site answers other websites (CORS)

CORS headers decide whether other websites can read responses from your site in a visitor's browser. The dangerous combination is a server that echoes back any origin and allows credentials.

curl -sI -H "Origin: https://example.org" https://yourapp.com | grep -i "access-control"

Run it against your homepage, then against any API route your own front end calls (find them in DevTools → Network, filtered to Fetch/XHR).

Output Meaning
Nothing No CORS headers. Fine for most pages.
access-control-allow-origin: * with no credentials header Any site can read this response, but without the visitor's cookies. Fine for public data, not for anything private.
access-control-allow-origin: https://example.org and access-control-allow-credentials: true Any website could read your signed-in users' data from this endpoint. Fix it.

Fix: CORS misconfiguration.

Test 7: Visit your own private pages signed out, then as a second user

No scanner can do this one for you, and it catches the most serious bugs. Broken access control is the top category in the OWASP Top 10.

  1. Sign in to your app and copy the addresses of a few private pages, such as /dashboard, /settings or /orders/123.
  2. Open a private/incognito window (signed out) and paste each one. You should be sent to a sign-in page, and no data should flash up first.
  3. Create a second account. Signed in as that second user, open the first user's private addresses, such as /orders/123. You should see an error or nothing, never the first user's data.
  4. Optional: in DevTools → Network, find a request that loads one of your own records, right-click → Copy as cURL, and run it in a terminal with the second user's session instead. The response should not contain the first user's data.

If the second user can see the first user's data, the check is happening only in the front end. Ask your builder to enforce ownership on the server or in your Supabase policies, then run this test again.

What these tests don't cover

These seven tests look at your live site from the outside. They can't see your database grants and policies in full, server-side logic, rate limits on endpoints that call paid AI APIs, Stripe webhook handling, dependencies or backups. The full checklist linked at the top of this guide covers those inside checks.

Run tests 1, 2, 3, 5 and 6 automatically

A free StackSiren scan runs the outside-in parts of this guide for you, using normal GET requests to the page you submit:

  • Test 1: requests /.env and /.git/config and flags them only if they return real env variables or a [core] block (values are never shown);
  • Test 2: matches known secret key formats (Stripe, OpenAI, Anthropic, AWS, GitHub, Slack, SendGrid, Supabase sb_secret_ and service_role keys, private keys) in the page HTML and up to 12 same-origin scripts, and shows them masked. It also notes Supabase anon/publishable keys and Firebase config as reminders to check RLS or rules;
  • Test 3: checks up to three source maps referenced by those scripts;
  • Test 5: HTTPS, the HTTP-to-HTTPS redirect, the six headers above, and cookie flags on that page's response;
  • Test 6: a CORS check on the page you submit.

It doesn't run Test 4 or Test 7. It never queries your Supabase database and never signs in, so the signed-out Supabase test and the two-account test stay with you.

Run a free scan of your app → You get a score and every issue for free. The full report adds evidence, explanations and a copy-paste fix prompt for each finding.

FAQ

Is it legal to test my own website like this? These tests only make the same kind of requests a normal visitor's browser makes. Run them only against sites and Supabase projects you own or have permission to test.

My Supabase test returned []. Am I safe? For that table, signed-out visitors see no rows, provided the table actually contains rows. Run the test on every table with user data, and do Test 7 too: signed-in users may still be able to read each other's rows if a policy only checks that someone is signed in.

How often should I repeat these tests? After every change to how you deploy, add a custom domain, change hosts or add a new table. AI builders can change config files when you ask for unrelated features, so re-test after big prompts too.

Platform notes: Lovable · Bolt · Replit · Cursor · v0 · Vercel

Check your app now

Free passive scan, results in under a minute.

Run a free scan

Fix guides for this topic

Platform checklists: Lovable · Bolt · Replit · Cursor · v0 · Vercel

Related posts

Sources