# Before you launch the app you built with AI — a complete pre-launch guide for non-engineers

> You built an app with Claude Code, Cursor, Lovable, v0 or Bolt, and you can't tell whether it's safe to launch. This guide lists what to check before you go live, ordered by what can actually hurt you. Every technical term is translated into plain language, and each item comes with how to fix it yourself plus an instruction you can paste into your AI tool.

- Published: 2026-08-06
- Author: 友田 陽大
- Tags: バイブコーディング, AI駆動開発, セキュリティ, 個人開発, Next.js
- URL: https://tomodahinata.com/en/blog/ai-app-pre-launch-guide-for-non-engineers
- Category: Running the app you built with AI

## Key points

- AI writes working code fast, but it does not implement 'who can see which data', 'nobody gets charged twice', or 'we can roll this back' unless you ask. Pre-launch incidents cluster almost entirely in those three areas.
- The order of danger is fixed. (1) Database permissions (RLS), (2) exposed secret keys, (3) server-side ownership checks, (4) duplicate payment processing — these four are binary, present or absent, not matters of degree.
- CVE-2025-48757 (CVSS 9.3 CRITICAL) is exactly this: missing RLS in AI-builder apps. The vendor disputed it on the grounds that protecting application data is the customer's responsibility — which is an official statement that the responsibility is yours.
- Veracode found security flaws in 45% of AI-generated code. GitGuardian measured AI-assisted commits leaking secrets at roughly twice the rate of human-only commits. This is structural, not a skill problem.
- The opposite of 'it works, so it's fine' is not 'rebuild it.' Work down a priority order and you can close the gaps without taking a live service offline.

---

"It works. I just don't know if it's safe to launch."

This is the sentence I hear most often from people who built an app with Claude Code, Cursor or Lovable. The instinct is correct. The reason, though, is probably not the one you have in mind.

The problem isn't "AI wrote the code, so it can't be trusted." It is simply this: **AI implements the features it is asked for, and does not implement the defences it isn't.** And someone building an app for the first time doesn't know what to ask for. Of course they don't — if they did, they could have written it themselves.

This guide lists the things that go unasked, ordered by what can actually hurt you. Every technical term is translated into plain language the first time it appears. Each item comes with a way to check it yourself and an instruction you can paste straight into Claude Code or Cursor.

The conclusion up front: there are **four** items you must close before launch. The rest can wait.

> There is a [free self-check](/vibe-coding/checkup) that turns this guide into 20 questions. No signup, and nothing you type leaves your browser. If you'd rather know where you stand before reading, start there.

---

## 1. Why "it works" and "it's safe" come apart — the structural reason

First, let's establish with measurements that this isn't just you.

Veracode tested more than 100 large language models across 80 coding tasks and found that **45% of cases introduced an OWASP Top 10 vulnerability**. Defences against cross-site scripting — where a script planted by someone else runs on your own site — failed in 86% of samples.

GitGuardian's 2026 study found that **commits written with AI assistance leaked API keys and passwords at roughly twice the rate of human-only commits**.

This is not the kind of problem a smarter model quietly removes. The reason is simple: **AI targets "does it run", not "can it be attacked."**

To put it in cooking terms: AI is extremely good at making the dish taste good, and doesn't think about food poisoning unless told to. And you cannot detect food poisoning by tasting.

### The opposite of "it works" is not "it's broken"

This is the part first-time builders find counterintuitive.

- **works / doesn't work** — you can determine this by trying it
- **safe / unsafe** — you cannot determine this by trying it

An app where other people's data is visible works flawlessly for as long as you use it with your own account. It never once looks broken. Not until someone changes one digit in a URL.

So before launch, you have to **deliberately try the bad things**. That is what this guide is.

---

## 2. The order of danger — what to close first

Listing everything at once is demoralising, so let's fix the order. The criterion is whether the damage can be undone.

| Rank | Item | What happens | Recoverable? |
| --- | --- | --- | --- |
| 1 | No database permissions (RLS) | Anyone can read and write every user's data | No (leaked data doesn't come back) |
| 2 | Secret keys exposed to the browser | Same as above, plus deleting and tampering | No |
| 3 | No server-side ownership check | Changing a number in the URL reveals someone else's data | No |
| 4 | Duplicate payment processing not prevented | Customers charged twice; goods granted twice | Refundable, but trust isn't |
| 5 | Legal gaps (personal data, commercial disclosures) | Non-compliance; payment provider review fails | Fixable |
| 6 | No cap on usage-based billing | A bill you didn't expect | Negotiable, painful |
| 7 | No backup or recovery path | You can't get back to a working state | Preparation is the only defence |
| 8 | No visibility into errors | Broken for months without anyone noticing | Fixable once noticed |

