app/api/stripe/webhook/route.tsNot checked yet
Anyone can fake a “payment succeeded”
The webhook trusts any request and never checks Stripe’s signature. Someone could mark an order paid without paying.
Read the free guideSAMPLE REPORT
This small shop app has the mistakes AI tools often make. Below is the full report Aster gives for it, word for word, so you know exactly what you’d get.
AUDIT RESULT
4 are critical severity. Start there.
1NEXT_PUBLIC_SUPABASE_URL=https://demo-project.supabase.co2SUPABASE_SERVICE_ROLE_KEY=eyJhbG••••••••3STRIPE_SECRET_KEY=sk_liv••••••••4
A value in the format of a supabase service_role key is committed here. The value is redacted; Aster does not test keys against the provider.
If this is a real credential, rotate it in the provider dashboard first, then move the new value to a server-only environment variable and remove the old one from the code and the repository history. Turn on GitHub secret scanning with push protection so the next one is blocked.
2 3export const openai = new OpenAI({4 apiKey: process.env.NEXT_PUBLIC_OPENAI_API_KEY,5 dangerouslyAllowBrowser: true6});
NEXT_PUBLIC_OPENAI_API_KEY is read here. Variables with this prefix are copied into the browser bundle at build time, so its value is visible to every visitor even though it is set in your hosting dashboard.
Move the call that needs this key to server code, rename the variable without the public prefix, and rotate the key because past builds already exposed it.
1create table public.profiles (2 id uuid primary key references auth.users,3 email text,
public.profiles is created here and no migration turns on Row Level Security for it. Tables in public are reachable through Supabase's public API, so anyone with your project URL and public key could read or change its rows.
Add a new migration that runs "alter table … enable row level security;" and one policy per action limited to the row owner, for example using ((select auth.uid()) = user_id), with "with check" on inserts and updates. Then test with only the public key: you should get [] back.
11);12alter table public.orders enable row level security;13create policy "orders are readable" on public.orders for select using (true);14
The SELECT policy "orders are readable" on public.orders uses true as its condition for public. Row Level Security is on, but this policy lets anyone with your public key read all rows.
Replace true with an ownership condition such as ((select auth.uid()) = user_id), and add "with check" with the same condition for inserts and updates. Keep a true SELECT policy only for tables whose every row is meant to be public.
1NEXT_PUBLIC_SUPABASE_URL=https://demo-project.supabase.co2SUPABASE_SERVICE_ROLE_KEY=eyJhbG••••••••3STRIPE_SECRET_KEY=sk_liv••••••••4
A sensitive-looking environment assignment has a non-placeholder value. The value is redacted; whether it is active is unknown.
Verify whether this value is confidential. Rotate real credentials and store them outside the repository. Keep only empty or clearly fake values in example files.
1NEXT_PUBLIC_SUPABASE_URL=https://demo-project.supabase.co2SUPABASE_SERVICE_ROLE_KEY=eyJhbG••••••••3STRIPE_SECRET_KEY=sk_liv••••••••4
A sensitive-looking environment assignment has a non-placeholder value. The value is redacted; whether it is active is unknown.
Verify whether this value is confidential. Rotate real credentials and store them outside the repository. Keep only empty or clearly fake values in example files.
3export async function GET(req: Request) {4 const q = new URL(req.url).searchParams.get("q") ?? "";5 const rows = await db.$queryRawUnsafe(`SELECT * FROM products WHERE name LIKE '%${q}%'`);6 return Response.json(rows);7}
An unsafe raw-query method receives a dynamic first argument. This is a review candidate, not a reproduced SQL injection.
Use the database client’s parameterized query API. Keep query structure fixed and pass user values as bound parameters.
7 },8 "dependencies": {9 "next": "15.1.0",10 "react": "19.0.0",11 "react-dom": "19.0.0",
This project uses the App Router with next 15.1.0, which is affected by CVE-2025-66478 / CVE-2025-55182 (GHSA-9qr9-h5gf-34mp). One crafted request can run code on your server; it has been exploited in the wild since December 2025.
Upgrade next to the latest patched release in your major (at least 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6 / 15.4.8 / 15.5.7 / 16.0.7, ideally the newest 16.x), redeploy, and rotate secrets the server could read if it was exposed while unpatched.
7 },8 "dependencies": {9 "next": "15.1.0",10 "react": "19.0.0",11 "react-dom": "19.0.0",
middleware.ts makes an authentication decision, and next 15.1.0 is affected by CVE-2025-29927 (GHSA-f82v-jwr5-mffw): a request with the x-middleware-subrequest header skips middleware entirely. Aster could not tell where this app is hosted.
Upgrade next to 12.3.5 / 13.5.9 / 14.2.25 / 15.2.3 or later (ideally the latest release), and also check the session inside each route handler and Server Action, not only in middleware.
1insert into storage.buckets (id, name, public)2values ('invoices', 'invoices', true);3
The bucket "invoices" is created with public = true. Files in a public bucket can be downloaded by anyone who has or guesses the URL, with no login and no policy check. The name suggests private files.
Make the bucket private (public = false), add storage.objects policies that limit each user to their own folder, for example (storage.foldername(name))[1] = (select auth.uid())::text, and hand out short-lived signed URLs instead of public links.
1node_modules2.next3
This project reads environment variables, but no pattern in the root .gitignore matches ".env". Templates such as Vite's only ignore "*.local", so a plain .env is committed by the next "git add .".
Add ".env" and ".env*.local" (or ".env*" followed by "!.env.example") to the root .gitignore. If a .env was ever committed, rotate its keys first.
3export default async function Product({ params }: { params: { id: string } }) {4 const { data } = await supabase.from("products").select("*").eq("id", params.id).single();5 return <article><h1>{data.name}</h1><div dangerouslySetInnerHTML={{ __html: data.description }} /></article>;6}7
dangerouslySetInnerHTML receives a dynamic value. Sanitization and input provenance require review.
Prefer normal React text rendering. If HTML is required, verify sanitization and the provenance of the content.
1module.exports = {2 typescript: { ignoreBuildErrors: true }3};4
An ignoreBuildErrors option is set to true. Check whether the application build consumes this setting.
Restore build-time type checking and resolve the errors instead of skipping them.
package.json2 findings.gitignore1 finding.env3 findingsnext.config.js1 findinglib/supabase.tsnothing foundlib/ai.ts1 findingsupabase/migrations/0001_init.sql2 findingssupabase/migrations/0002_storage.sql1 findingapp/layout.tsxnothing foundapp/page.tsxnothing foundapp/product/[id]/page.tsx1 findingapp/api/search/route.ts1 findingapp/api/chat/route.tsnothing foundapp/api/stripe/webhook/route.tsnothing foundapp/admin/page.tsxnothing foundmiddleware.tsnothing foundcomponents/Cart.tsxnothing foundlib/orders.tsnothing foundEngine source-checks-2.1 · 40 checks · no AI read this code.
WHAT THIS REPORT CAN’T TELL YOU
Aster checks keys, database rules, packages and risky code. It doesn’t yet check webhooks, admin access or rate limits. Our free guides cover those.
No findings means none of Aster’s checks matched, not that your app is secure.
app/api/stripe/webhook/route.tsNot checked yet
The webhook trusts any request and never checks Stripe’s signature. Someone could mark an order paid without paying.
Read the free guideapp/admin/page.tsxNot checked yet
It uses display: none for anyone who isn’t an admin. The page still loads, and the server never checks who is asking.
AFTER THE REPORT
Choose findings and a model. Aster Basic is free; premium models show their credit cost first.
Aster drafts the change. It opens as a draft on GitHub only after you confirm. You merge it.
A fresh audit compares before and after. If the finding is gone, it says so: “Your fix worked.”
Connect one repo and get this report for your own app. Read-only GitHub access, 1 free audit every month, no card needed.