Sample report

Scan + Report: sample output

DEMONSTRATION. This report was generated by the real LockedVibe pipeline against a deliberately vulnerable demo app we built ourselves. It is not a customer's app. Every key in it is fake. Secret values are redacted exactly as they would be in a customer report. (The demo repository on GitHub is temporarily unavailable to the public.)
16findings before fixes
12confirmed by deterministic checks
5critical
2left after the fix branch (both need a framework major upgrade)

Scope

Commit096e54df4b95dea8a10f993a390bfc8d3d6efc59
Files9 tracked files
Run2026-10-14 09:47 ET
MethodStatic analysis at one exact commit. Supabase SQL migrations were applied to a throwaway PostgreSQL instance and attacked as the anon and signed-in roles. No live systems were contacted and no found credentials were used.
Tool exit codesgitleaks 1, semgrep 0, osv-scanner 1, npm audit 1, rls ran (1 = issues found)

Status meanings: confirmed = proven by a deterministic check (an RLS attack that succeeded, an offline key decode, or a vulnerable version pinned in the lockfile). probable = a rule matched but exploitability was not proven automatically. An AI model agreeing is never counted as proof.

Findings

IDSeverityStatusFindingLocation
LV-001criticalconfirmedSupabase service_role key hardcoded in sourcelib/supabase.js:6
LV-002criticalconfirmednext@14.2.3: 35 known vulnerabilities (worst: critical)package-lock.json
LV-003criticalconfirmedRow Level Security is disabled on public.payment_eventssupabase/migrations/0001_init.sql:14
LV-004criticalconfirmedSigned-out visitors can read every row of public.orderssupabase/migrations/0001_init.sql:25
LV-005criticalconfirmedPolicy "admins read all profiles" trusts user_metadata, which users can edit themselvessupabase/migrations/0001_init.sql:35
LV-006highconfirmedNext.js route handler exports GET without any visible authentication check (getUser/getSession/auth()).app/api/admin/users/route.js:4
LV-007highconfirmedNext.js route handler exports DELETE without any visible authentication check (getUser/getSession/auth()).app/api/admin/users/route.js:9
LV-008highconfirmedNext.js route handler exports GET without any visible authentication check (getUser/getSession/auth()).app/api/orders/[id]/route.js:4
LV-009highconfirmedlodash@4.17.15: 6 known vulnerabilities (worst: high)package-lock.json
LV-010highconfirmedpostcss@8.4.31: 4 known vulnerabilities (worst: high)package-lock.json
LV-011highconfirmedUsers can set the privileged column profiles.is_admin on their own rowsupabase/migrations/0001_init.sql:6
LV-012mediumprobableSupabase row fetched by a request parameter with no owner filter (possible IDOR, depends on RLS and key used).app/api/orders/[id]/route.js:5
LV-013mediumprobabledangerouslySetInnerHTML renders raw HTML; XSS if the value is user-controlled.app/dashboard/page.js:8
LV-014mediumprobableDetection of dangerouslySetInnerHTML from non-constant definitionapp/dashboard/page.js:8
LV-015mediumprobableUpdate policy "update own profile" on public.profiles has with check (true), weaker than its using clausesupabase/migrations/0001_init.sql:31
LV-016lowconfirmed@supabase/auth-js@2.64.4: 1 known vulnerability (worst: low)package-lock.json
LV-001 · CRITICAL · confirmed · Supabase service_role key hardcoded in source

Location: lib/supabase.js:6 · Tool/rule: gitleaks / jwt

Evidence

JWT payload decoded offline: role='service_role', iss='supabase', ref='demofakeprojectref00', exp=2000000000. lib/supabase.js starts with "use client", so it is bundled into browser JavaScript. Imported by: README.md, app/api/admin/users/route.js, app/api/orders/[id]/route.js, app/dashboard/page.js, package-lock.json, package.json Secret value redacted; fingerprint sha256:7cf719bde78d. Validity against the live service was NOT tested (we never use found credentials).

