lovable security report · september 2026 · 70 public apps

We scanned 70 public Lovable apps. Here's what's actually open.

Published

In September 2026 we cloned 70 public GitHub repositories for apps built with Lovable, in two batches found two different ways, ran the same scanner a paid Ascertify report uses, and read every critical and high finding by hand. Twenty-eight of the 70 (40%) had a critical or high problem in their own code; the other 42 had none at all. The damage is concentrated: of the 47 apps with a database we could read, 14 (30%) had a policy that lets any signed-in user, or anyone at all, write to some of their tables, and of the 33 with edge functions, 19 (58%) expose one that anyone on the internet can call with no check at all.

Separately, 69 of 70 (99%) ship a dependency with a known vulnerability, almost all of it the build tooling that came with the template, which is why that number is on this page and not in the headline.

28 of 70
40% of the apps scanned

had a critical or high finding in their own code, with dependency findings excluded.

69 of 70

if we counted template dependency CVEs too. We could have led with this. Why we didn't →

Key findings

Ranked by how much it would matter if it were your app. Each row jumps to the full check.

Denominators differ on purpose, since you can't check migrations in an app that has none, or edge functions in an app that never deployed one. Every row says what it was counted against.

Most are fine at the data layer. The ones that aren't are wide open.

The grade is arithmetic, not opinion. Every app starts at 100 and loses 30 points for each critical finding, 15 for each high, 7 for each medium and 3 for each low, counting only “Am I exposed?” findings, never maintenance debt. 90 and up is an A, 75 a B, 55 a C, 35 a D, and anything below that is an F.

Graded on their own code
charted here

Dependency findings excluded. 41 of 70 (59%) come out at A, another 3 at B, and 13 at F. The median app had zero critical or high findings of its own.

41
3
6
7
13
Graded as a paid report does
deps counted

A dependency with a known vulnerability is a high finding, so two of the template's CVEs cost 30 points and take an otherwise clean app to a C.

1
0
23
15
31

Why show both? They answer different questions. The first asks what your team did. The second asks what a buyer or an auditor would see on the day they look, and an unpatched build tool counts there whether or not you introduced it. Median app in the paid view: 5 security findings, 3 critical or high, 37 findings once maintenance debt is counted. Neither number means these apps are broken. They ship, they work, and people pay for some of them.

Every check

Ten checks, run the same way on all 70 apps. Four use a smaller denominator, because you cannot check migrations in an app that has none, or edge functions in an app that never deployed one. Each card says what it was counted against, and opens for the full analysis.

criticalam i exposed?
14of 47 · 30%

A policy that lets everyone through

RLS was on, and then a policy let any signed-in user, or anyone at all, insert, update or delete rows in some of their tables. Not a missing lock, but a lock with the door propped open.

counted against the 47 apps with migrations we could read

criticalam i exposed?
19of 33 · 58%

An endpoint anyone on the internet can call

71 edge functions check nothing at all before they run, and they sit in 19 of the 33 apps. Anyone with the URL can run these. In this sample, one stores user-submitted medical text, one mutates published content, several spend the owner's money on a third-party API, one runs the data-retention purge, one renews and charges subscriptions, and one records a signed contract against any customer number you give it.

counted against the 33 apps that had edge functions

criticalam i exposed?
4of 70 · 6%

A private credential in the code

Four apps shipped a working private credential in the repository. One wrote an admin password as a literal in a React context, so it went out in the JavaScript bundle to every visitor.

counted against all 70 apps

criticalam i exposed?
2of 70 · 3%

Service-role key committed to the repo

The service-role key bypasses every access rule your database has, so a copy of it is a copy of your data. Two apps had it in the repository.

counted against all 70 apps

higham i exposed?
5of 33 · 15%

User input reaching a fetch or a file path

Five apps let a value from the incoming request decide which URL to fetch or which storage object to download, which is how a server ends up fetching things on an attacker's behalf, from inside your infrastructure.

counted against the 33 apps with edge functions

goodam i exposed?
1of 47 · 2%

Row-level security switched off

This is the good news of the whole study: RLS was enabled on essentially every table we saw. One app explicitly turned it back off, and one more created a roles table and never turned it on.

counted against the 47 apps with migrations we could read

contextwhat actually got built
23of 70 · 33%

No database migrations at all

Migration files are the written record of your database structure. Without them the structure exists in exactly one place, the live Supabase project, and nobody can rebuild it, review it, or tell you whether it is safe.

counted against all 70 apps

contextwhat actually got built
44of 70 · 63%

No tests of any kind

Nearly two thirds had not one test file. That is normal for a product still finding its shape, and it stops being normal the moment you or someone else starts changing the code.

counted against all 70 apps

contexthousekeeping
69of 70 · 99%

A dependency with a known vulnerability

Almost every app had at least one direct dependency with a published vulnerability, and mostly it is the vite, postcss and vitest versions that come with the Lovable template.

counted against all 70 apps

contexthousekeeping
3of 70 · 4%

