Eliminate shared cache session bleeding, Supabase RLS policy bypasses, and webhook replay vulnerabilities before deploying to production.
Execute this lightweight bash check to identify common configuration pitfalls across your environment and SQL files:
# 1. Search for leaked secret keys in public environment variables grep -rnE "NEXT_PUBLIC_.*(SECRET|PRIVATE_KEY|SERVICE_ROLE)" . \ --include="*.env*" --include="*.ts" --include="*.tsx" --include="*.js" # 2. Check for raw JSON parsing inside Stripe webhook handlers grep -rn "request.json()" app/api/webhooks/ \ && echo "WARNING: Stripe webhooks must read request.text(), not request.json()" # 3. Detect broad RLS policies that check auth without tenant filtering grep -rnE "USING\s*\(\s*auth\.uid\(\)\s+IS\s+NOT\s+NULL\s*\)" . \ --include="*.sql" && echo "WARNING: Policy checks login, but not tenant row ownership"
Failure Mode: An authenticated route handles token refresh and issues Set-Cookie headers (sb-<project_ref>-auth-token) while overriding cache protections with public caching directives (e.g. s-maxage). If an intermediate CDN or proxy caches such responses, it replays the Set-Cookie header to subsequent visitors. The recipient browser stores the token in its cookie jar as an HTTP cookie, causing session hijacking. Exploitation depends on whether intermediate caches capture responses with Set-Cookie.
Invariant: Always enforce Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate, Pragma: no-cache, and Expires: -1 across both successful (200) and unauthorized (401) responses. Retain refreshed or cleared cookies on outgoing responses, forward any anti-caching metadata, ensure external headers cannot weaken strict private cache directives, and verify that CDN edge rules do not override origin private cache headers.
Failure Mode: Prefixing SUPABASE_SERVICE_ROLE_KEY with NEXT_PUBLIC_ or importing it into client components.
Invariant: The Service Role key must strictly lack public framework prefixes and must never be referenced in client bundles. Only authenticated background workers should access this credential.
Failure Mode: Validating JWT tokens in middleware without synchronizing refreshed session cookies back to both request and response objects.
Invariant: Middleware must synchronize updated cookies via the standard @supabase/ssr pattern to prevent session desynchronization.
Failure Mode: Reading authorization roles directly from user.user_metadata.is_admin.
Invariant: Users can modify their own user_metadata via the client API. Authorization decisions must query database tables or cryptographically signed claims in app_metadata.
Failure Mode: SSE or WebSocket connections that authenticate once on connection and never revalidate expiring tokens.
Invariant: Long-lived streams must enforce periodic token revalidation or a maximum connection lifetime ceiling (e.g. 1 hour).
Failure Mode: Executing CREATE TABLE without ALTER TABLE ... ENABLE ROW LEVEL SECURITY;. PostgreSQL tables without RLS permit full read/write access via the anon key.
Invariant: Every newly created table must include explicit RLS enablement in the same migration file.
Failure Mode: Creating policies with USING (auth.uid() IS NOT NULL). This only verifies that a user is logged in, allowing any authenticated user to view records belonging to every other user.
Invariant: Policies must enforce row ownership: USING (auth.uid() = user_id) or join through a validated membership table.
Failure Mode: Defining stored functions with SECURITY DEFINER without setting search_path = '', permitting schema hijacking by unprivileged callers.
Invariant: Always specify SET search_path = '' on SECURITY DEFINER functions, and qualify table references with explicit schema names.
Failure Mode: Joining an RLS-protected table to an unprotected lookup table that reveals sensitive tenant relationships.
Invariant: Ensure all joined tables have appropriate RLS policies enabled.
Failure Mode: Testing RLS policies in the Supabase SQL editor while logged in as the administrative role postgres, which bypasses RLS.
Invariant: Always test policies using an unprivileged role context: SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.sub" = '<uuid>';.
Failure Mode: Calling await request.json() before Stripe HMAC signature verification. Parsing JSON and re-serializing it can mutate signed bytes (whitespace, property order, floating-point representations), breaking signature verification.
Invariant: Read the raw body as text using await request.text() before passing to stripe.webhooks.constructEvent().
Failure Mode: Processing transactions without checking if event.id was already processed, resulting in duplicate credits or double charges on retry storms.
Invariant: Record incoming event.id in a deduplication table within a serializable transaction. If present, return HTTP 200 immediately without reprocessing.
Failure Mode: Subscribing exclusively to checkout.session.completed while ignoring refunds, disputes, and cancellations.
Invariant: Handle refund and dispute webhook events to revoke access immediately upon chargeback.
Failure Mode: Executing heavy background tasks inside the webhook HTTP response handler, causing gateway timeout retries.
Invariant: Return HTTP 200 within 500ms; dispatch background processing to a durable task queue.
Failure Mode: Accidental public naming of private credentials (e.g. NEXT_PUBLIC_STRIPE_SECRET_KEY).
Invariant: Automated CI linters must reject any private key name containing public framework prefixes.
Failure Mode: Permitting wildcard * origins while accepting session credentials or cookies.
Invariant: Specify exact, trusted origins for authenticated requests.
Failure Mode: Using in-memory rate limiting counters in serverless functions, which reset on every cold start.
Invariant: Store rate limits in a centralized persistent store or Redis with atomic increments.
Failure Mode: Applying schema migrations without corresponding rollback procedures.
Invariant: Every forward migration must have a validated reverse migration script.
Continue hardening your production stack with our companion developer tools:
Comprehensive 10-point systemic resilience audit for modern web stacks. Verify zero-point dependencies, automated fallbacks, and fail-safe defaults.
The definitive developer directory of battle-tested open source libraries, infrastructure architectures, and automation pipelines.