IS ONLOOK SAFE? SECURITY ANALYSIS | VIBEEVAL

Is Onlook safe? The short answer

Onlook the platform is safe. Apps built with Onlook are safe when someone audits the Next.js layer the visual editor doesn’t show. Onlook produces standard Next.js + TailwindCSS code in a real repository — that’s a better starting position than closed platforms. But the generated apps consistently ship with the same gaps: secrets crossing into the client bundle, route handlers without auth, server actions treated as internal functions, and Supabase tables missing RLS. Each gap is a small fix; collectively they are the difference between a beautiful demo and a production app.

The Onlook generation pattern

Onlook’s core loop is visual: you manipulate the rendered app, and the AI writes the React underneath. The person shipping is often a designer; the code review step often doesn’t exist. That has a predictable cost:

  • The boundary blurs. Next.js splits code into server and client. AI edits under “make it work” pressure move fetches into client components or add NEXT_PUBLIC_ to a failing env var — and the secret ships to the browser.
  • Routes ship without auth. A generated app/api/orders/route.ts returns data without checking who is asking. The page in front of it is gated; the JSON is not.
  • Server actions are trusted as internal. They’re public HTTP endpoints, but generated code treats them like local function calls — no validation, no auth.
  • RLS is partial. The first tables get policies; the table added in week three doesn’t. The anon key reads it directly.
  • Nobody reads the diff. Visual edits produce code changes that deploy without engineering review — the defining Onlook-shaped risk.

These are not bugs in Onlook; they are characteristics of AI generation plus a visual workflow. Knowing the pattern is the entire fix — every audit item below targets one of those failure modes.

The 6 areas to harden in every Onlook app

1. The server/client component boundary

Anything imported into a "use client" component, and any env var prefixed NEXT_PUBLIC_, is delivered to every visitor. This is by design — the prefix means “public.” The vulnerability is applying it to values that aren’t.

Fix. Keep privileged reads in server components, route handlers, and server actions. Audit every NEXT_PUBLIC_ variable; check the deployed bundle with the Token Leak Checker; rotate anything that ever shipped.

# find env vars that will ship to the browser
grep -rn "NEXT_PUBLIC_" .env* app/ --include="*.ts" --include="*.tsx"

2. Auth on every route handler

Generated route handlers default to “anyone can call this.” The fix is mechanical: resolve the session at the top of every handler that touches user data.

// app/api/projects/route.ts
import { createClient } from "@/lib/supabase/server";

export async function GET() {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) return new Response("Unauthorized", { status: 401 });

  const { data } = await supabase
    .from("projects")
    .select("*")
    .eq("user_id", user.id); // ownership scoping, not just auth
  return Response.json(data);
}

Note the .eq("user_id", user.id) — auth without ownership scoping is the BOLA pattern that dominates AI-generated CRUD.

3. Server actions as public endpoints

A server action is an HTTP endpoint with a nicer calling convention. Anyone can invoke it with a crafted request, with any arguments.

Fix. Validate and authenticate inside every action:

"use server";
import { z } from "zod";

const UpdateProfile = z.object({
  displayName: z.string().min(1).max(80),
  // note: no role, no is_admin — allowlist fields explicitly
});

export async function updateProfile(input: unknown) {
  const data = UpdateProfile.parse(input);
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) throw new Error("Unauthorized");

  await supabase.from("profiles").update(data).eq("id", user.id);
}

The schema allowlist matters: an action that spreads its input into an update accepts { role: "admin" } too.

4. Supabase RLS on every table

The anon key in the browser is safe exactly as long as RLS holds everywhere. Policies must exist per operation — a select-only policy leaves writes open or broken, and a missing policy on one table exposes it entirely.

Fix. Enable RLS on every table, write policies scoped to auth.uid(), and verify from outside with the Supabase RLS Checker:

alter table projects enable row level security;

create policy "own rows" on projects
  for select using (auth.uid() = user_id);
create policy "own inserts" on projects
  for insert with check (auth.uid() = user_id);

Keep the service-role key server-side only — it bypasses RLS by design.

5. Middleware that doesn’t cover everything

Route protection via middleware.ts is only as good as its matcher. The recurring gap: pages matched, API routes not.

Fix. Treat middleware as defense-in-depth and keep per-handler auth as the real control:

export const config = {
  // pages AND api — but handlers still check auth themselves
  matcher: ["/dashboard/:path*", "/api/:path*"],
};

A route that fails closed when middleware misses it is fine; a route whose only gate is the matcher is one config edit from open.

6. Build artifacts and review discipline

Two deploy-side checks. First, source maps: confirm productionBrowserSourceMaps stays off, or your original TypeScript ships with the app — see source maps and .git exposed. Second, review: Onlook writes code a designer may never read. Route every deploy through git with a human skim of diffs touching app/api/, actions, middleware, and env usage.

