# Launch audit: the client

Scope consented by the owner on 2026-10-10. Only the consented repo paths and URLs were checked.

| Severity | Count |
| --- | --- |
| critical | 2 |
| high | 5 |
| medium | 2 |
| low | 1 |

Heuristic findings need a human look before anyone fixes them; they are marked (check).

## 1. [critical] Environment file committed to the repo

- Where: `.env`
- Why it matters: Real env files belong in the host's secret store, not git.
- Fix: Delete it from git history, rotate every key in it, add .env* to .gitignore.

## 2. [critical] Service-role key referenced from client code

- Where: `src/components/SignupForm.tsx`
- Why it matters: A service-role key bypasses row-level security for anyone who opens dev tools.
- Fix: Move the call behind a server route that checks the user.

## 3. [high] Record addressed by id with no owner check (IDOR candidate) (check)

- Where: `pages/api/invoices/[id].ts`
- Why it matters: Changing the id in the URL may return someone else's record.
- Fix: Load the record with the caller's user or tenant id in the same query; verify by hand.

## 4. [high] Secret-looking variable exposed to the browser

- Where: `src/components/SignupForm.tsx:4`
- Why it matters: NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY is bundled into client code by its public prefix.
- Fix: Rename without the public prefix and read it only on the server.

## 5. [high] API handler without rate limiting (check)

- Where: `pages/api/invoices/[id].ts`
- Why it matters: Unlimited calls invite brute force, scraping and surprise bills.
- Fix: Add a per-IP and per-user limiter in front of the handler.

## 6. [high] Table invoices has no row-level security

- Where: `supabase/migrations/0001_init.sql`
- Why it matters: Without RLS, any key that reaches the database can read or write every row.
- Fix: ENABLE (and FORCE) ROW LEVEL SECURITY and add per-user policies.

## 7. [high] Request body used without server-side validation (check)

- Where: `pages/api/invoices/[id].ts`
- Why it matters: Client-side checks can be skipped; the server must validate every field.
- Fix: Parse the body with a schema (zod, pydantic) and reject unknown fields.

## 8. [medium] Public forms without CAPTCHA

- Where: `src/components/SignupForm.tsx`
- Why it matters: Signup, login and contact forms get abused by bots.
- Fix: Add Turnstile or hCaptcha and verify the token on the server.

## 9. [medium] Wildcard CORS

- Where: `pages/api/invoices/[id].ts`
- Why it matters: Any website can call this API from a visitor's browser.
- Fix: Allow only your own origins; never combine * with credentials.

## 10. [low] No JavaScript lockfile found

- Where: `.`
- Why it matters: Without a lockfile, installs are not reproducible and cannot be audited.
- Fix: Commit a lockfile.

