Sample report
Scan + Report: sample output
Scope
| Commit | 096e54df4b95dea8a10f993a390bfc8d3d6efc59 |
|---|---|
| Files | 9 tracked files |
| Run | 2026-10-14 09:47 ET |
| Method | Static 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 codes | gitleaks 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
| ID | Severity | Status | Finding | Location |
|---|---|---|---|---|
| LV-001 | critical | confirmed | Supabase service_role key hardcoded in source | lib/supabase.js:6 |
| LV-002 | critical | confirmed | next@14.2.3: 35 known vulnerabilities (worst: critical) | package-lock.json |
| LV-003 | critical | confirmed | Row Level Security is disabled on public.payment_events | supabase/migrations/0001_init.sql:14 |
| LV-004 | critical | confirmed | Signed-out visitors can read every row of public.orders | supabase/migrations/0001_init.sql:25 |
| LV-005 | critical | confirmed | Policy "admins read all profiles" trusts user_metadata, which users can edit themselves | supabase/migrations/0001_init.sql:35 |
| LV-006 | high | confirmed | Next.js route handler exports GET without any visible authentication check (getUser/getSession/auth()). | app/api/admin/users/route.js:4 |
| LV-007 | high | confirmed | Next.js route handler exports DELETE without any visible authentication check (getUser/getSession/auth()). | app/api/admin/users/route.js:9 |
| LV-008 | high | confirmed | Next.js route handler exports GET without any visible authentication check (getUser/getSession/auth()). | app/api/orders/[id]/route.js:4 |
| LV-009 | high | confirmed | lodash@4.17.15: 6 known vulnerabilities (worst: high) | package-lock.json |
| LV-010 | high | confirmed | postcss@8.4.31: 4 known vulnerabilities (worst: high) | package-lock.json |
| LV-011 | high | confirmed | Users can set the privileged column profiles.is_admin on their own row | supabase/migrations/0001_init.sql:6 |
| LV-012 | medium | probable | Supabase 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-013 | medium | probable | dangerouslySetInnerHTML renders raw HTML; XSS if the value is user-controlled. | app/dashboard/page.js:8 |
| LV-014 | medium | probable | Detection of dangerouslySetInnerHTML from non-constant definition | app/dashboard/page.js:8 |
| LV-015 | medium | probable | Update policy "update own profile" on public.profiles has with check (true), weaker than its using clause | supabase/migrations/0001_init.sql:31 |
| LV-016 | low | confirmed | @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)
anon select count(*) on orders: ["ok", 1, [[2]]]user A reads other users' rows in orders: ["ok", 1, [[1]]]user A reassigns own orders row to user B: ["ok", 0, null]anon select on payment_events (RLS disabled): ["ok", 1, [[0]]]anon select count(*) on profiles: ["ok", 1, [[0]]]user A reads other users' rows in profiles: ["ok", 1, [[0]]]user A reassigns own profiles row to user B: ["error", 0, "new row violates row-level security policy for table \"profiles\""]user A sets privileged column profiles.is_admin: ["ok", 1, null]user A with self-set user_metadata.role=admin reads profiles: [["ok", 1, [[1]]], ["ok", 1, [[2]]]]
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.
| Remaining | Finding |
|---|---|
| critical | next@14.2.35: 23 known vulnerabilities (worst: critical) |
| high | postcss@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 sourcenot 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 varnot ok 4 - LV-008: GET /api/admin/users without a token returns 401not ok 5 - LV-009: DELETE /api/admin/users without a token returns 401not ok 6 - LV-010: GET /api/orders/1 without a token returns 401not ok 7 - XSS: no dangerouslySetInnerHTML in app/
Test output after the fix
ok 1 - LV-001: no service_role JWT anywhere in sourceok 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 varok 4 - LV-008: GET /api/admin/users without a token returns 401ok 5 - LV-009: DELETE /api/admin/users without a token returns 401ok 6 - LV-010: GET /api/orders/1 without a token returns 401ok 7 - XSS: no dangerouslySetInnerHTML in app/
Database attack tests (after)
anon select count(*) on orders: ["error", 0, "permission denied for table orders"]user A reads other users' rows in orders: ["ok", 1, [[0]]]user A reassigns own orders row to user B: ["ok", 0, null]anon select count(*) on payment_events: ["error", 0, "permission denied for table payment_events"]anon select count(*) on profiles: ["error", 0, "permission denied for table profiles"]user A reads other users' rows in profiles: ["ok", 1, [[0]]]user A reassigns own profiles row to user B: ["error", 0, "permission denied for table profiles"]user A sets privileged column profiles.is_admin: ["error", 0, "permission denied for table profiles"]
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
| Tool | Version |
|---|---|
| semgrep | 1.180.0 |
| gitleaks | 8.30.1 |
| osv-scanner | 2.7.0 |
| npm | 9.2.0 |
| postgres | postgres (PostgreSQL) 18.4 |
| git | git version 2.47.3 |
Limits
- Static analysis plus database-policy simulation. No dynamic testing of a running app.
- Dependency findings confirm that a vulnerable version is present, not that the vulnerable code is reachable.
- Policies changed in the Supabase dashboard but not committed to the repo are not seen.
- This is an AI-assisted automated analysis with automated evidence checks. It is not a penetration test or a compliance certification. No human reviewed each finding.