A package that does not exist

Three apps listed a dependency the npm registry has never heard of.

counted against all 70 apps

Wondering where your app falls?

Run the same scanner against your Lovable repo. The preview shows your critical findings before you pay.

Scan my app, free preview

What it looks like in a report

The file and the key below are invented for this page and match no app in the study.

CRITICALam i exposed? · service-role key in client codesrc/lib/admin.ts:14
  const supabaseAdmin = createClient(
    "https://example-project.supabase.co",
    // decoded payload: { "role": "service_role", ... }
    "eyJhbGciOiJIUzI1NiJ9.REDACTED-SERVICE-ROLE-KEY.REDACTED"
  );

What this means for you

This key ignores every access rule on your database. This module is imported by client-side code, so the key ends up in the JavaScript sent to every visitor, and anyone who copies it can read, change or delete any row in any table. Treat it as published.

First step

Rotate the key in your Supabase dashboard now, before you change any code. Anyone who already copied it keeps working access until you do.

If you built with Lovable

Ten minutes, in this order. Most of it you can do yourself.

Code your team owns

7 steps

This is what our 28 of 70 counts, with dependency findings excluded.

  1. Search your repo for service_role.

    If it appears anywhere outside your Supabase secrets, rotate the key in the dashboard first, then remove it. How to rotate and fix it

    2 of 70 apps · the worst thing on this list

  2. Search for a password, an API key or a token written as a plain string.

    If you find one, it is already public: rotate it, then move it to an environment variable, and add .env to .gitignore.

    4 of 70 · one shipped an admin password to every browser

  3. Open supabase/migrations/.

    If the folder is empty or missing, install the Supabase CLI, link your project, and run supabase db pull to write your live schema out as a migration. Commit the file it creates.

    23 of 70 had none

  4. Read every CREATE POLICY line.

    Any policy with using (true) or with check (true) on insert, update or delete lets anyone change those rows. Scope them to auth.uid(). If a policy is meant for the service role, say so with TO service_role, or it applies to everyone.

    14 of the 47 apps we could check

  5. Check RLS is on for every table.

    It almost certainly is, but it costs one query in the Supabase dashboard to be sure.

    1 of 47 switched off · 1 more never turned on

  6. List your edge functions with verify_jwt = false.

    For each one, ask what it verifies instead. A webhook should check its signature. Anything else should check the token.

    23 of 33 configured that way · 19 checked nothing

  7. Never let the request body pick a URL or a file path.

    Allow-list the destinations instead.

    5 of 33, the findings we would have called about

Housekeeping

3 steps

Outside the headline number on purpose.

  1. Move vite past 6.4.3, postcss to 8.5.18, vitest to 3.2.6.

    The vite fix never landed in the 5.x line the template pins, so npm update inside that range clears nothing. It takes a major bump.

    69 of 70 carried a known vulnerability

  2. Check every dependency actually exists on npm.

    Delete it or replace it with the real one.

    3 of 70 listed a package that does not exist

  3. Write one test.

    One test for the thing that would hurt most if it broke is worth more than a plan for a hundred.

    44 of 70 had zero

Or have the same scanner read your app.

Connect your GitHub repository and the free preview tells you how many critical findings you have, and in which category, before you pay anything. Lovable syncs to GitHub in two clicks. The access is read-only, we never run your code, and the clone is deleted when the scan finishes.

How we did this

The corpus

70 public GitHub repositories in two batches of 35, cloned on 14 and 16 September 2026, found two different ways on purpose. The first batch came from searching GitHub README text for the phrases Lovable writes into a new project, which finds the apps whose README was never edited: a real population, but a skewed one. The second came from a code search for the lovable-tagger package in package.json, which finds a Lovable app whether or not anyone went back to the repository; the search caps at a thousand results, so we sliced it by file size and pooled the slices.

Both batches were limited to the last twelve months, sampled evenly across those months so the set is not all from one release of the tool, filtered to things that are actually apps (a Supabase integration present, at least 30 source files, one repository per owner across both batches), and drawn by a seeded, stratified pick so nobody chose favourites.

What that sample is and is not

The two batches disagree about the README and agree about the code. In batch one, 31 of 35 (89%) still opened with the untouched template line; in batch two it was 15 of 35 (43%), so the second batch really does reach the people who went back to the repository.

Their code was not cleaner: 16 of 35 (46%) had a critical or high finding in their own code, against 12 of 35 (34%) in batch one. Both splits are in the stats file as bySource. It is still not a census of Lovable apps, and 70 is still small. Read the numbers as a picture of AI-built code that shipped without a second pair of eyes.

The scan

Public code only, read as text, nothing executed. We never installed a dependency, never ran a build, and never contacted a deployed app or a live database. The scan is the one behind a paid Ascertify report: our own Supabase checks for migrations, row-level security, policies and edge-function auth, our own rule packs run through Opengrep for code patterns, our own dependency checks for packages that do not exist, plus gitleaks for committed credentials and the OSV database for known vulnerabilities. Every number on this page comes from that same scanner, on the same settings a customer gets.

