This is the whole report.
Below is an unedited Sentris scan, rendered by the same code that renders a paying customer's. Nothing is trimmed for the screenshot and nothing is held back — what you are looking at is what lands in your dashboard.
- Repository
- sentris/demo-vulnerable-app
- Scanned
- 5 August 2026
That repo is ours and it is broken on purpose — it is the fixture the four checks are tested against on every commit. We do not publish customer findings, so the only report we can show you in full is one about a repo we own.
Security grade
At least one critical exposure.
5 exposures · 3 critical
3 confirmed — we read the thing itself, not a pattern that resembles it. 2 potential — the check is missing; whether it is reachable is not proven.
- criticalC2 · confirmed
Stripe live secret key exposed in your repository
sk_live_••••ABCD — .env
Fix
Remove the Stripe secret key (sk_live_…) from client code. Use the publishable key (pk_live_…) in the browser and keep sk_live_ server-side only. Roll it now: Stripe Dashboard → Developers → API keys → Roll key. - criticalC1 · confirmed
Policy "anyone can write orders" on public.orders allows unrestricted access
create policy "anyone can write orders" on public.orders for all using (true);
Fix
A for all policy with true lets anyone holding the anon key write this table. Scope it to the row owner: drop policy "anyone can write orders" on public.orders; create policy "anyone can write orders" on public.orders for all to authenticated using (auth.uid() = user_id) with check (auth.uid() = user_id);
- criticalC1 · potential
Table public.leads has no Row Level Security
create table public.leads ( id uuid primary key, email text, phone text ); — no "enable row level security" for public.leads anywhere in your SQL
Fix
Tables in the public schema are readable through your project's anon key unless RLS is on. Enable it and add a policy that scopes rows to their owner: alter table public.leads enable row level security; create policy "Owners read their own rows" on public.leads for select to authenticated using (auth.uid() = user_id); If you already enabled RLS from the Supabase dashboard, add the statement to a migration anyway so the next deploy cannot silently drop it.
- highC3 · potential
API route src/app/api/orders/[id]/route.ts takes a caller-supplied id with no auth check
Route handler reads a caller-controlled identifier and queries data, and no authentication or ownership check appears in the file, while using a service_role client that bypasses RLS
Fix
Static analysis cannot prove this is exploitable — it can only show the check is missing. Verify by calling the route as user A with user B's id. Identify the caller and scope the query to them: const { data: { user } } = await supabase.auth.getUser(); if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); const { data, error } = await supabase .from("items") .select("*") .eq("id", id) .eq("user_id", user.id) // ownership, not just the id from the URL .single(); This route uses a service_role client, which ignores RLS — the ownership check here is the ONLY thing standing between a caller and every other user's rows. Prefer a request-scoped anon client so RLS still applies.
- mediumC4 · confirmed
Storage bucket "invoices" is public
insert into storage.buckets (id, name, public) values ('invoices', 'invoices', true);
Fix
Every object in a public bucket is readable by anyone with the URL — no key, no login. That is correct for avatars and marketing assets, and a leak for anything user-owned (uploads, documents, invoices). If it should be private, flip the bucket and serve files through signed URLs: update storage.buckets set public = false where id = 'invoices'; create policy "Owners read their own files" on storage.objects for select to authenticated using (bucket_id = 'invoices' and owner = auth.uid()); Then hand out links with createSignedUrl(path, 60) instead of getPublicUrl(path).
Checked
- · Row Level Security on every table in your public schema, and policies that grant unrestricted access via using (true)
- · Hardcoded secrets committed to the repo — service_role, Stripe, and AI-provider keys, including .env files
- · API routes that take a caller-supplied id with no auth check (reported as potential — two requests with your own throwaway test accounts turn it into a proof)
- · Supabase storage buckets marked public in config.toml, a migration, or a createBucket() call
Out of scope
- · Server infrastructure & DDoS
- · Dependency CVEs
- · TLS / cryptography depth
- · Social engineering
- · Business logic beyond access control
- · Mobile apps
- · Load / performance
Point-in-time, findings-based results. Sentris is not a penetration test, not a certification, and carries no warranty. We report reproducible exposures with evidence and a fix — we prove them, we do not exploit them.
Now run it on yours.
Scanning is free and takes no login. You see how many exposures you have before you pay for anything.
Scan my app