All articles

Supabase security check: what to review before your data gets exposed

4 min read

Free website check

Is your website actually working right now?

Paste your URL. Uptime, checkout, login, SSL and API checked in 30 seconds.

Results in 30 secondsNo code access neededFree, no signup
Supabase security check: what to review before your data gets exposed

A serious Supabase security check starts with a simple question: if someone had your public app URL and ten minutes to poke around, what data could they read, modify, or delete without permission?

Supabase gives you strong primitives, especially Row Level Security, signed URLs, and server-side admin access. But those primitives only protect you if they are configured correctly. Most incidents do not happen because Supabase is weak. They happen because teams expose a privileged key, skip a policy, or assume the frontend is enforcing access rules that the database never sees.

That is why every launch should include a real Supabase security check. This guide covers the highest-risk failure modes, the code patterns behind them, and the quickest ways to validate your setup before users touch production data.

Risk
Impact
Fix first
Service role key exposed
Anyone can bypass row-level security and act like an admin.
Keep service keys server-side only and rotate immediately if exposed.
RLS missing or incomplete
Authenticated users can read or mutate rows they do not own.
Enable RLS on every user-facing table and write explicit policies.
Public storage buckets
Sensitive uploads, exports, or generated files become guessable or open.
Review bucket visibility and generate signed URLs only where needed.
Unsafe RPCs and Edge Functions
Backend logic can be called directly without the checks your UI assumes.
Validate caller identity and input inside every function boundary.

What a Supabase security check should cover

1. Key handling and client exposure

The most important part of a Supabase security check is verifying which key is used where. The anon key belongs on the client. The service_role key does not. If the service role key ever lands in frontend code, browser storage, public logs, or a JavaScript bundle, you should treat it as compromised.

// Client: use the anon key only
import { createClient } from '@supabase/supabase-js';

export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// Server: keep the service role key isolated
import 'server-only';

export const adminSupabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

If your current Supabase security check finds the wrong key in the wrong runtime, rotate it. Do not just move it and assume nobody copied it already.

2. Row Level Security and ownership rules

RLS is where most of the real protection lives. A table can look safe because the frontend filters rows correctly, while the database is still happy to return every record to any authenticated user. That is not a UI bug. That is a broken security model.

A proper Supabase security check tests whether one user can access another user's rows by changing an ID, calling the REST endpoint directly, or using the JavaScript client outside the intended UI flow.

-- Example: only allow users to read their own projects
alter table projects enable row level security;

create policy "Users can read their own projects"
on projects
for select
to authenticated
using (owner_id = auth.uid());

3. Storage buckets and generated files

The next part of a Supabase security check is storage. Teams often make a bucket public during development because image rendering or download links are easier that way. Then the app launches with invoices, resumes, exports, or internal uploads still sitting in a public bucket.

Review every bucket, every signed URL flow, and every upload path. If a file should not be world-readable, the bucket policy should make that impossible by default.

4. Edge Functions, RPCs, and server-side trust boundaries

Supabase Edge Functions and Postgres functions are powerful, but they also create a new trust boundary. A Supabase security check should confirm that these functions validate identity and input themselves instead of assuming the frontend already did it.

// app/api/projects/[id]/route.ts
import { NextResponse } from 'next/server';

export async function DELETE(request, { params }) {
  const userId = request.headers.get('x-user-id');
  const projectId = params.id;

  if (!userId || !projectId) {
    return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
  }

  // Verify ownership before delete, do not trust the UI alone
  return NextResponse.json({ ok: true });
}

Common Supabase security check failures

  • RLS enabled on some tables but not all tables. Attackers only need one weak table.
  • Policies that allow too much. For example, using broad authenticated access when row ownership is required.
  • Service role key reused in frontend or automation. Convenient, but extremely dangerous.
  • Public buckets holding sensitive content. Easy to miss because uploads "work" during testing.
  • Functions that trust request parameters too much. Especially when IDs are accepted from the client.

Supabase security check before launch

Before launch, your Supabase security check should verify:

  • Only the anon key is ever used in client-side code
  • The service_role key is isolated to server-only paths
  • RLS is enabled on every table with user-facing data
  • Policies are tested with a second user, not just the owner account
  • Storage buckets are intentionally public or intentionally private
  • RPCs and Edge Functions verify caller identity and input
  • Any previously exposed key has been rotated and rechecked

Supabase is strong when the boundaries are real

A Supabase security check is really a boundary check. Which key can do what, which user can see what, which bucket can expose what, and which function trusts what input. Once those boundaries are explicit, Supabase is a very strong foundation.

The dangerous version is the one that only looks secure in the UI. Audit the database rules, not just the screens, before you ship.

Don't lose revenue silently

Downtime doesn't announce itself.

Broken checkout, expired SSL, failing API. Get alerted in minutes, not days.

Continuous monitoringAlerts in minutesFree plan available
Supabase security check: what to review before your data gets exposed · AISHIPSAFE