**Items 1–4 are the "before launch, without exception" set.** Items 5–8 are fine to handle right after launch.

Let's take them in order.

---

## 3. [Highest priority] Database permissions — what RLS actually is

### What this is about

**RLS (Row Level Security) is the database-side setting that decides who can see which rows.**

Think of a "row" as one row in a spreadsheet. Say you have a table of users. RLS is what makes A able to see only A's row, and B only B's.

### Why it's fatal

With a service like Supabase, queries go **from the browser directly to the database**. The `anon` (anonymous) key used for this is **public by design**. Open the browser developer tools and anyone can read it — and that is the normal, intended state.

The problem is that **without RLS, that public key alone is enough to read and write all your data**.

Which plays out like this:

1. An attacker opens your app
2. They read the database URL and `anon` key from the developer tools (both public information)
3. They query the database directly
4. Every table, for every user, comes back

Having a login screen is irrelevant. The login screen is a browser-side concern, and the database doesn't know about it.

### This has actually happened

The vulnerability registered as CVE-2025-48757 is described as:

> An insufficient database Row-Level Security policy in generated sites allows remote unauthenticated attackers to read or write to arbitrary database tables

Severity: **CVSS 9.3 CRITICAL**. That is the highest band. The affected product was the AI app builder Lovable.

What matters here is that **the vendor has disputed this CVE**, and why. Their position was that protecting application data is the customer's responsibility.

Which means — **the responsibility for protecting the data in the app you built is officially yours.** Do not expect the tool to cover it for you.

### How to check

If you're on Supabase:

1. Open the Supabase dashboard → Table Editor
2. Look for warnings on the table list such as "RLS disabled" or "Unrestricted"
3. Any table showing one is fully public

RLS being enabled isn't enough either. If the policy (the condition that grants access) is `USING (true)`, it means "everyone can see every row" — identical in effect to having it off.

### The instruction to paste into your AI tool

```text
Read every Supabase migration/SQL file and report the following as a table.
(1) Tables where RLS is not enabled
(2) Tables where RLS is enabled but no policy exists
(3) Tables whose policy condition effectively allows everyone, such as USING (true)

Then, for each table, write the RLS policy SQL that scopes rows to "only the
logged-in owner". For performance, write auth.uid() in the form
(select auth.uid()).
```

That last point, `(select auth.uid())`, is the standard performance idiom. Without it, the database can slow down dramatically as the number of rows grows.

> For more detail, see [What is Supabase "RLS"? — how AI-built apps end up with all their data public](/blog/supabase-rls-explained-for-non-engineers). To check whether the policy you wrote actually scopes anything, paste it into the [free RLS checker](/aegis/rls-checker).

---

## 4. [Highest priority] Secret keys — why the "API key" AI wrote is visible to others

### What this is about

Apps hold "keys" so they can use external services: databases, payments, AI. Put a key in the wrong place and it leaves with anyone who visits.

The most common mistake is **adding `NEXT_PUBLIC_` to a value that must never have it**.

In Next.js, when an environment variable's name starts with `NEXT_PUBLIC_`, **its value is inlined into browser-facing files at build time**. This is documented, intended behaviour, not a bug — it exists for values that genuinely need to run in the browser.

The problem is that AI, trying to make things run, sometimes applies that prefix to a secret key. It does make it run. It also makes it readable by anyone.

### The keys that matter most

Supabase issues two kinds of key:

- **`anon` key** — public by design. Subject to RLS. Fine in the browser.
- **`service_role` key** — **a key that bypasses every restriction**. Goes straight past RLS. Must never be in the browser.

If the `service_role` key leaks, configuring RLS correctly buys you nothing. Everything is bypassed. AI-generated code reaches for this key because **"it just works"** — you can fetch data without writing a single RLS policy, so no error appears.

### How to check

1. Search your project for `NEXT_PUBLIC_`
2. For each value that comes back, ask: "would I be fine with a stranger having this?"
3. If you hesitate at all, it's a value that needs to move

Also, if your code is on GitHub, check whether a `.env` file has been committed. **A key you commit once stays in the history even after you delete the file.** Deleting is not a fix — the key itself has to be reissued.

