Supabase Security Checklist: Protect Your Database in Production
Quick answer: Supabase is not secure by default because the anon key is public; you must enable Row Level Security (RLS) on every table to prevent unauthorized access. To protect production apps, keep the service_role key server-side, enforce email confirmation, enable leaked-password protection, and restrict Edge Function CORS to your domain.
By Gabriel CA · Kraftwire Software
· 10 min readSupabase is not secure by default because the anon key is public by design; you must enable Row Level Security (RLS) on every table to prevent unauthorized data access. To protect a production app, you must also keep the service_role key strictly server-side, enforce email confirmation, enable leaked-password protection, and restrict Edge Function CORS to your specific domain.
Why Does Supabase Security Need Your Attention?
Supabase provides a powerful suite of tools · Postgres, Auth, Storage, and Edge Functions · but it operates on a shared responsibility model. While the infrastructure is hardened, the logic governing who can access your data is entirely in your hands.
The most critical concept to grasp is that the anon key is not a secret. It is a public identifier that ships in the JavaScript bundle of every Supabase app on the web.
If you are building with "vibe-coding" tools like Lovable, Bolt.new, or Cursor, these platforms often scaffold tables quickly. If you don't explicitly verify the security layer, you might be shipping an open database.
How Does Row-Level Security (RLS) Work?
RLS is the primary defense mechanism for Supabase. It allows you to define granular access rules directly on your database tables. Without RLS, your tables are "open" to anyone who has your project URL and anon key.
The RLS Trap: Public by Default
Creating a table without enabling RLS is the most common security failure. When RLS is disabled, the anon key provides unrestricted access to the entire table. An attacker doesn't need to "hack" your app; they simply use the public key to query your API directly.
How to Enable and Configure RLS
To secure a table, you must first enable RLS and then create specific policies for different actions (SELECT, INSERT, UPDATE, DELETE).
RLS Best Practices for Production
- Never trust client-side IDs: Always use the
auth.uid()function in your policies. This function extracts the user ID from the verified JWT, which cannot be spoofed by the client. - Avoid the
publicrole: Policies applied topublicaffect everyone, including unauthenticated visitors. Only use this for data that is intended to be world-readable, like public blog posts. - Test beyond the UI: Attackers won't use your frontend. Test your policies by attempting to query the API directly using a tool like
curlor Postman with theanonkey but no session token. - Use
TO authenticated: Explicitly restrict policies to logged-in users whenever possible to reduce the attack surface.
How Do You Protect Your Service Role Key?
Supabase projects come with two main API keys. Mixing them up is a fatal error for application security.
The Anon Key (Frontend Safe)
The anon key is meant to be used in the browser. It is restricted by RLS. If your RLS is solid, the anon key is safe.
The Service Role Key (Server Only)
The service_role key is the "God Mode" key. It bypasses all RLS policies and has full administrative access to your database. If this key is leaked, an attacker can delete your entire database or steal every record, regardless of your RLS settings.
Where to store it:
- Supabase Edge Functions (via
Deno.env.get) - Server-side environments (Next.js
getServerSideProps, Node.js backends) - GitHub Secrets or other CI/CD secret managers
Where it must NEVER appear:
- Any file in your
srcfolder that gets bundled (JS, TS, JSX) - Environment variables with prefixes like
VITE_,NEXT_PUBLIC_, orREACT_APP_ - Public or private Git repositories in plain text
If you suspect a leak, rotate the key immediately in the Supabase Dashboard.
How Should You Configure Supabase Authentication?
Authentication is more than just a login form; it's the gateway to your RLS policies.
Disable Auto-Confirm
During development, auto-confirming emails is convenient. In production, it is a liability. It allows users to sign up with non-existent or stolen email addresses. Ensure "Confirm Email" is enabled in your Auth providers settings.
Enable Leaked Password Protection
Supabase offers a leaked password protection feature. This checks user passwords against a database of known breaches. Enabling this prevents users from using compromised credentials, significantly lowering the risk of account takeover.
Rate Limiting and Brute Force
Verify that rate limits are active on your auth endpoints. Supabase has defaults, but for high-traffic apps, you may need to adjust these to prevent sophisticated brute-force attacks.
How Do You Secure Supabase Storage?
Storage buckets are often overlooked in security audits, yet they frequently contain sensitive user uploads.
Private vs. Public Buckets
- Public Buckets: Files are accessible via a public URL. Use these only for non-sensitive assets like UI icons or public avatars.
- Private Buckets: Access is governed by RLS policies. This is the requirement for any user-uploaded content.
Storage RLS Example
You can restrict file access based on the folder path, often mapping the folder name to the user's ID.
File Validation
Don't rely on the frontend to restrict file types. Use database triggers or Edge Functions to validate file extensions and sizes upon upload to prevent users from uploading malicious scripts or massive files that exhaust your storage quota.
How Do You Secure Edge Functions?
Edge Functions are the bridge between your frontend and sensitive backend logic.
JWT Verification
By default, Edge Functions verify the user's JWT. Never disable this (verify_jwt: false) unless you are building a public webhook or have implemented a custom, robust webhook signature verification process.
CORS Hardening
Do not use Access-Control-Allow-Origin: * in production. Explicitly list your production domain. This prevents malicious sites from making requests to your Edge Functions on behalf of your users.
How Do You Secure Database Functions?
When you write custom Postgres functions, you must choose between SECURITY INVOKER (runs with the permissions of the user calling it) and SECURITY DEFINER (runs with the permissions of the creator, usually the owner).
The Search Path Risk
If you use SECURITY DEFINER, you must set a secure search_path. Without this, a malicious user could potentially "hijack" the function by creating a fake object in a different schema that the function then interacts with.
The Complete Supabase Production Checklist
Database & RLS
- RLS is enabled on every table in the
publicschema. - No policies use the
publicrole unless the data is truly world-readable. - All policies use
auth.uid()to verify ownership. SECURITY DEFINERfunctions have a fixedsearch_path.- Realtime is only enabled for tables that actually require it.
Authentication
- Email confirmation is required for all signups.
- Leaked password protection is enabled.
- Rate limits are configured for Auth and API endpoints.
- Site URL and Redirect URLs are strictly defined (no wildcards).
Secrets & Keys
- The
service_rolekey is not present in any frontend code or environment variables. - Third-party API keys (OpenAI, Stripe) are stored in Supabase Vault or Edge Function secrets.
- All environment variables are linted for leaks using env-file-linter.
Storage & Functions
- Sensitive files are in private buckets.
- Storage RLS policies prevent users from accessing other users' files.
- Edge Functions have JWT verification enabled.
- CORS headers are restricted to your production domain.
How to Audit Your Supabase App Automatically
Manual checks are prone to human error, especially in "vibe-coded" environments where code is generated rapidly. SimplyScan provides a specialized security scanner that detects common Supabase pitfalls in seconds.
SimplyScan checks for:
- Exposed
service_rolekeys in your JS bundles. - Missing security headers that protect against XSS and CSRF.
- Misconfigured CORS settings on Edge Functions.
- Broken authentication flows.
You can run a free scan to get a grade across 8 dimensions, including security, speed, and GDPR compliance signals.
Scan your Supabase app now
Related Security Resources
- Is Supabase Safe? · A deep dive into the platform's security model.
- RLS Policies Explained · Advanced patterns for complex data relationships.
- Vibe Coding Security Checklist · How to stay safe when using AI to build apps.
- API Security Best Practices · General principles for modern backends.
Related Free Tools
- JWT Debugger · Inspect your Supabase session tokens.
- CORS Tester · Verify your Edge Function headers.
- Secret Scanner · Check if you've accidentally committed keys to Git.
Explore all 60 free tools to harden your production environment.
FAQ
Is it safe to expose the Supabase anon key in my frontend?
Yes, the anon key is designed to be public and ships in your JavaScript bundle by design. However, its safety depends entirely on Row-Level Security (RLS). The key only grants access to what your RLS policies allow. If a table lacks RLS, the anon key provides full access to it. Exposing the key is standard; exposing unprotected tables is the risk.
What happens if I forget to enable RLS on a Supabase table?
If RLS is disabled, anyone with your project URL and anon key can read, modify, or delete every record in that table. Since these credentials are visible in your frontend code, an attacker can use them to make direct API calls to your database. This is the most common cause of data breaches in the Supabase ecosystem.
How do I test my Supabase RLS policies?
You should test three specific scenarios. First, query as an unauthenticated user; private data should return an empty array. Second, query as "User A" and attempt to access "User B's" records. Third, attempt to modify or delete data via direct API calls (using tools like Postman) rather than your UI, as attackers will bypass your frontend logic entirely.
Where should I store my Supabase service role key?
The service_role key must stay strictly server-side. Use it in Supabase Edge Functions, backend server code (like Node.js or Python), or CI/CD secrets. Never place it in frontend files or environment variables prefixed with VITE_ or NEXT_PUBLIC_, as these are bundled into the browser-accessible code. This key bypasses all RLS and grants full database control.
Do I need email confirmation enabled in Supabase?
For production applications, yes. While auto-confirm is useful during development, leaving it on in production allows malicious actors to create unlimited accounts using fake or unverified email addresses. Disabling auto-confirm ensures that every user in your system has access to the email address they signed up with, which is a fundamental security requirement.
Can SimplyScan check my Supabase security?
Yes. SimplyScan includes specific checks for Supabase and AI-built applications. It scans your deployed app to find exposed service_role keys, missing security headers, and common RLS-related vulnerabilities. In about 30 seconds, it provides a comprehensive report on your security posture, helping you catch leaks that manual code reviews might miss.
Frequently asked questions
Is it safe to expose the Supabase anon key in my frontend?
Yes, the anon key is designed to be public and ships in your JavaScript bundle by design. However, its safety depends entirely on Row-Level Security (RLS). The key only grants access to what your RLS policies allow. If a table lacks RLS, the anon key provides full access to it. Exposing the key is standard; exposing unprotected tables is the risk.
What happens if I forget to enable RLS on a Supabase table?
If RLS is disabled, anyone with your project URL and anon key can read, modify, or delete every record in that table. Since these credentials are visible in your frontend code, an attacker can use them to make direct API calls to your database. This is the most common cause of data breaches in the Supabase ecosystem.
How do I test my Supabase RLS policies?
You should test three specific scenarios. First, query as an unauthenticated user; private data should return an empty array. Second, query as "User A" and attempt to access "User B's" records. Third, attempt to modify or delete data via direct API calls (using tools like Postman) rather than your UI, as attackers will bypass your frontend logic entirely.
Where should I store my Supabase service role key?
The service_role key must stay strictly server-side. Use it in Supabase Edge Functions, backend server code (like Node.js or Python), or CI/CD secrets. Never place it in frontend files or environment variables prefixed with VITE_ or NEXT_PUBLIC_, as these are bundled into the browser-accessible code. This key bypasses all RLS and grants full database control.
Do I need email confirmation enabled in Supabase?
For production applications, yes. While auto-confirm is useful during development, leaving it on in production allows malicious actors to create unlimited accounts using fake or unverified email addresses. Disabling auto-confirm ensures that every user in your system has access to the email address they signed up with, which is a fundamental security requirement.
Can SimplyScan check my Supabase security?
Yes. SimplyScan includes specific checks for Supabase and AI-built applications. It scans your deployed app to find exposed service_role keys, missing security headers, and common RLS-related vulnerabilities. In about 30 seconds, it provides a comprehensive report on your security posture, helping you catch leaks that manual code reviews might miss.