Project: NoteNest (demo app built to show what a report looks like; not a real customer) Order: SAMPLE-001 · Tier: Full · Scanned: Oct 15, 2026 Source checked: zip upload
ROTATE THESE KEYS NOW, before reading anything else. 1. OpenAI key found in
src/components/Chat.tsx. Rotate at platform.openai.com, under API keys. 2. Supabase service_role key found in the same file. Rotate in Supabase under Project Settings, then API. A key that was ever public must be treated as stolen, even after you delete it from the code.
Not ready to launch.
Right now a stranger could read every user's notes, use your OpenAI credits, and unlock your paid plan without paying. All of it is fixable, and most fixes take minutes with the prompts below.
| Critical | High | Medium | Low | Couldn't check |
|---|---|---|---|---|
| 6 | 3 | 2 | 0 | 0 |
Where: src/components/Chat.tsx line 1
What's wrong: Your OpenAI key is written into a file that ships to every visitor's browser.
Why it matters: Anyone can open their browser tools, copy the key, and run AI requests on your bill. Bots scan for this constantly.
How to fix: Rotate the key, then call OpenAI only from a server route that reads the key from an environment variable.
Paste this into your coding agent:
In src/components/Chat.tsx, remove the hardcoded OpenAI key on line 1.
Create a server route at app/api/chat/route.ts that reads process.env.OPENAI_API_KEY
and makes the OpenAI call. Change Chat.tsx to call /api/chat with fetch instead.
Do not change the chat UI or message format.
When done, search the whole project for "sk-" and confirm no keys remain outside .env files.
Where: src/components/Chat.tsx line 2
What's wrong: The service_role key is the master key to your database, and it's in the browser.
Why it matters: It ignores every security rule. With it, anyone can read, change or delete all your data.
How to fix: Rotate it. The browser should only ever use the anon key.
Paste this into your coding agent:
In src/components/Chat.tsx, the Supabase client on line 2 uses the service_role key.
Change it to use process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY. If any feature breaks
because it needed admin rights, move that one operation into a server route that uses
SUPABASE_SERVICE_ROLE_KEY from a server-only environment variable. Do not add the
service role key anywhere under src/. List every file you changed.
Where: supabase/migrations/001.sql line 1
What's wrong: The profiles table has no access rules turned on.
Why it matters: Anyone with your public Supabase key (which is in your site by design) can read every user's email.
How to fix: Turn on RLS and add an ownership rule.
Paste this into your coding agent:
Create a new migration that runs:
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "own profile" ON public.profiles FOR ALL USING (auth.uid() = id);
Do not edit existing migrations. After applying, confirm in the Supabase dashboard
that no table shows "RLS disabled".
Where: supabase/migrations/001.sql line 4
What's wrong: Notes has RLS on, but its rule is USING (true), which means "allow everyone."
Why it matters: This is the exact pattern behind CVE-2025-48757, which exposed data in 170+ apps. Every user can read every other user's notes.
How to fix: Replace it with a rule that checks the note belongs to the user.
Paste this into your coding agent:
Create a new migration that drops the "read" policy on public.notes and replaces it with:
CREATE POLICY "own notes" ON public.notes FOR SELECT USING (auth.uid() = user_id);
Add matching INSERT, UPDATE and DELETE policies using the same check.
Then sign in as two test users and confirm user A cannot see user B's notes.
Where: .env line 1
What's wrong: Your real environment file was included in the code.
Why it matters: If the repo is ever public, every key in it is public.
How to fix: Remove it from the repo, rotate its keys, add .env* to .gitignore (see finding 10).
Where: .env line 2 (NEXT_PUBLIC_OPENAI_SECRET)
What's wrong: Anything starting with NEXT_PUBLIC_ is copied into your website.
Why it matters: The name says secret, but the prefix makes it public.
How to fix: Rename it without the prefix and only read it in server code.
Where: app/api/checkout.ts line 1
What's wrong: The price comes from what the browser sends.
Why it matters: A buyer can edit the request and pay $0.01 for anything.
How to fix: Look up the price on the server.
Paste this into your coding agent:
In app/api/checkout.ts, stop reading the price from req.body. Accept only a productId,
look up the price from a server-side list or a Stripe Price ID, and pass that to
stripe.checkout.sessions.create. Reject unknown productIds with a 400 error.
Where: app/api/stripe/webhook/route.ts line 1
What's wrong: The webhook believes any request that says "payment completed."
Why it matters: Anyone can send a fake message and get your paid plan free.
How to fix: Verify Stripe's signature before trusting the event.
Paste this into your coding agent:
In app/api/stripe/webhook/route.ts, read the raw request body as text (not JSON) and verify it with
stripe.webhooks.constructEvent(rawBody, req.headers.get("stripe-signature"), process.env.STRIPE_WEBHOOK_SECRET).
Return 400 if verification throws. Store processed event IDs and skip duplicates.
Test with: stripe listen --forward-to localhost:3000/api/stripe/webhook
Where: package.json
What's wrong: A dependency name is one letter off a popular package.
Why it matters: Look-alike packages are a known way attackers slip malware into AI-written projects.
How to fix: Remove strpe and install stripe.
Where: .gitignore
How to fix: Add .env* and !.env.example.
Where: app/api/chat/route.ts
Why it matters: One bot calling this in a loop can run up your AI bill overnight.
How to fix: Add a per-user rate limit and set a monthly spending cap in your OpenAI account settings.
We reviewed the code you sent. We did not log in to your live app or dashboards, run a penetration test, or check business logic specific to your app. No guarantee: this report lowers your risk. It cannot prove your app is secure.
Apply the fixes, push, and reply "rescan" within 14 days for your free re-scan.
Your code was deleted after this report was written.