agentsclimarketplace

Supabase

Skill ndisisnd/cook/standards/supabase

Supabase platform standards — Row-Level Security, publishable/anon and secret/service_role key boundaries, Postgres and Edge functions, Storage, Realtime, and the CLI migration workflow. Use when working with RLS policies, Supabase clients, Edge Functions, or supabase/ migrations. Loads alongside the database (Postgres) domain.From its SKILL.md

Install
npx -y skills add ndisisnd/cook --skill supabase

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.2 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it

Supabase Standards

Supabase is Postgres underneath, so this domain co-loads with database (generic Postgres) and the auth / security concerns. It owns only the platform contract: RLS, the key boundary, Postgres/Edge function security, and the CLI migration workflow. Generic Postgres rules (types, constraints, indexing-in-general, expand-contract, N+1, transactions) live in database; auth flows (JWT verification, OAuth, password reset) live in global/refs/auth.md — this domain links to them rather than restating them.

Priority: P0 — Row-Level Security

  • RLS is OFF by default. Every table reachable through the API/PostgREST must ENABLE ROW LEVEL SECURITY in the same migration that creates it. A table left unguarded behind a public anon key is a full data leak. Signal: a create table in supabase/migrations with no matching alter table … enable row level security.
  • RLS-on denies by default — write an explicit policy per operation (select/insert/update/delete). Don't rely on one for all policy where operations need different predicates. Signal: a table with RLS enabled and no policy, or a blanket for all using (true).
  • Pair every UPDATE/DELETE policy with a SELECT policy. Postgres must read the existing row to evaluate the USING clause; without SELECT the row is invisible and the write silently affects nothing. Signal: an update/delete policy on a table with no select policy.
  • Use WITH CHECK on INSERT/UPDATE so a user can't write a row they couldn't own or read (e.g. inserting someone else's user_id). Signal: an insert/update policy with using but no with check.
  • Wrap auth calls as (select auth.uid()) / (select auth.jwt()) in policy predicates so the planner caches the result per-statement instead of re-evaluating per row. Signal: a bare auth.uid() in a policy on a table that gets scanned.
  • Never base a policy on auth.jwt() -> 'user_metadata' — user_metadata is editable by the authenticated user. Use app_metadata (server-controlled) or a roles table joined via a security definer helper. Signal: a policy reading user_metadata for an authorization decision.
  • Index every column referenced in an RLS predicate (user_id, tenant_id, …). RLS turns these into per-query filters; an unindexed predicate column is a full scan on every request. (Sharpens the database indexing rule.) Signal: an RLS predicate column absent from any index.

Priority: P0 — Keys & Client Boundary

  • Use publishable keys (sb_publishable_...) for public clients; treat legacy anon as the compatibility form. Public clients are only safe when RLS protects every exposed table for the anon / authenticated roles. Signal: a publishable or legacy anon client used against a table with RLS disabled.
  • Secret keys (sb_secret_...) and legacy service_role keys bypass RLS — backend / Edge Functions only. Never ship them to a browser or mobile bundle, never put them in a client-public env (NEXT_PUBLIC_*, EXPO_PUBLIC_*, VITE_*), and never pass them in URLs or query params. A leaked secret key is root on the database. Signal: sb_secret, SUPABASE_SECRET_KEYS, service_role, or SUPABASE_SERVICE_ROLE_KEY referenced in client-bundled code, a public-prefixed env var, a URL/query param, or unsanitized logs.
  • A user-session client and an admin client are separate instances. A client carrying a user session sends the user JWT (RLS applies); do not attach a user session to the admin client, and the user session must not override the admin API key. In SSR, build a dedicated admin client from a secret key / legacy service_role key. Signal: one shared client mixing a user session with sb_secret / service_role.

Priority: P0 — Postgres & Edge Functions

  • Prefer SECURITY INVOKER (the default) for Postgres functions. If a function must be SECURITY DEFINER, set search_path = '', schema-qualify every relation (public.table), and never create it in an API-exposed schema. Signal: security definer with no set search_path = '', or such a function in an exposed schema.
  • Edge Functions are publicly invokable by default — match verify_jwt to the caller credential and verify inside the handler when needed. Keep verify_jwt on for user-JWT calls. Turn it off for webhooks or API-key service calls, then verify the provider signature or apikey header in code. Publishable/secret keys are not JWTs and must not be sent as Authorization: Bearer .... Signal: an Edge Function reading user data with verify_jwt = false and no signature / apikey / authorization check; a publishable or secret key sent as a bearer token; a literal key in function source.
  • Treat Postgres as a pooled remote from Edge Functions — use the connection pooler / serverless-friendly client; don't open a fresh direct connection per invocation. Signal: a new Pool / direct-connect per request in a function.

Priority: P1 — Migrations & Workflow

  • All schema and RLS changes go through Supabase CLI migrations in supabase/migrations, version-controlled — never schema-edit only in the dashboard (silent drift). Write RLS policies as explicit SQL; ORM-generated migrations don't capture them. Signal: a dashboard-only schema change with no migration file.
  • Storage buckets are private by default; gate access with storage RLS policies. Signal: a public = true bucket holding user/private data.
  • P1 (design): enable Realtime per-table deliberately, and remember RLS applies to Realtime too — a postgres_changes subscription only emits rows the subscriber can read.

Anti-Patterns

  • Table exposed with RLS disabled
  • RLS on with no policy, or a blanket for all using (true)
  • update/delete policy without a select policy
  • insert/update policy without with check
  • bare auth.uid() per-row in a policy
  • policy keyed on user_metadata for authorization
  • unindexed RLS predicate column
  • sb_secret / service_role key in a client bundle, public-prefixed env var, URL, or log
  • user session attached to the admin client
  • security definer with no search_path = '', or in an exposed schema
  • Edge Function on user data with verify_jwt off and no in-code check
  • hardcoded secrets in Edge Function source
  • dashboard-only schema edits (drift)
  • public storage buckets holding private data

References

Load only what the task requires:

  • rls — enable-RLS migration pattern, per-operation policies, SELECT+UPDATE pairing, WITH CHECK, (select auth.uid()) wrapping, app_metadata vs user_metadata, indexing predicate columns
  • keys-and-clients — publishable/anon vs secret/service_role, browser/mobile boundary, SSR admin-client separation, public-env pitfalls
  • database-functions — SECURITY INVOKER default, SECURITY DEFINER + search_path = '' + schema qualification, exposed-schema rule, auth.uid() in helpers
  • edge-functions — Deno runtime, user JWT vs API-key auth, verify_jwt / in-code auth, project secrets, connection pooling for Postgres
  • migrations — CLI workflow, RLS-as-SQL, dashboard drift, storage and Realtime policies
  • checklist — pre-deploy review checklist

What ships with it: 7 files

21.6 KB alongside SKILL.md

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.