### The instruction to paste into your AI tool

```text
Search this entire project and check whether any secrets are stored in
environment variables prefixed with NEXT_PUBLIC_ (Supabase service_role key,
database passwords, secret keys for any API).

If you find any, move the value to a server-only environment variable and move
the code that reads it to the server side (Route Handler / Server Action).
List every place a client component references a secret.

Report only which file and line holds which kind of key — never print the
secret values themselves.
```

> The details are in [`NEXT_PUBLIC_` and `service_role` — why the "API key" AI wrote is visible to others](/blog/ai-app-api-key-exposure-explained).

---

## 5. [Highest priority] Ownership checks — "looks logged in" versus real permissions

### What this is about

This one is hard to explain, and the most frequently missed.

Suppose your app has a screen at `/orders/123`, where 123 is an order number. Now, **what happens if you change 123 to 124?**

If someone else's order appears, that is a vulnerability. OWASP — the standard body for web security — ranks it **number one** among API risks, the most common hole there is.

### Why it happens

Because it feels safe if there's no link to other people's data on screen. But **typing the URL directly still sends the request.** Whether a link exists is irrelevant.

Asked to "show a list of the logged-in user's orders," AI does filter the list properly. Asked to "show the order detail," it tends to write code that just looks up the number. And because the list is correctly filtered, **you will never notice while testing normally**.

### How to check — a browser is all you need

1. Create two test accounts (A and B)
2. Log in as A and create some data. Note the number (or ID) in the URL
3. Log out and log in as B
4. Type the noted URL straight into the address bar

**If B can see A's data, that's a failure.** If you get an error or "you don't have permission," it's working.

Anyone can do this within their own app, so do it once before launch.

### The instruction to paste into your AI tool

```text
Find every handler in this app that takes an ID and returns or updates data
(Route Handlers, Server Actions, APIs), and check one by one whether the server
verifies that the record belongs to the logged-in user.

Fix any that don't by adding an ownership check. Also report any place where
access is merely hidden in the frontend, since that is not a defence.
```

If RLS is configured correctly, this defence becomes two layers deep. Put another way: RLS and server-side checks are **both required, not either/or**.

> [Is the login AI built for you real? — "looks logged in" versus real authorization](/blog/ai-generated-login-vs-real-authorization) covers this in depth with worked examples.

---

## 6. [Highest priority] Payments — why double-charging happens

If your app has no payments, skip this section.

### What this is about

Stripe and other payment providers send your app a notification when a payment completes — a webhook. Your app receives it and hands over the goods, or increases a balance.

Here is the trap. **These notifications are delivered "at least once", which means the same one can arrive multiple times.** If the network hiccups, or your app is slow to respond, the provider decides it may not have arrived and resends. This is by design, and Stripe's official documentation says so.

An implementation that adds to a balance on every notification received will **grant twice**.

### Why AI misses it

Ask for "increase the balance when payment completes" and AI writes exactly that. It's a correct implementation. What it doesn't consider is "what if the same notification arrives twice" — because nobody asked.

And this defect never appears in testing. Locally the notification arrives once. It shows up in production, during your busiest hour.

### The fix — idempotency

The countermeasure has a name: **idempotency** — the property of producing the same result however many times you do it.

Concretely: store the event ID from the notification in the database, and when an already-processed ID arrives, do nothing and simply reply "received." It is not difficult. It is just forgotten.

On the payment platform I worked on, this design has kept **double-charges in production at zero**. This is not a technically advanced topic — the difference is only whether you decided to always do it.

### The instruction to paste into your AI tool

```text
Read this app's payment webhook handler and check whether the same event
arriving multiple times can be processed twice.

Add idempotency: store the event ID as a unique key and return early if it has
already been processed. Keep the record and the business change in a single
transaction, and return a status code that lets the provider retry on failure.

Also check (1) whether the amount uses a value sent from the client, and
(2) whether the webhook signature is verified.
```

> [Why double-charging happens when you let AI build payments, and how to prevent it](/blog/ai-app-payment-double-charge-prevention) also covers amount tampering and signature verification.

---

## 7. Code that fails quietly — it runs, but it's wrong

From here on these aren't "before launch, without exception," but they bite if left alone.

The most common defect in AI-generated code is not, in fact, a vulnerability. It is **errors in error handling and business logic**. What makes them awkward is that **nothing errors**.