Fix: Rotate the key in the Supabase dashboard now, remove it from code and git history, and only use the service_role key in server-side code via an environment variable.

LV-002 · CRITICAL · confirmed · next@14.2.3: 35 known vulnerabilities (worst: critical)

Location: package-lock.json · Tool/rule: osv-scanner / 35 advisories

Evidence

Lockfile pins next@14.2.3, inside the affected range of each advisory below. Confirmed = vulnerable version present; reachability not verified.
- GHSA-2xp9-vwfh-vxw4 [critical] Next.js: Unauthenticated Remote Code Execution in Image Optimization API when AVIF files are used
- GHSA-f82v-jwr5-mffw [critical] Authorization Bypass in Next.js Middleware
- GHSA-p293-qw3h-jr36 [critical] Next.js: Unauthenticated Remote Code Execution on windows-hosted servers
- GHSA-36qx-fr4f-26g5 [high] Next.js has a Middleware / Proxy bypass in Pages Router applications using i18n
- GHSA-5j59-xgg2-r9c4 [high] Next has a Denial of Service with Server Components - Incomplete Fix Follow-Up
- GHSA-7gfc-8cq8-jh5f [high] Next.js authorization bypass vulnerability
- GHSA-89xv-2m56-2m9x [high] Next.js: Server-Side Request Forgery in Server Actions on custom servers
- GHSA-8h8q-6873-q5fj [high] Next.js Vulnerable to Denial of Service with Server Components
- GHSA-c4j6-fc7j-m34r [high] Next.js vulnerable to server-side request forgery in applications using WebSocket upgrades
- GHSA-gp8f-8m3g-qvj9 [high] Next.js Cache Poisoning
- GHSA-h25m-26qc-wcjf [high] Next.js HTTP request deserialization can lead to DoS when using insecure React Server Components
- GHSA-m99w-x7hq-7vfj [high] Next.js: Denial of Service in App Router using Server Actions
- GHSA-mwv6-3258-q52c [high] Next Vulnerable to Denial of Service with Server Components
- GHSA-p9j2-gv94-2wf4 [high] Next.js: Server-Side Request Forgery in rewrites via attacker-controlled destination hostname
- GHSA-q4gf-8mx6-v5v3 [high] Next.js has a Denial of Service with Server Components
- GHSA-3x4c-7xq6-9pq8 [medium] Next.js: Unbounded next/image disk cache growth can exhaust storage
- GHSA-4342-x723-ch2f [medium] Next.js Improper Middleware Redirect Handling Leads to SSRF
- GHSA-4633-3j49-mh5q [medium] Next.js: Cache confusion of response bodies for requests with bodies containing invalid UTF-8 byte sequences
- GHSA-4c39-4ccg-62r3 [medium] Next.js: Unbounded Server Action payload in Edge runtime
- GHSA-68g3-v927-f742 [medium] Next.js: Cache confusion of response bodies for requests with bodies
- GHSA-7m27-7ghc-44w9 [medium] Next.js Allows a Denial of Service (DoS) with Server Actions
- GHSA-955p-x3mx-jcvp [medium] Next.js: Unauthenticated disclosure of internal Server Function endpoints
- GHSA-9g9p-9gw9-jx7f [medium] Next.js self-hosted applications vulnerable to DoS via Image Optimizer remotePatterns configuration
- GHSA-ffhc-5mcf-pf4q [medium] Next.js vulnerable to cross-site scripting in App Router applications using CSP nonces
- GHSA-g5qg-72qw-gw5v [medium] Next.js Affected by Cache Key Confusion for Image Optimization API Routes
- GHSA-g77x-44xx-532m [medium] Denial of Service condition in Next.js image optimization
- GHSA-ggv3-7p47-pfv8 [medium] Next.js: HTTP request smuggling in rewrites
- GHSA-gx5p-jg67-6x7h [medium] Next.js has cross-site scripting in beforeInteractive scripts with untrusted input
- GHSA-h64f-5h5j-jqjh [medium] Next.js has a Denial of Service in the Image Optimization API
- GHSA-wfc6-r584-vfw7 [medium] Next.js vulnerable to cache poisoning in React Server Component responses
- GHSA-xv57-4mr9-wg8v [medium] Next.js Content Injection Vulnerability for Image Optimization
- GHSA-3g8h-86w9-wvmq [low] Next.js's Middleware / Proxy redirects can be cache-poisoned
- GHSA-3h52-269p-cp9r [low] Information exposure in Next.js dev server due to lack of origin verification
- GHSA-qpjv-v59x-3qc4 [low] Next.js Race Condition to Cache Poisoning
- GHSA-vfv6-92ff-j949 [low] Next.js vulnerable to cache poisoning via collisions in React Server Component cache-busting

