Secrets Handling
How we treat credentials. Short, but not optional — one leaked service-role key exposes every user's data. If you're a new contributor, read this before asking anyone for keys.
The one rule
Never share a secret over chat, DM, email, screenshare, a ticket, or a
commit. Not the .env, not "just the one key," not "temporarily." If a secret
needs to move, it moves through a secret manager (the deploy provider's env
settings, a password manager share), never a channel that keeps history.
What counts as a secret
| Secret — never share | Public — safe to embed |
|---|---|
SUPABASE_SERVICE_ROLE_KEY (bypasses RLS — full DB access) | NEXT_PUBLIC_SUPABASE_URL |
SUPABASE_JWT_SECRET, *_HOOK_SECRET, *_CRON_SECRET, IMPERSONATION_SECRET | NEXT_PUBLIC_SUPABASE_ANON_KEY (RLS protects the data) |
API keys: OPENAI_API_KEY, ANTHROPIC_API_KEY, ELEVENLABS_API_KEY, REPLICATE_API_TOKEN, RESEND_API_KEY, ZENQUOTES_KEY | Any other NEXT_PUBLIC_* value |
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | |
OAuth client secrets + refresh tokens (Google, YouTube, Spotify), BSKY_APP_PASSWORD |
Rule of thumb: anything not prefixed NEXT_PUBLIC_ is a secret. The
NEXT_PUBLIC_ prefix means Next.js bakes it into the browser bundle, so it is
public by definition — never hide a real secret behind that prefix.
Contributors don't get production secrets
Local dev runs against your own free Supabase project, never production
(pfyocleyejmtxmbxrdcm…). See LOCAL_DEV_SETUP.md. Most
tasks need no third-party keys at all; the ones that do are handed out
per-feature, scoped to a sandbox/test account, when you reach that work —
never the production set, never up front.
If a secret leaks (assume the worst, rotate)
A secret that has touched any persistent channel is compromised — deleting the message does not undo it (it's already in history, search, backups, and any integration that ingested it). Rotate immediately:
- Supabase — Dashboard → Project Settings → API → roll the
service_role/ JWT secret. Update the deploy env (Railway/Vercel) with the new value. - Stripe / OpenAI / Anthropic / ElevenLabs / Resend / Replicate — revoke the key in that provider's dashboard and issue a new one; update the deploy env.
- OAuth — rotate the client secret + any stored refresh tokens.
- Then tell the team so everyone updates their env.
Where secrets actually live
- Local: in your gitignored
.env/.env.localonly..gitignoreblocks.env*except the committed, value-free*.env.exampletemplates. - Production: the deploy provider's env settings — backend on Railway, frontend on Vercel. Not in the repo, ever.
If you accidentally commit a secret, rotate it (above) — scrubbing git history is secondary; the key is already exposed the moment it's pushed.