← Leakproof

Security audit: Taskly

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.

Summary

#FindingSeverityFixed in
1todos table has no Row Level SecurityCRITICALPR #1
2OpenAI key shipped to the browser via NEXT_PUBLIC_OPENAI_API_KEYCRITICALPR #2
3deleteTodo server action deletes any user's todoHIGHPR #3
4Share page shows any user's list by incrementing the idHIGHPR #3
5No rate limit on /api/suggest (calls OpenAI)MEDIUMPR #4
6Todo notes rendered with dangerouslySetInnerHTMLMEDIUMPR #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.

Findings

1. todos table has no Row Level Security CRITICAL

Where: 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);

3. deleteTodo deletes any user's todo HIGH

Where: 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.)

What was checked and found fine

Sign-in flow (Supabase Auth, email + Google), password reset, the Stripe webhook (signature verified), CORS, cookie flags, dependency advisories (none reachable), storage buckets (private).

What this audit does not cover

Infrastructure outside the repository and Supabase project, denial-of-service resilience, and social engineering. This is not a compliance certification.

Get one for your app →