SENTRISSample report

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.

F

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.

critical×3high×1medium×1
  • 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