Fix: Fixed versions listed by these advisories: 14.2.7, 14.2.10, 14.2.15, 15.1.2, 15.1.6, 15.2.2, 15.2.3, 15.4.5, 15.4.7, 16.1.0-canary.17, 16.1.0-canary.19, 16.1.5, 16.1.7, 16.2.3, 16.2.5, 16.2.11, 16.3.3. Upgrade to the newest release in your current major line first and re-scan; advisories that are fixed only in a newer major need a planned major upgrade.

LV-003 · CRITICAL · confirmed · Row Level Security is disabled on public.payment_events

Location: supabase/migrations/0001_init.sql:14 · Tool/rule: rls-check / lv-rls-disabled

Evidence

pg_tables.rowsecurity = false. Grants to API roles: [('payment_events', 'anon', 'DELETE,INSERT,SELECT,UPDATE'), ('payment_events', 'authenticated', 'DELETE,INSERT,SELECT,UPDATE')]. As role anon: ok, result [(0,)].

Fix: alter table public.payment_events enable row level security; then add owner-scoped policies.

LV-004 · CRITICAL · confirmed · Signed-out visitors can read every row of public.orders

Location: supabase/migrations/0001_init.sql:25 · Tool/rule: rls-check / lv-rls-anon-read

Evidence

As role anon with no JWT, select count(*) returned 2 rows belonging to 2 different users. Policy: orders readable using (true).

Fix: Drop the anon policy and scope reads: create policy ... for select to authenticated using ((select auth.uid()) = user_id);

LV-005 · CRITICAL · confirmed · Policy "admins read all profiles" trusts user_metadata, which users can edit themselves

Location: supabase/migrations/0001_init.sql:35 · Tool/rule: rls-check / lv-rls-user-metadata

Evidence

Same user A: rows visible without metadata = 1, with user_metadata.role=admin = 2. Supabase lets a signed-in user update their own user_metadata via auth.updateUser().

Fix: Check roles in app_metadata (auth.jwt()->'app_metadata') or in a table users cannot write.

LV-006 · HIGH · confirmed · Next.js route handler exports GET without any visible authentication check (getUser/getSession/auth()).

Location: app/api/admin/users/route.js:4 · Tool/rule: semgrep / lv-next-route-no-auth

Evidence

Matched code:
export async function GET() {
  const { data } = await supabase.from("profiles").select("*");
  return Response.json(data);
}
 Deterministic check: handler body contains no auth call and imports the client built with the service_role key (finding above), so RLS does not apply.

Fix: Verify the caller first (supabase.auth.getUser() server-side), check their role, return 401/403 otherwise, and never use the service_role client for user-facing reads.

LV-007 · HIGH · confirmed · Next.js route handler exports DELETE without any visible authentication check (getUser/getSession/auth()).

Location: app/api/admin/users/route.js:9 · Tool/rule: semgrep / lv-next-route-no-auth

Evidence

