Supabase Secrets Management — Gap Analysis
Date: 2026-06-28 Author: Claude (p24-infra session) Status: Active — implementation in progress Related playbooks:
supabase-service-key-rotation.md— canonical rotation procedure (implemented as result of this analysis)n8n/n8n-supabase-credential-rotation.md— n8n-specific incident recoverystatic-api-key-incident-rotation.md— incident response (amended per Gap 3)
Supabase ref:
Key architectural finding
The Supabase management API fully supports programmatic project API key lifecycle management:
| Operation | Endpoint | Auth |
|---|---|---|
| List keys | GET /v1/projects/{ref}/api-keys | SUPABASE_ACCESS_TOKEN (sbp_…) |
| Create key | POST /v1/projects/{ref}/api-keys | SUPABASE_ACCESS_TOKEN + scope secrets:write |
| Delete key | DELETE /v1/projects/{ref}/api-keys/{id} | SUPABASE_ACCESS_TOKEN + scope secrets:write |
Full value is returned in the POST response — one-time reveal, like GitHub PATs.
No Playwright or dashboard access needed for key rotation. SUPABASE_ACCESS_TOKEN is
already in secrets/monitoring.env.sops and GH Secrets.
This means: one authorized management token can bootstrap and rotate all project API keys. Per-component key isolation (Gap 1) is fully automatable.
Context
Analysis performed 2026-06-28 against current state of:
supabase-access-token-rotation.md,n8n/n8n-supabase-credential-rotation.md,static-api-key-incident-rotation.mdpassword-rotation-procedures.md,secrets-sync.yml,secrets-rotation-log.md
Incident reference: 2026-06-27 GPS fleet freeze — 12h outage caused by a Supabase key rotation that updated SOPS but left 5 of 6 n8n credentials with the revoked key.
Gap 1 — No per-component key isolation
Severity: HIGH | Effort: MEDIUM | Blast radius: all consumers
Current state
One SUPABASE_SERVICE_ROLE_KEY (sb_secret_…) shared by:
- 6 n8n credentials on bms-4 (3× supabaseApi + 3× httpHeaderAuth)
- et-operational-platform Next.js backend (Vercel)
- brandpilot Next.js backend (Vercel)
- monitoring scripts on vps-i1 (cost-exporter, infra_operations log)
- CI/CD workflows (secrets-sync.yml, credential-rotation.yml)
One SUPABASE_ANON_KEY (sb_publishable_…) shared by:
- et-operational-platform frontend
- n8n credentials
supabase-dev-r+supabase-anon-apikey
Supabase recommendation
“We recommend creating separate secret keys for each backend component”
Gap
A single compromised or rotated key breaks all consumers simultaneously. Missing any one consumer (proven: 2026-06-27) causes multi-hour outage.
Proposed target state
| Key name in Supabase | SOPS var | Consumer | Scope |
|---|---|---|---|
p24-n8n | SUPABASE_SERVICE_KEY_N8N | bms-4 n8n (6 credentials) | service_role |
p24-vercel | SUPABASE_SERVICE_KEY_VERCEL | et-operational-platform + brandpilot | service_role |
p24-monitoring | SUPABASE_SERVICE_KEY_MONITORING | vps-i1 scripts, CI/CD | service_role |
p24-anon | SUPABASE_ANON_KEY | all frontends, public API | anon (publishable) |
Migration approach
Fully automatable via management API — see supabase-service-key-rotation.md.
No dashboard session required. Migration order: create new keys → distribute per-consumer →
verify each consumer → delete old shared key (after all verified).
Status
Done (2026-06-28) — 3 named keys created via management API (p24_n8n_20260628, p24_vercel_20260628, p24_monitoring_20260628). SOPS updated (n8n-bms4 + monitoring + brandpilot). All consumers migrated via secrets-sync. Old shared keys (default + n8n_service_key_2_kapibara_supabase) deleted. n8n credentials auto-updated by sync-n8n-supabase-creds.py. PRs #1955 #1956 #1959.
Gap 2 — n8n Supabase credential sync not automatic
Severity: HIGH | Effort: LOW | Root cause of 2026-06-27 outage
Current state
secrets-sync.yml bms-4 job:
- Deploys
.envfrom SOPS ✅ - Restarts n8n containers ✅
- Runs
sync-n8n-credentials.py— syncs Mezmo credential only ✅ - Does NOT run
sync-n8n-supabase-creds.py❌
sync-n8n-supabase-creds.py exists and is correct but must be triggered manually.
Playbook says “always run after rotating” — manual = forgettable.
Gap
Every Supabase key rotation goes through SOPS/secrets-sync silently to n8n.
n8n reads credentials from its own encrypted PostgreSQL vault, not from .env.
Fix
Add one step to secrets-sync.yml bms-4 job after n8n is ready:
python3 /opt/p24-infra/scripts/sync-n8n-supabase-creds.py --dry-run
python3 /opt/p24-infra/scripts/sync-n8n-supabase-creds.pyScript is idempotent (patches only credentials where key differs from SOPS).
Status
Pending — tracked as part of supabase-service-key-rotation.md Track B.
Gap 3 — Root cause analysis comes after rotation, not before
Severity: MEDIUM | Effort: LOW | Process alignment
Current state
static-api-key-incident-rotation.md Step 6 (last): Root cause analysis.
Rotation is completed before understanding why exposure happened.
Supabase recommendation
“Make sure you have fully considered the situation and have remediated the root cause of the suspicion or vulnerability first” “We recommend using the OWASP Risk Rating Methodology to determine the appropriate response urgency”
Fix
Insert Step 0c into static-api-key-incident-rotation.md before Step 1:
- Root cause hypothesis (< 5 min)
- Is the exposure vector still active?
- OWASP risk rating: Likelihood × Impact → response urgency
- Remediate vector before generating new key
Status
Done — Step 0c added to static-api-key-incident-rotation.md.
Gap 4 — No dedicated Supabase project API key rotation playbook
Severity: MEDIUM | Effort: LOW | Procedure gap
Current state
Three documents cover partial aspects but no canonical rotation procedure exists for
SUPABASE_SERVICE_ROLE_KEY + SUPABASE_ANON_KEY with all consumers enumerated.
The 2026-06-27 incident was partly caused by partial execution — no single document listed all consumers and required steps in sequence.
Fix
Created docs/playbooks/supabase-service-key-rotation.md covering:
- Consumer inventory
- Management API programmatic key creation
- Per-component key isolation migration plan
- Full rotation steps with consumer order
- Mandatory n8n sync step
- Mandatory dev_r_services update
- Pre-deletion verification gate
Status
Done — playbook created.
Gap 5 — Supabase audit log not monitored
Severity: MEDIUM | Effort: HIGH | Detection gap
Current state
Incident trigger list includes “audit log shows calls from unexpected IP” but no system actively monitors Supabase audit logs.
Gap
Unauthorized service_role API calls may go undetected until downstream effects appear (GPS staleness, data corruption, unexpected billing).
Proposed fix (deferred)
Option A: n8n scheduled workflow polls Supabase management API for unusual auth events. Option B: Edge Function log drain into Mezmo with Alertmanager rule.
Status
Deferred — requires design issue. Known gap, accepted risk for now.
Gap 6 — 6 n8n Supabase credentials across 2 types
Severity: MEDIUM | Effort: MEDIUM | Complexity risk
Current state
3× supabaseApi type + 3× httpHeaderAuth type for Supabase.
Dual-type complexity caused partial sync in 2026-06-27 incident.
| Credential ID | Name | Type | Key kind |
|---|---|---|---|
| 8bVcpRNbyLOHJDrU | Supabase account | supabaseApi | sb_secret_ |
| 1pvpQp7GfpAxNFIh | Supabase - kapibara | supabaseApi | sb_secret_ |
| rFL6zCStG02XlFGK | supabase-dev-r | supabaseApi | sb_publishable_ |
| cMpMbkDFAJzC3O4f | supabaseKapbaraAuthBearer | httpHeaderAuth | sb_secret_ |
| aMxMREZhtabQV3Z7 | supabase-anon-apikey | httpHeaderAuth | sb_publishable_ |
| ZNuDXmNs3nT58L6p | supabase-service-role-key | httpHeaderAuth | sb_secret_ |
Fix
When per-component keys are created (Gap 1 migration), consolidate n8n to:
- 1 credential for
p24-n8nservice key (supabaseApi type where possible) - 1 credential for anon/publishable key Reduce from 6 to 2–3. Audit which httpHeaderAuth creds can migrate to supabaseApi.
Status
Planned — part of Gap 1 migration. No standalone action yet.
Gap 7 — dev_r_services rotation tracking not enforced
Severity: MEDIUM | Effort: LOW | Audit accuracy
Current state
password-rotation-procedures.md documents UPDATE dev_r_services SET last_rotated...
but it is not a mandatory enforced step — omitting it leaves stale expiry dates that
cause premature or missed rotation alerts.
Fix
Added as mandatory step (not optional post-step) in supabase-service-key-rotation.md.
Status
Done — included in new rotation playbook.
Gap 8 — No pre-deletion verification gate
Severity: LOW | Effort: LOW | Safety net
Current state
Playbook instructs “revoke old key only after confirming new value works everywhere” but provides no structured checklist of consumers to verify before deletion. Supabase: “Deleting a secret key is irreversible.”
Fix
Added explicit consumer verification checklist to supabase-service-key-rotation.md
as a mandatory gate before the delete step.
Status
Done — included in new rotation playbook.
Implementation Summary
| Gap | Severity | Status |
|---|---|---|
| 1. No per-component key isolation | HIGH | Done — p24_n8n/p24_vercel/p24_monitoring created 2026-06-28, old keys deleted, PRs #1955 #1959 |
| 2. n8n sync not automatic | HIGH | Done — secrets-sync.yml auto-runs sync-n8n-supabase-creds.py, PR #1955 |
| 3. RCA after rotation | MEDIUM | Done — Step 0c added to incident playbook |
| 4. No service key rotation playbook | MEDIUM | Done — supabase-service-key-rotation.md created |
| 5. Supabase audit log not monitored | MEDIUM | Deferred — known gap, future issue |
| 6. 6 n8n creds / 2 types | MEDIUM | Planned — part of Gap 1 migration |
| 7. dev_r_services tracking unenforced | MEDIUM | Done — added to new rotation playbook |
| 8. No pre-deletion gate | LOW | Done — added to new rotation playbook |
Change sequencing — no-stop deployment
Track A — Documentation only (zero risk): Gaps 3, 4, 7, 8 — playbook files only. No infrastructure touch.
Track B — secrets-sync.yml (low risk, one CI run):
Gap 2 — adds idempotent SSH step to bms-4 job. n8n stays running.
Prerequisite: confirm sync-n8n-supabase-creds.py present at path on bms-4.
Track C — Key isolation migration (medium risk, planned):
Gap 1 — creates named keys via management API, migrates consumers, deletes old shared key last.
Order: monitoring scripts → Vercel → n8n → delete old key.
Old key stays active until last consumer confirmed. No service interruption.
Full procedure: supabase-service-key-rotation.md §Per-component migration.