# Shared responsibility

> Which parts of security SupaHost handles (servers, network, TLS, backups, updates) and which are yours (Row Level Security, keys, Auth settings, function code, your own backups).

Source: https://supahost.app/docs/security/shared-responsibility · Updated 2026-11-11

SupaHost runs the servers and the Supabase stack. You build the app on top. Both halves matter: a perfectly patched server won't help if a table has no Row Level Security, and careful policies won't help if the server is wide open. This page draws the line so nothing falls between the two.

The rule of thumb: **SupaHost is responsible for the platform staying up, patched, isolated and backed up. You're responsible for who can read and write your data, and for the code and settings you add.**

## The split

| Area | SupaHost | You |
|---|---|---|
| Cloud VM, operating system, disk | Runs and hardens the VM (Shielded VM, deletion protection), grows the disk | Choose the plan and region |
| Network | Exposes only HTTPS; keeps Postgres on localhost; opens SSH only on Business while keys exist; blocks metadata access | Keep SSH keys safe and remove ones you no longer use |
| TLS | HTTPS on every project; Let's Encrypt certificates for custom domains | Create the right DNS record; use "DNS only" on Cloudflare |
| Supabase components | Ships releases; snapshots before "Upgrade now"; automatic upgrades in your window | Turn on automatic upgrades or apply releases yourself |
| Project isolation | Separate database, JWT secret, keys and services per project | Put workloads that must be fully separated on different instances |
| Database access rules | Provides Postgres roles, RLS and the Data API | **Enable RLS on every exposed table and write correct policies** |
| API keys | Generates `anon` and `service_role` keys; only the owner can reveal `service_role` | Never ship `service_role` to browsers or apps; store keys in environment variables |
| Auth | Runs the Auth service and sends Auth emails | Set Site URL, redirect URLs, password length, sign-up and confirmation settings; configure OAuth apps |
| Edge Functions | Runs them in isolated workers with memory and time limits; stores secrets in Vault | Write safe code; verify users; keep "Require a JWT" on unless you need it off; set CORS properly |
| Storage | Serves files; enforces the 50 MB limit | Write Storage policies; choose public or private buckets |
| Backups | Daily database backups and WAL to a private bucket in-region; PITR on Pro and Business | Export Storage files yourself; keep your own off-site copies; test restores |
| Monitoring | Health checks every minute; downtime and usage alert emails | Keep alerts on; add a shared address; run your own uptime checks |
| Account access | SHA-256-hashed access tokens; owner-only writes; sign-in alerts | Use a strong password; scope and revoke access tokens; remove team members who leave |
| Compliance | Public beta; no SOC 2, ISO 27001, HIPAA or SLA | Decide whether that's acceptable for your data and customers |

## Where problems usually come from

Most real incidents with Supabase apps are on the app side of the table, not the platform side. The common ones:

1. **A table in `public` without RLS.** The Data API exposes `public` to anyone with the anon key, and the anon key is meant to be public. Without RLS, that's everyone.
2. **RLS enabled with a policy that's too broad,** such as `using (true)` on a table that holds private rows.
3. **The `service_role` key in front-end code,** often through an environment variable with a public prefix like `NEXT_PUBLIC_` or `VITE_`.
4. **An Edge Function that trusts any valid JWT.** On SupaHost, "Require a JWT" accepts any valid token for the project, including the anon key. A function that acts for a user must check who that user is.
5. **Open redirect URLs,** such as a wildcard that lets any site receive sign-in redirects.

A quick check for the first one, from Studio's SQL editor:

```sql
select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind in ('r', 'p')
  and not c.relrowsecurity;
```

Any row returned is a table anyone can read and write through the Data API, subject only to grants. Enable RLS and add policies:

```sql
alter table public.invoices enable row level security;

create policy "Owners read their invoices"
  on public.invoices for select
  to authenticated
  using ((select auth.uid()) = user_id);
```

For an Edge Function that acts for a user, check the user rather than trusting the token alone:

```ts
import { createClient } from 'npm:@supabase/supabase-js@2'

Deno.serve(async (req) => {
  const authHeader = req.headers.get('Authorization') ?? ''
  const token = authHeader.replace('Bearer ', '')

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_ANON_KEY')!,
    { global: { headers: { Authorization: authHeader } } }
  )

  // The anon key is a valid JWT too, but it has no user behind it
  const { data: { user } } = await supabase.auth.getUser(token)
  if (!user) return new Response('Unauthorized', { status: 401 })

  // Queries here run as this user, with RLS applied
  const { data } = await supabase.from('invoices').select('id, total')
  return Response.json(data)
})
```

The full list of things to check before launch is in the [Production checklist](/docs/security/production-checklist).

## Related

- [Security at SupaHost](/docs/security/overview)
- [Row Level Security](/docs/database/row-level-security)
- [Securing your API](/docs/api/securing-your-api)
- [Authentication and JWT verification](/docs/functions/auth)