Matched code:
export async function DELETE(request) {
  const { id } = await request.json();
  await supabase.auth.admin.deleteUser(id);
  return Response.json({ ok: true });
}
 Deterministic check: handler body contains no auth call and imports the client built with the service_role key (finding above), so RLS does not apply.

Fix: Verify the caller first (supabase.auth.getUser() server-side), check their role, return 401/403 otherwise, and never use the service_role client for user-facing reads.

LV-008 · HIGH · confirmed · Next.js route handler exports GET without any visible authentication check (getUser/getSession/auth()).

Location: app/api/orders/[id]/route.js:4 · Tool/rule: semgrep / lv-next-route-no-auth

Evidence

Matched code:
export async function GET(request, { params }) {
  const { data } = await supabase.from("orders").select("*").eq("id", params.id).single();
  return Response.json(data);
}
 Deterministic check: handler body contains no auth call and imports the client built with the service_role key (finding above), so RLS does not apply.

Fix: Verify the caller first (supabase.auth.getUser() server-side), check their role, return 401/403 otherwise, and never use the service_role client for user-facing reads.

LV-009 · HIGH · confirmed · lodash@4.17.15: 6 known vulnerabilities (worst: high)

Location: package-lock.json · Tool/rule: osv-scanner / 6 advisories

Evidence

Lockfile pins lodash@4.17.15, inside the affected range of each advisory below. Confirmed = vulnerable version present; reachability not verified.
- GHSA-35jh-r3h4-6jhm [high] Command Injection in lodash
- GHSA-p6mc-m468-83gw [high] Prototype Pollution in lodash
- GHSA-r5fr-rjxr-66jc [high] lodash vulnerable to Code Injection via `_.template` imports key names
- GHSA-29mw-wpgm-hmr9 [medium] Regular Expression Denial of Service (ReDoS) in lodash
- GHSA-f23m-r3pf-42rh [medium] lodash vulnerable to Prototype Pollution via array path bypass in `_.unset` and `_.omit`
- GHSA-xxjr-mmjv-4gpg [medium] Lodash has Prototype Pollution Vulnerability in `_.unset` and `_.omit` functions

Fix: Fixed versions listed by these advisories: 4.17.20, 4.17.21, 4.17.23, 4.18.0. Upgrade to the newest release in your current major line first and re-scan; advisories that are fixed only in a newer major need a planned major upgrade.

LV-010 · HIGH · confirmed · postcss@8.4.31: 4 known vulnerabilities (worst: high)

Location: package-lock.json · Tool/rule: osv-scanner / 4 advisories

Evidence

Lockfile pins postcss@8.4.31, inside the affected range of each advisory below. Confirmed = vulnerable version present; reachability not verified.
- GHSA-6g55-p6wh-862q [high] PostCSS: Arbitrary file read and information disclosure via attacker-controlled sourceMappingURL in CSS commen
- GHSA-r28c-9q8g-f849 [high] PostCSS: Path Traversal in Previous Source Map Auto-Loading (sourceMappingURL) leads to Arbitrary .map File Di
- GHSA-fxqj-rqcc-2cmp [medium] PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappingURL reads arbitrary .map fil
- GHSA-qx2v-qp2m-jg93 [medium] PostCSS has XSS via Unescaped </style> in its CSS Stringify Output

Fix: Fixed versions listed by these advisories: 8.5.10, 8.5.12, 8.5.18, 8.5.23. Upgrade to the newest release in your current major line first and re-scan; advisories that are fixed only in a newer major need a planned major upgrade.

LV-011 · HIGH · confirmed · Users can set the privileged column profiles.is_admin on their own row

Location: supabase/migrations/0001_init.sql:6 · Tool/rule: rls-check / lv-rls-privileged-column

Evidence

As authenticated user A, update profiles set is_admin=... on own row succeeded (1 row).

Fix: revoke update on public.profiles from authenticated; grant update (<safe columns>) on public.profiles to authenticated;

LV-012 · MEDIUM · probable · Supabase row fetched by a request parameter with no owner filter (possible IDOR, depends on RLS and key used).

