example report: a made-up app, real patterns

This is what the $79 Launch Check looks like for a dog-walking app built with Lovable. Your free score shows the totals and the most serious issue; the full report shows all of this, checked by a developer.

Launch report

pawwalk

Built with LovableReact + ViteSupabaseStripeOpenAI
critical
2
high
2
medium
1
low
1

Every finding confirmed: 4 by a developer, 2 read straight from the code. Two of them would let a stranger read customers' home addresses or get walks for free today.

41

not ready to launch yet

Who can see and change your data

2 of 4 tables need attention

The key your website uses to talk to Supabase is public. These rules are the only thing deciding who gets in.

TableAnyone (not logged in)Any logged-in userRisk
bookings
read: everything add: everything change: everything delete: everything
read: everything add: everything change: everything delete: everything
openno RLS
profiles
read: no add: no change: no delete: no
read: everything add: own rows change: own rows delete: no
check
walks
read: no add: no change: no delete: no
read: own rows add: own rows change: own rows delete: no
ok
reviews
read: everything add: no change: no delete: no
read: everything add: own rows change: own rows delete: own rows
ok

Everything we found

criticalAnyone can read every booking, including home addressesData access✓ confirmed

What this means

The bookings table has no access rules. With the public key from your website, anyone can download every booking: owner names, home addresses, phone numbers and the times nobody is home.

Where

  • supabase/migrations/20260912101500_bookings.sql:3
    create table public.bookings (id uuid primary key, owner_id uuid, address text, phone text, ...

How to fix it

paste into Lovable, Bolt, Cursor or Claude Code

Enable row level security on public.bookings. Add policies so an owner can read and change only rows where owner_id = auth.uid(), and a walker can read only rows where walker_id = auth.uid(). Do not add any policy for the anon role. Show me the SQL migration before applying it.

Technical details for a developer

Row level security is not enabled on public.bookings, so the anon and authenticated roles have full SELECT, INSERT, UPDATE and DELETE through the Supabase REST API. The anon key is in the JavaScript bundle by design.

criticalAnyone can mark a booking as paid without payingPayments✓ confirmed

What this means

Your payment webhook believes whatever it is sent. Someone can send it a fake "payment succeeded" message and get walks for free, or mark other people's bookings as paid.

Where

  • supabase/functions/stripe-webhook/index.ts:12
    const event = await req.json();

How to fix it

paste into Lovable, Bolt, Cursor or Claude Code

In supabase/functions/stripe-webhook/index.ts, read the raw body with await req.text() and verify it with stripe.webhooks.constructEventAsync(body, req.headers.get("stripe-signature"), Deno.env.get("STRIPE_WEBHOOK_SECRET")). Return 400 if verification fails. Only then update the booking. Keep verify_jwt off for this function.

Technical details for a developer

stripe-webhook reads the request body with req.json() and never calls stripe.webhooks.constructEventAsync with the stripe-signature header and STRIPE_WEBHOOK_SECRET. verify_jwt is off for this function (correct for Stripe), so the signature check is the only protection.

highAnyone can run up your OpenAI bill through the walk summaryBill shock✓ confirmed

What this means

The function that writes "how was the walk" summaries can be called by anyone, as often as they like, with any text. Each call is charged to your OpenAI account.

Where

  • supabase/functions/walk-summary/index.ts:9
    const { notes } = await req.json(); const r = await openai.chat.completions.create({ model, messages })

How to fix it

paste into Lovable, Bolt, Cursor or Claude Code

In supabase/functions/walk-summary/index.ts: get the user with supabase.auth.getUser() from the Authorization header and return 401 if there is none. Check the walk belongs to that user. Allow at most 20 summaries per user per day (count rows in a usage table). Set max_tokens: 400 on the OpenAI call.

Technical details for a developer

walk-summary accepts the public anon key (verify_jwt only checks that a valid JWT is present; the anon key is one), never calls supabase.auth.getUser(), has no per-user limit and does not set max_tokens.

highAny user can make themselves an adminLogins & roles✓ confirmed

What this means

Users are allowed to edit their own profile, and the profile is where the app stores who is an admin. So anyone can sign up and give themselves admin rights.

Where

  • supabase/migrations/20260914093000_profiles.sql:18
    create policy "own profile" on public.profiles for update using (auth.uid() = id);

How to fix it

paste into Lovable, Bolt, Cursor or Claude Code

Stop users changing profiles.role. Revoke UPDATE on that column (revoke update (role) on public.profiles from authenticated) or move roles to a separate user_roles table that only the service role can write. Keep users able to edit their name and photo.

Technical details for a developer

The UPDATE policy on public.profiles is using (auth.uid() = id) with no column restriction, and the admin pages check profiles.role = 'admin'. A user can PATCH their own row with role=admin.

mediumNo security headers on the websiteLaunch checklist✓ confirmed

What this means

Your hosting sends no security headers, so other sites can show your app inside a frame (used for click tricks) and the browser has no rules for which scripts may run.

Where

  • netlify.toml

How to fix it

paste into Lovable, Bolt, Cursor or Claude Code

Add a [[headers]] block for "/*" in netlify.toml with X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin, X-Content-Type-Options: nosniff and a Content-Security-Policy that allows only my own scripts plus Supabase and Stripe. Test checkout and login afterwards.

Technical details for a developer

netlify.toml has no [[headers]] block: no Content-Security-Policy, X-Frame-Options/frame-ancestors, Referrer-Policy or Strict-Transport-Security.

lowReferral codes can be guessedCode✓ confirmed

What this means

Referral codes are made with a function that is not random enough, so someone could guess valid codes and collect free walks.

Where

  • src/lib/referrals.ts:7
    const code = Math.random().toString(36).slice(2, 8);

How to fix it

paste into Lovable, Bolt, Cursor or Claude Code

Generate referral codes with crypto.getRandomValues (or gen_random_uuid() in the database) and limit redemption attempts per user to 10 per hour.

Technical details for a developer

Math.random().toString(36).slice(2, 8) is not cryptographically random and gives about 2 billion values; codes are not rate limited.

Questions

What is in a Redproof report?

A launch-readiness score, a table of who can read and change each database table, and every problem found in plain words with the exact file and line, technical details for a developer, and a fix prompt you can paste into Lovable, Bolt, Cursor or Claude Code.

What do I see for free?

The score, how many critical, high, medium and low issues there are, and the most serious issue in full. The $79 report shows everything, checked by a developer.

Is this a real app?

No. Pawwalk is made up so we can show a full report without exposing anyone. Every finding is a pattern we regularly see in real apps built with AI tools.

See what your app's report says.

The score takes about a minute and costs nothing. Public GitHub repo or a zip of your project.

Get my free score