← 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)
- No SQL injection found via Prisma in 14 of 15 routes (one exception noted above). All others use parameterized queries. (Category 1)
- No hardcoded API keys in source, all secrets loaded from
process.envon server-only paths. (Category 3) - Better Auth session cookies set with
httpOnly; secure; sameSite=lax; sessions expire after 7 days; logout invalidates the session row. (Categories 4, 39) - CSP, HSTS, X-Frame-Options configured in
next.config.js headers(). No inline scripts found outside Next.js-internal hydration. (Category 32)
Final Tally
| Severity | Count |
|---|---|
| Critical | 2 |
| High | 4 |
| Medium | 5 |
| Low | 3 |
| Passed | 38 |
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