Location: app/api/orders/[id]/route.js:5 · Tool/rule: semgrep / lv-supabase-query-by-param-no-owner

Evidence

Matched code:
  const { data } = await supabase.from("orders").select("*").eq("id", params.id).single();

Fix: Filter by the signed-in user (.eq('user_id', user.id)) and rely on owner-scoped RLS with the anon key, not the service_role key.

LV-013 · MEDIUM · probable · dangerouslySetInnerHTML renders raw HTML; XSS if the value is user-controlled.

Location: app/dashboard/page.js:8 · Tool/rule: semgrep / lv-dangerously-set-inner-html

Evidence

Matched code:
  return <div dangerouslySetInnerHTML={{ __html: searchParams.welcome }} data-theme={settings.theme} />;

Fix: Render user input as text, or sanitize with DOMPurify before using dangerouslySetInnerHTML.

LV-014 · MEDIUM · probable · Detection of dangerouslySetInnerHTML from non-constant definition

Location: app/dashboard/page.js:8 · Tool/rule: semgrep / react-dangerouslysetinnerhtml

Evidence

Matched code:
  return <div dangerouslySetInnerHTML={{ __html: searchParams.welcome }} data-theme={settings.theme} />;

Fix: Render user input as text, or sanitize with DOMPurify before using dangerouslySetInnerHTML.

LV-015 · MEDIUM · probable · Update policy "update own profile" on public.profiles has with check (true), weaker than its using clause

Location: supabase/migrations/0001_init.sql:31 · Tool/rule: rls-check / lv-rls-weak-with-check

Evidence

pg_policies: using ((( SELECT auth.uid() AS uid) = user_id)) with check (true). Ownership-transfer attack as user A: ['error', 0, 'new row violates row-level security policy for table "profiles"']. It was blocked today only because the owner-scoped SELECT policy is also applied to the new row; adding any broad select policy would make it exploitable (we reproduced that in our RLS guide tests).

Fix: with check ((select auth.uid()) = user_id)

LV-016 · LOW · confirmed · @supabase/auth-js@2.64.4: 1 known vulnerability (worst: low)

Location: package-lock.json · Tool/rule: osv-scanner / 1 advisories

Evidence

Lockfile pins @supabase/auth-js@2.64.4, inside the affected range of each advisory below. Confirmed = vulnerable version present; reachability not verified.
- GHSA-8r88-6cj9-9fh5 [low] auth-js Vulnerable to Insecure Path Routing from Malformed User Input

Fix: Fixed versions listed by these advisories: 2.70.0. Upgrade to the newest release in your current major line first and re-scan; advisories that are fixed only in a newer major need a planned major upgrade.

Database attack tests (before)

After the fix branch

The remediation branch was built and tested, then re-scanned with the same pipeline at commit 5b8b87256458. Tool exit codes: gitleaks 0, semgrep 0, osv-scanner 1, npm audit 1, rls ran.

RemainingFinding
criticalnext@14.2.35: 23 known vulnerabilities (worst: critical)
highpostcss@8.4.31: 4 known vulnerabilities (worst: high)

Regression tests before the fix: 0 pass / 7 fail. After: 7 pass / 0 fail. next build exit code after the fix: 0.

Test output before the fix
  • not ok 1 - LV-001: no service_role JWT anywhere in source
  • not ok 2 - LV-001: no service_role JWT in the built browser bundle (.next/static)
  • not ok 3 - LV-001: client modules never reference the service role env var
  • not ok 4 - LV-008: GET /api/admin/users without a token returns 401
  • not ok 5 - LV-009: DELETE /api/admin/users without a token returns 401
  • not ok 6 - LV-010: GET /api/orders/1 without a token returns 401
  • not ok 7 - XSS: no dangerouslySetInnerHTML in app/