Security headers — the 5-minute win

Generated Next.js apps usually omit security headers. Add them once in next.config:

const securityHeaders = [
  { key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
  { key: "X-Frame-Options", value: "DENY" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
];

module.exports = {
  async headers() {
    return [{ source: "/(.*)", headers: securityHeaders }];
  },
};

Add a Content-Security-Policy once you’ve inventoried your script sources.

Pre-launch Onlook checklist

A practical sequence to run before pushing an Onlook app to a real audience:

  1. No server secrets in client components or behind NEXT_PUBLIC_; deployed bundle checked.
  2. RLS enabled on every Supabase table, policies per operation.
  3. Every route handler checks auth; every ID-keyed query scopes to the session user.
  4. Every server action validates input and checks auth/ownership.
  5. Middleware matcher reviewed; no route relies on it as the only gate.
  6. Production source maps off; errors generic in production.
  7. Security headers in place (HSTS, X-Frame-Options, X-Content-Type-Options).
  8. Rate limiting on auth and any LLM-calling endpoint.
  9. Two-account BOLA test passed on every user-scoped route.
  10. Run VibeEval against the deployed URL for the dynamic pass.

Onlook vs Lovable vs v0 — security positioning

  • Onlook. Visual editor over real Next.js + Supabase code. Failure modes are the server/client boundary, unauthenticated handlers and actions, RLS gaps, and unreviewed designer-shipped changes.
  • Lovable. Opinionated Supabase + frontend generator. Failure modes concentrate in RLS, anon keys, and BOLA.
  • v0. UI-first generator. Failure modes follow whatever backend you wire up.

Onlook’s advantage is that everything is inspectable standard code; its risk is that the workflow doesn’t require anyone to inspect it. Pick it when someone on the team will read the diffs.

The verdict

Onlook produces polished Next.js applications quickly, and being open source with standard output is a genuine security advantage. But the visual workflow ships code without forcing a review, and the generated layer consistently leaves out auth, ownership checks, RLS completeness, and boundary discipline. Treat every Onlook-built app as needing a security pass before production — the list is mechanical and scannable. Run it before launch, every launch.

Scan your Onlook app

Let VibeEval scan your deployed Onlook application for the boundary leaks, open routes, BOLA, and RLS gaps that visual AI workflows most often leave in.

COMMON QUESTIONS

01
Is Onlook safe to use?
Onlook the platform is safe — it's an open-source visual editor with a hosted build-and-deploy service, and the code it produces is standard Next.js + TailwindCSS. The risk sits in the generated apps: secrets crossing the server/client boundary into the browser bundle, API route handlers shipping without auth, server actions callable by anyone, and Supabase tables missing RLS policies.
Q&A
02
What is the most common Onlook app vulnerability?
Missing authorization on the API layer. The visual pages look protected, but the route handlers and server actions behind them frequently ship without a session check or an ownership check — so anyone who calls the endpoint directly reads or writes data the UI would never show them. The two-account test (user B replays user A's request) catches it in minutes.
Q&A
03
Does Onlook expose my API keys?
It can, through the Next.js boundary rather than the platform itself. Any value read in a client component or prefixed NEXT_PUBLIC_ ships to every visitor's browser. AI edits sometimes add that prefix or move a privileged call client-side to fix a build error. Audit the deployed bundle for key prefixes and rotate anything that ever appeared there.
Q&A
04
Is the Supabase anon key safe in an Onlook app?
Yes — but only when Row Level Security is enabled on every table with policies for select, insert, update, and delete. The anon key is designed to be public; RLS is what makes it harmless. One table with RLS off is readable by anyone holding the key, no application bug required. The service-role key is different: it bypasses RLS and must never reach the client.
Q&A
05
Are Next.js server actions in Onlook apps secure by default?
No. Server actions compile to HTTP endpoints that anyone can invoke with a crafted POST — the form in front of one is not an access control. Every action needs input validation, an auth check, and an ownership check, exactly like an API route. Generated actions often skip all three because they were 'only called from one form.'
Q&A
06
Can designers safely ship code with Onlook?
Yes, with a review gate. Onlook's model — edit visually, code changes underneath — means changes can deploy without anyone reading the diff. Keep the project in git, deploy from a protected branch, and have a React-literate reviewer skim diffs that touch route handlers, server actions, middleware, or environment variable usage.
Q&A
07
What does Onlook do well from a security standpoint?
The output is standard, auditable Next.js — no proprietary runtime, and the code lives in a real repo you can review, diff, and scan. Being open source, the tool itself is inspectable. React escapes output by default, and Next.js keeps server-only code out of the bundle when the boundary is respected. The gaps are application-level: auth, RLS, validation, and the boundary discipline the generator doesn't enforce.
Q&A

SCAN YOUR APP

14-day trial. No card. Results in under 60 seconds.

START FREE SCAN