Securing Next.js + Supabase, in depth
The specific mistakes that get apps on this stack hacked — and the exact fix for each. No generic checklists.
- ·6 min read
Fix: Supabase anonymous sign-ins are disabled
The 422 'Anonymous sign-ins are disabled' error means the provider is off. Enable it in Auth settings — then fix the RLS policies it just widened.
SupabaseAuthRLSErrors - ·6 min read
app_metadata vs user_metadata in Supabase RLS
user_metadata is editable by the user, so a role stored there is self-service admin. Put authorization claims in app_metadata — here's the difference.
SupabaseRLSAuthRBAC - ·6 min read
Supabase multi-tenant RLS: isolating tenant data
Multi-tenant RLS leaks when the policy's subquery isn't scoped to auth.uid(). Here's the tenant isolation pattern that actually holds, with SQL.
SupabaseRLSMulti-tenantPostgres - ·6 min read
Supabase signed URLs: expiry, leaks, revocation
A Supabase signed URL works for anyone who holds it until it expires — and can't be revoked without contacting support. How to scope expiry properly.
SupabaseStorageSigned URLsSecurity - ·6 min read
getSession vs getUser: which is safe on the server?
getSession() reads the cookie without verifying it, so it can be spoofed on the server. Use getUser() or getClaims() for any authorization decision.
SupabaseNext.jsAuthMiddlewareRLS - ·6 min read
Supabase publishable vs secret keys, explained
Publishable keys (sb_publishable_) replace the anon key and are safe to ship. Secret keys (sb_secret_) replace service_role and bypass RLS — server only.
SupabaseSecretsRLSNext.js - ·7 min read
Supabase Realtime not working with RLS?
Realtime filters Postgres Changes through your RLS policies, so a subscriber who can't SELECT a row never sees its event. The three causes and the fixes.
SupabaseRLSRealtimePostgres - ·6 min read
Supabase Security Definer View: an RLS bypass
A Postgres view runs as its owner, so a view over an RLS-protected table can return every row to anon. Fix it with security_invoker = on.
SupabaseRLSPostgresDatabase - ·8 min read
Why auth.uid() = user_id fails in Supabase RLS: uuid vs text
auth.uid() returns uuid but a text user_id can't be compared — Postgres won't cast either way, so the RLS policy errors 'operator does not exist: uuid = text'.
SupabaseRLSPostgresAuthType mismatch - ·8 min read
Why supabase.auth.getUser() returns null on the server in Next.js
getUser() returns null on the server because your Supabase client was never wired to the request cookies — no user JWT reaches Auth. Here's the two-file fix.
SupabaseNext.jsAuthSSRMiddleware - ·7 min read
How to fix 'infinite recursion detected in policy' in Supabase RLS
The infinite recursion detected in policy error means an RLS policy queries its own table. Fix it with a SECURITY DEFINER helper function.
SupabaseRLSPostgresSecurity - ·8 min read
Fix: new row violates row-level security policy on Supabase Storage upload
Uploading to Supabase Storage throws new row violates row-level security policy for table objects? Add an INSERT policy on storage.objects. Here's the fix.
SupabaseStorageRLSPostgresAuth - ·9 min read
AWS Cognito + Supabase RLS: setup and gotchas
AWS Cognito works with Supabase RLS via native third-party auth — its sub is UUID-shaped, so auth.uid() works in practice, but auth.jwt()->>'sub' is the portable pattern.
SupabaseRLSCognitoAuthPostgres - ·7 min read
Fix Supabase 'invalid claim: missing sub claim' (401)
Supabase's 'invalid claim: missing sub claim' is a GoTrue Auth error, not PostgREST — you sent an API key where a user access token belonged.
SupabaseAuthJWTGoTrueRLS - ·8 min read
Supabase: tables not exposed to the Data API after the 2026 grant change
In 2026 Supabase stopped auto-exposing tables to the Data and GraphQL API. Why your REST call now returns 404, and the safe grant fix.
SupabaseData APIRLSGrantsPostgREST - ·8 min read
WorkOS and Supabase RLS: fixing the auth.uid() trap for prefixed user ids
WorkOS is natively supported by Supabase, but its user ids are strings, not UUIDs. Here is the correct text-column RLS pattern for WorkOS + Supabase.
supabaseworkosrlsauthkitpostgres - ·5 min read
How to test your Supabase RLS policies
The Supabase SQL editor bypasses RLS, so testing there proves nothing. Here's how to actually test your policies with pgTAP and the client SDK.
SupabaseRLSTestingNext.js - ·6 min read
Is it safe to expose the Supabase anon key?
Yes — the Supabase anon key is public by design, but only if RLS is enabled on every table. Here's what makes it safe, and the one thing that makes it a leak.
SupabaseRLSSecretsNext.js - ·5 min read
Fix: Supabase Leaked Password Protection Disabled
Supabase's Leaked Password Protection Disabled warning means users can sign up with known-breached passwords. Enable it in Auth settings — here's how and why.
SupabaseAuthPasswordsSecurity - ·6 min read
Auth0 + Supabase RLS: the setup and the gotchas
Auth0 works with Supabase RLS via third-party auth — but you must send the ID token (not the access token), add a role claim, and use auth.jwt()->>'sub'.
SupabaseRLSAuth0AuthPostgres - ·6 min read
better-auth + Supabase RLS: the secure way
better-auth isn't a Supabase third-party auth provider, so don't share your JWT secret to fake it. Here's the secure way to enforce RLS under better-auth.
SupabaseRLSbetter-authAuthPostgres - ·9 min read
Clerk + Supabase RLS: why auth.uid() is null
With Clerk auth, auth.uid() fails because Clerk IDs aren't UUIDs — use Supabase's native integration and compare auth.jwt()->>'sub' to a text column.
SupabaseRLSClerkAuthPostgres - ·6 min read
Firebase Auth + Supabase RLS: setup and gotchas
Firebase Auth works with Supabase RLS via third-party auth — add a role:authenticated custom claim, force-refresh the first token, and match auth.jwt()->>'sub'.
SupabaseRLSFirebaseAuthPostgres - ·7 min read
NextAuth + Supabase RLS Without a Shared Secret
NextAuth isn't a Supabase-trusted auth provider, so signing your own JWT is risky. Here is the secure restricted-role pattern that keeps RLS enforced.
NextAuthSupabaseRLSPostgresAuthentication - ·7 min read
Next.js Middleware Auth Bypass: CVE-2025-29927
A spoofable x-middleware-subrequest header let requests skip Next.js middleware, bypassing auth. Upgrade to the patched version; don't trust middleware alone.
Next.jsMiddlewareSecurityCVE - ·10 min read
Does Prisma respect Supabase RLS? No — here's why
Prisma and Drizzle connect as the postgres role and bypass Supabase RLS entirely, so your policies never protect ORM queries. Here's how to fix it.
SupabaseRLSPostgresPrismaDrizzle - ·8 min read
Fix: Stripe Webhook Signature Verification Failed
Stripe webhook signature verification failed in Next.js? The top cause is a parsed body — pass the raw request text to constructEvent. Full fix here.
Next.jsStripeWebhooksSecurity - ·9 min read
Why auth.uid() returns NULL in a Supabase RLS policy
auth.uid() returns NULL when no user JWT reaches Postgres — you tested in the SQL editor, or your server client never forwarded the session.
SupabaseRLSPostgresAuthNext.js - ·6 min read
Supabase CORS error in Next.js: the real fix
A Supabase CORS error in Next.js is usually a bad env var or missing grants, not CORS. Here is how to diagnose it and fix Edge Function CORS properly.
SupabaseNextjsCORSSecurityEdge-Functions - ·8 min read
Fix Supabase JWT Expired (PGRST301) in Next.js
A Supabase PGRST301 'JWT expired' 401 means the access token aged out with nothing refreshing the cookie, and the @supabase/ssr middleware fixes it.
SupabaseNextjsAuthJWTRLS - ·10 min read
Fix: new row violates row-level security policy
Supabase error 42501 on insert means RLS is on but the row failed a WITH CHECK policy. Add or fix an INSERT policy matching auth.uid().
SupabaseRLSPostgresAuthNext.js - ·7 min read
Supabase: permission denied for table (42501)
The 42501 permission denied for table error is a missing Postgres GRANT, not an RLS failure. Here is the exact SQL fix.
SupabasePostgresRLSPermissionsSecurity - ·10 min read
Why Supabase RLS queries are slow (and how to fix)
Supabase RLS queries are slow because auth.uid() re-runs per row and policy columns lack indexes. Here are the verified fixes with SQL.
SupabaseRLSPostgresPerformance - ·5 min read
Supabase security breaches: what actually happened
Supabase itself hasn't been breached — but apps built on it leak data through disabled RLS and exposed keys. The documented incidents and how to check yours.
SupabaseRLSSecurityData Breach - ·9 min read
Supabase Function Search Path Mutable, fixed
A SECURITY DEFINER function without a pinned search_path can run attacker-controlled code as the owner. Fix it with set search_path = '' and qualified names.
SupabasePostgresSecurityRLS - ·6 min read
Is Supabase Secure? What's Safe and What's on You
Yes — Supabase is SOC 2 Type II and safe for production. The risks that actually leak data are in your config: RLS, the service_role key, and exposed secrets.
SupabaseRLSSecuritySecrets - ·7 min read
Next.js App-Layer Security: Beyond RLS and Secrets
Once RLS and secrets are locked down, the Next.js app layer is where bugs hide: XSS, eval, CORS, input validation, dependencies. The footguns and the fixes.
Next.jsSecurityXSSCORS - ·6 min read
Snyk vs GitGuardian vs GuardLayer for Indie Devs
A head-to-head of Snyk, GitGuardian, and GuardLayer for solo devs: what each actually catches, verified 2026 pricing, and which fits a Next.js + Supabase stack.
SecurityToolsNext.jsSupabase - ·6 min read
Why a Static Scanner Can't Stop Prompt Injection
A static scanner has no runtime or taint analysis, so it can't detect prompt injection. Here are the 5 concrete things it does catch in AI-built apps instead.
AISecurityPrompt InjectionSupabase - ·4 min read
Catch API Keys Before They Hit GitHub
A leaked API key is scraped within minutes of hitting GitHub. Here's how to catch secrets with a local pre-commit hook plus a scan on every push.
SecretsGitNext.jsCI - ·4 min read
Keeping Secrets Out of a Next.js App
The four ways secrets leak from a Next.js app — NEXT_PUBLIC_, client imports, hardcoding, and git history — and the exact pattern that keeps each one server-side.
Next.jsSecretsSupabaseSecurity - ·4 min read
Your MCP Config Is Leaking Secrets
MCP config files like .mcp.json get committed to git — and a database password or API key pasted inline leaks to everyone. Here's the ${env} fix.
MCPSecretsAISupabase - ·5 min read
Supabase Row Level Security: the Complete Guide
How Supabase RLS actually works, how to enable it, write correct policies, and avoid the mistakes that leak whole tables through the public anon key.
SupabaseRLSPostgresSecurity - ·4 min read
A Security Checklist for Vibe-Coded SaaS Apps
AI coding tools ship working code with predictable security holes. A concrete checklist to harden a vibe-coded Next.js + Supabase SaaS before real users hit it.
AISupabaseNext.jsSecurityRLS - ·4 min read
Do you need a paid security scanner as a solo dev?
When a free security scanner is genuinely enough for a solo dev — and the specific signals that mean it's time to pay. An honest decision guide, not a sales pitch.
SecurityToolsSolo DevFree Tier - ·4 min read
Free security scanning for a Next.js + Supabase repo (no card)
What you actually get on GuardLayer's free tier: continuous scanning for one Next.js + Supabase repo — every push, PR comments, a merge gate, every check, no credit card.
SecurityNext.jsSupabaseFree Tier - ·9 min read
AI Coding Agents Keep Leaving RLS Off — Catch It Before You Push
Cursor, Lovable, v0 and Claude routinely generate Supabase tables with no RLS — a public-data leak via the anon key. Why it happens and how to catch it.
SupabaseRLSAICursorLovable - ·9 min read
Best Security Scanner for Solo Devs & Indie Hackers (2026)
An honest buyer's guide to security scanners for solo devs, with verified June 2026 pricing for Snyk, GitGuardian, Socket, Semgrep, Sonar, and GuardLayer.
SecurityToolsNext.jsSupabase - ·8 min read
The Lovable RLS Vulnerability (CVE-2025-48757), Explained
CVE-2025-48757: AI-built Lovable apps shipped with Supabase RLS off, leaking data to anyone with the anon key. The root cause, the fix, and a self-check.
LovableSupabaseRLSCVEAI - ·8 min read
Is Your Next.js App Leaking Secrets to the Browser?
The three ways a Next.js app leaks secrets client-side — NEXT_PUBLIC_ misuse, server imports, and serialized props — and how to detect and fix each one.
Next.jsSecretsSecurityApp Router - ·7 min read
The Next.js + Supabase Security Checklist
A pre-launch security checklist for Next.js + Supabase apps covering RLS, secrets, the app layer, and dependencies — each item with the why and the fix.
SupabaseNext.jsRLSSecurityChecklist - ·9 min read
The Supabase MCP Lethal Trifecta: How an Agent Reads Your DB
Hand an AI agent the Supabase service_role key over MCP and you build the lethal trifecta. Here's the real risk and the fix that actually holds.
SupabaseMCPAIRLSPrompt Injection - ·8 min read
Vibe-Coding Security: What Goes Wrong in AI-Built Apps
AI tools like Lovable, Bolt and v0 ship Next.js + Supabase apps fast — and with the same handful of holes. Here are the recurring failures and fixes.
Vibe CodingSupabaseRLSNext.jsAI - ·5 min read
Is dangerouslySetInnerHTML safe? When React's escape hatch turns into XSS
dangerouslySetInnerHTML bypasses React's auto-escaping. Here's exactly when it becomes XSS, why the name is a warning, and the precise fix.
ReactXSSNext.jsSecurity - ·6 min read
eval() in JavaScript: the one line that turns user input into remote code execution
eval() runs whatever string you hand it. The moment any part is user-controlled, it's remote code execution. Here's why, and the exact fix.
JavaScriptevalRCENode.js - ·7 min read
Hardcoded API keys in a Next.js app: why deleting the line isn't enough
A hardcoded API key stays in git history after you delete the line. Here's why, and how to actually rotate it and purge it from your Next.js repo.
SecretsGitNext.jsStripe - ·6 min read
NEXT_PUBLIC_ leaked my API key — how it happens and how to catch it
NEXT_PUBLIC_ inlines any value into your client bundle. Here's how it ships your API key to every visitor — and why anon keys are safe but secrets aren't.
Next.jsSecretsAPI KeysEnv Vars - ·6 min read
How to validate the request body in a Next.js API route (and why request.json() is a hole)
request.json() returns any, so attackers can send any shape. Here's the mass-assignment risk and the exact zod safeParse fix for Next.js routes.
Next.jsValidationZodSecurity - ·6 min read
Access-Control-Allow-Origin: * — the CORS wildcard that quietly leaks your Next.js API
Access-Control-Allow-Origin: * in a Next.js route hands your API responses to any website. Why it leaks data, and the exact allowlist fix.
Next.jsCORSAPISecurity - ·6 min read
Your Next.js middleware runs on every request — add a matcher
Next.js middleware with no config.matcher runs on every request — static assets, images, prefetches included. Why that's a bug, and the exact fix.
Next.jsMiddlewareAuthPerformance - ·7 min read
Are Next.js Server Actions secure? The auth check everyone forgets
Server Actions are public POST endpoints anyone can call. Here's why a 'use server' mutation with no auth check is exploitable — and the fix to add.
Next.jsServer ActionsAuthApp Router - ·6 min read
Supabase Edge Functions are public by default — verify the JWT
Supabase Edge Functions are publicly invocable the second you deploy them. Here is why that is dangerous and how to verify the caller's JWT.
SupabaseEdge FunctionsAuthJWT - ·7 min read
Stop building SQL strings: injection in Next.js + Supabase apps
Interpolating a user value into a SQL string is how SQL injection reaches Supabase apps — even behind RLS. Here's the risk and the exact fix.
SupabaseSQL InjectionNext.jsRPC - ·7 min read
Public Supabase storage buckets: the silent data leak
A public Supabase storage bucket makes every object readable by anyone with the URL — no auth, no RLS. Here's why public: true leaks user data, and the fix.
SupabaseStorageSigned URLsSecurity - ·7 min read
Disabling RLS in Supabase: what it exposes and the fix
Disabling Row Level Security on a Supabase table makes it fully public via the anon key. What it exposes, how to re-enable RLS, and the exact fix.
SupabaseRLSPostgresMigrations - ·6 min read
Your Supabase RLS policy isn't scoped to the user: the missing auth.uid()
An RLS policy with a real WHERE-style condition can still leak every user's rows. The cause: the predicate never references auth.uid().
SupabaseRLSPostgresSecurity - ·7 min read
Your Supabase RLS is enabled and still wide open: the USING (true) trap
RLS being enabled doesn't mean it protects anything. A policy with USING (true) returns every row to every caller.
SupabaseRLSPostgresSecurity - ·6 min read
Hardcoding your Supabase service_role JWT is a permanent breach, even after you delete it
Pasting the service_role JWT (eyJ...) straight into source bypasses every RLS policy and lives in git history forever. Here's why, and the exact fix.
SupabaseSecretsJWTGit - ·8 min read
Every Supabase table needs RLS — the one you forgot is public
New Supabase tables ship with RLS off. The one table you forgot to enable it on is readable by anyone holding your anon key. How to catch it.
SupabaseRLSPostgresMigrations - ·6 min read
Vulnerable npm dependencies: catch them before they ship, not after
Most breaches start in node_modules, not your code. Here's how known-vulnerable npm packages slip past review — and how to stop them at the PR.
npmDependenciesNext.jsSupply Chain - ·5 min read
Your Supabase service role key is one NEXT_PUBLIC_ away from a full database breach
The Supabase service_role key bypasses every RLS policy. Here's exactly how it leaks into the browser, why it's catastrophic, and the precise fix.
SupabaseRLSSecretsNext.js