Launch-Ready Report — demo-shop (lab audit)
Forthbeam Studio · Lab audit of our own test app · October 2026
Stack: React (Vite) · Supabase (auth, Postgres, Edge Functions) · Stripe Checkout
Reviewed: source repository, database migrations, Edge Function, and a local production build. The app was not deployed, so hosting-level checks are marked not assessed.
cd audit && npm install && npm run evidence.Summary
demo-shop is not ready to take real payments. Anyone can read every customer's order, email and address with the public key, and the Stripe secret key ships inside the website's JavaScript. Both are fixable in under a day. After the critical and high fixes, it is in reasonable shape for a small launch.
| Severity | Count | What it means |
|---|---|---|
| Critical | 2 | Fix before taking any real user data or payments |
| High | 2 | Fix before launch |
| Medium | 3 | Fix in the first weeks after launch |
Of the 13 checks we could assess from the code, 6 pass. 6 checks need a live deployment and 1 does not apply.
Findings
Orders readable without login
- Where
supabase/migrations/0001_init.sql, tablepublic.orders- Impact
productsandprofileshave row-level security;ordersdoes not. With Supabase's default grants, anyone holding the public anon key (shipped in every page load) can list every order, including customer emails and delivery addresses. Signed-in customers can also read each other's orders.- Evidence
- in a Postgres instance with Supabase's roles and grants, a logged-out (
anon) query returned all 3 seeded orders with emails and addresses; a signed-in customer could read another customer's order. (audit/tests/c1-orders-rls.test.ts) - Fix
audit/fixes/0002_orders_rls.sqlenables RLS; customers read and create only their own orders, new orders must start aspending, admins read all. After the fix: anon sees 0 orders, a customer sees only their 2, the admin sees all 3, and forged inserts are rejected.- Effort
- 1 hour
Stripe secret key shipped to the browser
- Where
src/lib/checkout.tsreadsVITE_STRIPE_SECRET_KEY- Impact
- Vite copies every
VITE_variable into the public JavaScript. Anyone can open the site, copy the key, and issue refunds, read customer data or create charges on the Stripe account. - Evidence
- a production build with a test key placed it in plain text in
dist/assets/index-*.js. - Fix
- move Checkout Session creation into an Edge Function that holds the key server-side; rotate the key in Stripe.
- Effort
- 2–3 hours, together with H2
Payment webhook accepts forged events
- Where
supabase/functions/stripe-webhook/handler.ts- Impact
- the handler never checks the
Stripe-Signatureheader. Anyone who finds the URL can send a fake "checkout completed" event and mark an unpaid order as paid. - Evidence
- an unsigned forged event returned
200and marked the order paid. (audit/tests/h1-webhook-forgery.test.ts) - Fix
- verify the signature with the webhook signing secret (
stripe.) before reading the payload; record processed event IDs.webhooks. constructEventAsync - Effort
- 1–2 hours
Checkout trusts the price computed in the browser
- Where
src/lib/cart.ts,src/lib/checkout.ts- Impact
- prices live in the cart in
localStorageand the browser sends the total to Stripe. A customer can edit the cart and pay any amount. - Evidence
- a cart edited to 1 cent produced a Stripe
unit_amountof1for a $32.00 product. (audit/tests/h2-client-price.test.ts) - Fix
- send only product IDs and quantities to the server; look up prices in the database (or use Stripe Price IDs) when creating the session and the order.
- Effort
- 1–2 hours, alongside C2
Admin page protected only in the interface
- Where
src/App.tsx,src/pages/Admin.tsx- Impact
- the "Admin" link is hidden from non-admins, but
/adminis a normal route and the page queries all orders without checking the role. Before C1 was fixed, any visitor could open it and see every order. After C1, the database protects the data, but the page still relies on that single rule. - Evidence
- —
- Fix
- add a route guard that checks
is_admin, and keep admin-only data behind database policies (now in place) or a server function. - Effort
- 1 hour
No error tracking
- Where
- —
- Impact
- failed checkouts, webhook errors and front-end crashes are invisible until a customer complains.
- Evidence
- no error-tracking package or setup in the app.
- Fix
- add an error tracker (a free tier is enough) to the front end and the Edge Function, with alerts on new errors.
- Effort
- 1 hour
No automated tests on checkout or the webhook
- Where
- —
- Impact
- every change to payments is tested by hand, or by customers. C1, H1 and H2 would each have been caught by a short test.
- Evidence
- no test files and no
testscript in the app. - Fix
- keep the audit's tests as a starting suite and run them in CI on every push.
- Effort
- 1–2 hours
Scorecard
| Area | Check | Result |
|---|---|---|
| Secrets & access | Keys in the client bundle | Fail · C2 |
| Auth on every API route | Pass | |
| Admin pages locked down | Fail · M1 | |
| Environment separation | Not assessed | |
| Data protection | Row-level security on every table | Fail · C1 (fixed) |
| Input validation | Pass | |
| File upload limits | Not applicable | |
| CORS and rate limits | Not assessed | |
| Payments | Webhook signature checks | Fail · H1 |
| Idempotent order updates | Pass | |
| Server-side price checks | Fail · H2 | |
| Failed-payment handling | Pass | |
| Reliability | Error tracking | Fail · M2 |
| Useful logs | Pass | |
| Vulnerable dependencies | Pass (npm audit: 0) | |
| Email deliverability | Not assessed | |
| Deploy & recovery | Backups you can restore | Not assessed |
| Tests on signup and checkout | Fail · M3 | |
| A repeatable deploy | Not assessed | |
| A way to roll back | Not assessed |
Fix plan
- Done in this audit: C1.
- Before launch: C2 and H2 together (one server function), then H1.
- First two weeks: M1, M2, M3.
Example Fix Sprint quote for C2, H1, H2 and M1–M3: $900 fixed, 4 working days, including tests on checkout and the webhook in CI, and a handover video. The audit fee is credited if booked within 14 days.
Raw output: audit/out/evidence.txt.
Reproduce it yourself · cd audit && npm install && npm run evidence