← back
Sample, anonymized

Security Audit Report

A typical Snitch report on a small Next.js + Stripe + Supabase project. Code excerpts have been redacted; CWE / CVSS / file:line formatting matches real output.


Summary

Overall Risk
High
Findings
2 Critical · 4 High · 5 Medium · 3 Low
Standards
CWE Top 25 (2025), OWASP Top 10 (2025), CVSS 4.0
Detected stack
Next.js 14, Prisma, Stripe, Supabase, Resend, Vercel
Scan mode
Full System Scan (67 categories)

Critical findings

1. Stripe webhook signature not verified

Severity
Critical · CVSS 4.0 ~9.1
CWE
CWE-345 (Insufficient Verification of Data Authenticity)
OWASP
A02:2025 Cryptographic Failures
File
app/api/webhooks/stripe/route.ts:18
Evidence
export async function POST(req: Request) {
  const event = await req.json();   // ← raw JSON parse, no signature check
  if (event.type === "checkout.session.completed") {
    await markOrderPaid(event.data.object.metadata.orderId);
  }
  return new Response("ok");
}
Risk
Any attacker can POST a forged Stripe event to this endpoint and mark arbitrary orders as paid. No signature verification means the webhook is functionally a public RPC for order state.
Fix
Call stripe.webhooks.constructEvent(rawBody, sig, STRIPE_WEBHOOK_SECRET) before any side effect. Use the raw body (not the parsed JSON) for signature verification.
Priority
P1 (Fix First)
Confidence
High

2. Supabase service-role key shipped to client bundle

Severity
Critical · CVSS 4.0 ~8.9
CWE
CWE-798 (Hardcoded Credentials)
OWASP
A07:2025 Identification and Authentication Failures
File
lib/supabase-admin.ts:5
Evidence
import { createClient } from "@supabase/supabase-js";

// This file is imported from a "use client" component.
export const admin = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY!  // ← NEXT_PUBLIC_ exposes to browser
);
Risk
The service role key bypasses RLS and grants full database access. Prefixing it NEXT_PUBLIC_* ships it into every client bundle Vercel deploys; anyone viewing the site source has full DB write.
Fix
Rename to SUPABASE_SERVICE_ROLE_KEY (drop NEXT_PUBLIC_ prefix), restrict import to server modules (add 'server-only' at the top), and rotate the key in the Supabase dashboard immediately.
Priority
P1 (Fix First)
Confidence
High

High findings (4)

Middleware auth missing matcher, bypassed on /api/internal/*
High · middleware.ts:23
Add matcher: ['/((?!_next/static|_next/image|favicon).*)'] to middleware config.
SQL injection via Prisma $queryRawUnsafe with req.query.id
High · app/api/users/route.ts:67
Switch to prisma.$queryRaw with tagged template (auto-parameterized) or prisma.user.findUnique({ where: { id } }).
Server Action accepts userId without auth check
High · app/actions/billing.ts:8
First line of every Server Action: const session = await auth(); if (!session) throw new Error('unauth');
Resend API key in NEXT_PUBLIC_RESEND_KEY
High · .env.example:14
Rename to RESEND_API_KEY, restrict to server modules, rotate.

Passed checks (excerpt)


Final Tally

SeverityCount
Critical2
High4
Medium5
Low3
Passed38

Categories scanned: 67 of 67  ·  Sinks traced: 184  ·  Confidence: High

Top finding, fix this first

[CRIT-001] Stripe webhook signature not verified, app/api/webhooks/stripe/route.ts:18

Any actor can POST a forged Stripe event and mark arbitrary orders as paid. Public endpoint, financial side effect, no authentication.

Quick fix: stripe.webhooks.constructEvent(rawBody, sig, STRIPE_WEBHOOK_SECRET) at the top of the handler, before any DB write.

Scanned by Snitch for Claude Code v1.0.0, 67 categories. snitchplugin.com