Supabase Security Checklist: Protect Your Database in Production
Quick answer: Supabase’s anon key is intended for client use. Review database privileges, RLS policies, server credentials, authentication settings, and Storage permissions. Configure CORS for the intended browser clients and test protected operations directly.
By Gabriel CA · Kraftwire Software
· 10 min readSupabase’s anon key is intended for public use. Review table privileges, RLS policies, and bypass credentials to control database access. Keep the service_role key server-side. Review authentication settings and available password protections, configure CORS for the intended browser clients, and test protected operations directly.
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. Review your application’s access rules, configuration, and credential handling alongside the platform’s controls.
The anon key is not a secret. It is intended for client applications. If you use it in browser code, users can inspect it. Review the tables and operations reachable with that key and with each authenticated role; the development tool does not establish whether those permissions are correct.
These live figures describe all scanned sites. They do not establish prevalence for an AI-built subgroup, compare development methods, or attribute findings to AI generation.
30% of the 119 live sites SimplyScan has scanned (36 sites) have at least one high or critical security issue.
How Does Row-Level Security (RLS) Work?
RLS is a database access-control mechanism in Supabase. It allows you to define granular access rules directly on your database tables. With RLS disabled, access depends on table privileges; it is not automatically public.
Review RLS and Table Privileges
Review RLS state and privileges whenever a table is created. Clients can call the Data API directly, so test its permissions independently of the UI. This is a core part of any application security checklist.
How to Enable and Configure RLS
For a table exposed through the Data API, review table privileges, enable RLS where row filtering is required, and define policies for the intended actions (SELECT, INSERT, UPDATE, DELETE). With RLS enabled and no applicable policy, ordinary roles are denied; privileged bypass roles are a separate case. Include the anon role when reviewing anonymous access.
RLS Best Practices for Production
Never trust client-side IDs
For user-owned records, compare auth.uid() with the stored owner ID. It reflects the authenticated request identity; clients must not be trusted to supply an arbitrary owner ID. Other access models may require role or membership checks.
Step 2
Review the public role: Policies applied to public apply to all roles, but access still depends on table privileges and the policy expression. Use explicit roles and conditions that match the intended audience.
Test beyond the UI
Attackers will not use your frontend. Test your policies by attempting to query the API directly using a tool like curl or Postman with the anon key but no session token.
Step 4
Use TO authenticated: Explicitly restrict policies to logged-in users whenever possible to reduce the attack surface. For more details, see our guide on Supabase RLS explained.
How Do You Protect Your Service Role Key?
Distinguish client credentials from elevated server credentials. The legacy anon and service_role keys have different privileges; projects may also use publishable and secret API keys.
The Anon Key (Frontend Safe)
The anon key is intended for client use. For database access, review table privileges, RLS state, and applicable policies; RLS alone does not describe every accessible operation. Test the anon role without a signed-in session and test authenticated roles separately. You can read more about why the Supabase anon key is safe when configured correctly.
The Service Role Key (Server Only)
A service_role credential bypasses RLS; its database privileges still determine which operations it can perform. Keep it out of client-side code.
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. You can use our secret scanner to check if you've accidentally committed keys to Git.
How Should You Configure Supabase Authentication?
Authentication is more than just a login form; it's the gateway to your RLS policies. Proper Supabase auth security is essential.
Review Email Confirmation
Enable email confirmation when the account model requires proof of access to a supplied address. Test the confirmation and recovery flows; mailbox access does not establish a person’s identity or ongoing account security.
Enable Leaked Password Protection
Supabase offers a leaked password protection feature. This checks user passwords against a database of known breaches. Enable it where available and verify the configured behavior. It checks known leaked passwords; it does not identify every compromised credential or establish that accounts cannot be taken over.
Rate Limiting and Brute Force
Verify that rate limits are active on your auth endpoints. Review the configured limits against expected traffic and abuse scenarios, and test how the application handles rejected requests.
How Do You Secure Supabase Storage?
Inventory storage buckets and identify which files are intended to be public or private.
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: Downloads require authorized access or a valid signed URL. Use private buckets for sensitive uploads and review the relevant policies and signed-URL lifetimes.
Storage RLS Example
The following policy uses FOR ALL for the covered operations and USING to select existing rows. With no explicit new-row check, PostgreSQL reuses the same condition for proposed rows. It assumes a private bucket and maps the first folder component to auth.uid(); test other policies and required privileges too.
File Validation
Do not rely on frontend restrictions. Enforce upload size and content-type limits server-side, inspect content where required, and control how uploaded files are served. A filename extension alone does not establish that a file is safe.
How Do You Secure Edge Functions?
Edge Functions are the bridge between your frontend and sensitive backend logic.
JWT Verification
Check the deployed function’s authentication configuration and validate caller identity and authorization in its handler. A valid project API credential is not necessarily an authenticated user session. If using verify_jwt: false, implement the authentication appropriate to the endpoint, such as webhook signature verification for a webhook.
CORS Hardening
Choose allowed origins according to the browser clients that need to read responses. Access-Control-Allow-Origin: * can be appropriate for intentionally public, non-credentialed responses. For private cross-origin access, restrict origins as needed and enforce server-side authentication, authorization, and CSRF defenses. CORS controls browser response access; it does not prevent all cross-origin requests. See our CORS explained guide for more details.
How Do You Secure Database Functions?
PostgreSQL functions use SECURITY INVOKER by default, with the caller’s privileges. SECURITY DEFINER uses the function owner’s privileges. Restrict who can execute elevated functions and authorize sensitive operations within them.
The Search Path Risk
For SECURITY DEFINER, keep untrusted schemas out of search_path, qualify referenced objects, and restrict execution. The example below illustrates name resolution only: returning secrets requires an application-specific authorization check before it is suitable for use.
The Complete Supabase Production Checklist
Database & RLS
- Exposed tables that need row-level restrictions, including those in the
publicschema, have RLS enabled with appropriate policies and grants. - Policies using the
publicrole have conditions and privileges appropriate to all roles. - Ownership policies use
auth.uid()where appropriate; role and membership policies match the application’s access model. SECURITY DEFINERfunctions exclude untrusted schemas fromsearch_path, use qualified object names, and restrict execution privileges.- Realtime is only enabled for tables that actually require it.
Authentication
- Email confirmation matches the account model and required proof of mailbox access.
- Available leaked-password protection is configured and tested.
- Rate limits are configured for Auth and API endpoints.
- Site URL and Redirect URLs match intended deployments; any wildcard is narrowly scoped and tested.
Secrets & Keys
- The
service_rolekey is absent from frontend code and browser-exposed environment variables. - Third-party API keys (OpenAI, Stripe) are stored in Supabase Vault or Edge Function secrets.
- Supplied environment-file text is checked with the env-file-linter; inspect deployed bundles and repository history separately.
Storage & Functions
- Sensitive files are in private buckets.
- Storage policies deny unintended cross-user access; tests cover private test files.
- Edge Functions validate the intended caller and authorize protected operations.
- CORS response headers match the intended browser clients.
Addressing Vibe-Coding Risks
Review generated code and deployed configuration directly, and test application-specific behavior alongside the supported automated checks. See Windsurf, Cursor.
13% of the 119 live sites SimplyScan has scanned (15 sites) have at least one high-severity web-security issue, such as a missing Content-Security-Policy or HSTS header.
Review the web-security findings individually; exposed API keys and Supabase findings have their own categories.
Windsurf and Cursor Security
If you are using Windsurf or Cursor, always double-check the SQL it generates for RLS. Inspect the generated SQL, policy conditions, request roles, and table privileges.
Lovable and Bolt.new
For platforms like Lovable or Bolt.new, ensure that your environment variables are correctly categorized. Never put your service_role key in the "Public" or "Client" environment variable sections.
How to Audit Your Supabase App Automatically
Use the SimplyScan security scanner for supported observable checks alongside code review and authenticated tests. Anonymous URL checks do not verify cross-user access or server-only protections.
Supported checks include exposed service_role keys in inspected browser code, missing security headers, and selected CORS signals. Header presence does not establish that XSS or CSRF is prevented, and these checks do not cover every authentication flow.
You can run a free scan to get a grade across 8 dimensions, including security, speed, and GDPR compliance signals.
The average security score across the 119 live sites SimplyScan has scanned is 86 out of 100.
There is no room for complacency.
Advanced Security: AEO and AI Visibility
For content intended to be public, review crawler access and the information exposed to search systems. See Answer Engine Optimization (AEO). If your Supabase-powered site is a Single Page Application (SPA), ensure it is crawlable. Check crawler access, metadata, and the content available without client-side execution. See AI visibility tool, SEO vs AEO.
Performance and Reliability
Security doesn't exist in a vacuum. A slow app can be a sign of architectural issues.
79% of the 119 live sites SimplyScan has scanned (94 sites) have at least one medium-severity speed issue, such as render-blocking scripts or no route-based code splitting.
Review timeouts, retries, and multi-step operations. A PostgreSQL transaction is atomic; a workflow spanning multiple transactions or external services needs explicit failure handling. Use uptime monitoring to detect availability failures and alerts to investigate changes. It does not prevent outages or establish that security controls are working. See uptime monitoring.
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.
- React Security Best Practices · Securing the frontend of your Supabase app.
Related Free Tools
- JWT Debugger · Inspect your Supabase session tokens.
- CORS Tester · Verify your Edge Function headers.
- Inspect the certificate, hostname coverage, and supported TLS configuration, and investigate any reported errors. See SSL Checker.
- Security Headers Tool · Check for CSP, HSTS, and other vital headers.
Explore all our free tools to harden your production environment.
Frequently asked questions
Is it safe to expose the Supabase anon key in my frontend?
Yes, the anon key is intended for client use. Database access depends on table privileges, RLS state, and applicable policies. Review exposed operations and test private data access with unauthenticated and authenticated requests; exposing the public key does not establish whether those permissions are correct.
What happens if I forget to enable RLS on a Supabase table?
With RLS disabled, table privileges determine access; the table is not automatically public. A client can call the Data API directly using its public API key and any user session credentials it holds. Inspect the privileges for the resulting role and test whether private data is exposed.
How do I test my Supabase RLS policies?
Use disposable private records and distinct test accounts. Test unauthenticated reads, each owner’s allowed operations, and attempts to read or modify another account’s records. Denied access may return an error or no rows depending on the operation and permissions. Test direct API calls as well as the UI.
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. A service_role credential bypasses RLS; its database privileges still determine which operations it can perform. Keep it out of client-side code.
Do I need email confirmation enabled in Supabase?
Enable email confirmation when your account model requires proof of access to the supplied address. It verifies access during that flow; it does not establish a person’s identity, prevent all fake accounts, or guarantee ongoing account security.
Can SimplyScan check my Supabase security?
SimplyScan checks supported deployed signals such as exposed service_role keys, missing security headers, and selected backend responses. Its anonymous URL scan does not verify cross-user access or server-only protections. Review findings alongside source code, database privileges and policies, and owner-run authenticated tests.