The GIMS gnome
Evan Brown
Systems / Solutions Architect
Sample reportDog-Ear Exam Prep is a test app I built to copy the mistakes behind real incidents. It isn’t a client, and no real people are in it.
Vibe Code Audit · Launch Check report

Dog-Ear Exam Prep, a test app

Fix #1 to #6 before launch, because a stranger can grade exams, delete students and read private messages, and a student can give themselves Pro. Fix #7 and #8 soon after, while the Stripe webhook, the exam attempts and the public Supabase key came back clear.

Each of the five checks found something.

  • Logins1 finding

    The dashboard’s server code checks for the instructor, but the database functions behind it let strangers in (#1).

  • API keys1 finding

    The OpenAI key ships to the browser (#5), while the Supabase key there is the publishable one, meant to be public.

  • Payments1 finding

    The webhook checks Stripe’s signature and checkout takes only the app’s own prices, but the success page gives anyone Pro (#4).

  • Data access3 findings

    Two tables have no row-level security (#2) and two rules let anyone in (#3, #7), though each student sees only their own exam attempts.

  • When something fails2 findings

    The grade export opens up when its token check fails (#6), and the agent is told to run a command that would wipe the live database (#8).

Eight findings, ranked by risk, each tied to the real incident it copies.

#1HighLogins

A signed-out stranger can grade exams and delete students.

supabase/migrations/20260315000000_admin_tools.sql, lines 16 and 28

It’s the same mistake as an exam app featured by Lovable in February 2026, whose admin check was written backwards. It blocked signed-in users and let strangers in, and the researcher counted about 18,700 user records open.

What happens

Both admin functions turn away signed-in users and let everyone else through, and anyone can call them with the public key.

What it costs

You’d have grades nobody can trust, and students deleted with all their work.

15    -- Block regular users; only the admin dashboard may call this
16    if auth.role() = 'authenticated' then

Evidence

  • On my copy, a signed-out stranger raised a 62 to 100 and deleted a student, while a signed-in student got “Access denied”.

The fix

Let only the server call these functions, and revoke everyone else’s right to call them.

if auth.role() is distinct from 'service_role' then
  raise exception 'Access denied';
end if;

Checked on a copy: the stranger and the student both get “permission denied”, and the dashboard still grades, 71 to 88.

#2HighData access

Private messages and API tokens are open to anyone.

supabase/migrations/20260301000000_init.sql, lines 64 to 79

It’s the same mistake as Moltbook, which went viral in January 2026 with its database open to anyone. Wiz counted 1.5 million API keys, 35,000 email addresses and thousands of private messages anyone could read, all because one setting was off.

What happens

These two tables never got row-level security, so the public key in every visitor’s browser can read and change them, whatever the pages ask for.

What it costs

It exposes every message between students and the instructor, and the token behind #6.

64    create table public.messages (
 …
73    create table public.api_tokens (

Evidence

  • The scan listed both tables, and on my copy a stranger read 2 of 2 messages and 1 of 1 tokens, then rewrote both messages.

The fix

Turn row-level security on for both, with rules that limit messages to the two people in them and tokens to their owner.

alter table public.messages enable row level security;
create policy "Participants read their messages" on public.messages
  for select to authenticated using (auth.uid() in (sender_id, recipient_id));

Checked on a copy, with tokens done the same way: the stranger gets nothing, and student 1 still reads their own 2 messages.

#3HighData access

Anyone can read every student’s name, email and phone number.

supabase/migrations/20260301000000_init.sql, line 14

It’s the same mistake as in CVE-2025-48757, where researchers checked 1,645 apps on an AI app builder’s showcase in 2025 and found 170 in which strangers could read users’ names, phone numbers and payment details. The builder disputes the CVE.

What happens

Row-level security is on, which passes a quick look, but the rule under it lets everyone read every row, signed in or not.

What it costs

Your whole student list, with contact details, is open to anyone who asks.

14    create policy "Enable read access for all users"
        on public.profiles for select using (true);

Evidence

  • The scan flagged line 14, and a stranger read all 4 profiles on my copy.

The fix

Drop that rule, and let each person read their own profile and the instructor read everyone’s.

create policy "Read your own profile" on public.profiles
  for select to authenticated using (auth.uid() = id or public.is_admin());

Checked on a copy: the stranger reads none, student 1 reads only their own, and the instructor reads all 4.

#4HighPayments

Students can give themselves Pro, or make themselves the instructor.

src/pages/UpgradeSuccess.tsx, line 8, and init.sql, line 15

It’s the same harm as in CVE-2025-48757, where researchers marked an order “paid” on one app without paying. In March 2025, people were also bypassing the subscription on a solo founder’s app.

What happens

The success page turns on Pro for whoever opens it, and the profile rule lets each user change any column of their own row, including the role the dashboard checks.

What it costs

Students get the AI tutor free on your OpenAI bill, and any of them can take the instructor’s powers.

UpgradeSuccess.tsx
 8    if (user) supabase.from("profiles").update({ subscription_status: "active" })…
init.sql
15    create policy "Users can update their own profile" … using (auth.uid() = id);

Evidence

  • On my copy, a signed-in student made themselves Pro and then the instructor, after which they could see every student’s attempts.

The fix

Take the update out of the success page, so only the verified webhook sets the plan, and let people change only their name and phone.

revoke update on public.profiles from anon, authenticated;
grant update (full_name, phone) on public.profiles to authenticated;

Checked on a copy: both changes now get “permission denied”, and saving a name and phone still works.

#5HighAPI keys

The OpenAI key ships to every visitor’s browser.

.env, line 4, and src/lib/ai.ts, lines 5 and 6

It’s the same symptom as a solo founder’s app, whose API keys were maxed out in March 2025, two days after he posted that AI wrote all of it. He never said where the keys were.

What happens

Anything named VITE_ is built into the JavaScript every visitor downloads, so anyone can copy this key and spend on your OpenAI account.

What it costs

A stranger can run up your OpenAI bill at whatever pace they like.

.env
 4    VITE_OPENAI_API_KEY=sk-proj-****
src/lib/ai.ts
 5      apiKey: import.meta.env.VITE_OPENAI_API_KEY,
 6      dangerouslyAllowBrowser: true,

Evidence

  • The scan found the key in a tracked .env, under a browser name, and in commit 5369d97, and an offline build put it in the JavaScript the app ships.

The fix

Rotate the key, since the old one stays in the history, then move the tutor’s call to a Supabase Edge Function that keeps the key secret and checks for Pro.

This fix wasn’t run on the copy, because rotating the key happens in your OpenAI account.

#6HighWhen something fails

The grade export hands over every grade when its token check fails.

supabase/functions/export-grades/handler.ts, lines 4 to 8

It’s the same kind of bug as at METR, an AI research nonprofit, where a vibe-coded dashboard quietly switched its login off in March 2026. A stranger then used its AI key for three weeks, about $600,000 worth of donated credits.

What happens

A wrong token, or none, makes the lookup fail, and the catch on line 8 just logs it and carries on, so the export hands over every grade.

What it costs

Every student’s grades go to anyone who finds its address.

 6    if (error) throw error;
 7    if (!data) return new Response("Bad token", { status: 401 });
 8  } catch (e) { console.error("token check failed,
        continuing so the sync doesn't break", e); }

Evidence

  • The scan listed line 8 as a catch that only logs, and a test I wrote on a copy passed only 1 of 3 cases, since a wrong token and no token both got every grade.

The fix

Make it fail closed, so no token means no grades, and fix #2 as well, because anyone can read the token today.

if (error || !data) return new Response("Bad token", { status: 401 });

Checked on a copy: with the fix, all 3 cases pass.

#7MediumData access

Anyone can add questions to the bank students study from.

supabase/migrations/20260301000000_init.sql, line 47

It’s the same symptom the founder in #5 reported, with junk filling his database.

What happens

The public landing page’s suggestion box writes directly into the question bank, and its rule lets anyone add rows, with no review.

What it costs

Practice exams can fill with junk that someone has to clear out by hand.

47    create policy "Anyone can suggest a question"
        on public.questions for insert with check (true);

Evidence

  • The scan printed line 47, and on my copy a stranger added 1,000 questions to an exam in one go.

The fix

Drop that rule, let only the instructor add questions, and send suggestions to a table of their own for review.

Checked on a copy: row-level security now refuses the stranger’s rows.

#8MediumWhen something fails

A command the coding agent is told to run would wipe the live database.

package.json, line 9, and AGENTS.md, line 5

These are the same conditions as in two real deletions, by Replit’s AI agent in July 2025 and by an AI coding agent at PocketOS in April 2026. Each wiped a live database, and both got their data back.

What happens

The agent’s notes say to run the reset when the schema gets messy, and it wipes the linked Supabase project, which here is the live one.

What it costs

You’d lose every account, attempt and message for as long as a restore takes, if one works.

package.json
 9    "db:reset": "supabase db reset --linked",
AGENTS.md
 5    - If the schema gets messy, run `npm run db:reset` to start fresh.

Evidence

  • The scan flagged the reset on line 9 and the note that tells the agent to run it. I didn’t run it, on purpose.

The fix

Build against a separate Supabase project, take the reset out of the notes, and keep backups outside the live project. No check can stop an agent from acting, so this one reduces the damage.

On the call, I’d ask where your backups live and when you last restored one.

You also get a tour of your code.

It’s one HTML file that opens in any browser, with no server or account. This one has 18 stops, built with no AI model, so they show the code and how it fits together without written explanations.

repo-tour’s tour of the test app at stop 7 of 18: the file supabase/functions/export-grades/handler.ts, with lines 1 to 6 highlighted, the file tree on the left, and the tour panel on the right.
repo-tour on this test app · stop 7 of 18the grade export from #6

We go through it together, and then you decide.

On a 20-minute call I’d walk you through #1 to #6 and answer your questions. Then you can fix it yourself from this report, or have me fix it in a Rescue Sprint at one fixed price between $1,500 and $2,500, with your $299 credited toward it.

How this report was made

  • The Launch Check’s scan read the code and its 4 commits with no AI and no network, then Claude Code read every file against the five checks, and every finding except #8 was confirmed by running it on a copy, since running #8 would wipe the database.
  • With no live Supabase project, the database ran in Postgres 16 on my machine with a stand-in for Supabase’s roles, and every number above comes from that copy.
  • There are no test replays, since the app has no tests.
  • Not checked: rate limits, settings that live only in the Supabase or Stripe dashboards, and anything outside the five checks. A clean result means those five passed, and it doesn’t mean nothing else can go wrong.
Vibe Code Audit · Launch Check $299

Would you like this report for your own app?

Book a free 20-minute check, and tell me what you built and when you launch.