VVibeSafe

Scan report

demo.vibesafe.app

Checked 4 public files in 2.8s.

Grade

F

0/100

Sample report with fictional findings, so you can see an unlocked fix prompt. It is not a live scan.

4 critical2 high1 medium0 low0 info
Secrets in the bundleF
Secret-looking Next public env Stripe live secret key in the client bundle Supabase service_role key in the client bundle
Data exposureF
Environment file is publicly readable Supabase table "profiles" is readable without a user
Browser hardeningA
Missing Content-Security-Policy
Source exposureB
Source map is publicly downloadable

Findings

Critical

Environment file is publicly readable

/.env

HEAD /.env returned 200 (text/plain). VibeSafe did not download the file. This sample is fictional.

.env answered a HEAD request with a non-HTML body. Environment files hold the secrets the frontend was not supposed to contain. VibeSafe did not download the file, but anyone else can.

You are fixing a security issue in this app. The project may have been built in Lovable, Bolt, Cursor, v0, or Replit. Change the code. Do not print secret values.

.env is being served as a static file.

1. Block public access to .env and every .env.* file at the host.
2. Remove those files from the web root and from the static export output.
3. Rotate every secret that was in the file.
4. Confirm HEAD /.env returns 404.

When you are done, summarize the files you changed and the credential the human still needs to rotate in the provider dashboard.
Critical

Stripe live secret key in the client bundle

/assets/index-demo.js

A Stripe live secret (sk_live_••••9f3a) is in /assets/index-demo.js. Secret keys can create charges and refunds. Publishable keys (pk_) are fine in the browser; this one is not.

Stripe secret keys (sk_ and rk_) must stay on the server. The publishable key (pk_) is the one that belongs in the browser. A secret in the bundle can be copied by anyone who loads the page, including a live key that moves real money.

You are fixing a security issue in this app. The project may have been built in Lovable, Bolt, Cursor, v0, or Replit. Change the code. Do not print secret values.

A client bundle contains a Stripe secret key. It was found in /assets/index-demo.js. The value is already redacted and must not be pasted back into the code.

1. Search the repo for sk_live_, sk_test_, and rk_live_. Remove every occurrence from client components, NEXT_PUBLIC_ and VITE_ variables, and any file imported by the frontend.
2. Put the secret in a server-only variable named STRIPE_SECRET_KEY with no public prefix.
3. Move Stripe API calls into a server action or route handler. The browser should call your server, and the server should call Stripe.
4. Rotate the leaked key in the Stripe Dashboard before doing anything else. Assume it is copied.
5. Rebuild and confirm the new JavaScript no longer contains sk_live_ or sk_test_.

When you are done, summarize the files you changed and the credential the human still needs to rotate in the provider dashboard.
Critical

Supabase service_role key in the client bundle

/assets/index-demo.js

A JWT whose role is service_role is in /assets/index-demo.js. Project ref demoproject. This key bypasses row level security. It was not used to read the database.

The service_role key skips row level security and can read and write every table. It is a server secret. The anon key is the one Supabase expects in the browser.

You are fixing a security issue in this app. The project may have been built in Lovable, Bolt, Cursor, v0, or Replit. Change the code. Do not print secret values.

A Supabase service_role JWT is in the client bundle at /assets/index-demo.js. Do not send this key anywhere.

1. Remove the service_role key from the repo, the client, and every NEXT_PUBLIC_ or VITE_ variable.
2. Rotate the service_role key in Supabase Project Settings → API.
3. Use the anon key in the browser and the new service_role key only in server code.
4. Turn on row level security for every table in the public schema and add policies for the operations the app needs.

When you are done, summarize the files you changed and the credential the human still needs to rotate in the provider dashboard.
Critical

Supabase table "profiles" is readable without a user

demoproject.supabase.co/rest/v1/profiles

GET /rest/v1/profiles?select=*&limit=1 with the anon key returned a row. Column names: id, email, plan. Row values were not saved.

