Scope: repository taskly (Next.js 15, Supabase, 4.1k lines), Supabase project policies (read-only).
Package: Audit + Fix PRs. Method: automated checks, agent code review, human verification of every finding.
| # | Finding | Severity | Fixed in |
|---|---|---|---|
| 1 | todos table has no Row Level Security | CRITICAL | PR #1 |
| 2 | OpenAI key shipped to the browser via NEXT_PUBLIC_OPENAI_API_KEY | CRITICAL | PR #2 |
| 3 | deleteTodo server action deletes any user's todo | HIGH | PR #3 |
| 4 | Share page shows any user's list by incrementing the id | HIGH | PR #3 |
| 5 | No rate limit on /api/suggest (calls OpenAI) | MEDIUM | PR #4 |
| 6 | Todo notes rendered with dangerouslySetInnerHTML | MEDIUM | PR #4 |
Most urgent: findings 1 and 2. Together they let anyone read every user's todos and spend money on the owner's OpenAI account, using only what the browser already downloads. Rotate the OpenAI key today, even after merging PR #2.
todos table has no Row Level Security CRITICALWhere: supabase/migrations/0001_init.sql:3; confirmed in the live project policies.
What happens: Supabase exposes every table through its REST API. The anon key is public by design and protected only by RLS. With RLS off, the anon key alone reads and rewrites every row.
Proof (read-only):
curl "$SUPABASE_URL/rest/v1/todos?select=*" -H "apikey: $ANON_KEY" → 200 OK, 1,204 rows from 38 different user_ids
Fix (PR #1):
alter table public.todos enable row level security; create policy "owner can do anything" on public.todos for all to authenticated using (auth.uid() = user_id) with check (auth.uid() = user_id);
deleteTodo deletes any user's todo HIGHWhere: app/actions.ts:14
What happens: the action checks that someone is signed in, but deletes by id alone, using the service-role client, so RLS doesn't apply. Any signed-in user can delete any todo by passing its id.
Fix (PR #3): use the user's own Supabase client (so RLS applies) and scope the delete: .delete().eq('id', id).eq('user_id', user.id).
(Findings 2, 4, 5 and 6 follow the same format in the full report: where, what happens, proof, fix.)
Sign-in flow (Supabase Auth, email + Google), password reset, the Stripe webhook (signature verified), CORS, cookie flags, dependency advisories (none reachable), storage buckets (private).
Infrastructure outside the repository and Supabase project, denial-of-service resilience, and social engineering. This is not a compliance certification.