Skip to content

Contributor camera limit has TOCTOU race — concurrent inserts can exceed cap #18

@NewCoder3294

Description

@NewCoder3294

Where

`apps/web/app/(app)/me/cameras/new/actions.ts` lines 44–85

Problem

The contributor cap check is a classic check-then-act:

```ts
const { count } = await supabase
.from("cameras")
.select("id", { count: "exact", head: true })
.eq("source", "contributor");
if ((count ?? 0) >= limit) {
return { ok: false, message: "waitlist: contributor onboarding is at capacity" };
}
// ... then later: INSERT into cameras ...
```

Two simultaneous `createCamera()` calls both see `count = limit - 1`, both pass the gate, both insert, total = `limit + 1`. As contributor signups grow this gets more likely (and is trivially exploitable by anyone holding two browser tabs).

Suggested fix

Move the cap into the database, where atomicity is free. Two options:

  1. Postgres function: `create_contributor_camera(...)` that runs the count + insert inside a single transaction with `SELECT ... FOR UPDATE` on a counter row.
  2. Partial unique constraint or trigger: a `BEFORE INSERT` trigger that raises if `COUNT(*) WHERE source = 'contributor' >= ?`. Keep the limit in a settings table so it's runtime-tunable.

Option 1 is more code; option 2 is more declarative. Either is correct.

Severity

Med — small blast radius today, but the bug grows with the user base.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions