Keeping keys safe

Which SupaHost keys are safe to publish and which must stay on servers: anon, service_role, access tokens, SSH keys and function secrets. Plus what to do if one leaks.

Every SupaHost project has two API keys, and your account can have access tokens and SSH keys too. They're very different in what they allow. This page explains which ones can be public, where each should live, and what to do if one gets out.

The keys at a glance

Key Looks like Can be public? What it allows
anon (labelled "public" on Connect) A JWT starting eyJ Yes Acts as the anon role. Everything it reaches is limited by RLS and grants
service_role (labelled "secret") A JWT starting eyJ Never Acts as service_role, which bypasses RLS. Full read and write on the project's data, Auth admin API and Storage
Access token shp_ + 48 hex characters Never MCP access to a project or the whole instance
SSH private key Your own key file Never SSH and the Postgres tunnel on Business and Enterprise
Database password From Show connection string (Business and Enterprise) Never The postgres role, shared by every project's database on the instance
Function secrets Whatever you store Never Read by your Edge Functions with Deno.env.get()

SupaHost uses the legacy JWT-style keys. There are no sb_publishable_... or sb_secret_... keys, so if a guide tells you to use a publishable key, use anon; for a secret key, use service_role.

Why the anon key can be public

The anon key is meant to ship in your web and mobile apps. It doesn't identify a user; it just says "this request is for this project". What protects your data is Row Level Security. A table with RLS enabled and a policy like this is safe no matter who holds the anon key:

create policy "Members read their own bookings"
  on public.bookings for select
  to authenticated
  using ((select auth.uid()) = guest_id);

So the real rule is: the anon key is safe only if RLS is right. See the Production checklist.

Keeping service_role on the server

Only the owner can see the service_role key, with Reveal on the project's Connect tab. Use it only in code that runs on a server you control: API routes, background workers, Edge Functions (which get it automatically as SUPABASE_SERVICE_ROLE_KEY).

Environment variable names matter. Frameworks copy variables with certain prefixes into the browser bundle:

Framework Exposed to the browser Server only
Next.js NEXT_PUBLIC_... Anything else
Vite VITE_... Anything else
Nuxt runtimeConfig.public runtimeConfig (top level)
SvelteKit $env/static/public (PUBLIC_...) $env/static/private
Expo EXPO_PUBLIC_... Not available in the app; use a server

A typical Next.js .env.local:

NEXT_PUBLIC_SUPABASE_URL=https://app-one.acme.supahost.space
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...   # fine in the browser
SUPABASE_SERVICE_ROLE_KEY=eyJ...       # server only: no NEXT_PUBLIC_ prefix

And a server-only admin client:

// lib/supabase-admin.ts: import only from server code
import 'server-only'
import { createClient } from '@supabase/supabase-js'

export const supabaseAdmin = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!,
  { auth: { persistSession: false, autoRefreshToken: false } }
)

Mobile apps can't keep a secret: anything in the app binary can be extracted. Put admin actions behind an Edge Function or your own API instead.

Function JWTs aren't user checks

An Edge Function with "Require a JWT" on accepts any valid JWT for the project, including the anon key. That stops random internet traffic, but it doesn't tell you who's calling. For anything user-specific, verify the user inside the function by passing the caller's token to supabase.auth.getUser(token). See Authentication and JWT verification.

Keys can't be rotated

Project keys are long-lived (they're issued with a 10-year expiry), and SupaHost has no way to rotate them or the JWT secret yet. That makes prevention important:

  • Keep service_role out of client code, logs, screenshots, support tickets and chat.
  • Don't hand it to teammates as a workaround for view-only access.
  • Give AI agents a scoped access token instead of the service_role key.

If something leaks

An access token: revoke it on Access tokens, then check Activity for writes by MCP (<token name>).

An SSH key: remove it from the instance and add a new one.

Function secrets: replace the value on the Functions tab's Secrets card and rotate the secret at its source (for example, roll the Stripe key in Stripe).

The anon key: usually no action is needed, since it's public by design. Use it as a prompt to re-check your RLS.

The service_role key:

  1. Remove it from wherever it leaked (the repo, the bundle, the log) and redeploy.
  2. Check for misuse: unexpected rows, users or files. Studio's SQL editor and the Auth users list are the quickest places to look.
  3. Email [email protected] straight away with the project name. There is no self-service rotation, so ask us about the options for that project.
  4. If you find damage, a point-in-time restore can roll the database back.

Read Rotating API keys and the JWT secret for more on this.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us