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.
- ·8 min read
"url" parameter is not allowed: Next.js 16 next/image
Next.js 16 returns "url" parameter is not allowed when an image host resolves to a private IP; gate dangerouslyAllowLocalIP to next dev, never commit true.
Next.jsImagesSSRFSupabaseSecurity - ·9 min read
Supabase webhook secret: DB webhooks aren't signed
There is no Supabase Database Webhook secret to find: the requests are unsigned, so send your own token header and check it with timingSafeEqual.
SupabaseWebhooksNext.jsSecretsSecurity - ·7 min read
Fix "Legacy API keys are disabled" in Supabase
Supabase's 'Legacy API keys are disabled' means a client sent an old anon or service_role JWT; give that client a new key instead of re-enabling the old pair.
SupabaseAPI KeysSecretsMigrations - ·7 min read
Supabase: Public Can Execute SECURITY DEFINER Function
The warning means anon can call a SECURITY DEFINER function over RPC, and revoking from anon alone won't clear it: revoke from public, anon and authenticated.
SupabasePostgresSECURITY DEFINERSecurity AdvisorGrants - ·7 min read
Next.js open redirect: why startsWith('/') fails
A Next.js redirect to a ?next= or returnTo param is an open redirect, and startsWith('/') won't stop /\evil.com. Check the parsed origin instead.
Next.jsOpen RedirectAuthSecurity - ·8 min read
Rate limiting a Next.js server action (Supabase)
Next.js has no built-in rate limiter, and Server Actions are public POST endpoints. Limit by IP in a shared store, and close the Supabase anon-key back door.
Next.jsServer ActionsSupabaseRate LimitingSecurity - ·7 min read
React2Shell in Next.js: which versions are safe?
React2Shell (CVE-2025-55182) is a CVSS 10 RCE in Next.js 15.x and 16.x App Router apps. Here's how to check your version, patch it, and rotate secrets.
Next.jsCVEReact Server ComponentsDependenciesSupabase - ·7 min read
use server vs server-only: they're opposites
"use server" doesn't mean server-only. It makes functions callable from the browser. Use import "server-only" to keep a module off the client.
Next.jsServer ActionsSupabaseSecurity - ·7 min read
Next.js 16: middleware.ts is now proxy.ts
Next.js 16 deprecates middleware.ts in favour of proxy.ts. Here's the codemod, the Node-runtime trap, and the security checks that silently stop matching.
Next.jsMiddlewareMigrationAuthSecurity - ·7 min read
Next.js 16 auth: proxy.ts is the wrong place
Next.js 16's Proxy can be deployed to your CDN, separate from your app — an optimization layer, not an authorization boundary. Here's where checks belong.
Next.jsAuthProxyServer ActionsSecurity - ·7 min read
Supabase 22P02: invalid input syntax for uuid
Error 22P02 means a non-uuid string reached a uuid column — usually an empty string from a form. Validate at the edge; never cast the column to text.
SupabasePostgresValidationRLSNext.js - ·7 min read
Supabase UPDATE policy: the WITH CHECK (true) trap
An UPDATE policy needs no WITH CHECK — Postgres reuses USING. Adding WITH CHECK (true) is what lets a user hand their row to someone else.
SupabaseRLSPostgresPoliciesSecurity - ·7 min read
"You can only request this after 56 seconds"
Supabase's per-address email cooldown is stored on the user row, so it can only fire for addresses that have an account. That makes the rate limit an oracle.
SupabaseAuthRate LimitsEnumerationSecurity - ·7 min read
Supabase reset password email not sending
resetPasswordForEmail() returns success and sends nothing when the address has no account. That's the anti-enumeration design — here's how to triage it.
SupabaseAuthPasswordsEnumerationSecurity - ·8 min read
Supabase signUp returns a user for any password
Sign up with an existing unconfirmed email and any password at all, and Supabase returns that account's real user object. Here's the branch that does it.
SupabaseAuthEnumerationNext.jsSecurity - ·8 min read
"Signups not allowed for otp": the real cause
Supabase raises "Signups not allowed for otp" only when shouldCreateUser is false and the address has no account — making the error an existence oracle.
SupabaseAuthMagic LinkEnumerationSecurity - ·7 min read
supabase.auth.admin.getUserByEmail is not a function
There is no getUserByEmail in the Supabase admin API. Here's the undocumented filter param that does work, and the four workarounds ranked by blast radius.
SupabaseAuthAdmin APIEnumerationSecurity - ·8 min read
Supabase email_address_invalid on password reset
resetPasswordForEmail can return email_address_invalid for a real, confirmed user while returning plain success for an address that doesn't exist. Here's why.
SupabaseAuthPasswordsEnumerationSecurity - ·8 min read
Supabase RLS with no policies: is it safe?
Enabling RLS with zero policies and routing every read through SECURITY DEFINER RPCs works — but it removes your last backstop. Here's what you take on.
SupabaseRLSPostgresSECURITY DEFINERSecurity - ·7 min read
The Supabase RLS policy that fails open
using (auth.uid() is null or user_id = auth.uid()) returns every row to anonymous callers. Supabase's own linter does not flag it. Here's how to find yours.
SupabaseRLSPostgresSecurity - ·6 min read
Fix: Database error granting user (Supabase)
This login 500 is usually a trigger that fires when sign-in updates auth.users, then throws. Fix the trigger function — don't loosen RLS to make it pass.
SupabaseAuthPostgresTriggersRLS - ·6 min read
Fix: Invalid API key error in Supabase
Invalid API key means the key doesn't match the project at that URL. Fix the key/URL pairing — don't obey the hint by putting service_role in the browser.
SupabaseAPI KeysNext.jsSecurity - ·7 min read
Supabase "Invalid login credentials": 3 real causes
Supabase returns Invalid login credentials for a wrong password, a missing account, and a passwordless account alike. Here's how to tell which one you hit.
SupabaseAuthEnumerationSecurity - ·6 min read
Supabase select returns empty array: RLS, not a bug
An empty [] with no error while the table editor shows rows is RLS filtering the query. Diagnose the policy and role — don't disable RLS to test it.
SupabaseRLSPostgresDebugging - ·7 min read
Supabase auth rate limit exceeded: the full table
429s from Supabase Auth come from nine documented limits — 2 emails/hour, 30 OTPs/hour, a 60-second per-user window. Here's each one and how to work with it.
SupabaseAuthRate LimitsSecurity - ·8 min read
Supabase signup with an existing email: why no error
Supabase returns a fake user instead of "email already exists" on purpose — it blocks user enumeration. The usual workaround puts the hole right back.
SupabaseAuthEnumerationRLSSecurity - ·7 min read
Fix: Error sending confirmation email (Supabase)
This 500 is your email provider failing, not your code. Read the Auth logs for the real reason — and don't fix it by turning email confirmation off.
SupabaseAuthSMTPEmailSecurity - ·7 min read
Fix: Failed to delete user (Supabase)
Database error deleting user is almost always a foreign key pointing at auth.users without ON DELETE CASCADE. Fix the constraint, not the delete.
SupabaseAuthPostgresGDPRRLS - ·7 min read
Fix: Auth session missing in Next.js Route Handlers
Middleware refreshes your Supabase session fine, but getUser() throws AuthSessionMissingError inside /api. Here's why the Route Handler never sees it.
SupabaseNext.jsAuthRoute HandlersSessions - ·8 min read
Fix: Route couldn't be rendered statically (cookies)
Next.js throws DynamicServerError when something swallows the bailout that should opt your Supabase route into dynamic rendering. Here's the real fix.
SupabaseNext.jsApp RouterBuildSessions - ·8 min read
Fix: Supabase invalid flow state, no valid flow state
Supabase's invalid flow state error means the PKCE row can't be matched to the code you're exchanging — usually a misrouted OAuth redirect URI.
SupabaseAuthOAuthPKCENext.js - ·7 min read
Fix: Email link is invalid or has expired (otp_expired)
Supabase's otp_expired error is usually a corporate mail scanner clicking the link before your user does. Here are the two documented fixes.
SupabaseAuthMagic LinkEmailNext.js - ·7 min read
Fix: permission denied for schema public (Supabase)
This error means USAGE on the public schema was revoked from anon and authenticated — usually by an ORM migration. RLS never even gets a chance to run.
SupabasePostgresGrantsPrismaRLS - ·6 min read
Fix 431 REQUEST_HEADER_FIELDS_TOO_LARGE on Vercel
Supabase stores OAuth provider tokens in the session cookie. Add enough scopes and the request header overflows, so Vercel returns 431. Here's the fix.
SupabaseNext.jsVercelAuthCookies - ·6 min read
Fix: output claims do not conform to expected schema
Supabase's custom access token hook must return the whole event object. Return only your new claims and Auth sees null — here's the correct function.
SupabaseAuthJWTRBACPostgres - ·7 min read
Supabase MFA blocks password reset (AAL2 required)
With MFA on, a recovery link yields an AAL1 session but updateUser({password}) demands AAL2, so the reset 401s. Here's the flow that actually works.
SupabaseAuthMFAPasswordsSecurity - ·7 min read
Fix: auth code and code verifier should be non-empty
Supabase's PKCE code verifier lives in a cookie set by the browser that started the login. If your callback route can't read it, the exchange fails.
SupabaseAuthNext.jsOAuthPKCE - ·7 min read
Fix: Invalid Refresh Token: Refresh Token Not Found
Supabase logs users out roughly every day when your middleware never persists the rotated refresh token. Here's the cookie bug and the exact fix.
SupabaseAuthNext.jsSessionsMiddleware - ·6 min read
Fix 42501: must be owner of table objects
Supabase blocked ALTER TABLE on the storage schema in April 2025. RLS is already on for storage.objects — delete the line and just create your policies.
SupabaseStorageRLSMigrations - ·6 min read
Fix: Failed to parse cookie string base64-eyJ
The base64-eyJ cookie parse error means deprecated @supabase/auth-helpers can't read the new cookie format. Migrating to @supabase/ssr is the only real fix.
SupabaseAuthNext.jsDependencies - ·6 min read
Fix: cookies() should be awaited (Next.js 15)
Next.js 15 made cookies() async, which breaks the Supabase server client. Make createClient async and await the cookie store — here's the exact code.
SupabaseNext.jsSessionsAuth - ·8 min read
Fix: Invalid Refresh Token: Already Used
Supabase's Already Used refresh error is a race, not an expiry. Concurrent App Router layouts refresh the same token — here's the cause and the fix.
SupabaseAuthNext.jsSessions - ·6 min read
Fix supabaseUrl is required on Vercel
supabaseUrl is required on a Vercel build means the env var is missing for that environment — not that your code is wrong. Here's the fix, and the trap.
SupabaseNext.jsVercelSecrets - ·6 min read
Fix Supabase 'AuthApiError: User not allowed'
AuthApiError: User not allowed means you called auth.admin with a non-service-role key. Fix it on the server — moving the key to the browser is a breach.
SupabaseAuthSecretsNext.js - ·7 min read
Fix: Database error saving new user (Supabase)
AuthApiError: Database error saving new user almost always means your trigger on auth.users failed. Here's how to read the real error and fix it safely.
SupabaseAuthRLSPostgres - ·7 min read
Supabase JWT signing keys broke auth.uid()
After migrating to asymmetric JWT signing keys, PostgREST can resolve valid tokens as anon — auth.uid() goes NULL and writes silently no-op. The fix.
SupabaseRLSAuthJWT - ·7 min read
Fix PGRST116: multiple (or no) rows returned
PGRST116 means .single() got 0 or 2+ rows. On Supabase it's usually RLS silently filtering the row out — here's how to tell that apart from a bad id.
SupabaseRLSPostgRESTNext.js - ·8 min read
Fix @supabase/ssr 'Detected stale cookie data'
The @supabase/ssr stale cookie warning means leftover auth-token chunk cookies are shadowing the real session. Here's the cause and the exact fix.
SupabaseNext.jsAuthCookies - ·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 API keys: sb_publishable vs anon vs service_role
The anon and service_role keys are being replaced. sb_publishable_ is safe in the browser; sb_secret_ replaces service_role and bypasses 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 - ·9 min read
supabase.auth.getUser() returns null on the server (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
Fix: infinite recursion detected in policy (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
Supabase Storage: new row violates row-level security policy
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, 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 - ·9 min read
Supabase: table not exposed to the Data API (2026 grants)
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 + Supabase RLS: the auth.uid() trap for prefixed 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 - ·6 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 - ·6 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 - ·11 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 - ·9 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 - ·13 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 - ·7 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 - ·16 min read
Supabase Function Search Path Mutable, fixed
The Supabase database linter flags function_search_path_mutable when a SECURITY DEFINER function has no pinned search_path. The real risk, and the exact fix.
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 - ·5 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, git history — and the 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 - ·28 min read
Supabase Row Level Security: the Complete Guide
The complete practical guide to Supabase RLS: grants vs policies, USING vs WITH CHECK, the patterns that hold, what bypasses RLS, and how to verify it.
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 signals that mean it's time to pay. An honest decision guide, not a sales pitch.
SecurityToolsSolo DevFree Tier - ·6 min read
Free security scanning for a Next.js + Supabase repo (no card)
What GuardLayer's free tier gives you: every push scanned on unlimited public Next.js + Supabase repos, PR comments, a merge-gate 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 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 - ·8 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 - ·6 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 - ·7 min read
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 - ·14 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 - ·7 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