- Totals are slightly off (a boundary date isn't included)
- Search results drop entries (order is non-deterministic, so pagination loses rows)
- Something failed but the screen says it succeeded (the exception was swallowed)

All of these look normal on screen. The numbers are just a little wrong. So nobody notices for months.

The countermeasure is to state the conditions that must never break, up front. "The total always equals the sum of the line items." "The same search run twice returns the same order." Write down obvious things like these and have the AI turn them into tests.

> Covered in ["It runs, but it's wrong" — the quietest and most common AI-generated bug](/blog/ai-generated-code-silent-bugs).

---

## 8. Money and law — two easily missed items

### Usage-based billing

Cloud services and AI APIs bill for what you use. With unexpected traffic or an accidental loop, **the bill spikes easily**. Surprise bills in the thousands are not rare for solo builders.

Three things to do right after launch: turn on billing alerts for each service, set a per-key spending limit on AI APIs, and add rate limiting in the app — a mechanism that caps repeated requests from the same visitor.

> [The bill spiked after launch — usage-billing incidents and how to cap them](/blog/ai-app-cloud-bill-spike-prevention)

### Legal

If you hold even one email address, **publishing a privacy policy is an obligation** under Japan's personal data protection law. "I'm an individual" and "it's free" are not exemptions.

If you charge, you also need the **commercial-transaction disclosures** — in Japan, the notice required by the Specified Commercial Transactions Act. The seller, contact details, price, payment timing, and refund terms are all required by law. Stripe's onboarding review can also fail if this is missing.

> [Personal data, commercial disclosures and privacy policy — the legal check before selling an AI-built app](/blog/ai-app-legal-checklist-japan)

---

## 9. Can you get back? — the operational minimum

Finally, three things that matter after launch.

**Backups.** Asking an AI to "tidy up this table" can delete data you didn't mean to lose. Free tiers often have short retention, or no automatic backups at all. "By the time I noticed, the recovery window had closed" does happen.

**Rollback.** When a deploy breaks things, do you know how to get back to the previous version? Hours of downtime in the middle of the night is preventable by trying the procedure once. Note that database schema changes, unlike code, do not roll back automatically.

**Error visibility.** The most common failure for solo builders is not noticing. A contact form broken for months genuinely happens. Errors that fail in the browser never reach your server logs at all. Adding one error-monitoring service with a free tier changes the picture substantially.

> If you're thinking as far as handover, see [Preparing to consult or hand over to an engineer](/blog/handing-over-ai-app-to-engineer).

---

## 10. Pre-launch checklist (printable)

Top to bottom, one at a time.

**Before launch, without exception**

- [ ] RLS is enabled on every table, with policies scoped to "the owner's rows only"
- [ ] Writes (INSERT/UPDATE) are restricted too (`WITH CHECK`)
- [ ] No `service_role` key or database password sits in a `NEXT_PUBLIC_` variable
- [ ] `.env` is not committed to GitHub
- [ ] Any key that was ever visible to others has been reissued
- [ ] Verified with a second account that changing the URL number does not expose another user's data
- [ ] Admin features verify permissions on the server
- [ ] (If you have payments) A duplicate webhook cannot be processed twice
- [ ] (If you have payments) The amount is decided on the server
- [ ] (If you have payments) The webhook signature is verified

**Right after launch**

- [ ] Published a privacy policy
- [ ] (If paid) Published the commercial-transaction disclosures
- [ ] Set billing alerts and caps on every service
- [ ] Confirmed whether backups exist and their retention period
- [ ] Tried the rollback procedure once
- [ ] Added error monitoring, or made failures of critical operations reach you
- [ ] Confirmed on the production URL that signup and password reset actually work

> There's a [free self-check](/vibe-coding/checkup) that turns this checklist into 20 questions, orders the result by severity, and hands you fix instructions to paste into your AI tool. No signup, about five minutes.

---

## Summary — building it this way was not the mistake

One last thing.

Building an app with generative AI was entirely the right call. I write production products with Claude Code every day. It is genuinely fast and cheap, and there is no reason to avoid it.

But **shipping fast and shipping safely are two different jobs.** AI got dramatically better at the first. The second is still ours.

And the second takes less time than you'd think. Closing the four items in this guide — RLS, exposed keys, ownership checks, payment idempotency — prevents nearly every catastrophic incident. You don't have to rebuild anything.

If an item is unclear, start by pasting that section's instruction into your AI tool. Bring in a human only for what's left — particularly authorization design, payments, and handling personal data.
