Quick Answer
Supabase anon keys and Firebase client configs are designed to be public - they are used in browser-side code. The danger is the Supabase service_role key and Firebase Admin SDK credentials, which bypass all security rules. AI tools like Bolt and Lovable frequently commit the wrong key to Git or expose it in client-side code, giving anyone who finds it full database access.
Supabase Has Two Keys - Only One Is Safe to Expose
Supabase projects come with two kinds of API key, accessible from your project dashboard under Settings > API Keys. Older projects use the JWT-based anon and service_role keys; Supabase now issues a publishable key (sb_publishable_...) and a secret key (sb_secret_...) in their place and is deprecating the legacy keys by the end of 2026. The split is the same either way, and most developers, and almost all AI-generated code, conflate the two:
The anon key (anonymous key) is the public-facing credential. It is designed to be included in client-side code, committed to public repositories, and bundled into your JavaScript. It respects every Row Level Security (RLS) policy you define on your tables. If RLS is configured correctly, the anon key can only access data that unauthenticated users are supposed to see.
The service_role key is entirely different. It is a superuser credential. It bypasses all RLS policies entirely and has full read/write access to your entire database. It is intended only for server-side administrative tasks - like a backend function that needs to create users or access data across all accounts. It should never appear in client-side code, never be committed to Git, and never be prefixed with NEXT_PUBLIC_.
Supabase service keys are easy to expose in vibe-coded projects, because AI tools generating full-stack apps often reach for the most powerful credential available to make things work quickly.
How AI Tools Get This Wrong
When you prompt Bolt or Lovable to "connect to my Supabase database," it scaffolds a Supabase client. If you paste both keys from your dashboard into the prompt context, the AI will often use the service_role key - because it works for everything, while the anon key requires RLS to be set up first.
// ❌ BAD - service_role key in client-side Next.js code
// lib/supabase.ts (loaded in the browser)
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
'https://xyzxyzxyz.supabase.co',
// This is the service_role key - it bypasses ALL Row Level Security.
// Anyone who opens DevTools Network tab can see this key.
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh5enh5enh5eiIsInJvbGUiOiJzZXJ2aWNlX3JvbGUiLCJpYXQiOjE2MjkxOTI4MDAsImV4cCI6MTk0NDc2ODgwMH0.EXAMPLE'
);
The decoded JWT payload of a service_role key contains "role": "service_role". Anyone can base64-decode the middle segment of any JWT and see exactly what role it grants. Your key's role is not a secret - which means if you expose the service_role key, you've handed over administrative database access.
// ✅ GOOD - Correct key separation in Next.js
// lib/supabase-client.ts (browser-safe, uses anon key)
import { createBrowserClient } from '@supabase/ssr';
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // anon key - safe to expose
);
}
// lib/supabase-server.ts (server-only, uses service_role key)
import { createClient as createAdminClient } from '@supabase/supabase-js';
export function createAdminSupabase() {
return createAdminClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY! // NOT NEXT_PUBLIC_ - server only
);
}
Firebase: The Config Object vs. Admin SDK
Firebase has an analogous split. The Firebase client config object - the one with apiKey, authDomain, projectId - is designed to be public. Despite the name, the apiKey in this config is not a secret; it identifies your Firebase project to Google's servers. Firebase Security Rules govern what authenticated and unauthenticated users can access.
The Firebase Admin SDK credentials are a different matter entirely. The service account JSON file downloaded from the Google Cloud Console contains a private RSA key. This credential bypasses all Firestore Security Rules and Firebase Auth checks. The Cloud Security Alliance's Top Threats to Cloud Computing 2024 report ranks misconfiguration and inadequate change control as the most significant cloud threat, with identity and access management second.
| Credential | Safe in Client Code? | Safe to Commit to Git? | Bypasses Security Rules? |
|---|---|---|---|
| Supabase anon key | Yes (with RLS) | Yes (with RLS) | No |
| Supabase service_role key | Never | Never | Yes - full bypass |
| Firebase client config | Yes (with Security Rules) | Yes (with Security Rules) | No |
| Firebase Admin SDK JSON | Never | Never | Yes - full bypass |
| Firebase Database Secret (legacy) | Never | Never | Yes |
Row Level Security: The Anon Key's Only Defense
The Supabase anon key being safe to expose is conditional on RLS being enabled and correctly configured. This is a critical caveat. Vibe-coded apps often ship with RLS disabled or with policies that are too loose - either because the AI scaffolded the project that way, because the developer turned it off to make queries work, or because they never enabled it in the first place. CVE-2025-48757 describes RLS policies in Lovable-generated sites that let unauthenticated attackers read and write arbitrary tables (Lovable disputes the CVE).
If RLS is disabled on a Supabase table, the anon key effectively acts like a service_role key for that table. Anyone with your anon key (which is public) can read and write all rows. Broken access control is first on the OWASP Top 10 2025, and RLS misconfiguration is how that flaw shows up in Supabase.
Before treating your anon key as safe, verify that every table with sensitive data has RLS enabled and that your policies are tested:
// ✅ GOOD - Verify RLS is enabled via Supabase SQL editor
// Run this query to check which tables have RLS disabled:
// SELECT schemaname, tablename, rowsecurity
// FROM pg_tables
// WHERE schemaname = 'public'
// ORDER BY tablename;
// Any row where rowsecurity = false on a table with user data
// means your anon key can read ALL rows in that table.
How to Find Exposed Credentials in Your Repo
Tools like VibeDoctor's Vibe Check run Gitleaks on every file in your repository, which flags a hardcoded Supabase key as a JWT and the private key inside a Firebase service account file, with the file and line of each. A flagged Supabase JWT can also be your public anon key, so decode it and check the role. Free to sign up.
Manually, look for these patterns: any JWT starting with eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 that decodes to a service_role payload, any .json file containing "private_key_id" (the telltale field in Firebase service account files), and any environment variable named NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY - a mistake where both parts of the naming convention are wrong simultaneously.
FAQ
My Supabase anon key is already on GitHub - do I need to rotate it?
If RLS is enabled and correctly configured, a public anon key is expected and not a vulnerability. However, if you have any tables with RLS disabled, or if you are unsure of your RLS configuration, treat it as compromised. Under Settings > API Keys in the Supabase dashboard you can move your app to the newer publishable and secret keys and then disable the legacy keys.
What does the Supabase service_role key actually give an attacker?
Full read and write access to every table in your database, bypassing all Row Level Security policies. An attacker can read all user data, modify records, delete data, and - depending on your Supabase configuration - use it to create admin-level auth tokens. Treat it with the same sensitivity as a database root password.
Is it safe to commit the Firebase firebaseConfig object?
Yes, Firebase's client config (with apiKey, authDomain, etc.) is designed to be public. Google's documentation explicitly states these values do not need to be treated as secrets, as long as the API key is restricted to Firebase services. Your Firebase Security Rules are what protect your data. However, the Firebase Admin SDK service account JSON file is a private key and must never be committed.
How do I use the service_role key safely in a Next.js app?
Store it as a server-only environment variable (no NEXT_PUBLIC_ prefix) and only instantiate the admin Supabase client inside server-side code: API Routes, Server Actions, or getServerSideProps. Never import or use the admin client in any file that is included in the client bundle.
Can Bolt or Lovable apps have RLS enabled?
Yes. Supabase allows you to enable RLS from the Table Editor and write policies in the SQL editor. Enabling RLS without any policies blocks all access with the anon key, so queries come back empty. The risk is the quick fix: turning RLS off again, or adding a policy such as using (true) that lets anyone read every row. Always test your policies with an authenticated user and an anonymous user before deploying.