The triage

Supabase anon keys and Stripe publishable keys, public by design, are dropped by the scanner before anyone looks.

The 183 candidates we set aside deserve an explanation rather than a shrug. Most of the exclusions are a few patterns a reader can see at a glance from the pinned line: a fetch whose host is hard-coded, where only a path or query value varies (47); a policy a later migration had already dropped or replaced (33); a key that is public by design, or a placeholder, that the credential rule matched anyway (47); an insert-only policy on a contact form, waitlist or newsletter table, where letting anyone add a row is the point (28).

The first three were batch-one findings about our own scanner, and we fixed them between the batches: the fetch rule now recognises a literal host, the policy check follows drop and replace statements across the migration history, and the credential check knows the public key formats. Batch two ran with those fixes, and the difference shows: of its 126 candidates, 84 survived hand review, two in three, where batch one kept fewer than one in three. Two smaller rule bugs the first batch exposed are fixed as well, one that matched table and policy statements sitting inside SQL comments, and one that flagged policies scoped only to service_role.

Triage also went the other way 71 times, when reading the code showed a finding was worse than the rule could know: those are the open edge functions, recorded as triage.severityUpgrades. Medium and low findings were not hand-checked and appear only in the density figures, and the dependency cards sit outside hand triage entirely: they come from the OSV database and the npm registry and were not read one by one.

What “in their own code” means

Our headline figure, 28 of 70 (40%), counts apps with a surviving critical or high finding after dependency findings are taken out, so known vulnerabilities in packages and packages that do not exist are not in it. Those are reported on their own cards instead.

Both grade distributions are in the published stats file: grades.security is the one a paid report gives you, with dependency findings counted, and grades.securityExcludingDeps is the one graded on their own code. The headline itself is density.anyCriticalHighExcludingDeps, and the hand-checked edge-function figures are stats.jwtOffVerifiesNothing and stats.jwtOffHighUpgraded, sitting beside the raw configuration count in stats.jwtVerificationOff.

Disclosure

22 owners were contacted before this was published: the 19 whose apps expose an edge function anyone can call with no check, and three more whose apps have a credential committed in the repository or a request value reaching a fetch or a storage path. Two of those 19 are also the apps with the service-role key in the repository, so they heard about both. The first eight notes gave the file, the line and what to do about it, and nothing else; the later fourteen kept those details out of the public issue and sent them through a private channel instead. This page went live at least seven days after the last of those notices. No repository, owner, app or screenshot is identified anywhere, and the finding card above is synthetic.

Who we are

Ascertify sells code audits, including audits of Lovable apps, so we have an obvious interest in this being interesting. That is why the raw numbers are published as a file you can read, at ascertify.io/lovable-security-report/stats.json, with the exclusion rules, both grade distributions and the counts for every check. Check our arithmetic.

Fair questions.

Is this a fair sample?

Fairer than most. The first 35 came from searching README text, which selects for people who never revisited the repository: 31 of those 35 still had the template README. So we drew a second 35 from a code search on package.json, which finds a Lovable app whether or not anyone touched the README, and only 15 of those 35 had the template line.

The second batch was not cleaner: 16 of its 35 had a critical or high finding in their own code, against 12 of the first 35. 70 is still a small number. We are reporting counts, not claiming a margin of error, and every count, including the split by batch, is in the public stats file so you can see how thin any individual cell is.

Did you tell the owners?

Yes, for the ones where it mattered, and we waited. 22 owners got a short note on their repository and no sales pitch: the 19 whose apps have an edge function anyone can call with no check at all, and three more with a credential committed in the repository or a request value reaching a fetch or a storage path. Two of those 19 also have the service-role key in the repository, so they heard about both. The first eight notes carried the file, the line and what to do; the later fourteen kept those details out of the public issue and pointed to a private channel where they followed.

To be exact about what we are claiming: nothing here is an exposure we observed in a running app. We read code that was published on GitHub, and we never contacted anybody's live system.

Why not name the apps?

Because the point is the pattern, not the people. These are mostly solo founders and small teams who shipped something real with a tool that let them, and naming them would turn a research post into a target list. No repository, owner, URL or screenshot appears on this page, the code sample is invented, and the published stats file contains counts only.

Does this mean Lovable is unsafe?

No, and we would rather say that plainly than sell you a scare. Judged on the code their own teams shipped, 42 of the 70 (60%) had nothing critical or high at all. Row-level security was on nearly everywhere, and almost nobody had a real credential committed in their code.

What the data shows is narrower and more useful: when something is open, it is open in one of two ways, a policy that lets anyone write to a table, or an edge function that checks nothing, and those two account for nearly all of the 28 of 70 (40%) that did have a problem.

You would find the same shape in any AI-generated codebase that shipped without a second pair of eyes. The fix is not a different tool. It is reading the code, or having someone read it for you.