Security

Key scope = blast radius

Risk surfaceUse
Backend / edge functionserver virtual key. Keep in env vars / secrets manager.
Browser, native appNever ship a server key. Mint short-lived EUTs from your backend.
Public widgetbrowser virtual key + strict allowed-models + low budget cap.

Rotation

  • Rotate keys on staff change, suspected leak, or quarterly.
  • Workspace → Virtual keys → Rotate issues a new key and grants a grace period before the old one stops working.
  • All key events appear under Key audit with IP + UA.

EUT (end-user tokens)

When a browser must call odnoga directly:

  1. Backend mints an EUT (short-lived JWT) for the signed-in user, scoped to a workspace + budget + allowed models.
  2. Browser sends Authorization: Bearer <eut>.
  3. odnoga enforces the embedded budget per end-user; cost still rolls up to the workspace.

EUTs cannot pin a version_id on prompts (use label).

Raw capture

Off by default. When on, vendor request and response bodies are stored for the configured TTL. Turn it on for debugging, turn it off when done. Sensitive prompts are visible to anyone with workspace admin.

Service-role keys

Never put a Supabase service_role key in browser code. Never pass it as the caller token to an edge function. odnoga functions use SUPABASE_SERVICE_ROLE_KEY internally via Deno.env.get.

PII in prompts

You're responsible for what you send to vendors. Strip PII before the call, or use a vendor whose terms allow it.

SMS country policy (signup)

Signup requires a phone, but SMS delivery isn't always possible (e.g. Twilio trial accounts can't text unverified destinations). Two env vars on the edge functions decide which countries get an SMS code vs. fall back to email-only phone verification — the phone is still recorded (hashed) for one-account-per-phone enforcement.

VariableMeaningDefault
SMS_ALLOWED_COUNTRIESComma-separated ISO-3166-1 alpha-2 list, or * for all.*
SMS_BLOCKED_COUNTRIESComma-separated ISO list. Evaluated after the allowlist.DE

When SMS is skipped, the row in account_phone_hashes is stamped verified_via = 'email_only' so admins can audit which accounts bypassed SMS. Change either var in Supabase → Edge Functions → Secrets — no redeploy needed.