Teleperson
Architecture

Edge Functions & encryption

How privileged operations run on Supabase Edge Functions with AES-256-GCM at rest and SHA-256 at the boundary.

Every privileged operation in Teleperson runs in a Supabase Edge Function (Deno + TypeScript). The extension never executes a privileged operation directly — it always asks an Edge Function. This is where:

  • Plaid access tokens get encrypted before going to Postgres.
  • Stripe API calls happen with the platform secret key.
  • Anthropic calls happen for cached company-level operations.
  • Vendor OAuth token exchanges happen.
  • Audit log entries get written.

Why Edge Functions

  • Secret isolation. API keys (Stripe, Anthropic, Plaid client secret, ElevenLabs) live as environment variables on the Edge Function side, never reaching the extension.
  • AES-256-GCM availability. The Web Crypto API used by _shared/crypto.ts is the same crypto in browsers, so the encryption code is symmetric in shape but the key lives only on the server.
  • Authoritative plan checks. Plan gating happens server-side in the function itself, not in extension UI alone — the UI gate is a hint, the function gate is the policy.

Encryption at rest

For data that must persist but should never be retrievable in plaintext without the key:

// supabase/functions/_shared/crypto.ts
export async function encrypt(plaintext: string, keyBase64: string) {
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const key = await importKey(keyBase64);
  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    new TextEncoder().encode(plaintext)
  );
  return { ciphertext: new Uint8Array(ciphertext), iv };
}

Stored as (ciphertext, iv) pairs:

TableFieldKey env var
user_plaid_connectionsaccess_token_ciphertext, token_ivPLAID_TOKEN_ENCRYPTION_KEY
user_vendor_connectionsaccess_token_ciphertext, refresh_token_ciphertext, token_ivVENDOR_TOKEN_ENCRYPTION_KEY

Plaintext access tokens exist only inside an Edge Function request — they never leave the function.

Hashing at the boundary

For things we don't need to recover, hashes:

TableFieldAlgorithm
extension_tokenstoken_hashSHA-256
voice_call_logto_number_hashSHA-256

Lost tokens can't be recovered, only revoked and replaced — by design.

Key rotation

Encryption keys are versioned via a tag on the ciphertext (v1:, v2:, …). Rotation:

  1. Add the new key to the Edge Function environment as *_KEY_V2.
  2. Re-encrypt rows lazily on next access (reads with V1 → re-encrypt under V2 → write back).
  3. Once all rows are re-encrypted (verified via a SQL count of v1: prefixed rows = 0), retire the V1 env var.

The same pattern applies to the SHA-256 hashing of tokens — though hash-only fields don't need re-hashing; you simply roll the salt forward on new issuance.

What Edge Functions can't do

  • No long-running jobs. Edge Functions have time limits. Long-running work (multi-step Plaid sync, retroactive merchant normalization) is decomposed into iterations and driven by background queues.
  • No filesystem. Persistent state lives in Postgres or external object storage.
  • No raw TCP. Outbound HTTP only.

On this page