# Runbook — Secrets Rotation

> When to rotate: scheduled (every 90 days), after any team-member offboard,
> or immediately if exposure is suspected. See `incident-response.md` § C
> for the exposure flow.

---

## Inventory

| Secret | Where it lives | Rotation method | Downstream consumers |
| --- | --- | --- | --- |
| `SUPABASE_SERVICE_ROLE_KEY` | Vercel env (encrypted) + per-tenant `cp_tenant_environments.supabase_service_role_key` | Supabase Dashboard → Settings → API → "Reset" | All `/api/admin/*`, `/api/owner/*` writes |
| `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` | Vercel env | Supabase Dashboard → API Keys → JWT-based key. **Only rotate if leaked.** Customer apps embed this key. | Browser (frontend) |
| `SUPABASE_DB_PASSWORD` | Operator's password manager. Used for `pg_dump`/`psql`. | Supabase Dashboard → Settings → Database → "Reset password" | Operator only |
| `VERCEL_TOKEN` | Operator's password manager (server-side `.env` files). | Vercel Dashboard → Settings → Tokens → revoke + recreate | CLI scripts, automation |
| `GITHUB_PAT` | Operator's password manager. | https://github.com/settings/tokens → revoke + recreate | CI, push automation |
| `GROQ_API_KEY` | Operator's password manager. | https://console.groq.com → revoke + recreate | Telegram voice transcription (off-app) |
| `SENTRY_DSN` (when enabled) | Vercel env. | Sentry project settings | Server-side error reporting |
| `CSRF token` | Per-session cookie, server-minted. | Cookie expiry on logout. No manual rotation. | Browser |

## Rotation procedure (Supabase service-role key)

> Recovery time: ~5 min downtime per project if you flip the env without first staging the new key.

1. **Stage the new key.** In Supabase Dashboard for the target project:
   - Settings → API → service_role key → "Reset" → copy the new key.
   - Old key remains valid until you click "Reset" (Supabase rotates atomically).
2. **Push the new key to Vercel preview environment first.**
   ```bash
   curl -X PATCH "https://api.vercel.com/v10/projects/$PROJECT_ID/env/$ENV_ID" \
     -H "Authorization: Bearer $VERCEL_TOKEN" \
     -d '{"value":"<new-key>"}'
   ```
3. **Trigger a preview deployment + smoke-test.**
   ```bash
   vercel deploy --no-clipboard
   curl -sI https://<preview-url>/api/v1/health
   ```
4. **Promote to production env.** Same as step 2 with `target=["production"]`.
5. **Redeploy production.**
   ```bash
   vercel deploy --prod
   ```
6. **Verify masteradmin login.** End-to-end: log in, list users, lock+unlock a test user.
7. **For each tenant in `cp_tenant_environments`:** flip
   `supabase_service_role_key` to the new key (the tenant Supabase project's
   service-role-key, not the control-plane one — they are separate).
8. **Document the rotation** in `docs/runbooks/rotations/<YYYY-MM-DD>.md`.

## Rotation procedure (DB password)

> Triggers a 30-second blip while pgBouncer reconnects.

1. Supabase Dashboard → Settings → Database → "Reset password".
2. Note the new password in the password manager.
3. **No app-side update needed** if Supabase manages the connection (the
   service does the reset across all consumers atomically).
4. If you have direct `psql`/`pg_dump` consumers (operator scripts), update
   `<operator-secret-store>`.

## Rotation procedure (Vercel token)

1. Vercel Dashboard → Settings → Tokens → revoke old.
2. Create new token with the same scope.
3. Update operator's password manager and any automation file
   (`<operator-secret-store>`).
4. Re-run a probe command to verify:
   ```bash
   curl -s "https://api.vercel.com/v2/user" -H "Authorization: Bearer $VERCEL_TOKEN" | jq .username
   ```

## Rotation procedure (GitHub PAT)

1. https://github.com/settings/tokens → revoke old.
2. Create new PAT with required scopes:
   - `repo` (always)
   - `workflow` (only if pushing changes to `.github/workflows/`)
   - `admin:org` only if you need to manage org-level settings
3. Update operator's password manager + `<operator-secret-store>`
   `GITHUB_PERSONAL_ACCESS_TOKEN`.
4. Re-run a probe:
   ```bash
   curl -s -H "Authorization: token $GITHUB_PERSONAL_ACCESS_TOKEN" https://api.github.com/user | jq .login
   ```

## Coordinated rotation after exposure

If multiple secrets were in the same place (e.g. a leaked `.env` file):

1. Rotate ALL of them in the order: GitHub PAT → Vercel token → Supabase
   service-role → Supabase DB password.
2. Force-push a commit that overwrites the leaked file (e.g. `gitleaks`
   detected a key in a previous commit).
3. If the leak hit a public surface (e.g. a public PR comment), assume
   the secret is compromised even if no exploitation is yet observed.

## Annual review

- [ ] Review this list every January for new secrets that have crept in.
- [ ] Confirm `npm run check:secrets` flags every entry that should be redacted.
- [ ] Confirm `gitleaks` rules cover the formats above.
