Lovable vs Bolt vs Cursor: Same Spec, Three Apps, Three Security Profiles (2026)
We gave Lovable, Bolt.new, and Cursor the exact same one-paragraph spec - a freelancer invoice-tracking SaaS. Each built a working app. We scanned all three. The findings are different, the totals are different, and the failure modes are not what platform marketing pages would have you expect.
This is a controlled experiment. One spec, three platforms, three deployed apps, one scan. The numbers below are not aggregates from a corpus - they are findings from three specific apps, all generated and deployed in March 2026 from the same starting prompt.
The study shows that “is platform X secure” is a question with a structurally different answer for each platform, even when the spec is identical.
We publish it as a data study, not a league table, because ranking tools by total CVSS-ish bars without category context misleads buyers. Read the critical matrix and the failure-profile sections before the totals.
The spec
Verbatim, given as the first message to each platform’s generator:
Build a SaaS web app for freelancers to send invoices. Users sign up with email + password. Each user can create invoices with line items, tax, and a due date. Invoices can be marked sent, paid, or overdue. Users see a dashboard with the total outstanding amount and a list of recent invoices. Stripe integration to collect payment when the client clicks the invoice link. Use a backend so I can add features later.
We accepted the first deployable build on each platform. No follow-up prompts, no refactoring, no manual edits.
Results - total findings
| Severity | Lovable | Bolt.new | Cursor |
|---|---|---|---|
| Critical | 4 | 3 | 1 |
| High | 6 | 9 | 4 |
| Medium | 11 | 7 | 9 |
| Low | 8 | 5 | 12 |
| Total | 29 | 24 | 26 |
Total counts are within noise of each other. The story is in the distribution.
Critical findings, side by side
| # | Finding | Lovable | Bolt.new | Cursor |
|---|---|---|---|---|
| 1 | Missing RLS on invoices table |
yes | n/a (no Supabase) | n/a (no Supabase) |
| 2 | Missing RLS on users table |
yes | n/a | n/a |
| 3 | Stripe sk_live_ in frontend bundle |
no | yes | no |
| 4 | Supabase service-role JWT in bundle | yes | n/a | n/a |
| 5 | OpenAI key in bundle (used for invoice description AI) | no | yes | no |
| 6 | BOLA on GET /api/invoices/:id |
n/a (PostgREST) | yes | yes |
| 7 | BOLA on PATCH /api/invoices/:id |
yes (RLS gap) | yes | no |
Three completely different failure profiles emerge from the same spec.
Failure profile by platform
Lovable
Concentration: database authorization. All four critical findings are RLS gaps. The Stripe integration is correct; Lovable consistently routes Stripe through Supabase Edge Functions and does not ship the secret key. The auth flow is correct; users are authenticated via Supabase Auth.
What fails is RLS on the tables Lovable’s generator created - invoices, users, line_items, and payments. None had policies. The dashboard shows them as RLS-disabled.
Modal failure mode. Permissive policy or policy missing entirely.
Single-line fix per table. Add using (auth.uid() = user_id) to a select policy on each.
Bolt.new
Concentration: secret handling. Bolt produced a frontend-only app with serverless functions - no Supabase, just Postgres via a connection string. The connection string ended up in the frontend bundle. The Stripe sk_live_ key, also in the bundle. The OpenAI key Bolt added for an “AI-generated invoice description” feature, also in the bundle.
The authorization on the API endpoints is mostly correct, but it is the only thing standing between the public bundle and full database access - and a connection string in the bundle bypasses the API entirely.
Modal failure mode. Secrets shipped to the browser via VITE_* environment variables.
Single-line fix per secret. Move the secret to a server-only variable, route the integration through a backend handler.
Cursor
Concentration: custom API authorization. Cursor produced the most architecturally clean app - Next.js with API routes, JWT-based auth, environment-variable secrets. The secrets are correctly server-only. The auth flow is correct.
What fails is the authorization logic in the API routes. Most route handlers fetch the resource by ID and return it without checking ownership. Two routes accept arbitrary fields in the update body, including fields that should be immutable.
Modal failure mode. Missing ownership check in route handlers.
Single-line fix per route. Add if (resource.user_id !== session.user.id) return new Response(null, { status: 404 }).
What this means for the “is X safe” question
The three apps produced from the same spec have:
- Comparable total finding counts (24, 26, 29)
- Three completely different concentrations of critical findings
- Three completely different audit checklists for the builder
A founder reading “is Lovable safe” and getting a generic answer will not realize that the Lovable-specific audit is different from the Bolt-specific audit which is different from the Cursor-specific audit. Each platform’s safety posture is shaped by where it routes integrations and how it handles state - not by a generic “yes/no”.
The platform-specific safety reviews (Lovable, Bolt, Cursor) carry the per-platform fix lists.
CWE / OWASP profile per platform
The same total finding count, three completely different CWE distributions. Each platform’s “modal failure” maps to a different fix surface.
| Platform | Modal CWE family | Modal OWASP | Fix surface |
|---|---|---|---|
| Lovable | CWE-862 Missing Authorization · CWE-863 Incorrect Authorization | A01 Broken Access Control · API1 BOLA | Supabase RLS policies on every table the generator added |
| Bolt | CWE-798 Hard-coded Credentials · CWE-540 Sensitive Info in Source | A02 Cryptographic Failures · A05 Security Misconfiguration | Move every secret to server-only env; add a backend handler |
| Cursor | CWE-639 Auth Bypass via Key · CWE-915 Mass Assignment | A04 Insecure Design · API1 BOLA · API6 Mass Assignment | Add ownership checks in API route handlers; allow-list update fields |
| All three (shared baseline) | CWE-352 CSRF · CWE-770 No Rate Limit · CWE-693 Protection Mechanism Failure | A05 Security Misconfiguration | Platform-independent middleware: CSRF tokens, rate limit, security headers |
The Lovable and Cursor profiles overlap on OWASP API1 (BOLA) but the layer differs - Lovable’s BOLA is in the database via missing RLS, Cursor’s is in the application via missing checks in the route handler. The fix per-finding is short in either case; the fix per-platform requires a checklist shaped to where the platform routes the data.
Pattern walkthroughs per failure profile
Each modal failure surfaced in this experiment has a companion pattern walkthrough that shows the bug on a live URL and walks the fix per stack:
- Lovable shape - The Supabase service-role key in your frontend bundle and BOLA in AI-generated CRUD (when RLS is bypassed via service-role)
- Bolt shape - The Supabase service-role key in your frontend bundle (same root pattern: secrets in the bundle) and Source maps and .git in production
- Cursor shape - BOLA in AI-generated CRUD and Mass assignment
- Shared baseline - CORS = * with credentials = true, Stripe webhook trust, and the auth-flow gaps in Magic links, OTP, and password resets
What was the same
All three apps:
- Allowed CSRF on state-changing endpoints (no platform added CSRF protection by default)
- Shipped without rate limits on signup or login
- Returned verbose error messages including stack traces in development mode (still on at deploy time)
- Lacked HSTS headers
- Lacked Content-Security-Policy headers
These five items are the platform-independent baseline failures of vibe-coded apps. They are not failures of one platform’s generator; they are failures of the entire category.
Why shared baseline failures persist
Generators optimize for interactive demos on localhost and preview URLs. Rate limits annoy demos. CSP breaks inline scripts and third-party embeds the model loves. Stack traces speed up the agent’s own debug loop. HSTS is invisible until you care about cookie flags and HTTPS downgrade - rare in a 20-minute build.
The industry fix is not “prompt better” alone; it is host defaults + CI gates that re-introduce baseline controls after generation. Until platforms ship secure-by-default headers and auth throttles, expect this cluster on every uncontrolled first deploy.
Methodology
Builds. Each app was created on a fresh trial account using the platform’s standard new-project flow. The full prompt above was the first and only message; we accepted the first deployable build. No iterations, no follow-ups, no manual edits. All three were built within a 48-hour window in March 2026.
Scan. Identical scan run against each deployed URL. The full VibeEval probe set (310 probes) ran with the same configuration on each.
Reproducibility. The prompt is reproducible. The artifacts are not - AI builders are non-deterministic. We expect any rerun to produce comparable but not identical findings; the failure-profile shape should hold across reruns even if individual counts shift.
Vendor outreach. Lovable, StackBlitz (Bolt.new), and Anysphere (Cursor) were notified 30 days before publication. Responses included where provided.
Calibration via gapbench equivalents. Each per-platform modal failure has a matched scenario on gapbench.vibe-eval.com that reproduces the same shape of bug independent of the specific apps in this experiment. This lets readers verify the detection (against the public benchmark) without relying on access to the specific deployed URLs of the experiment apps. Every detection that fired in this experiment also fires against its matched scenario, and is silent against ref0.
What we did not do. We did not attempt social engineering, physical access, or dependency confusion against the platforms themselves. We did not keep the apps online after the study window. We did not tune prompts toward insecure outcomes - the insecure outcomes were the defaults.
Scoring. Severity labels follow VibeEval’s internal rubric aligned with common-sense impact (cross-user PII = Critical, secrets in browser = Critical, missing headers = Low/Medium). Totals count distinct findings, not CVSS sums.
Reproduce on the public benchmark
The deployed experiment apps are not public - they were built on platform trial accounts, and re-publishing the URLs would surface user PII the platforms generated as test data. The reproducibility anchor for this study is the matched gapbench scenarios:
| Profile in the experiment | Equivalent gapbench scenario | What reproduces |
|---|---|---|
| Lovable - RLS gaps on invoices/users/line_items/payments | supabase-clone | RLS off, permissive policy, partial coverage all on one app |
| Lovable - service-role JWT in bundle | supabase-clone, config-leak | Service-role JWT inlined |
| Bolt - Stripe / OpenAI / connection-string in bundle | indie-saas | Stripe sk_live_ + secrets stack |
| Cursor - BOLA on GET / PATCH | multi-tenant-saas, fintech-app | Cross-account read and write |
| Shared baseline - no rate limit, no CSRF, no HSTS | auth-system | Authentication surface with the platform-independent gaps |
| Clean reference for false-positive calibration | ref0, ref-rls | Same scan; no findings |
Reading the severity matrix correctly
Totals of 24 / 26 / 29 invite a false ranking (“Bolt wins”). That ranking is wrong for three reasons:
- Criticals are not equal. Four open-database criticals (Lovable) can expose every invoice and user email in one curl. Nine “high” secret findings (Bolt) may include overlapping keys. One BOLA critical (Cursor) may still dump all invoices via IDOR automation.
- n/a is not a free pass. Lovable has no “Stripe in bundle” critical because Stripe was server-routed - good - while still failing the data plane. Cursor has no Supabase RLS critical because it never used Supabase - it failed authorization in API routes instead.
- Low findings accumulate compliance pain. Missing HSTS and verbose errors rarely trend on Twitter; they still fail enterprise questionnaires.
Score products by modal failure + time-to-fix, not by total bars on a chart.
Time-to-fix estimates from this run
Approximate engineer effort to clear criticals only after the scan (experienced full-stack, not including product redesign):
| Platform | Critical theme | Estimated fix time | Verification |
|---|---|---|---|
| Lovable | RLS on 4 tables + service role removal | 1–3 hours | Anon curl empty; two-user tests |
| Bolt | Relocate 3 secrets + kill connection string in client | 2–4 hours | Bundle grep clean; secrets server-only |
| Cursor | Ownership checks on invoice GET/PATCH (+ mass assign) | 2–5 hours | User B 404 on A’s ids |
Shared baseline (rate limit, CSRF, headers) adds half a day to a day across all three if done properly with middleware and config - usually skipped in MVP panic.
What the invoice domain forces into the open
The freelancer-invoice spec was deliberate: it requires auth, multi-row CRUD, money, and a third-party payment provider. Toys like “todo list” under-test generators. Invoices create:
- Tables an attacker would target (amounts, client emails)
- A payment trust boundary (Stripe)
- Status fields (
paid,overdue) attractive for mass assignment - Dashboard aggregates that tempt over-fetch without scoping
If your product is less sensitive, you may see fewer criticals - but the routing of risk (RLS vs secrets vs route authz) still tracks the platform’s architecture defaults.
Secondary findings
Beyond the critical table, the scan noted recurring mediums:
- Verbose Supabase/PostgREST errors leaking schema hints (Lovable)
- OpenAPI / REST schema exposure aiding table discovery (Lovable)
- Missing security headers on all three hosts
- Login without rate limit enabling stuffing (all three)
- CSRF-ish cookie patterns where cookie sessions existed without anti-CSRF (Cursor more than others)
- Client-side-only route guards for “sent invoices” views (all three)
These did not change the headline profiles but would matter in a customer security review.
Threat-model mapping
| Attacker goal | Lovable path | Bolt path | Cursor path |
|---|---|---|---|
| Dump all invoices | Anon REST select | Use leaked DB URL or BOLA | BOLA on GET |
| Steal payment capability | Abuse Edge Function if mis-authed | Use sk_live_ from bundle |
Hit API with user session + BOLA |
| Become “paid” without paying | Forge row / status if writes open | Replay or fake Stripe if webhook weak | Mass assign status fields |
| Account takeover | Auth config + session issues | Same + secret-rich bundle aids pivot | Same + API authz gaps |
Different paths, same business impact: fraud and PII loss.
Limits of a single-spec experiment
- One prompt, one build each - generators are stochastic; a second run might move a medium to high.
- March 2026 generators - platform updates can shift defaults (we re-run quarterly).
- Expert operators might prompt better - this study measures default first ship, not best-possible use.
- Scanner coverage - 310 probes are broad; exotic business logic may need human pentest.
- No mobile clients - only web deploys.
Still, the profile shape (RLS vs secrets vs handler authz) has been stable across our wider corpus, not only this trio.
Practical checklist derived from the study
If you build with Lovable: every table, every command, RLS; Storage; never service role in client; rescan after each schema prompt.
If you build with Bolt: treat every VITE_ as public; payments and AI keys only on server; audit functions for auth; grep bundles in CI.
If you build with Cursor (or any IDE agent): invent no magic - add ownership checks and allowlists on every route; write tests user A/B; do not trust “clean architecture” aesthetics.
If you build with any of them: rate limits, headers, CSRF strategy, webhook signatures, no stack traces in prod.
Vendor responses
[Reserved for vendor responses received during the disclosure window. None received as of publication. We will append responses below as they arrive.]
Citations
VibeEval. Lovable vs Bolt vs Cursor: Same Spec, Three Apps, Three Security Profiles. May 2026. https://vibe-eval.com/data-studies/lovable-bolt-cursor-same-spec/
What “same spec” experiments measure
Holding product requirements constant and varying generators surfaces systematic security gaps rather than one-off mistakes. Readers should replicate with their own prompts and scanners - treat published figures as directional unless methodology is fully open.
Builder takeaway
Tool ranking by security alone is unstable; process ranking (scan gates, dual-user tests) predicts outcomes better.
Prompt text as a controlled variable
We deliberately kept the prompt product-only: invoices, Stripe, dashboard, “use a backend.” We did not add “enable RLS,” “never put secrets in the client,” or “add ownership checks on every route.” Those instructions improve real projects - and would have confounded the experiment. The question was default first ship under founder product pressure, not expert-secured best case.
Follow-up (planned): same platforms plus a one-paragraph security addendum; measure delta in criticals. Until then, treat this study as a default-path benchmark.
Invoice abuse cases we exercised
| Abuse | Lovable | Bolt | Cursor |
|---|---|---|---|
| List all freelancers’ invoices | Succeeded (RLS off) | Partial via BOLA + secrets | Succeeded via BOLA GET |
Mark another user’s invoice paid |
Succeeded where writes open | Succeeded via BOLA PATCH | Ownership blocked PATCH; GET still open* |
| Extract Stripe secret | N/A (server-routed) | From bundle | N/A (server env) |
| Extract DB connection string | N/A | From bundle | N/A |
| 200 failed logins without lockout | All three | All three | All three |
*Inconsistent authz across sibling handlers is common when agents patch one route and not the other.
Architecture of the three builds
Lovable: Browser React → supabase-js (anon + user JWT) → PostgREST/Auth/Storage → Postgres with RLS off on core tables; Edge Function → Stripe (secret server-side). Secret placement good; data plane open.
Bolt: Browser Vite SPA with VITE_* inlined secrets (Stripe, OpenAI, DB URL) → partial serverless authz → Postgres. Secret placement is the modal failure.
Cursor: Browser Next.js → API routes with JWT present → handlers fetch by id without owner check → server-only env for Stripe/OpenAI. Secrets good; handler authz fails.
Totals converge; fix surfaces diverge: Lovable needs SQL policies, Bolt needs env/bundle hygiene, Cursor needs dual-user handler tests.
Statistical humility (n=3)
Three deploys cannot support “platform X is 33% worse.” What n=3 supports: existence of each modal failure under one product prompt; proof that total-count rankings mislead; a methodology others can rerun quarterly; alignment with larger corpora (2026 benchmark, RLS atlas).
When vendors cite this page, demand the critical matrix - not a bar chart of totals.
Agency briefing language
Do not say “we avoid Lovable because of the study.” Say: whatever builder you used, we test three layers - database policies, secret placement, API ownership. This study shows each popular builder tends to fail a different layer first, so our checklist is layer-complete, not brand-based. Then attach the 48-hour hardening lists from Bolt vs Lovable security.
Scanner calibration
All three experiment apps and gapbench equivalents used the same 310-probe config. Supabase OpenAPI discovery fires on Lovable-shaped apps; VITE_ secret patterns are Bolt-weighted; Next.js route BOLA is Cursor-weighted; shared probes cover rate limit, HSTS, verbose errors. ref0 stayed clean - profile differences are signal, not host bias.
v2 protocol wishlist
- Three builds per platform (median criticals).
- Export + rescan after an “add teams” follow-up (longitudinal Lovable study).
- Blind second-reviewer severity labels.
- Time-to-first-critical from deploy (related).
- v0 + custom backend arm once artifact shape is normalized.
Reproduce dual-user invoice test
# Custom API (Bolt/Cursor shape)
curl -s -X GET "$API/invoices/$ID" -H "Authorization: Bearer $B_TOKEN"
curl -s -X PATCH "$API/invoices/$ID" \
-H "Authorization: Bearer $B_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"paid"}'
# Expect 403/404
# Lovable / PostgREST shape
curl -s "$SUPA/rest/v1/invoices?id=eq.$ID&select=*" \
-H "apikey: $ANON" -H "Authorization: Bearer $B_JWT"
Ship this script in every freelancer-SaaS PR template regardless of generator.
Line-item and tax edge cases (why the domain matters)
The invoice spec forced generators to model:
- Header row (
invoices) plus child rows (line_items) - Money fields (
amount,tax,currency) - Lifecycle status (
draft,sent,paid,overdue) - A third-party payment provider
Child tables without RLS (or without ownership via parent join) are a classic AI miss: parent locked, line items open. Status fields invite mass assignment (paid without Stripe). Tax and currency fields invite client-side price trust.
Any product with parent/child CRUD + money should expect the same three-platform split we observed: policy layer vs secret layer vs handler layer.
What “first deployable build” excluded
We did not:
- Ask the model to “make it production ready”
- Run a second prompt pass (“add security”)
- Manually edit generated files
- Choose the “best of N” samples from multiple attempts
That is stricter than how some power users work and closer to how non-experts ship. Power users who iterate security prompts will score better - and should still run the dual-user script, because models still skip WITH CHECK and ownership on sibling routes.
Stripe integration quality notes
| Platform | How Stripe was wired | Secret placement | Webhook quality (this build) |
|---|---|---|---|
| Lovable | Edge Function | Server secret | Partial - verify signatures in your hardening pass |
| Bolt | Mixed client/server | Secret in bundle | Unreliable if secret client-side |
| Cursor | API route | Server env | Handler present; still verify signature + raw body |
Payment security is not “use Stripe” - it is server secrets + signed webhooks + no client paid writes. See Stripe webhook trust.
How to cite this study without misuse
Fair: “Under a single product-only prompt, three popular AI builders produced apps with similar finding totals but different critical failure modes (RLS vs secrets vs route authz).”
Unfair: “Bolt is more secure than Lovable because it had fewer total findings.”
Fair for procurement: “Regardless of builder, require dual-user tests, secret classification, and a live scan before user PII.”
Include the methodology date (March 2026 builds, May 2026 publication) so readers account for generator drift.
Quarterly re-run plan
- Same verbatim prompt.
- Fresh trial accounts.
- Same 310-probe config (+ changelog if probes added).
- Publish delta table: criticals by category, not only totals.
- Update gapbench mapping if modal failures shift.
Subscribe via the data-studies index for re-run posts. Until then, treat profiles as directional defaults, not eternal rankings.
Appendix: finding-count raw table (this run)
| Category | Lovable | Bolt | Cursor |
|---|---|---|---|
| Critical | 4 | 3 | 1 |
| High | 6 | 9 | 4 |
| Medium | 11 | 7 | 9 |
| Low | 8 | 5 | 12 |
| Total | 29 | 24 | 26 |
Critical themes (repeat for skimmers): Lovable = data plane (RLS + one service-role leak); Bolt = secrets in client; Cursor = incomplete ownership on API routes. Shared baseline failures (rate limit, CSRF, HSTS, verbose errors) are excluded from the “modal” story but counted in totals.
Related
- Pattern walkthrough: The Supabase service-role key in your frontend bundle - Lovable + Bolt root pattern
- Pattern walkthrough: BOLA in AI-generated CRUD - Cursor root pattern
- Pattern walkthrough: Mass assignment - the self-editable role finding
- Pattern walkthrough: CORS = * with credentials = true - shared baseline failure
- Data study: 2026 AI App Security Benchmark
- Data study: Supabase RLS in the Wild
- Data study: Lovable Regression Longitudinal Study - what happens to the Lovable app after this snapshot
- Comparison: Best Security Scanner for AI-Generated Apps
- Safety reviews: Is Lovable Safe? · Is Bolt Safe? · Is Cursor Safe?
RUN IT YOURSELF
Each scenario below is live on the public benchmark. The commands are copy-paste ready. Outputs may evolve as we tune the scenarios; the bug stays.
curl -s 'https://gapbench.vibe-eval.com/site/supabase-clone/rest/v1/invoices?select=*' -H 'apikey: ANON_KEY'
curl -s https://gapbench.vibe-eval.com/site/indie-saas/ | grep -oE 'sk_(live|test)_[A-Za-z0-9]{20,}'
curl -s -X PATCH https://gapbench.vibe-eval.com/site/multi-tenant-saas/api/projects/1 -H 'Authorization: Bearer USER_B_TOKEN' -d '{"name":"hijacked"}'
for i in $(seq 1 200); do curl -s -X POST https://gapbench.vibe-eval.com/site/auth-system/api/login -d '{"email":"x@y.z","password":"wrong"}' & done; wait
curl -s -I https://gapbench.vibe-eval.com/site/ref0/
Common questions
Is one platform more secure than another based on this study?
Why these three platforms and not Replit or V0?
Was the prompt the same for all three platforms?
Will the result be the same if I run it tomorrow?
Did you tell the platforms in advance?
Where can I see equivalent failure shapes on a public benchmark?
How can the totals be similar but the profiles different?
What CWE / OWASP categories did the experiment surface?
Run the same scan on your app
We used one methodology across tools. Apply that same probe to your deploy and see where you sit.
14-day free trial · No credit card · Cancel anytime