The table profiles returned a row to a request that only sent the anon key. Visitors can do the same from the browser console. Row level security should be on, with policies that match what signed-in users are allowed to see.

You are fixing a security issue in this app. The project may have been built in Lovable, Bolt, Cursor, v0, or Replit. Change the code. Do not print secret values.

The Supabase table "profiles" returned data to the anon key with GET /rest/v1/profiles?select=*&limit=1. No user JWT was sent.

1. In the SQL editor, enable RLS: alter table public.profiles enable row level security;
2. Add explicit policies for select, insert, update, and delete. Do not add a policy of using (true) for the anon role unless the data is meant to be public.
3. Repeat for every table in the public schema, not only this one.
4. If this table is intentionally public, say so in a comment and make sure it contains no emails, tokens, or private user fields.
5. Re-test with the anon key and confirm the request returns an empty list or a permission error for private rows.

When you are done, summarize the files you changed and the credential the human still needs to rotate in the provider dashboard.
High

Secret-looking Next public env

page HTML

NEXT_PUBLIC_OPENAI_API_KEY is compiled into page HTML with value sk-proj-••••. Variables prefixed NEXT_PUBLIC_ or VITE_ are shipped to every visitor.

NEXT_PUBLIC_OPENAI_API_KEY is prefixed so the bundler inlines it into JavaScript. Anything in NEXT_PUBLIC_ or VITE_ is visible in the site source. Names that say secret, token, or password do not belong there.

You are fixing a security issue in this app. The project may have been built in Lovable, Bolt, Cursor, v0, or Replit. Change the code. Do not print secret values.

NEXT_PUBLIC_OPENAI_API_KEY holds a secret-looking value and is shipped to the browser from page HTML.

1. Rename NEXT_PUBLIC_OPENAI_API_KEY so it no longer starts with NEXT_PUBLIC_ or VITE_.
2. Read it only in server code (server components, route handlers, or server actions).
3. If the browser truly needs a value, make it a publishable or anon key, never a secret.
4. Rotate the value that was exposed.

When you are done, summarize the files you changed and the credential the human still needs to rotate in the provider dashboard.
High

Source map is publicly downloadable

/assets/index-demo.js.map

HEAD /assets/index-demo.js.map returned 200 (application/json). Source maps often contain original components and env names.

Source maps reconstruct the original files from the minified bundle. If the map is public, someone can read component code, comments, and the names of env vars you thought were hidden by minification.

You are fixing a security issue in this app. The project may have been built in Lovable, Bolt, Cursor, v0, or Replit. Change the code. Do not print secret values.

A source map is exposed for /assets/index-demo.js.map.

1. Stop publishing .map files to production, or block them at the host.
2. In Vite, set build.sourcemap to false for production. In Next.js, do not deploy browser source maps unless the host keeps them private.
3. Remove sourceMappingURL comments from production bundles, including inline data: maps.
4. Confirm a request for the .map URL returns 404.

When you are done, summarize the files you changed and the credential the human still needs to rotate in the provider dashboard.
Medium

Missing Content-Security-Policy

response headers

The HTML response has no Content-Security-Policy header.

Content-Security-Policy tells the browser which scripts and frames are allowed to run. Without an enforcing policy, a cross-site scripting bug is much easier to exploit. Vibe-coded apps often render user text as HTML, so this header matters.

You are fixing a security issue in this app. The project may have been built in Lovable, Bolt, Cursor, v0, or Replit. Change the code. Do not print secret values.

Add an enforcing Content-Security-Policy. Report-only mode does not block anything.

1. Add the header on every HTML response. On Vercel or Next.js, set it in next.config headers or middleware.
2. Start with: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' https://*.supabase.co; object-src 'none'; base-uri 'self'; frame-ancestors 'none'.
3. Adjust connect-src and script-src for the hosts the app actually calls. Do not use default-src *.
4. Reload the app and fix violations in the browser console until the product still works.

When you are done, summarize the files you changed and the credential the human still needs to rotate in the provider dashboard.