Guide · Start here · Updated
Vibe coding security checklist: what to check before you launch an AI-built app
You described an app, an AI builder wrote it, and it works. Before you post the link, it is worth an evening to look at it the way a stranger would. This is the list we ran against our own site while writing it. It doesn’t matter whether the app came from Lovable, Bolt, v0, Replit, Cursor or something else: the same handful of mistakes keep showing up, and each one can be checked from your own terminal without touching anyone’s data.
Why a checklist, and why now
AI builders are good at making things work. They are less reliable at making them safe, mostly because nobody asked. As the security researcher Joel Margolis put it to WIRED, these tools “do what you ask them to do. And unless you ask them to do it securely, they’re not going to go out of their way to do that.” In tests of code written by more than a hundred language models, Veracode found that AI-generated code “introduced risky security flaws in 45% of tests.” The live apps look similar. Escape’s research team analyzed more than 5,600 publicly available vibe-coded apps and found over 2,000 vulnerabilities, more than 400 exposed secrets and 175 instances of personal data, and noted that most of it was reachable without logging in. In May 2026, WIRED reported that the security firm RedAccess had found more than 5,000 apps built with Lovable, Replit, Base44 and Netlify with “virtually no security or authentication of any kind,” and that around 40 percent appeared to expose sensitive data. WIRED adds that it couldn’t confirm all of that data was real. The researchers found the apps with ordinary Google and Bing searches.
None of this is new to security people. OWASP’s 2025 Top 10 puts broken access control first, and says “100% of the applications tested were found to have some form of broken access control.” What vibe coding changed is the speed. An app can go from idea to public in an afternoon, with nobody in between whose job is to ask “who can see this?”
We started LockShip by writing a scanner that checks these things from the outside, and building it taught us where the trouble usually is. Our first version only read the scripts a homepage loads directly. Code for screens that load later sits in separate files, so a key used only on, say, an admin screen went unseen. We had to teach it to follow those files, and then to read a few linked pages as well. That habit, looking past the first page, runs through this whole list.
How to use this list
Each item has a short reason it matters, a check you can run yourself, what a good answer looks like, and a link to the longer guide where we have one. Three rules for every check:
- Only test an app you own. Replace
your-app.comwith your own address. - Read-only. Every command here sends GET or HEAD requests, the same kind your browser sends when it opens a page. Don’t send POST, PUT or DELETE at a live app to see what happens. Those really run.
- Write down what you change. When you ask your AI builder to fix something, ask it to list what it changed, then run the check again.
The commands need a terminal with curl and grep, which macOS and Linux have, and Windows has through WSL or Git Bash. We ran each one against a small, deliberately careless test app on our own machine and against lockship.dev, and we say below what came back.
- No secret keys in the JavaScript you ship
- Your database rules hold up against a stranger
- Every API route checks who is asking
- Every record belongs to the person asking
- Server Actions and server functions check for themselves
- Your framework version has no known advisories
- Security headers are set, and the CSP does something
- CORS only lets in the sites you meant
- Anything that costs money has a limit
- No source maps, .env or .git on the live site
- After launch, keep checking
The checklist
01
No secret keys in the JavaScript you ship
Why. Anything your front end can read, every visitor can read. Both popular build tools say so plainly. Vite’s docs say variables starting with VITE_ “should not contain sensitive information such as API keys,” because their values “are bundled into your source code at build time.” Next.js inlines any variable starting with NEXT_PUBLIC_ “into any JavaScript sent to the browser.” OpenAI’s own key safety page is just as direct: “Never deploy your key in client-side environments like browsers or mobile apps.” A leaked AI key usually means someone else’s usage on your bill. A leaked payment or database key can be much worse.
Check. This downloads three of your pages and the scripts they load, then searches for strings shaped like secret keys and database passwords. Add the pages people see after signing in to PAGES:
APP=https://your-app.com
PAGES="/ /login /dashboard"
PATTERN="sk_live_[A-Za-z0-9]+|rk_live_[A-Za-z0-9]+|sk-(proj|ant)-[A-Za-z0-9_-]{20,}|sb_secret_[A-Za-z0-9_-]+|AKIA[0-9A-Z]{16}|postgres(ql)?://[^\"' ]+|mongodb(\+srv)?://[^\"' ]+"
for p in $PAGES; do curl -s "$APP$p"; done > pages.html
grep -oE '(src|href)="/[^"]+\.js"' pages.html | cut -d'"' -f2 | sort -u \
| while read -r f; do curl -s "$APP$f"; done > scripts.js
grep -ohE "$PATTERN" pages.html scripts.js | sort -uOn our careless test app, it printed a fake Stripe secret key, a fake OpenAI key, a fake AWS key and a Postgres connection string with a password in it. On lockship.dev it printed nothing, which is what you want. Not everything key-shaped is a secret. Supabase’s publishable key, a Firebase API key, and a Stripe key starting with pk_ are meant to be public (Stripe’s docs: “Only publishable keys are safe to expose outside your application’s backend”), and the pattern skips them. A legacy Supabase service_role key is a long string starting with eyJ that this short pattern doesn’t decode, which is one reason our longer guide has a script for it.
Fix. Treat any real key that shows up as already public. Rotate it at the provider first, then move the call behind your server:
Find every place the front end uses an API key, token or password for a paid or private service. Move each call into a server route or server function that reads the key from an environment variable without a public prefix (no VITE_ or NEXT_PUBLIC_). Then delete the public variable. List what you moved and don't change anything else.Go deeper: Leaked API keys in your frontend bundle has a script that follows every file your app loads later, a check for the role inside a Supabase key, how to search your git history, and the order to rotate keys in.
02
Your database rules hold up against a stranger
Why. Many AI-built apps talk to the database straight from the browser, through Supabase or Firebase. That is a normal design, but it means the database rules are the only lock. This is the failure behind CVE-2025-48757, which the NVD describes as an insufficient Row Level Security policy in Lovable through 15 April 2025 that “allows remote unauthenticated attackers to read or write to arbitrary database tables of generated sites.” Lovable disputes the record, on the grounds that each customer is responsible for their own app’s data. Matt Palmer’s write-up explains how it worked: a request could be changed to ask for every row of a table, and the exposed data included personal details, API keys and financial records. Firebase has the same shape. In 2024, researchers who scanned more than five million domains found 916 sites with missing or wrong Firebase rules, and close to 19 million plaintext passwords, as BleepingComputer reported.
Supabase puts the rule in one line at the top of its Row Level Security docs: “A table in an exposed schema without RLS is readable and writable by any role with a grant on it.” Firebase’s guide to insecure rules warns that with open rules, “anyone who guesses your project ID can steal, modify, or delete the data.”
Check. Ask your database for a table the way a stranger would, with only the public key from your page. For Supabase, use your project address and publishable key, and one of your real table names:
curl -s "https://YOUR-PROJECT.supabase.co/rest/v1/YOUR_TABLE?select=*&limit=1" \
-H "apikey: YOUR_PUBLISHABLE_KEY"For Firestore, with the project ID and API key from your Firebase config:
curl "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/users?pageSize=1&key=YOUR_API_KEY"On our test app, the careless table answered with a row containing an email address. The protected one answered permission denied. For a table that holds anything private, you want an error or an empty list []. Real rows mean a stranger can read them too. Run it for every table, not just the obvious ones.
Fix. Turn on Row Level Security for every table and write a policy that ties each row to its owner, or tighten your Firebase rules the same way. Watch for the rule that only checks whether someone is signed in. Firebase’s docs flag auth != null on its own, because then any signed-in user can read everything, and in most apps anyone can sign up.
Go deeper: A Supabase RLS checklist has SQL that audits every table, policy, view and storage bucket. A Firebase Security Rules checklist has emulator-tested rules. CVE-2025-48757 explained covers the incident itself.
03
Every API route checks who is asking
Why. If your app has its own server, the browser fetches data from addresses like /api/orders. Those addresses are not part of the page. Anyone can call them directly, with no browser and no login. OWASP says it simply: “Access control is only effective when implemented in trusted server-side code.” Hiding a button for signed-out visitors doesn’t protect the route behind it. OWASP’s example of the mistake is an app that puts its access control in the front end, beaten by one curl command.
Check. First, list your routes. If you have the code, run this in the project folder:
# Run in your project folder
# 1. Next.js API routes
find . -path ./node_modules -prune -o -path ./.next -prune -o -name "route.[jt]s" -print
# 2. Express-style routes
grep -rnE "\.(get|post|put|patch|delete)\(\s*[\"'\`]/api" \
--include=*.ts --include=*.js --exclude-dir=node_modules --exclude-dir=dist .
# 3. Server Actions
grep -rnE --include=*.ts --include=*.tsx --include=*.js --include=*.jsx \
--exclude-dir=node_modules --exclude-dir=.next "[\"']use server[\"']" .Then ask each route that returns data for its data, with no cookie at all:
curl -i https://your-app.com/api/ordersOn our test app, the careless route answered 200 OK with the data. The one that checked for a session answered 401 Unauthorized. A route meant for signed-in people should give you the second.
Fix. Ask your builder to put a sign-in check at the top of every route that reads or changes user data. The prompt under the next item does this and the ownership check together.
Go deeper: Is my Replit app secure? walks through this for an Express server, and Is my v0 app secure? for Next.js route handlers.
04
Every record belongs to the person asking
Why. This is the one we’d bet on finding in a new app. A route checks that someone is signed in, then returns order 1042 to whoever asks, without checking that order 1042 is theirs. Change the number and you’re reading a stranger’s order. Security people call it an insecure direct object reference, or IDOR. OWASP’s API list ranks it first and says “every API endpoint that receives an ID of an object, and performs any action on the object, should implement object-level authorization checks.” Long random IDs make guessing harder, but they don’t replace the check.
Check. Make two test accounts, A and B. Create something as A, note its address or ID, then sign in as B and open it. You should get nothing. You can do the same from the terminal with B’s cookie:
# Signed in as account B: copy the Cookie header of any request
# from DevTools → Network. Then ask for something account A owns.
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Cookie: PASTE_ACCOUNT_B_COOKIE" \
https://your-app.com/api/notes/ID_THAT_BELONGS_TO_ACCOUNT_AOn our test app, a route that only checked for a login returned 200 for B, with A’s note. The careful route returned 404, and still returned 200 when B asked for B’s own note. If you have an admin page, sign in as an ordinary user and open its address too. A page that hides the admin menu isn’t a lock; the route behind it has to check the role.
Fix. The check belongs in the code that touches the record. Next.js’s data security guide shows the shape, and it works the same anywhere:
'use server'
export async function deletePost(postId: string) {
const session = await auth()
if (!session?.user) throw new Error('Unauthorized')
const post = await db.post.findUnique({ where: { id: postId } })
// Is it theirs? Logged in is not enough.
if (post.authorId !== session.user.id) throw new Error('Forbidden')
await db.post.delete({ where: { id: postId } })
}To have your builder apply it everywhere, describe who should see what in your own words:
Here is who should see what in this app: [describe it, e.g. "each customer sees and edits only their own orders; only I can change prices"]. Go through every API route, server function and Server Action. Make sure each one first checks that someone is signed in, then checks that the record it reads or changes belongs to them (or that they have the right role). Return 401 for no login and 404 for someone else's record. List every one you changed and the rule it now enforces.05
Server Actions and server functions check for themselves
Why. Next.js Server Actions, Supabase Edge Functions and similar server functions feel like part of the page. They aren’t. Next.js’s docs say an exported Server Action “is reachable via a direct POST request, not just through your application’s UI,” and that “a page-level authentication check does not extend to the Server Actions defined within it.” The same is true of a function URL that a page calls.
Check. This one is a code check, because testing an action from outside means sending it a POST, which you shouldn’t do to a live app. Use the third command in the route list above to find every "use server", and open each match. In each function, the first lines should find out who is calling and stop if it’s nobody, and the next should check that the record is theirs. Our scanner works the same way: it counts the Server Actions it can see in your page’s code and lists them as untested, because it never sends them a POST.
Fix. Same prompt as above. It covers Server Actions and server functions too.
Go deeper: Is my v0 app secure? has a tested Server Action with a Supabase ownership check. Is my Lovable app secure? and Is my Bolt app secure? cover Supabase Edge Functions that answer without a key.
06
Your framework version has no known advisories
Why. Some bugs aren’t yours at all. They’re in the framework, and the fix is an upgrade. Next.js has had several serious ones in the past two years. In March 2025, CVE-2025-29927 let attackers skip authorization checks that lived in middleware. Vercel’s postmortem adds that it doesn’t recommend middleware “to be the sole method of protecting routes.” In December 2025, CVE-2025-66478, part of what Vercel calls React2Shell, was rated CVSS 10.0 and could allow remote code execution. On 14 October 2026, Next.js shipped 16.4.1 and 15.5.28 to fix, among others, two critical remote code execution bugs, one of them in image optimization with SVG files (CVE-2026-96889). The support policy lists 16.x and 15.x as the only supported major versions; 14.x and older get no regular security patches.
We were running 15.5.27 ourselves when that October advisory came out, and moved lockship.dev to 15.5.28 three days later. That is the normal state of things. Being one patch behind isn’t a failure. Not knowing you are is.
Check. For a Next.js app, this prints the version your live site is running:
APP=https://your-app.com
curl -s "$APP/" | grep -oE '/_next/static/chunks/[^"]+\.js' | sort -u \
| while read -r f; do curl -s "$APP$f"; done \
| grep -oE 'window\.next=\{version:"[^"]+"'On lockship.dev it printed window.next={version:"15.5.28". Compare yours with the advisories above. For any app, and any framework, the package manager will list known advisories in your dependencies:
# In your project folder: known advisories in what your app ships with
npm audit --omit=devRead the output rather than just counting. When we ran it on LockShip while writing this, it reported nine (one moderate, eight high). All of them were in build tooling: the command-line part of a component library we only use for one stylesheet, and a copy of PostCSS that Next.js uses to process our own CSS. That made them low priority for us. They might not be for you, and you only know after reading each one.
Fix. Ask your builder to upgrade the framework to the newest patch release of the version line you’re on, and to tell you the old and new version numbers. After a critical remote code execution fix like React2Shell, Next.js also recommends rotating your app’s secrets.
Go deeper: Is my v0 app secure? covers the Next.js advisories and middleware in more detail.
07
Security headers are set, and the CSP does something
Why. Headers matter less than everything above, and a missing one won’t leak data by itself. They tell browsers how to protect your visitors: don’t let other sites frame this page, don’t guess file types, only use HTTPS, and only run scripts from places you trust. The last one is the Content Security Policy, which MDN describes as mainly a defense against cross-site scripting. OWASP keeps a cheat sheet with recommended values for each header.
Check.
curl -sI https://your-app.com | grep -iE "^(content-security-policy|content-security-policy-report-only|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy|permissions-policy|x-powered-by)"You want to see a content-security-policy, strict-transport-security, x-content-type-options: nosniff, and either x-frame-options or a CSP with frame-ancestors. Our careless test app sent none of them, plus X-Powered-By: Express.
We’ll be honest about our own result. When we ran this on lockship.dev while writing this guide, the security headers were there, but so was x-powered-by: Next.js. Next.js adds it by default, and OWASP recommends removing it. We switched it off in the same release as this page. Our CSP also still allows inline scripts with 'unsafe-inline', which MDN warns “defeats much of the purpose of having a CSP.” The Next.js CSP guide explains why we haven’t fixed that yet: the stricter, nonce-based policy needs every page to be rendered on each request, and ours are mostly static. It’s on our list.
Fix. Ask your builder to add these headers in your host’s config or your server, and to start the CSP as Content-Security-Policy-Report-Only. MDN explains that in report-only mode the policy “is not enforced,” but violations are reported. Watch the browser console for what it would have blocked, fix those, then switch to the real header.
08
CORS only lets in the sites you meant
Why. CORS headers decide which other websites’ JavaScript may read your API’s answers in a visitor’s browser. The dangerous version is an API that answers any site that asks, with cookies included. MDN’s CORS guide explains that when a server names the asking site in Access-Control-Allow-Origin and adds Access-Control-Allow-Credentials: true, the browser lets that site read the response, cookies and all. If your API copies back whatever origin it is sent, any website a signed-in user visits can read their data. OWASP lists this kind of misconfiguration under broken access control.
Two things from the popular Express cors package are worth knowing. Its default is origin: "*", any site. And origin: true reflects whatever origin the request names. Its docs also clear up the most common misunderstanding: “CORS is not access control. Any HTTP client (curl, Postman, another server) can call your API regardless of CORS settings.” CORS doesn’t replace checks 3 and 4.
Check. Pretend to be another website and see what your API says:
curl -s -o /dev/null -D - -H "Origin: https://evil.example" https://your-app.com/api/me \
| grep -iE "^access-control-allow-(origin|credentials)"Nothing printed is the best answer for an app whose pages and API share one address. That’s what lockship.dev returned. access-control-allow-origin: * on its own, which our careless test app sent, is fine for truly public data, because MDN says the browser won’t hand a response sent with cookies to another site when the answer is the * wildcard. The answer to worry about is https://evil.example echoed back together with access-control-allow-credentials: true. Our test route that reflected origins returned exactly that.
Fix. List the exact sites that need access, and nothing else. If your front end and API live on the same address, you may not need CORS at all.
09
Anything that costs money has a limit
Why. Vibe-coded apps often call paid services on every click: an AI model, an email sender, a text message. Without a limit, one script can run up a bill or drain a quota. OWASP’s API list has an example where a password-reset text costs $0.05, a script triggers it tens of thousands of times, and the company loses “thousands of dollars in a matter of minutes.” Sign-in forms need limits too, so nobody can try passwords forever.
Check. Pick one of your own read-only endpoints, like search or a list, and ask for it 30 times quickly:
# 30 quick reads of one of your own search or list endpoints. GET only.
for i in $(seq 1 30); do
curl -s -o /dev/null -w "%{http_code}\n" "https://your-app.com/api/search?q=test"
done | sort | uniq -cOn our test app, the endpoint without a limit answered 200 all 30 times. The one with a limit of ten answered 200 ten times and 429 Too Many Requests twenty times. Thirty 200s means nothing is slowing anyone down there. For sign-in, try a wrong password ten times on your own test account in the browser, and see whether the app ever makes you wait. Don’t point this loop at an endpoint that sends email, texts or calls an AI model, because each request will really cost you.
Fix. Ask your builder to add per-user and per-address limits on sign-in, sign-up, password reset, and anything that calls a paid service. Then set spending caps with each provider, which OWASP recommends and which a code bug can’t switch off. OpenAI, for example, lets you set monthly spend limits with hard enforcement, and notes that “spend alerts alone don’t stop API traffic.”
10
No source maps, .env or .git on the live site
Why. Some files should never be served. A source map turns your compressed JavaScript back into readable source, comments included. Vite turns them off for production by default, and so does Next.js, “to prevent you leaking your source on the client,” as its docs put it. One setting turns them back on. A .env file holds your keys, and a .git folder can hold your whole history, old keys included. OWASP’s advice is to make sure “file metadata (e.g., .git) and backup files are not present within web roots.”
Check.
APP=https://your-app.com
f=$(curl -s "$APP/" | grep -oE 'src="/[^"]+\.js"' | cut -d'"' -f2 | head -1)
for p in "$f.map" /.env /.env.local /.env.production /.git/HEAD /.git/config; do
curl -s -o /dev/null -w "%{http_code} %{content_type} $p\n" "$APP$p"
doneRead the first two columns. A 404 or 403 is what you want; lockship.dev returned 403 for the map and 404 for everything else. Many apps send their homepage for any address they don’t know, so a 200 with text/html is usually just that. A 200 with application/json for the map, or text/plain for .env or .git, means the file is public. Our careless test app served all three.
Fix. If the map is public, turn off production source maps in your build config. If .env or .git is public, your server is handing out the project folder instead of the built one. Fix that first, then rotate every key that was in the file, because you can’t know who downloaded it.
11
After launch, keep checking
Why. A clean run today says nothing about next week. Your builder will add routes, a teammate will add a table, and new advisories come out for packages you already use. None of the problems above stop an app from working, which is exactly why they get missed.
Check, on a schedule.
- Every time you add something that saves or shows data, repeat checks 2 to 5 for it.
- Turn on dependency alerts. If your code is on GitHub, Dependabot alerts you when a new advisory matches one of your packages. Its docs also note that “alerts can’t catch every security issue,” so keep reading release notes for your framework.
- Once a month, rerun checks 1, 6, 7 and 10 against the live site. Put it in your calendar. It takes ten minutes.
- Watch your bills. A sudden jump in AI or email usage can be the first sign a key leaked or a limit is missing.
What a clean run means
If you went through all eleven and found nothing, that’s good news. It means nothing obvious is open from the outside right now. It doesn’t mean the app is secure. These checks don’t send attack payloads, don’t test every route after sign-in, and can’t see your settings or your code’s logic. For an app that holds health, payment or other sensitive data, get a person to test it properly before you scale.
Where LockShip fits
We built LockShip because the outside-in half of this list is easy to forget and dull to repeat. You paste your app’s address and prove you own it. The scan then reads your app the way a stranger would. It downloads your homepage and a few pages linked from it, using GET only and skipping links that look like logout, delete or checkout. It follows the scripts they load, including screens that load later, reads any public source maps, and searches all of it for secret keys and database passwords. It tries /.env and /.git. It calls up to eight /api/ addresses named in your JavaScript, once each, with a plain GET and no credentials. It reads your headers, your cookie flags, your CORS answer to a made-up origin, your certificate, and for a Next.js app, the live version against known advisories. If your app uses Supabase or Firebase, it checks what their public keys can read.
What it doesn’t do matters as much. It never signs in, so it can’t do the two-account test in check 4. It never sends POST, PUT or DELETE, so Server Actions, write rules and anything that changes data are listed as not tested. It doesn’t test rate limits, doesn’t send attack payloads, and never sees your source code. It is a point-in-time, best-effort check, not a penetration test.
The summary is free: a score and the single worst thing we found. The $39 Launch Safe certificate gives you every finding with a fix prompt you can paste into your AI builder, a PDF, and seven days of re-scans while you fix things. A passing re-scan gives you a badge for your site. If you want the “keep checking” part handled, Monitor re-scans weekly for $9 a month, and the badge turns grey if a later scan fails.
Want to see what a stranger sees?
Paste your app’s address, prove you own it, and get the summary for free. Pay only if you want the full report.
Run a free scanBefore you rely on a result, read the disclaimer.
Questions people ask
Is my AI-generated app secure?
Nobody can tell you that from the outside, us included. What you can find out is whether it has the mistakes that keep turning up in AI-built apps: secret keys in the JavaScript, database tables a stranger can read, API routes and Server Actions that don’t check who is asking, an outdated framework, and files that should never be public. The checks on this page cover those, and most of them take a few minutes each.
What should I check first before launching a vibe-coded app?
Two things: whether a secret key ended up in the code your visitors download, and whether a stranger can read your database. Those are the failures behind the incidents on this page, including CVE-2025-48757 and the Firebase password leak, and both can be checked with one command each from your own terminal. Then knock on your API routes without signing in.
Do I need a penetration test before launch?
For most first launches, no. A paid penetration test makes sense once you hold data that would really hurt to lose, or a customer asks for one. Before that, the checks here catch the common, cheap-to-fix problems. They are not a substitute for a test by a person who tries to break your app on purpose, and we don’t claim they are.
Is the Supabase or Firebase key in my JavaScript a problem?
Not on its own. Supabase’s publishable (or legacy anon) key and Firebase’s web API key are meant to be in the browser. What protects your data is Row Level Security in Supabase and Security Rules in Firebase. A Supabase secret or service_role key in the browser is a real problem, because it skips Row Level Security entirely.
Are these checks safe to run on my live app?
Every command on this page sends GET or HEAD requests only, the same kind a browser sends when it opens a page, so they don’t change data. Run them only against an app you own. Don’t send POST, PUT or DELETE requests at a live app to see what happens, because those really run.
Every guide, by what you built with
This page is the overview. Each guide below goes deep on one tool or one kind of mistake.
- A Supabase RLS checklist for vibe-coded apps
Why the Supabase key in your bundle is normal but a table without Row Level Security is not, plus SQL you can paste to check every table, policy, view, and storage bucket.
- Is my Lovable app secure? Seven checks you can do yourself
A plain-language way to check a Lovable app before strangers do: which key your page ships, whether your tables open up to anyone, and what to look at in your functions, storage buckets, and headers.
- CVE-2025-48757 explained: what happened to Lovable apps, and how to check yours
What the Lovable and Supabase RLS CVE actually says, why a public anon key plus a missing rule opens your tables to strangers, five read-only checks for your own app, and what has changed since.
- A Firebase Security Rules checklist for vibe-coded apps
Why the Firebase API key in your bundle is fine but test-mode rules are not, plus emulator-tested Firestore, Realtime Database, and Storage rules that tie every read and write to the right user.
- Leaked API keys in your frontend bundle: how to find them and what to do
Which keys are fine in your JavaScript and which must never ship, how VITE_ and NEXT_PUBLIC_ quietly publish secrets, tested commands to search your build and live site, and how to rotate a leaked key without leaking the new one.
- Is my Bolt app secure? A plain check before you launch
What a stranger can see in an app built with Bolt.new, and eight checks you can do yourself: Bolt’s security audit, who each table is for, a read-only test of your database, keys in your page, server functions, file storage, sharing settings, and headers.
- Is my v0 app secure? A plain check before you launch
What a stranger can reach in a Next.js app built with v0, and eight checks you can do yourself: your live Next.js version, NEXT_PUBLIC_ variables, Server Actions, API routes, middleware, the database behind it, preview deployments and chat sharing, and headers.
- Is my Replit app secure? A plain check before you launch
What a stranger can reach in an app built with Replit Agent, and eight checks you can do yourself: Replit’s security scan, who your published app is open to, Secrets and VITE_ variables, API routes without a login, whether each record belongs to the person asking, your production database, stray files, and headers and cookies.
Sources
We checked each of these on 18 October 2026. We ran every command above against a small test app on our own machine, built to make each mistake on purpose with fake keys, and against lockship.dev. If something here disagrees with a provider’s docs, trust the docs and tell us at hi@lockship.dev.
- Thousands of vibe-coded apps expose corporate and personal data on the open web, WIRED (7 May 2026)
- Methodology: how we discovered over 2k high-impact vulnerabilities in apps built with vibe coding platforms, Escape (29 October 2025)
- 2025 GenAI Code Security Report, Veracode (October 2025 update)
- CVE-2025-48757, NIST National Vulnerability Database
- CVE-2025-48757, Matt Palmer (29 May 2025)
- Misconfigured Firebase instances leaked 19 million plaintext passwords, BleepingComputer (19 March 2024)
- A01:2025 Broken Access Control, OWASP Top 10
- API1:2023 Broken Object Level Authorization, OWASP API Security Top 10
- API4:2023 Unrestricted Resource Consumption, OWASP API Security Top 10
- Env variables and modes, Vite docs
- Environment variables, Next.js docs
- Best practices for API key safety, OpenAI Help Center
- API keys, Supabase docs
- Learn about using and managing API keys for Firebase, Firebase docs
- API keys, Stripe docs
- Row Level Security, Supabase docs
- Avoid insecure rules, Firebase docs
- Data security, Next.js docs
- Next.js security update, 14 October 2026, Next.js blog
- CVE-2025-66478, Next.js blog
- Authorization bypass in Next.js middleware (CVE-2025-29927), Next.js on GitHub
- Postmortem on Next.js middleware bypass, Vercel blog
- Support policy, Next.js
- Content Security Policy, MDN
- Content Security Policy, Next.js docs
- HTTP headers cheat sheet, OWASP
- poweredByHeader, Next.js docs
- Cross-Origin Resource Sharing (CORS), MDN
- cors (configuration options and common misconceptions), Express on GitHub
- Build options (build.sourcemap), Vite docs
- productionBrowserSourceMaps, Next.js docs
- About Dependabot alerts, GitHub docs