Vault Architecture
This page is a technical reference for security-conscious operators and contributors who want to understand how Switchbord’s vault integration works end to end. For setup instructions, see Vault Configuration.Storage Model
Switchbord uses two layers of database objects to store workspace secrets:vault.secrets (Supabase built-in)
The actual encrypted secret values live here. Each row contains a secret column encrypted with pgsodium (AES-256-GCM), a name column for human-readable identification, and a key_id referencing the pgsodium key used for encryption. Rows are identified by a UUID.
app_private.workspace_secret_bindings (Switchbord custom)
This table maps a workspace to its secrets:
vault.secrets.
Why app_private Schema
The app_private schema is used for tables and functions that must not be exposed via PostgREST (Supabase’s auto-generated REST API). By default, PostgREST only exposes objects in the public schema (and schemas explicitly listed in db_schema). Placing workspace_secret_bindings in app_private ensures that:
- No anonymous or authenticated client can directly
SELECTfrom the bindings table via the Supabase client library - No REST endpoint for
/app_private/workspace_secret_bindingsis generated - Access is only possible through explicitly defined functions that run as
SECURITY DEFINER
workspace_secret_bindings as a defense-in-depth measure, but the app_private schema placement is the primary isolation layer.
The
app_private schema pattern is a standard Supabase convention for sensitive schema objects. It is documented in Supabase’s own “Row Level Security” and “Functions” guides as the recommended way to protect data that should never be directly accessible.Read Path Architecture
The read path is designed to avoid requiring a direct database connection from the worker service. All sensitive reads go through Supabase RPC functions defined withSECURITY DEFINER.
SECURITY DEFINER Functions
To ensure vault items are never exposed via the standard public schema or auto-generated REST endpoints, Switchbord uses the app_private schema pattern.
app_private.read_workspace_secret
Joins app_private.workspace_secret_bindings with vault.decrypted_secrets to return the plaintext value. It runs as SECURITY DEFINER, allowing it to access the vault schema which is normally restricted.
app_private.resolve_workspace_by_verify_token
Used during Meta webhook verification to route requests to the correct workspace without exposing tokens to the client.
Public RPC Wrappers
Because PostgREST only exposes thepublic schema, we provide thin wrappers that enforce authorization:
public.sb_read_workspace_secret: Validatesauth.role() == 'service_role'before delegating to the private function.public.sb_resolve_workspace_by_verify_token: Follows the same security pattern for token resolution.
Write Path
The write path flows through the Switchbord web application and does not involve the worker:SUPABASE_DB_URL) when calling vault.create_secret() from outside the application server, but the Next.js API route handles this using the Supabase service role client. Self-hosters do not need SUPABASE_DB_URL in the worker — only in the web application if vault writes are needed from server-side code.
Encryption at Rest
Supabase Vault uses pgsodium, a PostgreSQL extension wrapping libsodium.- Algorithm: XChaCha20-Poly1305 (a stream cipher with authentication)
- Key management: pgsodium uses a root key derived from a secret stored outside the database (in Supabase’s key management infrastructure for Cloud, or configured separately for self-hosted)
- Encryption scope: Each secret in
vault.secretsis encrypted with a key derived from the root key. The encrypted ciphertext and key ID are stored together; the plaintext is never persisted. - Decryption: The
vault.decrypted_secretsview performs decryption transparently usingpgsodium.crypto_aead_det_decrypt(). Only database roles with SELECT permission on this view can read plaintext values.
For Supabase Cloud deployments, the pgsodium root key is managed by Supabase and is never exposed to project owners. For self-hosted Supabase, you are responsible for securing the root key — see the Supabase self-hosting guide for key configuration.
Access Control Summary
The worker authenticates all RPC calls using the service role JWT (
SUPABASE_SECRET_KEY). This key must be kept secret and must never be exposed to browser clients or logged.
Migration Reference
The vault integration is composed of two layers: Supabase built-in (no action required)vaultschema andvault.secretstablevault.decrypted_secretsviewvault.create_secret()function- pgsodium extension
CREATE EXTENSION supabase_vault.
Switchbord custom (applied via migrations)
app_privateschema creationapp_private.workspace_secret_bindingstableapp_private.read_workspace_secret()functionapp_private.resolve_workspace_by_verify_token()functionpublic.sb_read_workspace_secret()wrapperpublic.sb_resolve_workspace_by_verify_token()wrapper
supabase/migrations/. Running supabase db push applies all pending migrations and ensures these objects exist.
If you need to inspect the function definitions directly:
Security Considerations for Self-Hosters
Service role key exposure TheSUPABASE_SECRET_KEY can call any RPC function and bypasses RLS. Treat it with the same care as a database root password. Rotate it immediately if you suspect exposure.
Self-hosted Supabase pgsodium key
If running Supabase locally or on your own infrastructure, the pgsodium root key must be set before any secrets are stored. If the key changes or is lost, all stored vault secrets become unrecoverable — there is no key escrow.
Migration integrity
The SECURITY DEFINER functions in app_private are only as secure as the database owner role. Ensure that only trusted parties have the ability to redefine or replace these functions via migrations.
Related Guides
- Vault Configuration — self-hosting setup, env vars, and troubleshooting
- Secrets and Encryption — security policy, API key hashing, and HTTP security headers
- Multi-Tenancy — workspace isolation model
- Authentication — JWT handling and session model