Sample report generated by the Safe2Vibe kit from VibeShop, a demo app we built with deliberate flaws. No real app or data was reviewed. Get your free review
Your AI provider is called directly from the browser
The app calls OpenAI or another AI provider from code that runs in the visitor's browser, so the API key travels with it. Anyone can copy the key and run up charges on your account.
Move every AI API call in src/lib/ai.ts:3, src/lib/ai.ts:4 to a server-side function (for example a Supabase Edge Function or an API route). Keep the API key in a server environment variable, require a signed-in user, and have the browser call your function instead of the AI provider. Remove options like dangerouslyAllowBrowser.
After you apply the fix, ask Safe2Vibe to re-check it.
Critical#2
A secret key is saved in your code
A private key or password is written in your code. Anyone who can see the code, or the files your app sends to browsers, can use it. Deleting it now isn't enough: it stays in the history and must be replaced.
Where (2)
.env:3OpenAI key: sk-pro…[redacted]
.env:4Stripe secret key: sk_liv…[redacted]
Paste this into Lovable, Cursor or Bolt
Remove the secret found in .env:3, .env:4 from the code and load it from an environment variable on the server instead. Make sure it's never sent to the browser. Then tell me exactly where to rotate (replace) this key with the provider, because the old one was exposed.
After you apply the fix, ask Safe2Vibe to re-check it.
Critical#3
Database queries are built by joining text
This query is built by pasting values into the SQL text. Someone can type special characters into a form or URL and run their own commands on your database.
Where (1)
api/search.ts:6const result = await db.query(`select * from products where name ilike '%${req.query.q}%'`);
Paste this into Lovable, Cursor or Bolt
In api/search.ts:6, replace the SQL built with string concatenation or template literals with a parameterized query (placeholders like $1 with the values passed separately), or use the query builder's methods. Do the same for every query that includes user input.
After you apply the fix, ask Safe2Vibe to re-check it.
Critical#4
Row Level Security is off on a database table
This Supabase table was created without Row Level Security. Anyone with your app's public key, which every visitor has, can read and change every row in it without signing in.
Where (1)
customerscreated at supabase/migrations/20260101000000_init.sql:1, no 'enable row level security' found
Paste this into Lovable, Cursor or Bolt
Enable Row Level Security on the Supabase table customers. Add policies so a signed-in user can only select, insert, update and delete rows that belong to them (for example where user_id = auth.uid()). Don't allow anonymous access unless the table is meant to be public. Show me the SQL before applying it.
After you apply the fix, ask Safe2Vibe to re-check it.
Critical#5
The Supabase admin key is in the app's code
The service_role key skips every security rule in your database. It appears in code that can reach the browser, so anyone could use it to read, change or delete all your data.
Where (1)
src/lib/supabase.ts:6JWT with role=service_role: eyJhbG…[redacted]
Paste this into Lovable, Cursor or Bolt
Remove the Supabase service_role key from src/lib/supabase.ts:6 and from any frontend code. Use only the anon/publishable key in the browser. If server code needs admin access, read the key from a server-side environment variable. Then tell me to rotate the service_role key in the Supabase dashboard, because it was exposed.
After you apply the fix, ask Safe2Vibe to re-check it.
CriticalNeeds expert confirmation#6
A database policy lets everyone read or change data
Row Level Security is on, but this policy allows everyone, including people who aren't signed in, to read or change all rows. It has the same effect as having no protection.
Our automatic checks suggest this issue. A Safe2Vibe expert will confirm it with you, because it depends on how your app works.
Where (2)
"Anyone can read orders" on ordersfor select using (true) — supabase/migrations/20260101000000_init.sql:12
"Public catalog" on productsfor select using (true) — supabase/migrations/20260101000000_init.sql:19
Paste this into Lovable, Cursor or Bolt
Review the Supabase policy "Anyone can read orders" on orders, "Public catalog" on products. Replace the condition 'true' with a rule that limits access to the row's owner (for example auth.uid() = user_id), and restrict it to the authenticated role. Show me the SQL before applying it.
After you apply the fix, ask Safe2Vibe to re-check it.
High#7
A library your app uses has a known vulnerability
One of the packages your app depends on has a publicly known security flaw. Attackers look for apps using vulnerable versions.
vite (<=6.4.3)Vite XSS vulnerability in `server.transformIndexHtml` via URL payload; Vite dev server option `server.fs.deny` can be bypassed when hosted o → fix: vite@5.4.21
Paste this into Lovable, Cursor or Bolt
Update axios (<=0.32.0), react-router-dom (6.0.0-alpha.0 - 6.30.2), vite (<=6.4.3) to a version without the known vulnerability (run npm audit to see the fixed version), then run the app and its tests to make sure nothing broke.
After you apply the fix, ask Safe2Vibe to re-check it.
High#8
A .env file with settings is saved in the repository
Files like .env usually contain passwords and API keys. When they're saved in the repository, everyone with access to the code, now or in the future, can read them.
Remove .env from the repository, add .env and .env.* to .gitignore (keep a .env.example with empty values), and move the real values to your hosting provider's environment variables. Then list every key that was in the file so I can rotate them.
After you apply the fix, ask Safe2Vibe to re-check it.
HighNeeds expert confirmation#9
An admin page may open without signing in
This admin route doesn't appear to check that the visitor is signed in and has an admin role. Hiding it from the menu doesn't protect it: anyone who types the address can open it.
Our automatic checks suggest this issue. A Safe2Vibe expert will confirm it with you, because it depends on how your app works.
src/pages/Admin.tsxadmin page with no sign-in or role check in the file
Paste this into Lovable, Cursor or Bolt
Protect the admin route in src/App.tsx:12, src/pages/Admin.tsx. Require a signed-in user and verify on the server that their role is admin before showing the page or returning any data. Redirect everyone else to sign in.
After you apply the fix, ask Safe2Vibe to re-check it.
HighNeeds expert confirmation#10
A record may be readable by users who don't own it
This code loads a record using an ID from the URL or request without checking that it belongs to the signed-in user. If that's the case, any user can see other people's data just by changing the number.
Our automatic checks suggest this issue. A Safe2Vibe expert will confirm it with you, because it depends on how your app works.
Where (1)
api/orders/[id].ts:6const { data } = await supabase.from("orders").select("*").eq("id", req.query.id).single();
Paste this into Lovable, Cursor or Bolt
In api/orders/[id].ts:6, before returning a record loaded by its ID, check that it belongs to the signed-in user (or that the user has permission to see it). If not, return 404. Apply the same check to every endpoint that loads, updates or deletes a record by ID.
After you apply the fix, ask Safe2Vibe to re-check it.
HighNeeds expert confirmation#11
An AI feature has no usage limit
This endpoint calls an AI model on every request and doesn't limit how often each user can call it. One person, or a bot, could send thousands of requests and you'd pay for all of them.
Our automatic checks suggest this issue. A Safe2Vibe expert will confirm it with you, because it depends on how your app works.
Where (1)
api/chat.tscalls an AI provider and has no rate-limiting code
Paste this into Lovable, Cursor or Bolt
Add rate limiting to the endpoint in api/chat.ts: for example at most 30 requests per user per hour and 5 per minute. Require a signed-in user and return a clear message when the limit is reached. Also set a monthly budget cap with the AI provider.
After you apply the fix, ask Safe2Vibe to re-check it.
Medium#12
Your API accepts requests from any website
The server allows any website to call it from a visitor's browser (Access-Control-Allow-Origin: *). Combined with other issues, this lets malicious sites use your API on behalf of your users.
In api/chat.ts:6, replace the wildcard CORS origin with the exact domains of your app (for example https://myapp.com). Don't combine a wildcard with credentials.
After you apply the fix, ask Safe2Vibe to re-check it.
Medium#13
HTML is inserted into the page without cleaning it
The app inserts raw HTML into the page. If any of it comes from users, someone could inject a script that runs in other users' browsers and steals their sessions.
Where (1)
src/components/Review.tsx:2return <div className="review" dangerouslySetInnerHTML={{ __html: html }} />;
Paste this into Lovable, Cursor or Bolt
In src/components/Review.tsx:2, avoid dangerouslySetInnerHTML or innerHTML for content that can come from users. Render it as text, or sanitize it first with a library like DOMPurify.
After you apply the fix, ask Safe2Vibe to re-check it.
Medium#14
Security headers aren't configured
The project doesn't set the headers that tell browsers to block common attacks, like other sites loading yours in a frame to trick your users.
Where (1)
vercel.json / netlify.toml / next.configno security headers configuration found
Paste this into Lovable, Cursor or Bolt
Add security headers to every response in vercel.json / netlify.toml / next.config: Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin, and a Content-Security-Policy that only allows the domains the app uses.
After you apply the fix, ask Safe2Vibe to re-check it.
Medium#15
A file storage bucket is public
Anyone who guesses or finds a file's address can download it. That's fine for logos, but not for invoices, IDs or user uploads.
Check the Supabase storage bucket invoices. If it holds user files or anything private, make it private and serve files with signed URLs that expire. Keep it public only if every file in it is meant to be public.
After you apply the fix, ask Safe2Vibe to re-check it.
What we checked
This review combined automatic checks (secrets, database configuration, code patterns, dependencies and, when authorized, the live site) with Safe2Vibe's standard for apps built with AI.
Checks run
gitleaks: 3 secret(s) after excluding public keys
semgrep: 4 result(s)
npm audit (package-lock.json): 3 direct vulnerable package(s)
No review finds every problem, and your app changes over time. Read our Risk notice: https://safe2vibe.com/disclaimer