Auditing Lovable-Generated Auth Before It Becomes a Data Breach
A post-incident walkthrough of the nine checks WitsCode runs on every Lovable auth flow, covering BOLA, RLS alignment, session invalidation, and magic-link replay.
When the first wave of Lovable BOLA reports landed in 2025, the uncomfortable part was not that the vulnerabilities existed. It was that they were almost identical across apps. Different domains, different products, the same six or seven shapes of broken authorization, repeated by a code generator that had no particular reason to vary its output. Researchers pulled a public sample of Lovable projects, probed the exposed endpoints, and found that roughly one in five let an authenticated user read or mutate another user's records simply by changing an ID in the request. A subset leaked their Supabase service-role key in the browser bundle on top of that, which turned the whole access model into theatre.
We have spent the months since then cleaning up those same patterns for founders who shipped a Lovable build, got traction, and then realised the auth layer was the weakest part of a product they were starting to charge for. This article is the checklist we use. Nine checks, run in order, against any Lovable-generated auth flow before it is allowed near a paying user. It is the walkthrough, not the sales pitch, and it is written so a non-security engineer can follow it end to end.
What the Lovable BOLA reports actually found
BOLA stands for Broken Object-Level Authorization, and it is the most common vulnerability class in modern API-backed apps. The pattern is mechanical. An endpoint accepts an identifier from the request, usually a user ID, an organisation ID, or a record ID, and then uses that identifier to look up or modify data. The bug is that the server does not verify the caller is actually allowed to touch that object. It trusts the ID because it came with a valid session, which is the wrong trust boundary.
In Lovable's case, the BOLA pattern is industrial because the generator wires the front end directly to Supabase through the anon key and pushes the access decision onto Row Level Security. When RLS is missing, loose, or evaluated in the wrong place, the whole application leaks. The reports that circulated last year were not the work of a nation-state. They were a researcher opening DevTools, changing a UUID in a POST body, and watching another user's data come back. That is the bar we are defending against.
The second finding that travelled with those reports was service-role key leakage. Lovable projects were shipping SUPABASE_SERVICE_ROLE_KEY inside edge functions that were effectively public, or, worse, inlining it into client code that ran in the browser. Service role bypasses RLS entirely by design. A leaked service-role key is a total read-write on every table, regardless of how beautifully your policies are written. These two classes of bug compound. You cannot audit one without the other.
The nine checks we run on every Lovable auth flow
What follows is the running order. Each check is independent enough that a finding at step three does not invalidate the work at step one, and each one maps to a specific class of breach we have either cleaned up after or prevented. The deliverable at the end is a severity-rated list that tells you which fixes are blocking a launch and which are hardening.
Check one: role segmentation and the self-update trap
Every Lovable app we have seen stores user roles in a profiles table with a role column. The front end reads that column, decides whether to render admin UI, and routes accordingly. That part is usually fine. The part that fails is the write path. If RLS on profiles allows a row owner to update any column on their own row, a regular user can issue a direct Supabase call from their browser console and set role = 'admin' on themselves. The app now believes they are an admin because the source of truth is the same column they just wrote.
The fix is twofold. The RLS update policy on profiles must exclude the role column using a column-level check or a trigger that rejects role changes from non-service contexts. Role changes must happen via a privileged server function that logs the change. Finally, any server-side check that cares about the role should resolve it fresh from the database, not from a JWT claim that was set at login and never refreshed.
Check two: session invalidation on credential events
When a user changes their password, resets their email, or signs out from a device, Supabase does not automatically revoke other active sessions unless the application tells it to. Lovable templates rarely do. The audit check is a straight functional test. Sign in on two browsers. Change the password in browser A. Refresh browser B. If browser B is still authenticated, the app has a session invalidation gap, and any token stolen before the password change still works.
The corresponding fix is a server action that calls Supabase's admin-scoped signOut with the global scope after any credential-changing event. On paid tiers there are also JWT revocation hooks that can enforce this at the token-verification layer. The default, out of the box, is not safe for accounts that hold customer data.
Check three: token handling and accidental exposure
Tokens leak into places developers forget to look. The audit checks are: grep the codebase for logger calls that include access_token, refresh_token, session, or the full data object returned by supabase.auth. Open the browser network tab and confirm tokens are not sent as query parameters on any request, because query parameters are written to server logs, analytics, and the Referer header. Confirm tokens are stored in localStorage only if the app has no third-party scripts that could read them, otherwise migrate to HTTP-only cookies via the SSR helpers. Sentry and Posthog breadcrumbs are the most common place we find tokens sitting in plain text.
Check four: password reset flow under attack
A password reset flow has four failure modes we check for. First, the reset endpoint has no rate limit, which allows email-bomb attacks and user enumeration through timing. Second, the reset link lands on a client-only page that accepts a new password and calls Supabase directly without any server mediation, which means a stolen reset link is a full takeover even if the user is already logged in on another device. Third, completing the reset does not invalidate existing sessions, so an attacker who stole a session can keep it after the user rotates their password in response. Fourth, the reset email is templated in a way that causes mail providers like Microsoft Safe Links to preload the URL, consuming the token before the user clicks it, which can either break the flow or, in some configurations, leave the token cached where a subsequent request can replay it.
Check five: magic-link replay and single-use enforcement
Supabase magic-link tokens are single-use and short-lived by design, but the single-use guarantee only holds if the token is exchanged on the server in a PKCE flow. Lovable often generates a client-side landing page that calls getSession with the token sitting in the URL fragment. That fragment gets captured by browser history, by service workers, by analytics scripts that bind to window.location, and sometimes by browser-sync features on logged-in Google or Microsoft accounts. The token is still technically single-use, but the race between the user clicking and an automated system consuming the link is not always one the user wins.
The audit check is to trace the magic-link callback. If the callback is a client component, it is wrong. If the callback is a server action or route handler that calls exchangeCodeForSession and then redirects, it is right. The token should never sit on a rendered client page, and it should never travel as a query parameter to any route that is not the dedicated exchange endpoint.
Check six: RLS alignment with the API layer
This is the check that catches the most Lovable projects and the one that is almost never discussed in the vibe-coder content that circulates on X. RLS policies are evaluated in Postgres, based on the JWT that Supabase's PostgREST layer presents on the request. When your Next.js route, edge function, or server action uses the service-role key, RLS is bypassed entirely. The policy can be perfect, and it will never run.
The misalignment happens because Lovable often scaffolds a server function that uses the service-role client for convenience, then trusts the user identity passed in the request body. At that point, the access decision is whatever the application code does, and RLS is decorative. The fix is either to use a user-scoped Supabase client that carries the caller's JWT, so RLS is evaluated as intended, or, if the service role must be used, to enforce ownership checks in application code with the same rigour RLS would have. The check is concrete. Open every server route. Note which Supabase client it uses. If it uses the service-role key, audit every query in that route for explicit ownership enforcement. If it uses the anon key with the user's JWT, verify RLS is enabled on every table it touches.
Check seven: BOLA on every read and mutation
This is the mechanical one. Grep the codebase for every Supabase query and every API route that accepts an ID from req.query, req.body, params, useParams, searchParams, or any equivalent. For each of them, determine whether the query filter ties that ID to the authenticated user, either directly by combining .eq('id', x).eq('user_id', auth.uid()) or indirectly through a join that enforces ownership. If the filter is the ID alone, and there is no RLS policy backing it up, the endpoint is vulnerable. Swap in another user's UUID during testing and confirm whether the call succeeds.
The test has to be run for every endpoint, not just the ones a developer expects to be sensitive. The reports from 2025 found BOLA on endpoints like GET /api/notes/:id, which sounds harmless until the notes table holds draft emails, billing notes, or private journal entries. The pattern does not care about the semantic sensitivity of the data. It cares about whether the access decision is enforced, everywhere, or not.
Check eight: refresh-token rotation and reuse detection
Supabase supports refresh-token rotation with reuse detection, and it is off by default. With rotation enabled, every refresh consumes the current token and issues a new one. If the old token is ever presented again, Supabase assumes it was stolen, revokes the entire token family, and signs the user out on all devices. Without rotation, a refresh token stolen once grants access for the full refresh lifetime, which can be days or weeks depending on the project configuration.
The audit check is a single dashboard setting. Auth, Security, Refresh token rotation enabled, with a reuse interval in the range of ten seconds to cover legitimate race conditions during page navigation. The application also needs to handle the global sign-out event cleanly, showing the user a clear reason they were logged out, rather than looping them through the login screen with no explanation.
Check nine: secret hygiene and environment scoping
The last check is the one that compounds everything above. If the service-role key is exposed, the other eight checks become secondary, because an attacker does not need to defeat RLS or BOLA protection. They read the tables directly. The audit steps are to search the client bundle for any string beginning with eyJ that is not the anon key, to search the repo for the service-role key outside of dotenv files and edge-function secrets, and to confirm that edge functions scope their environment so the service-role key is not leaked through error messages or debug endpoints. The Supabase dashboard will also show the last-used timestamp of each key, which is a useful sanity check for whether something is calling the service role from a location you did not expect.
Beyond the service role, the same discipline applies to third-party keys. Stripe secret keys, Resend API keys, and OpenAI keys all belong on the server, never in the browser. Lovable's client-first generation makes it easy to slip one of these into a client component by accident, and the audit is the first time anyone looks at the bundle carefully enough to notice.
What a fixed auth flow looks like
After the nine checks, a properly patched Lovable app has a specific shape. RLS is enabled on every table and enforced by policies that reference auth.uid(). Server routes use user-scoped Supabase clients, not the service role, except for clearly delineated admin operations that log every call. Magic links and password resets exchange their tokens on the server. Session invalidation is wired into password and email changes. Refresh-token rotation is on. The service-role key appears only in server environments that are not shipped to the client. And the profiles table cannot be used to escalate privilege because role writes go through a controlled code path.
None of this requires rewriting the Lovable app. Most of it is configuration, a handful of route changes, and a set of RLS policies that should have been there from the start. The work is finite, and it is the work we have done enough times to quote it as a fixed scope.
How WitsCode runs this audit
WitsCode Lovable Security Audit (fixed-scope): the nine checks, a severity-rated report, and a patched branch in your repo. Book a slot.
We take the Lovable repo, clone it, and run the nine checks in the order above, documenting each finding with the exact file, the exact line, the severity, and the recommended fix. Where the fix is mechanical, we apply it on a branch and open a pull request so your team can review the diff rather than guess at the remediation. Where the fix needs a product decision, we note the tradeoff and recommend the safer default. The deliverable is a report you can hand to a prospective enterprise customer, and a branch you can merge the same week. If the audit turns up a critical exposure, we will tell you to shut off the endpoint before you read the rest of the report, and we will help you do it.
The audit exists because the Lovable generator, left alone, produces the same nine-part vulnerability surface over and over. The fix is not to stop using Lovable. The fix is to run the checklist before the app has users, and again before it has paying users, and to treat the auth layer as the part of the product that gets an extra pass from someone whose job is to break it on purpose.
Get weekly field notes.
Practical writing on shipping products, straight to your inbox. No spam.
Need help with this?
MVP Development
We design and build web apps, MVPs, and SaaS products. Talk to us about what you are working on.
Talk to usWant to discuss vibe coders for your business?
Start a project and we'll talk through where you are, what's working, and the highest-leverage moves for the next 90 days.