[{"data":1,"prerenderedAt":29},["ShallowReactive",2],{"$fS2HbuWJCMkeEXsoKmG5ODJmjCTsMpcU0lAzk_aD7tBA":3},{"slug":4,"title":5,"description":6,"date":7,"tags":8,"draft":13,"readingMinutes":14,"html":15,"related":16},"vibe-coding-security","Vibe Coding Security: The Five Things I Check First","Vibe coding security in plain terms: the five checks I run on an AI-built app before real users arrive, from database rules to keys in the bundle.","2026-11-03",[9,10,11,12],"security","vibe-coding","supabase","ai-built-apps",false,4,"\u003Cp>The biggest security hole in a vibe-coded app is almost never clever. It&#39;s a database that answers anyone who asks. The app looks finished, the login screen works, and the data behind it is readable with the public key that ships in every browser. So when someone sends me an app built with Lovable, Bolt, Cursor or a similar tool, I don&#39;t start with a scanner. I start by asking the backend for data as a stranger, and most of what matters shows up in the first ten minutes.\u003C/p>\n\u003Cp>This isn&#39;t a theory. In 2025 Matt Palmer scanned 1,645 apps built with Lovable and found 170 that let anyone read or write their tables, because the row level security rules were missing or too loose. It became \u003Ca href=\"https://www.superblocks.com/blog/lovable-rls-breach\">CVE-2025-48757\u003C/a>. Separately, \u003Ca href=\"https://www.veracode.com/resources/analyst-reports/2025-genai-code-security-report/\">Veracode&#39;s 2025 GenAI code security report\u003C/a> found security flaws in 45% of the code samples the models wrote. The tools have added scanners since then, and they help. They don&#39;t replace looking.\u003C/p>\n\u003Ch2>1. Ask the database as a stranger\u003C/h2>\n\u003Cp>Most of these apps use Supabase. The anon key is public by design: it&#39;s in the JavaScript bundle and anyone can copy it from the network tab. That&#39;s fine only if every table has row level security turned on and policies that check who&#39;s asking.\u003C/p>\n\u003Cp>So I open the site logged out, copy the project URL and anon key from a request, and call the REST endpoint for each table. If \u003Ccode>GET /rest/v1/profiles?select=*\u003C/code> returns rows, the app is leaking. Then I log in as a normal test user and try to read another user&#39;s rows by changing the id in the request. Policies that say \u003Ccode>using (true)\u003C/code> are the classic mistake: RLS is &quot;on&quot;, and it still lets everyone through. The \u003Ca href=\"https://supabase.com/docs/guides/database/postgres/row-level-security\">Supabase RLS docs\u003C/a> explain the policy model well if you want to fix it yourself.\u003C/p>\n\u003Cimg src=\"/blog/vibe-coding-security/vibe-coding-security-stranger-request.webp\" alt=\"A browser network tab where a logged-out request with the public key returns every user profile, flagged as the leak to fix first\" width=\"1600\" height=\"900\" loading=\"lazy\">\u003Ch2>2. Look for secret keys in the bundle\u003C/h2>\n\u003Cp>The second check is a search. I download the built JavaScript and look for \u003Ccode>service_role\u003C/code>, \u003Ccode>sk_live\u003C/code>, \u003Ccode>sk-\u003C/code> and any long random string next to words like secret, token or key. The Supabase service role key skips RLS completely, so if it&#39;s in the frontend, nothing in point 1 matters. Stripe secret keys, OpenAI keys and email API keys belong in a server function or an environment variable on the server, never in code the browser downloads. I also check the git history, because a key that was &quot;removed&quot; in a later commit is still there.\u003C/p>\n\u003Ch2>3. Read the server functions as an attacker would\u003C/h2>\n\u003Cp>AI tools write edge functions and API routes fast, and they often skip the boring part: checking who&#39;s calling. For each function I ask three things. Does it verify the user&#39;s token, or does it trust a \u003Ccode>userId\u003C/code> sent in the body? Does it check the user is allowed to touch that record? And does a webhook check its signature? A payment webhook without a signature check means anyone can mark an order paid. The \u003Ca href=\"https://owasp.org/API-Security/\">OWASP API Security Top 10\u003C/a> is a good list to read against, and broken object level authorization sits at number one for a reason.\u003C/p>\n\u003Ch2>4. Check storage buckets and file URLs\u003C/h2>\n\u003Cp>File uploads are where a lot of private data ends up: ID photos, invoices, contracts. I check whether the bucket is public, whether file names are guessable, and whether a logged-in user can list or download other people&#39;s files. A public bucket is fine for avatars. It isn&#39;t fine for anything a user would be upset to see on someone else&#39;s screen.\u003C/p>\n\u003Ch2>5. Try the abuse cases\u003C/h2>\n\u003Cp>Last, I try what a bored person with a script would do. Sign up a thousand times. Hit the password reset endpoint in a loop. Call the AI feature until the bill climbs. Paste a script tag into every text field and see if it runs for another user. Rate limits, email confirmation, a spending cap on the AI provider and output escaping close most of this, and generated code rarely adds them unless you ask.\u003C/p>\n\u003Ch2>What I don&#39;t worry about first\u003C/h2>\n\u003Cp>Headers, cookie flags and the exact version of every dependency matter, and a scanner will list them. But I&#39;ve never seen a missing header be the reason an app&#39;s users lost their data. Open tables, leaked keys and functions that trust whatever they&#39;re sent are. Fix those first, then work down the scanner&#39;s list.\u003C/p>\n\u003Cp>If you want a second pair of eyes before launch, I do these reviews, and \u003Ca href=\"https://calendly.com/director-norsky/meeting\">the first call is free\u003C/a>. If your app was built with Lovable specifically, my agency \u003Ca href=\"https://lovable2live.com/\">Lovable 2 Live\u003C/a> does security reviews and the fixes that come out of them.\u003C/p>\n",[17,21,25],{"slug":18,"title":19,"description":20},"supabase-storage","Supabase Storage in Production: Policies, URLs and Limits","Supabase Storage checked November 2026: RLS on storage.objects, public vs signed URLs, the 50 MB and 6 MB lines, resumable uploads, and what it bills.",{"slug":22,"title":23,"description":24},"supabase-rls","Supabase RLS: What It Actually Protects","Supabase row level security explained for 2026: grants vs policies, the publishable key, why UPDATE needs a SELECT policy, views that leak, and fast policies.",{"slug":26,"title":27,"description":28},"supabase-cli","Supabase CLI: Migrations, Types and the Docker Catch","The Supabase CLI in late 2026: how to install it, why local dev needs a container runtime, and the migration commands that keep production in sync.",1794181975174]