# Security rules ## Working style - Show proposed migrations and code diffs before applying them. Wait for my approval before changing the schema, RLS policies, grants, auth settings, storage policies, env vars or dependencies. - If you're unsure, say so. Never invent package names, dashboard menu labels or API options. ## Database (Supabase / Postgres) - Never disable Row Level Security on any table, including `storage.objects`, not even temporarily or to fix an error. - New `public` tables: one migration with, in order, `GRANT` (only the privileges the app uses), `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`, then policies. - No `anon` grants unless the data is meant to be public. - INSERT policies check ownership in `WITH CHECK` (INSERT takes no `USING`), e.g. `(select auth.uid()) = user_id`. - UPDATE policies on owned rows: `USING` and `WITH CHECK` both `(select auth.uid()) = user_id`, so `user_id` can't be changed to someone else's. Use a different `WITH CHECK` only when the new-row rule must differ. - No `USING (true)` or `WITH CHECK (true)` on private data. - Never base authorization on `user_metadata` / `raw_user_meta_data`; users can edit it. - Views use `with (security_invoker = true)`. - `42501 permission denied for table` means a missing grant. Fix it with a narrow `GRANT`, never by disabling RLS or switching to a secret key. ## Keys and secrets - Never use a Supabase secret key (`sb_secret_...`) or `service_role` key in client-side code. - No secrets in `VITE_`, `NEXT_PUBLIC_` or other browser-exposed env vars. Only the Supabase URL and publishable key may be public. - Call AI and payment providers from server code only. One server-only env var per key, so rotation is a config change. - Never hard-code keys, and never log them. Keep `.env*` files out of git. - After a production build, search the build output for `sb_secret_` and for legacy JWTs whose role is `service_role`. ## Auth and redirects - `next` / `redirect` / `returnTo` values: allow same-site relative paths only, or parse the URL and check scheme, host and port against an allow-list. - Enforce sign-in and roles on the server or in RLS. Never trust a role or user ID sent by the client. ## Storage - User files go in private buckets and are served with short-lived signed URLs. - `storage.objects` policies are limited to the user's own folder. Buckets have size and type limits. ## Input and abuse - Validate input on the server with a schema (allow-list), with length and size limits. - Rate-limit or cap endpoints that cost money or send messages: AI calls, email/SMS, sign-up, password reset, uploads, exports. ## Dependencies - Before adding a package, confirm it exists in the official registry and is the intended one (name, maintainer, repository). Flag look-alike names. - Commit the lockfile. CI uses `npm ci`. - Don't approve dependency install scripts or use `--dangerously-allow-all-scripts` without explaining why. ## Background - Supabase RLS: https://supabase.com/docs/guides/database/postgres/row-level-security - Supabase API keys: https://supabase.com/docs/guides/api/api-keys - Postgres CREATE POLICY (USING vs WITH CHECK): https://www.postgresql.org/docs/current/sql-createpolicy.html - Oct 30, 2026 grants change: https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically