BLOG
Security Anti-Patterns Caused by AI Coding Tools
Agentic platforms like Lovable, Claude, and Codex cause unexpected security anti-patterns
By c0a15726-c5b1-4b0d-85e6-fe15553df9e2 ·
Last week was tough for vibe coders and people who use tools like Lovable and Vercel to build and deploy applications.
I want to talk about why last week was so shitty for so many of us, and I’m gonna go into detail. Prepare yourselves.
Lovable and Vercel incidents
On April 20, Vercel was compromised. It turns out a Vercel employee used the AI platform Context.ai, and an Context employee was infected with the Lumma stealer malware. The Vercel employee gave Context "Allow All" access to their Google Workspace as part of the Context onboarding workflow. From there the threat actors got access to Vercel’s infrastructure in a yet unexplained pivot.
Thousands of Vercel customers had to quickly learn how to do cloud-native incident response:
Identifying and rotating any credentials you had saved in Vercel. It turns out that build processes need a lot of credentials!
Analyzing Vercel activity and deployment logs
Rotating any Vercel personal access tokens (PATs)
Adding MFA and hardening your Vercel environments.
Then on April 21, Lovable announced that it had a security incident. They shipped a backend change in February 2026 that reintroduced public access to chat histories and source code on public projects. A fix shipped within two hours of the public report, and on April 23 they published a formal incident report acknowledging the chain. Remediation steps included reverting the regression, making all historically public projects private by default (except official templates), and emailing owners of projects that had been viewed by other accounts during the window. They are still working through the access-log review to determine which public projects were actually accessed by non-owners. Third-party guidance was to assume any credentials embedded in a pre-April-20 public project source (especially Supabase service keys) were exposed, and to rotate them. It also recommended grepping AI chat history for any secrets pasted mid-build.
Root cause. A Broken Object Level Authorization (BOLA) bug:
/projects/{id}/*endpoints validated Firebase auth but did not verify project ownership, so any logged-in user could access other tenants’ project data by ID. Lovable had fixed this problem before, but unfortunately, a backend permissions-unification change regressed an earlier fix and re-enabled access to chats on public projects.Blast radius. Source code and AI chat history (including embedded secrets) were exposed for public projects during the window; private projects and Lovable Cloud were not. Unfortunately, this meant that anything customers shared in their chat conversations with Lovable was potentially exposed. Customers routinely add API keys, passwords and other sensitive data to the chat as Lovable will ask for these things
Disclosure failure. Lovable has a bug bounty program with HackerOne and researchers started reporting the regression on Feb 22, 2026, but reports were closed because triage docs incorrectly said public chat visibility was intended behaviour. Lovable did not recognize the regression until it surfaced on X, and they re-fixed it April 20th.
Response. Initial PR was rough: Lovable denied a breach, called the exposure intentional, blamed documentation, and then blamed HackerOne.

OpenSourceMalware (OSM) uses some of these platforms, and as part of our own research and incident response we wanted to look at how some of our own infrastructure and applications were configured. Was there additional hardening that we could do? As part of that incident response the OSM team wrote and published a Vercel security incident response playbook, which you can find on GitHub.
AI full-stack development platforms select for insecure defaults
The rest of this blog covers what we learned from this experience...and it's not great. When an agentic platform “builds you a SaaS,” it isn’t just generating code — it is making architecture and integration decisions the way a human staff engineer would. The problem is that these platforms are optimized for time-to-green (a working demo, a successful deploy, fewer steps), and the secure version of “make it work” almost always requires more steps: more configuration, more secrets hygiene, more authorization checks, more review.
So the issue isn’t only that anti-patterns appear — it’s that the product loops and scaffolding patterns systematically select for insecure integration shortcuts, and then reproduce them at scale.
The anti-pattern taxonomy
Credential anti-patterns: secret sprawl, public bundling, hardcoded fallbacks
Authorization anti-patterns: BOLA, missing ownership checks, permissive or missing RLS
Exposure anti-patterns: public buckets, overly-broad project visibility, chat/source leakage
Supply-chain anti-patterns: stale scaffolds, hallucinated/typosquatted dependencies
Webhook anti-patterns: handlers without signature verificationAgentic platforms have known patterns
While using agentic coding platforms our team has noticed a theme across many of them: Regardless of whether you are using Lovable, Claude, Codex, Curser, or something else, part of the value these platforms provide is their experience building SaaS apps. You are paying them, in part, to suggest patterns, and make opinionated choices about infrastructure.
And here’s where it gets interesting: When you ask any of these tools to "build me a SaaS," the answer collapses to roughly the same dependency graph: Next.js or React+Vite, Tailwind, shadcn/ui, a Postgres BaaS (Supabase or Neon), Clerk or Auth.js, Stripe, Resend or SendGrid for email, Vercel or Netlify for hosting.
Agentic Coding Platform Default Stacks
This blueprint is the canonical 2025–2026 vibe-coded SaaS stack, but each platform has its unique way of laying down that infrastructure. To understand these patterns and why they come with security problems, we needed to document and describe these patterns for you.
Here’s our breakdown and analysis of the different stack patterns that vibe-coding platforms are using. This is not to say that if you ask Lovable for a specific build architecture, that it won’t deviate from its standard pattern. But, if you don’t specify, based on our experience, this is what you’ll get:
Platform
Frontend default
UI library
Backend / Data
Auth
Deployment target
Lock-in shape
Lovable
React + Vite + TypeScript (not Next.js)
Tailwind + shadcn/ui + Radix
Supabase (Postgres + Edge Functions in Deno)
Supabase Auth, sometimes Clerk
Lovable hosted preview, GitHub export
Heavy Supabase dependency; anon key shipped to client
v0 (Vercel)
Next.js App Router + TypeScript
Tailwind + shadcn/ui
Next.js Server Actions / API routes; Drizzle + Neon, or Supabase
Clerk, Auth.js (NextAuth), Stack Auth
Vercel
Tight Vercel ecosystem coupling
Bolt.new
React + Vite by default; offers Next/Vue/Svelte/Astro/Remix
Tailwind
WebContainer-based: Hono/Express in browser, or Supabase
Variable, often Supabase Auth
Netlify (default), Cloudflare Pages
WebContainer constraints: no native binaries, limited Python, no real git (uses isomorphic-git)
Base44
React (not user-selectable)
Proprietary component set
Their own managed BaaS: DB, auth, email, SMS all integrated
Built-in
Their hosted infra only
Total platform lock-in; code not exportable on free tier
Replit Agent
Multi-language; for web typically React + Node/Express, or Flask/FastAPI for Python
Tailwind common
Replit DB or Neon Postgres
Replit Auth (proprietary)
Replit Autoscale / Reserved VM
Replit Secrets manager, hosted DB
Cursor (IDE)
Whatever’s in the repo; greenfield → Next.js + TS
Tailwind + shadcn/ui
Prisma + Postgres (Neon, Supabase, PlanetScale)
Clerk or Auth.js
Vercel
Mostly conventions, not platform lock-in
Windsurf (IDE)
Same as Cursor
Tailwind + shadcn/ui
Same
Same
Vercel/Railway
Same
Codex / GPT-5 codex CLI
Similar to Cursor; OpenAI training leans Python harder: FastAPI is reflexive
Tailwind
Postgres + SQLAlchemy or Drizzle
Auth.js, Clerk
Vercel, Render
Conventions, follows repo
Claude Code / Claude chat
JS: Next.js + TS + Tailwind + Prisma. Python: FastAPI + Pydantic + SQLAlchemy
Tailwind + shadcn/ui
Postgres (Supabase/Neon) for JS; Postgres + SQLAlchemy for Python
Clerk, Auth.js, lucia-auth
JS → Vercel, Python → Railway/Render/Fly.io
Conventions, follows repo
Client-direct vs. server-mediated backends
The biggest structural security difference between these platforms isn’t framework choice: it’s whether the backend is client-direct or server-mediated. Lovable, Bolt, and any “React + Vite + Supabase” output put the database one HTTP call away from the user’s browser, with security enforced entirely by Postgres RLS policies the AI may or may not have written correctly. CVE-2025-48757 (Lovable’s RLS misconfiguration class), the Vercel breach’s environment-variable angle, and most BOLA findings in this category all reduce to the same pattern. Server-mediated stacks (Next.js Server Actions, FastAPI, Express) at least force authorization through middleware the model has to actively skip rather than forget.
Secrets handling
Another thing to consider is how the AI development platforms integrate with the platforms they are using to store and access secrets. We have found that each of the platforms has its own pattern for what it suggests, and then what it falls back to if there is any resistance in deploying an application.
v0 / Vercel: secrets in Vercel project env vars; the April 2026 incident showed these can be enumerated if not marked “sensitive”
Lovable: historically embedded Supabase keys in source code that turned out to be readable by other tenants
Replit: secrets manager built in
IDE tools (Cursor, Claude Code, Windsurf, Codex): rely on the user’s local
.envdiscipline
In fact, many of these tools will fall back to creating .env files in the local source repo, which then get pushed to GitHub, GitLab, etc. Even worse, sometimes those .env files get added to the web content and can be found via scanning for file paths in URLs.
Dependency profile
Vite-based stacks pull in vite, @vitejs/plugin-react, postcss, tailwindcss. Next.js stacks pull in the larger Next runtime, swc, and a deeper transitive tree. shadcn/ui isn’t itself a dependency (it’s copy-paste components) but pulls Radix UI primitives, lucide-react, class-variance-authority, and tailwind-merge: five-or-so packages that show up in essentially every vibe-coded app and would make extremely high-leverage targets for a TeamPCP-style postcss-adjacent compromise.
Python axis
Narrower than the JS side. Claude defaults to FastAPI + uvicorn + Pydantic + SQLAlchemy + Alembic, deployed to Railway or Render, sometimes Fly.io. Codex pushes a similar stack. Cursor follows whatever’s already in the repo. Replit defaults to Flask for simple work and FastAPI when async or typing comes up. None of the no-code builders (Lovable / Bolt / Base44 / v0) generate Python services as a primary deliverable: Lovable explicitly tells users to export and rebuild the backend if they need Python, and Base44 abstracts the backend entirely.
Integration is where the risk concentrates
The platforms above don't just pick a stack: they wire it together, which means the agent is making integration decisions a human architect would normally make. This is the layer where the most consistently insecure choices get baked in, because the model is solving for "make it work" and the secure version of "make it work" usually requires more steps. A few patterns recur across every agentic platform regardless of which underlying services get bundled.
Service-role keys end up in source code
The canonical example: Lovable + Supabase. Supabase issues two keys per project: the anon key, designed to be shipped to the client and restricted by Row Level Security, and the service_role key, which bypasses RLS entirely and is meant only for trusted server contexts. These keys are named poorly, and both humans and AI agents conflate them constantly. When the model needs to perform an "admin" operation (create a user record from a webhook, run a migration, seed data), it reaches for service_role and frequently instantiates the Supabase client with it in code paths that get bundled into the browser, or commits the key to a .env.example, or pastes it into the chat history of a public project: which is exactly what made the recent Lovable disclosure so catastrophic. This pattern is not Lovable-specific; it shows up any time an agent wires a service that has both a public and a privileged key (Stripe pk_ vs sk_, OpenAI / Anthropic keys, SendGrid full-access vs restricted, Twilio account SID + auth token).
What's causing the anti-patterns
This “how the anti-pattern is born” loop is painfully consistent:
You ask for a real feature (“admin dashboard”, “image upload”, “Stripe webhook”).
The agent hits friction (auth errors, CORS, RLS denies, missing env vars).
The agent applies the shortest bypass that restores forward progress (service-role key, public bucket, permissive RLS,
NEXT_PUBLIC_*secret).The app works — and the bypass becomes the production security posture.
Even when you have defined a Supabase Edge Function Secret, which is the suggested best practices for these types of key, they often still end up exposed in source code for two reasons:
The AI agents will bypass the best practices when they hit any kind of obstacle or resistance. it is their job to find a way to deliver success for your prompt, and if that means they have to add the
service_rolekey to source code to do it, they will.The developer, doesn’t understand that a “service_role” key is supposed to be secret. You can see that this is a thing by searching GitHub for these keys. They are everywhere, and yes, agents are adding them directly to source code, but so are developers.

Public env var prefixes used as a workaround
Both Vite (VITE_*) and Next.js (NEXT_PUBLIC_*) inline any env var with the matching prefix into the client bundle at build time. The intended use is publishable keys and feature flags. The actual use, when an agent needs something "available in the browser," is to prefix the variable and move on. The result is NEXT_PUBLIC_OPENAI_API_KEY and similar in production bundles, retrievable by anyone who opens DevTools. This is the modern equivalent of git grep -r AWS_SECRET on GitHub.
RLS off or permissive by default
Row-level Security, or RLS, is a permissioning system that restricts access to database table rows based on policies that the agent (or human) creates.
The reality is that administering RLS in a secure way is difficult. This is particularly true if you have never had to manage a database before. Especially a database that allows direct access via HTTP. When Lovable or Bolt creates Supabase tables on a user's behalf, RLS is frequently either not enabled or enabled with policies like USING (true) or USING (auth.uid() IS NOT NULL): the latter restricts to authenticated users but does nothing about cross-tenant access. The agent has fulfilled the literal request ("let logged-in users read their data") without addressing the implicit one ("only their own data"). The CVE-2025-48757 cluster and the bulk of the BOLA findings in vibe-coded apps sit on top of this exact failure mode.
Even worse, Supabase says that they have enabled RLS by default for new tables in 2026, but that’s not been the experience of many users, including this team. In fact, just today I got this pop up in Supabase.
Wait, I thought it was enabled by default? Is it not? Who knows!

Bad package choices
Agentic platforms routinely pick dependencies by pattern-matching training data, not by verifying what is current today. Two failure modes show up constantly:
Out-of-date “best practice” stacks: the agent installs older major versions or deprecated packages because they were common when the model last saw lots of examples. The result is breakage (wrong APIs), missed security fixes, and copy-pasted configs that no longer match the ecosystem.
Hallucinated package names: the agent confidently tells you to
npm iorpip installlibraries that do not exist, were renamed, or are unofficial forks. Teams then waste time chasing 404s, or worse, they grab a similarly named third-party package and accidentally pull in something unvetted (a classic typosquat opportunity). Even worse, bad guys have picked up on this and routinely create “slop-squatted” packages that match these hallucinated package names.
Practical takeaway: treat every agent-suggested dependency list as untrusted input. Verify that each package actually exists, is maintained, and is the intended official project. Prefer pinning versions, and run npm audit / pip-audit early—before the stack calcifies into production. Oh, and yes, reference OpenSourceMalware to verify that the packages you are using are not malicious.
Storage buckets flipped to public
Supabase Storage and S3 buckets default to private, but agents flip them to public when wiring up image uploads because the alternative: signed URLs, server-side proxying, or RLS policies on the storage schema: is more code. A public Supabase bucket on a vibe-coded app is essentially an open S3 in 2013 terms, and the bucket URL pattern is predictable enough to enumerate.

Webhook handlers without signature verification
Every payment, email, and source-control integration in the canonical SaaS template ships a webhook endpoint. The signature verification step (stripe.webhooks.constructEvent, GitHub's X-Hub-Signature-256, Resend's signing secret) is consistently skipped by agentic generators because it requires a separate env var, a raw-body parser configuration, and an extra try/catch. The shipped endpoint accepts and trusts any JSON-shaped payload at the documented URL. For Stripe specifically, this turns a webhook handler that grants premium access into a public "give me premium access" endpoint.
Hardcoded fallback secrets
process.env.JWT_SECRET || "secret" and os.environ.get("SECRET_KEY", "dev-key-change-in-prod") are reflexive patterns across nearly every agent. They exist because the model is trying to make the app run on first boot without configuration, but they survive into production deployments where the env var was never set and the fallback becomes the actual signing secret. A grep across deployed agent-generated apps for literal strings like "your-secret-here" or "changeme" is a viable threat-hunting query.
What this means for your vibe-coded app
The integration-layer failures above are not detectable as a malicious package or a CVE: they're configuration vulnerabilities baked in by the generator. But they create a predictable, fingerprintable exposure pattern per platform: a Lovable app probably has a service_role key somewhere it shouldn't, a v0 app probably has a NEXT_PUBLIC_* secret leak risk, a Bolt + Supabase app probably has RLS gaps, anything with a Stripe webhook probably skips signature verification. From a threat intelligence standpoint, this is closer to "default credentials in embedded devices" than to traditional package compromise: the surface area is created by the generator, not by an attacker, and it's consistent enough across instances to be enumerated.