Security
Key scope = blast radius
| Risk surface | Use |
|---|---|
| Backend / edge function | server virtual key. Keep in env vars / secrets manager. |
| Browser, native app | Never ship a server key. Mint short-lived EUTs from your backend. |
| Public widget | browser 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:
- Backend mints an EUT (short-lived JWT) for the signed-in user, scoped to a workspace + budget + allowed models.
- Browser sends
Authorization: Bearer <eut>. - 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.
| Variable | Meaning | Default |
|---|---|---|
SMS_ALLOWED_COUNTRIES | Comma-separated ISO-3166-1 alpha-2 list, or * for all. | * |
SMS_BLOCKED_COUNTRIES | Comma-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.