ONLOOK SECURITY CHECKLIST
Onlook is an open-source visual editor for Next.js + TailwindCSS apps — you edit the rendered page, Onlook writes the React code, and the hosted version builds and deploys with AI assistance, commonly on Supabase. The most common security issues come from two patterns: Next.js has a hard server/client boundary that AI-generated code crosses casually (secrets and privileged calls ending up client-side), and the API layer — route handlers and server actions — ships without the auth and ownership checks the visual layer implies. The checklist below is what we look for first when we audit a Next.js + Supabase app built visually.
Treat Critical as launch-blocking. High is week-one. Medium is the cleanup once you have users.
How to use this checklist
Walk it once against the code (the Onlook project is a real Next.js repo — open it) and a second time against the deployed URL with two test accounts. After each fix, sign in as user B and try to read user A’s data. After the whole list passes, run a black-box scan against the deployed URL.
Critical (fix before launch)
1. Confirm no server secrets reach the client bundle
Why it matters. Anything imported into a "use client" component or prefixed NEXT_PUBLIC_ ships to every visitor’s browser. AI edits under “make it work” pressure routinely move a fetch into a client component — taking the key with it — or add the NEXT_PUBLIC_ prefix to silence a build error.
How to check. Grep the repo for NEXT_PUBLIC_ and verify every match is genuinely public-safe. Then check the deployed bundle for key prefixes (sk_, sk-proj-, re_, eyJ service-role JWTs) — the Token Leak Checker automates this.
How to fix. Read secrets only in server components, route handlers, and server actions. Remove the NEXT_PUBLIC_ prefix from anything sensitive and rotate any key that ever shipped in a bundle.
2. Enable RLS on every Supabase table
Why it matters. The Supabase anon key is designed to be public — but only when Row Level Security is enabled on every table. One table without RLS is readable and often writable by anyone with the anon key, no app bug required.
How to check. In Supabase, list all tables and confirm RLS is enabled with policies for select, insert, update, and delete. The Supabase RLS Checker tests this from outside.
How to fix. Enable RLS per table with policies scoping rows to auth.uid(). Deny by default; add policies per operation, not a single permissive one.
3. Add auth to every route handler
Why it matters. Generated app/api/*/route.ts handlers usually ship without a session check. The pages calling them look protected, but the JSON endpoints answer anyone with curl.
How to check. Open every route.ts. Each handler that reads or writes user data must resolve the session and return 401 when absent. Then test unauthenticated: curl https://yourapp.com/api/... should not return data.
How to fix. Resolve the user at the top of every handler (supabase.auth.getUser() or your auth library’s equivalent) and reject before touching the database.
4. Add ownership checks to ID-keyed routes and actions (BOLA)
Why it matters. Auth alone means “some logged-in user.” Generated CRUD looks up by ID and returns whatever it finds — including other users’ records. This is the most common exploitable finding in AI-generated Next.js apps. See BOLA in AI-generated CRUD.
How to check. Sign in as user A, capture a request containing a record ID. Sign in as user B and replay it with A’s ID. The response must be 403 or 404, not 200.
How to fix. Filter every query by the session user (.eq("user_id", user.id)) or rely on RLS policies that do the same — and verify with the two-account test either way.
5. Treat server actions as public endpoints
Why it matters. Server actions compile to HTTP endpoints callable by anyone with a crafted POST. The form that “calls” the action is not an access control. An action without validation and auth is an open API route wearing a button.
How to check. List every "use server" function. Each must validate its arguments with a schema and check auth/ownership — same standard as a route handler.
How to fix. Add schema validation (Zod or equivalent) and a session check at the top of every action. Never trust IDs or role fields arriving in action arguments.
6. Fix middleware so API routes aren’t left out
Why it matters. Middleware-based protection commonly ships with a matcher covering pages (/dashboard/:path*) but not /api/*. Everything the matcher misses is unprotected, and matcher gaps fail open.
How to check. Read middleware.ts and its config.matcher. List routes it does not cover and confirm each has its own auth check.
How to fix. Keep per-handler auth as the primary control; treat middleware as defense-in-depth, not the only gate.
High (fix in the first week)
7. Disable production source maps
Confirm productionBrowserSourceMaps is not enabled in next.config. Shipped source maps hand attackers your original TypeScript, comments included. See source maps and .git exposed.
8. Put visual edits behind code review
Onlook’s model is designers shipping code without reading it. Keep the project in git, deploy from a protected branch, and have a React-literate reviewer skim any diff touching app/api/, server actions, middleware.ts, or env usage.
9. Add rate limiting on auth and paid endpoints
Next.js adds no throttles by default. Rate-limit login, password reset, and any route or action that calls an LLM or other metered API — per user and per IP.
10. Restrict OAuth redirect URLs
In Supabase auth settings, allow only your production domain as a redirect target. Extra dev URLs and wildcards are an account-takeover surface. See SSRF / open redirect / OAuth.
11. Sanitize rendered user content
Search for dangerouslySetInnerHTML. Every use rendering user- or LLM-authored content needs sanitization first. See LLM-rendered HTML/Markdown.
12. Harden file uploads
Size cap, MIME allowlist, server-generated filenames — enforced in the handler or Supabase Storage policy, not the dropzone component. See file upload zip-slip / XXE.
Medium (fix when you can)
13. Strip role fields from client-writable payloads
If a profile update action accepts arbitrary fields and the table has role or is_admin, a user can promote themselves. Allowlist updatable columns server-side.
14. Return generic errors in production
Confirm API errors return a generic message, not stack traces or Postgres error details that map your schema.
15. Disable email enumeration
Signup, password reset, and magic-link flows should return identical responses whether the account exists or not.
16. Audit who can edit the project
Anyone with edit access to the Onlook project or the git repo can change auth code. Trim the collaborator list.
17. Verify webhook signatures
If Stripe or similar posts to a route handler, verify the signature before trusting the event body.
18. Set up logging on auth and data access
Ship Supabase logs somewhere queryable and alert on bulk reads from a single user — that’s enumeration.
After every Onlook edit session
- Diff the generated code before deploying — especially
app/api/, server actions, andmiddleware.ts. - Grep for new
NEXT_PUBLIC_variables and new"use client"directives on files that touch secrets. - Re-run the two-account test on any route the session touched.
- Confirm no new table shipped without RLS.
Common attack patterns we see in Next.js + Supabase apps
The unprotected JSON behind a protected page. Middleware gates /dashboard; /api/orders answers anyone. Attacker skips the UI entirely.
The RLS-less table. Every table has RLS except the messages table added last week. The anon key dumps it.
The self-promoting profile update. Server action accepts { role: "admin" } in the update payload. One POST later the attacker is admin.
The leaked service-role key. A “quick fix” moved a Supabase admin call client-side; the service-role key shipped in the bundle and bypasses RLS entirely.
Related Resources
How to Secure Onlook
Step-by-step guide for hardening an Onlook app — the server/client boundary, route handler auth, RLS, and server actions in long form.
Is Onlook Safe?
In-depth analysis of Onlook’s defaults — what the platform handles, what the generated Next.js code leaves open, and what we find when we audit a typical app at launch.
Automate Your Checklist
A checklist tells you what to look for. A scanner tells you what’s actually broken in the deployed app right now. VibeEval drives a real browser through your Onlook deployment, attempts the cross-user BOLA, open-route, and secret-exposure attacks above, and reports what got through — with the route or table to fix.
SCAN YOUR ONLOOK APP
14-day trial. No card. Results in under 60 seconds.