Test output after the fix
  • ok 1 - LV-001: no service_role JWT anywhere in source
  • ok 2 - LV-001: no service_role JWT in the built browser bundle (.next/static)
  • ok 3 - LV-001: client modules never reference the service role env var
  • ok 4 - LV-008: GET /api/admin/users without a token returns 401
  • ok 5 - LV-009: DELETE /api/admin/users without a token returns 401
  • ok 6 - LV-010: GET /api/orders/1 without a token returns 401
  • ok 7 - XSS: no dangerouslySetInnerHTML in app/

Database attack tests (after)

Prepared pull request description

Fix PRs are opened for you to review. LockedVibe never merges them.

# [LockedVibe] Security fixes for DEMO-0001 (not opened; prepared branch `lockedvibe/fix-DEMO-0001`)

**Do not auto-merge.** Please review, rotate the exposed key, and test in staging before merging.

Base: `096e54df4b95` · Branch commits:
1. `7db33dd` chore: add root layout. The app did not build before (`next build` exit 1: "dashboard/page.js doesn't have a root layout"). This commit is not a security change.
2. `06e01b3` test: 7 security regression tests (`npm test`). At this commit, `next build` exits 0 and the tests fail 7 of 7 (exit 1).
3. `5b8b872` fix(security): remediation. After this commit, `next build` exits 0 and the tests pass 7 of 7 (exit 0).

## Fixed (verified by re-scan of 5b8b872 and by tests)
| Finding | Change | Verification |
|---|---|---|
| service_role key in browser code (critical) | `lib/supabase.js` now uses the anon key from env vars. Server-only `lib/supabaseServer.js` reads `SUPABASE_SERVICE_ROLE_KEY` from env. | gitleaks exit 0. Tests 1–3 pass, including "no service_role JWT in .next/static" (failed before the fix). |
| /api/admin/users GET and DELETE had no auth (high) | `requireUser(..., {admin:true})` checks `app_metadata.role`. Returns 401 or 403. | Tests 4–5 return 401 with no token. The semgrep rule no longer matches. |
| /api/orders/[id] had no auth and an IDOR (high) | Requires a user, adds a `user_id` filter, uses the caller's token so RLS applies. | Test 6 returns 401. The IDOR rule no longer matches. |
| RLS disabled on payment_events (critical) | New migration `0002_lockedvibe_rls_fixes.sql`: enables RLS and revokes API roles. | anon query: permission denied. |
| anon could read all orders (critical) | Owner-only select policy; anon revoked. | anon: permission denied. User A sees 0 rows from other users. |
| Admin policy trusted user_metadata (critical) | Now checks `app_metadata`. | Re-scan: the policy no longer references user_metadata. |
| Users could set is_admin (high); weak with check (medium) | Column grant limited to `update (email)`; with check now matches using. | Both attacks: permission denied. |
| XSS via dangerouslySetInnerHTML (medium) | Renders text; theme is allowlisted. | Test 7 passes. The semgrep rules no longer match. |
| lodash@4.17.15 (6 advisories) | Removed; no longer used. | No longer in the lockfile. |
| @supabase/auth-js@2.64.4 (1 advisory) | supabase-js upgraded to 2.117.3. | No longer reported. |

## NOT fixed (needs your decision)
- **next@14.2.35** (upgraded from 14.2.3): 23 advisories remain, and **postcss@8.4.31** (bundled by next) has 4. Their fixes are only in newer Next majors, so this needs a planned upgrade to Next 15/16 with its own testing. We did not make that upgrade.
- **Key rotation:** the old key is still in git history. Rotate it in Supabase and treat it as compromised. Rewriting history is your call.
- The admin and orders routes have only been tested for the unauthenticated case. The signed-in paths call Supabase and were not exercised, because no live project was used.

Tools

ToolVersion
semgrep1.180.0
gitleaks8.30.1
osv-scanner2.7.0
npm9.2.0
postgrespostgres (PostgreSQL) 18.4
gitgit version 2.47.3

Limits

See pricing