Production Audit · March 2025

Meadowlark Music Academy
Project Tracker

Full-stack Next.js + Supabase · Security audit, workflow & cost plan
0%
Phase 0 — Emergency Do This Right Now
0 / 5 done
Rotate Supabase anon key Critical
Supabase Dashboard → Settings → API → Regenerate anon key. Update .env.local. The old key is compromised — it was visible during the audit session.
Revoke and replace OpenAI API key Critical
platform.openai.com → API Keys → Delete old key → Create new. Update .env.local immediately.
Remove hardcoded auth bypass in login/signup pages Critical
Delete lines 27–34 in src/app/login/page.tsx and lines 28–32 in src/app/signup/page.tsx. Anyone can enter [email protected] with any password to get admin access right now.
Check if .env.local is committed to git history Critical
Run: git log --all --full-history -- .env.local — if it appears, use BFG Repo Cleaner to purge it. Then force-push and rotate all keys again.
Add .env.local to .gitignore and verify Critical
Ensure .env.local and all .env.* files are in .gitignore. Run git status to confirm they're untracked.
Phase 1 — Security Foundation Week 1–2
0 / 8 done
Create src/middleware.ts with Supabase session refresh + route protection High
Wire up updateSession() from src/lib/supabase/middleware.ts in a real Next.js middleware.ts at the project root. Protect /dashboard/* routes — redirect unauthenticated users to /login.
Add server-side auth guard to both dashboard pages High
In each dashboard page.tsx, call supabase.auth.getUser() server-side. If no session, redirect. Do NOT rely on client-side checks alone.
Design and commit database schema (supabase/migrations/) High
Create: profiles, courses, subscriptions, materials, events tables. Use the schema from the audit report as a starting point. Never create tables manually in the dashboard without a migration file.
Enable RLS on ALL Supabase tables and write policies High
For every table: ALTER TABLE x ENABLE ROW LEVEL SECURITY. Then write policies for each role. Verify in Supabase dashboard that RLS is ON (shown as a shield icon).
Add authentication requirement to /api/chat route High
Create a Supabase server client in the route, call getUser(). If no valid session, return 401. This prevents unauthenticated OpenAI API abuse.
Add rate limiting to /api/chat (max 20 requests/user/day) High
Use Upstash Redis (free tier) with @upstash/ratelimit, or a simple Supabase table tracking daily usage. Without this, one user can burn your entire OpenAI budget.
Add security headers to next.config.ts High
X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy. Add CSP header after testing — start in report-only mode.
Fix AI SDK version mismatch — @ai-sdk/openai vs ai package versions High
Run: npm install ai@latest @ai-sdk/openai@latest @ai-sdk/react@latest. Verify chat still works after upgrade. The current ^0.0.66 for @ai-sdk/openai will cause runtime failures.
Phase 2 — Core Features Week 3–6
0 / 9 done
Implement RBAC — profiles.role column with admin/teacher/student enforcement
Create a profiles table with a role column. In middleware, read role from DB. Protect /dashboard/admin to role=admin only. Protect teacher upload routes to role=teacher+.
Build real student dashboard with live Supabase data
Replace all hardcoded mock data with real queries: enrolled courses, recent activity, upcoming events. Add skeleton loaders while data loads.
Build real admin dashboard with live stats and user management
Real user count, revenue stats, recent signups, content management. Admin-only RLS policy must be verified before connecting real data.
Wire up contact form to a backend (Resend email or Supabase insert)
Currently submits nowhere. Use Resend (free tier: 3,000 emails/month) or simply insert into a contact_messages table in Supabase.
Implement subscription system (course enrollment + access gating)
subscriptions table → RLS policy checks subscription before serving materials. Build enrollment UI for students. Build course creation UI for teachers/admins.
Build teacher upload UI (videos via YouTube embed, PDFs to Supabase Storage private bucket)
Teachers upload PDFs to a private Supabase Storage bucket. Videos → YouTube unlisted embed URL. Serve PDFs via signed URLs (1 hour expiry) to subscribed users only.
Add input validation with Zod on all forms and API routes
npm install zod. Validate login, signup, contact, chat API body. Prevents malformed data from reaching Supabase or OpenAI.
Add pagination to all list views (courses, users, materials)
Use .range(from, to) on all Supabase queries. Add next/prev UI controls. Without pagination, a table with 10,000 rows will crash the browser.
Add loading.tsx, error.tsx, and not-found.tsx to app directory
Next.js App Router special files. loading.tsx shows a skeleton, error.tsx catches runtime errors gracefully, not-found.tsx shows a 404 page. Currently none exist.
Phase 3 — Polish & QA Week 7–10
0 / 7 done
UI redesign — implement design system with consistent tokens
Define color palette, typography scale, spacing system in tailwind.config.ts or CSS variables. Every component should use shared tokens — no one-off hex colors.
Mobile responsiveness audit and fixes
Test every page at 375px, 768px, 1280px. Fix broken layouts, oversized text, touch targets <44px. Test navigation on mobile browsers.
Set up Supabase type generation
Run: supabase gen types typescript --project-id YOUR_ID > src/types/supabase.ts. Gives full TypeScript autocomplete for all DB queries. Eliminates an entire class of bugs.
Set up Vitest unit tests for critical business logic
Test: auth helper functions, subscription check logic, role validation, API input validation. npm install -D vitest @testing-library/react.
Set up Playwright E2E tests for core user flows
Test flows: signup → verify email → login → enroll in course → access content → logout. Also test: admin login → view users → access denied for non-admin.
Add DB indexes for query performance
CREATE INDEX idx_subscriptions_user_id ON subscriptions(user_id). INDEX on materials(course_id), enrollments(user_id), profiles(role). Without indexes, queries slow to seconds at 10k+ rows.
Set up error monitoring (Sentry free tier)
npm install @sentry/nextjs. Configure DSN in environment variables. Get alerted on production errors before users report them. Free tier: 5,000 errors/month.
Phase 4 — Scale & Growth After Launch
0 / 5 done
Add payment integration (Razorpay for India, Stripe for global)
Razorpay is cheaper in India. Webhook → update subscriptions table server-side on successful payment. Never grant subscription access client-side.
Add caching layer for public pages (Vercel Edge Cache)
Add cache-control headers to courses listing, teachers page, events. Public content doesn't need to hit Supabase on every request. Use Next.js fetch() with revalidate.
Implement chords/tabs community feature
Public UltimateGuitar-style feature. Needs: chord_posts table, RLS (public read, authenticated write), moderation queue for admins. Anti-spam: rate limit submissions per user.
Add admin audit logging (who changed what)
Create an audit_log table. Log admin actions: user role changes, content deletes, subscription modifications. RLS: admin read-only, insert via server-side function only.
Performance audit with Lighthouse + Core Web Vitals monitoring
Run Lighthouse on all pages. Target: LCP <2.5s, CLS <0.1, FID <100ms. Enable Vercel Analytics for real user monitoring. Optimize images with next/image.
Indian market pricing — all costs in ₹ (INR) Prices use ₹1 = USD $0.012 (approx ₹84/USD). USD-billed services (Vercel, Supabase, OpenAI) are converted at this rate. Indian GST of 18% applies on most foreign digital services billed to an Indian entity — factor this into your quotes to the business owner. All "free tier" services are genuinely free with no INR charge.
Exchange rate used
₹1 = $0.012  |  $1 = ₹84
GST on foreign services
+18% on USD-billed items
Payment method note
International card needed for USD services

Domain Registration — Indian Options

Domain — .com
Annual
meadowlarkmusic.com — BigRock₹849–999/yr
meadowlarkmusic.com — Hostinger India₹699–849/yr
meadowlarkmusic.com — GoDaddy India₹849/yr (renewal ₹1,499)
meadowlarkmusic.com — Namecheap~₹1,050/yr (USD billing)
Renewal tipGoDaddy cheap first yr, renews costly
Domain — .in / .co.in
Budget Pick
meadowlarkmusic.in — BigRock₹599–699/yr
meadowlarkmusic.in — Hostinger₹499–599/yr
meadowlarkmusic.co.in₹399–499/yr
Cloudflare DNS (after buying)Free — point NS records
SSL (Cloudflare or Vercel)Free
Recommended: Buy from Hostinger India or BigRock, then use Cloudflare for free DNS Buy the domain at Hostinger India (cheapest, INR payment, UPI accepted). Change nameservers to Cloudflare after purchase. Cloudflare gives you free CDN, DDoS protection, and SSL — BigRock/Hostinger DNS has none of that. This two-step gives you the best of both: low INR price + Cloudflare protection.

Hosting & Backend Infrastructure

Vercel (Next.js Hosting)
Free Start
Hobby tier (launch phase)₹0/mo
Pro tier — $20/mo~₹1,680/mo + 18% GST = ₹1,982/mo
Bandwidth — Hobby100 GB/mo free
Custom domainFree on both tiers
Billed inUSD — needs international card
Supabase (DB + Auth + Storage)
Free Start
Free tier — everything you need to launch₹0/mo
Free: DB storage500 MB
Free: Auth MAUs50,000 users
Pro tier — $25/mo~₹2,100/mo + 18% GST = ₹2,478/mo
Billed inUSD — needs international card
Cloudflare (CDN + Security)
Mostly Free
DNS + CDN + SSL + DDoS₹0/mo — genuinely free
Basic rate limiting₹0/mo
Pro tier (rarely needed)~₹1,680/mo + GST
India edge nodes includedMumbai, Chennai, Delhi — free
Alternative: Hostinger VPS (India)
If avoiding USD billing
KVM 1 VPS (1 vCPU, 4 GB RAM)₹279/mo (promo) → ₹699/mo
LocationMumbai available
PaymentUPI / Debit card / Net banking
Trade-offYou manage server — more work
VerdictUse Vercel free tier instead

Payment Gateway Comparison (India)

Razorpay ⭐ Recommended
Per Transaction
Account setupFree (KYC required)
UPI transactions0% (up to ₹1 lakh/mo) then 1.8%
Debit / Credit card2% + GST
Net banking1.5% + ₹9 + GST
Subscriptions (recurring)Built-in — no extra fee
SettlementT+2 days to bank account
Example: ₹999 course feeYou receive ~₹979 (UPI)
Cashfree Payments
Per Transaction
Account setupFree (KYC required)
UPI transactions0.25%
Debit / Credit card1.75% + GST
Net banking1.5% + GST
SubscriptionsSupported
VerdictSlightly cheaper than Razorpay for cards
PayU India
Per Transaction
Account setupFree
All cards + UPI + Wallets2% + GST (flat)
SubscriptionsSupported
VerdictSimpler pricing, slightly higher rate
Stripe (Global — avoid for now)
Not Ideal India
Account setupFree (complex KYC)
Domestic cards (India)3% + ₹2 per txn
UPI supportNot supported natively
INR settlementLimited — currency conversion fees apply
VerdictOnly if you have global customers

Third-Party Services (INR Converted)

OpenAI API (AI Chat)
Variable — USD
GPT-4o-mini — recommended$0.15/1M tokens (≈₹13/1M)
100 users × 10 msgs/day~₹170–420/mo
500 active users~₹840–2,100/mo
Without rate limiting (!)Can reach ₹42,000+/mo instantly
+18% GST on USD billFactor into budget
Paid viaInternational credit card only
Email — Resend / Brevo
Free Start
Resend free tier3,000 emails/mo — ₹0
Resend Pro — $20/mo~₹1,680/mo + GST = ₹1,982/mo
Brevo (Indian-friendly)300 emails/day free — ₹0
Brevo Starter₹1,265/mo (INR billing available)
RecommendationResend free tier for now
Video Hosting
Free Recommended
YouTube unlisted embeds₹0 — strongly recommended
Vimeo Basic₹0 (5 GB/wk upload limit)
Vimeo Starter — $9/mo~₹756/mo + GST = ₹892/mo
Supabase Storage for videoAVOID — ₹7.5/GB bandwidth
VerdictYouTube unlisted saves ₹10,000s/mo
Monitoring & Tools
Mostly Free
Sentry (error monitoring) free₹0 — 5,000 errors/mo
Upstash Redis (rate limiting)₹0 — 10,000 reqs/day free
Vercel Analytics (on Pro)Included in Pro plan
Google Analytics 4₹0 — Indian users familiar

One-Time Setup Costs

Domain registration — .com (Hostinger India)₹699–849 (Year 1)
Domain renewal — .com (Year 2 onwards)₹849–999/yr
Domain — .in option (cheaper)₹499–599/yr all years
Razorpay KYC / account setupFree — needs GST + PAN + bank account
International card (for Vercel/Supabase USD billing)₹0 — use existing or apply for one
Cursor AI coding tool (optional)~₹1,680/mo (USD billing)
Design assets — Google Fonts already used₹0

Monthly Cost Summary — Indian Market (₹)

Phase 0–1 — Building, zero users
Domain amortised (~₹70/mo). Vercel, Supabase, Cloudflare all free. No users = no OpenAI cost.
~₹70/mo
Phase 2–3 — 100–500 active users
Still on all free tiers. OpenAI ~₹400–800/mo with rate limiting. Domain ~₹70/mo.
~₹500–900/mo
Phase 4 — 1,000–5,000 active users
Vercel Pro ₹1,982 + Supabase Pro ₹2,478 + OpenAI ₹1,680 + domain ₹70. All incl. GST.
~₹6,200/mo
At 50,000 users — mature product
Supabase Team $599 (~₹59,000) OR self-host on DigitalOcean Mumbai + Vercel Pro + OpenAI + Sentry.
~₹15,000–20,000/mo

Revenue vs Cost Reality Check

Scenario: 200 students × ₹499/mo subscription₹99,800/mo revenue
Infra cost at this stage~₹900/mo
Razorpay fee (2% avg)~₹2,000/mo
Total operating cost~₹2,900/mo (2.9% of revenue)
Net margin (infra only)~₹96,900/mo
Scenario: 1,000 students × ₹799/mo₹7,99,000/mo revenue
Infra cost at this stage~₹6,200/mo
Razorpay fee (2% avg)~₹15,980/mo
Total operating cost~₹22,180/mo (2.8% of revenue)
Net margin (infra only)~₹7,76,820/mo
The India-specific lean strategy 1. Buy domain from Hostinger India — UPI payment accepted, no international card needed for the domain. 2. Use YouTube unlisted embeds for all videos — this alone saves ₹10,000–50,000/mo in bandwidth vs self-hosting. 3. Use Razorpay (not Stripe) — lower fees, UPI support, INR settlement in T+2. 4. Use GPT-4o-mini and cap messages at 10/user/day. 5. Stay on Supabase free tier until you hit 500 paying users — the ₹2,478/mo Pro upgrade is worth it only at real revenue. 6. Use Brevo instead of Resend if you want INR billing and avoid international card dependency.
GST compliance note for the business owner Foreign digital services (Vercel, Supabase, OpenAI) attract 18% GST under the OIDAR (Online Information and Database Access or Retrieval) rules. If the business is GST-registered, this is claimable as input tax credit. If not registered yet, factor 18% on top of USD costs in your pitch. For a bootstrapped start, Razorpay and Hostinger India are fully INR-billed and GST-compliant without any foreign currency headache.
Overview: GitHub Repo → Custom Domain on Vercel Your code stays on GitHub. Vercel connects to your GitHub repo and deploys automatically on every push. You buy a domain separately and point it at Vercel. This is the standard setup for Next.js apps.

Step-by-Step: Domain Purchase

1
Choose and buy your domain
Go to Cloudflare Registrar (registrar.cloudflare.com) — at-cost pricing, no markup. Namecheap is a solid alternative. Search for "meadowlarkmusic.com" or "meadowlarkmusicacademy.com". A .com domain is ~$10/year. If .com is taken, consider .academy (more expensive ~$25/yr), .music, or just go with .in for ₹600–900/year.
# Recommended: Cloudflare Registrar https://registrar.cloudflare.com # Alternative: Namecheap https://namecheap.com # Best .com alternatives meadowlarkmusic.com / meadowlarkmusicacademy.in / meadowlark.music
2
Point DNS to Vercel
In your domain registrar's DNS settings, add these records. If using Cloudflare as registrar AND DNS proxy, set "Proxy status" to DNS only (grey cloud) for the A/CNAME records pointing to Vercel — Vercel handles its own SSL.
# Add these DNS records at your registrar: Type: A Name: @ (root domain) Value: 76.76.21.21 (Vercel's IP) TTL: Auto Type: CNAME Name: www Value: cname.vercel-dns.com TTL: Auto
3
Connect GitHub repo to Vercel
Go to vercel.com → New Project → Import Git Repository → Select your Meadowlark Music Academy repo. Vercel auto-detects Next.js. Set your environment variables (NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, OPENAI_API_KEY) in Vercel's dashboard under Settings → Environment Variables.
# Environment variables to set in Vercel dashboard: NEXT_PUBLIC_SUPABASE_URL = https://xxx.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY = eyJ... OPENAI_API_KEY = sk-... # NOT prefixed with NEXT_PUBLIC_ # NEVER put service role key in NEXT_PUBLIC_ variables
4
Add custom domain in Vercel
In Vercel project → Settings → Domains → Add "meadowlarkmusic.com" and "www.meadowlarkmusic.com". Vercel will verify the DNS records and provision an SSL certificate automatically within a few minutes.
5
Update Supabase allowed URLs
In Supabase dashboard → Authentication → URL Configuration → Add your production URL (https://meadowlarkmusic.com) to Site URL and Redirect URLs. Otherwise auth redirects will fail in production.
# Supabase Auth → URL Configuration: Site URL: https://meadowlarkmusic.com Additional Redirect URLs: https://meadowlarkmusic.com/auth/callback https://www.meadowlarkmusic.com/auth/callback http://localhost:3000/auth/callback # keep for dev
6
Set up auto-deploy from GitHub
Vercel auto-deploys on every push to main branch. Pushes to feature branches create preview URLs (https://meadowlark-xxx.vercel.app) — great for testing before merging. No additional config needed.
# Workflow after setup: git push origin main # → Vercel builds and deploys automatically # → Production: https://meadowlarkmusic.com # → Preview: https://meadowlark-abc123.vercel.app
Do not skip: Add Supabase domain allowlist After setting up your domain, go to Supabase → Authentication → URL Configuration and add your production domain. Without this, email confirmation links and OAuth redirects will fail — users won't be able to verify their email or log in via OAuth.
UI upgrade philosophy for Meadowlark Music Academy A music academy should feel premium, artistic, and trustworthy. The current site is functional but looks like a default Next.js template. The upgrades below are ordered by impact — do the high-impact ones first.

High Impact — Do These First

1. Typography — choose a distinctive font pair
The current site uses system fonts. A music academy needs character. Use a serif display font for headings and a clean sans for body. This single change transforms the feel of the site.
Current (generic)
font-family: system-ui
All headings: font-weight: 700
No personality or brand identity
Recommended
Headings: Playfair Display or Cormorant Garamond (elegant, musical)
Body: DM Sans or Plus Jakarta Sans
Code/labels: DM Mono
2. Color system — define brand tokens in tailwind.config.ts
Replace ad-hoc color usage with a proper design system. Define 3 brand colors + semantic tokens. Every component reads from these variables — changing the brand color takes one edit, not a search-and-replace across 30 files.
// tailwind.config.ts const config = { theme: { extend: { colors: { brand: { primary: '#1a1035', // deep navy — trust, prestige accent: '#e8901a', // warm gold — music, creativity surface: '#f8f6f2', // warm off-white — not cold }, }, }, }, };
3. Loading states — skeleton loaders everywhere
Currently all dashboard pages show nothing while loading — this feels broken. Add skeleton loaders that match the shape of the content being loaded. Tailwind's animate-pulse makes this easy.
Current
Blank screen → sudden content
No indication anything is loading
Feels broken to users
Fixed
Shimmer skeleton matching content shape → real content fades in
Add loading.tsx files in app directory
4. Dashboard layout — sidebar navigation
A music academy dashboard needs a persistent sidebar with navigation. The current full-page layout wastes space and makes navigation slow. A sidebar with instrument icons, course shortcuts, and user info is standard for SaaS dashboards.
Current
Top navbar only
No dashboard-specific nav
Poor content hierarchy
Recommended
Fixed left sidebar (240px) + main content area
Mobile: collapsible drawer sidebar
Breadcrumbs in header

Medium Impact

5. Course cards — premium music-academy aesthetic
Course cards should show the instrument image, teacher name, level badge, price, and an enrollment CTA. Think Masterclass or Coursera, but with a warm brand feel. Use next/image for all images with proper aspect ratios.
6. Landing page hero — animated, compelling CTA
The current hero is static text. Add a subtle background animation (audio waveform, floating music notes using CSS), a strong headline, a clear CTA ("Start your free trial"), and social proof (student count, rating). Framer Motion is already installed — use it.
7. Mobile navigation — hamburger menu
The current navbar may overflow on mobile. Add a proper mobile menu with smooth open/close animation. Test on real phone at 375px width. Touch targets must be at least 44×44px (Apple HIG requirement).
8. Empty states — designed, not blank
When a student has no courses yet, show an illustrated empty state: a music-themed illustration + encouraging text + CTA to browse courses. Every list view needs an empty state. Never show a blank white rectangle.

Design References (for inspiration)

Study these sites for UI patterns • masterclass.com — course card design, premium feel, dark hero
• tonehall.com — music-specific SaaS, see their dashboard patterns
• coursera.org — enrollment flow, progress tracking UI
• linear.app — sidebar navigation, loading states, empty states
• stripe.com/docs — clean typography system with good hierarchy
These are real attacks on your current codebase All five attacks below work right now on the current code. They are documented here so you know exactly what to fix. After Phase 0 and Phase 1 are complete, re-test each one — they should all fail.
Attack 01 — Currently works
Admin bypass with known email
  1. Open your site and go to /login
  2. Enter email: [email protected] with any password (or blank)
  3. Click login — no Supabase call is made
  4. You are instantly redirected to /dashboard/admin with full access
  5. Also works with: [email protected] and [email protected]
FIX: Delete lines 27–34 in src/app/login/page.tsx. Replace with: const { error } = await supabase.auth.signInWithPassword({ email, password })
Attack 02 — Currently works
Direct URL access to admin dashboard
  1. Open a browser in incognito (no session)
  2. Navigate directly to https://yoursite.com/dashboard/admin
  3. The admin dashboard page renders with no auth check
  4. Currently shows mock data — once real data is connected, exposes everything
FIX: Add server-side auth guard. In page.tsx: const { data: { user } } = await supabase.auth.getUser(); if (!user) redirect('/login');
Attack 03 — Depends on RLS status (VERIFY NOW)
Dump entire database via browser console
  1. Sign up with a free account on your site
  2. Open browser DevTools → Console
  3. Run: const { data } = await supabase.from('users').select('*')
  4. If RLS is off on ANY table → all rows are returned to any authenticated user
  5. Can also try: supabase.from('subscriptions').select('*') to get all payment records
FIX: In Supabase SQL editor: ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; — run for every table. Then add per-table policies.
Attack 04 — Currently works
Burn all OpenAI credits via unauthenticated API loop
  1. Open browser DevTools → Network tab — find a chat request to /api/chat
  2. No authentication header is required by the route
  3. Run in Console: for(let i=0;i<100;i++) fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({messages:[{role:'user',content:'x'.repeat(5000)}]})})
  4. 100 requests × 5,000 tokens = 500,000 tokens = ~$75 in minutes at GPT-4o pricing
FIX: Add auth check at top of route.ts + per-user daily limit via Upstash Redis (free tier). 5 lines of code prevents hundreds of dollars in abuse.
Attack 05 — If roles are added without server enforcement
Privilege escalation via client-side profile update
  1. Sign up normally as a student
  2. In browser console: await supabase.from('profiles').update({role:'admin'}).eq('id', 'your-user-id')
  3. If no UPDATE RLS policy exists → you are now admin
  4. Refresh — admin dashboard is now accessible
FIX: RLS UPDATE policy: CREATE POLICY "only_admins_change_roles" ON profiles FOR UPDATE USING (auth.uid() = id) WITH CHECK (role = OLD.role OR (SELECT role FROM profiles WHERE id = auth.uid()) = 'admin');
How to perform the next audit without exposing API keys The audit that produced this report required reading .env.local directly. The steps below describe how to run future audits safely — without ever passing real credentials to any AI tool.

Method 1 — Sanitize before sharing (Recommended)

1
Create a sanitized copy of your .env.local
Before sharing code with any AI tool or collaborator, replace all secret values with placeholder strings. Keep the variable names — the auditor needs to know which keys exist, just not their values.
# .env.local.safe — share this instead of .env.local NEXT_PUBLIC_SUPABASE_URL=https://<YOUR_PROJECT_REF>.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=<REDACTED_ANON_KEY> OPENAI_API_KEY=<REDACTED_OPENAI_KEY> SUPABASE_SERVICE_ROLE_KEY=<REDACTED_SERVICE_ROLE_KEY>
2
Use git-secrets or secretlint in CI to block accidental commits
Install git-secrets as a pre-commit hook. It scans every commit for patterns that look like API keys (sk-, eyJ, etc.) and blocks the commit if found. This prevents the leak at the source.
# Install git-secrets (macOS/Linux) brew install git-secrets cd your-project git secrets --install git secrets --register-aws # adds AWS patterns # Add OpenAI/Supabase patterns: git secrets --add 'sk-[a-zA-Z0-9]{48}' git secrets --add 'eyJ[a-zA-Z0-9_-]{50,}' # Now: git commit will fail if secrets detected
3
Use GitHub Secret Scanning (free on public & private repos)
GitHub automatically scans for known API key patterns and alerts you if one is committed. Go to GitHub repo → Settings → Security → Secret scanning → Enable. Works for OpenAI, Supabase, Stripe, AWS, and 200+ other providers.

Method 2 — Scope the audit (Share only what's needed)

4
For architecture/code audits — exclude env files entirely
Most audit tasks (reviewing auth logic, RLS policies, component structure) don't require real credentials. When providing code to an AI tool, explicitly skip env files and note what variables exist by name only.
# When providing codebase for audit, exclude secrets: find src -name "*.ts" -o -name "*.tsx" | \ xargs tar czf audit-package.tar.gz # Include: package.json, tsconfig.json, next.config.ts # Exclude: .env*, .env.local, any file with "secret" in name
5
For RLS/security audits — use a staging project
Create a separate Supabase project for staging with test data only. Run security tests against the staging project. The staging anon key can be shared safely — it only accesses fake data. Production keys never leave your environment.
# Supabase CLI: create a local dev stack npx supabase init npx supabase start # runs local Supabase on Docker # Local URL: http://localhost:54321 # Local anon key printed in terminal — safe to share # All data is local — no production exposure

Method 3 — Automated security scanning (run regularly)

6
Set up Snyk or npm audit for dependency vulnerabilities
These tools run without needing your secrets. They scan your package.json and source files for known vulnerabilities. Free for open-source and small teams.
# Built-in npm audit (free, no account needed): npm audit npm audit fix # auto-fixes non-breaking issues # Snyk (free tier, deeper analysis): npm install -g snyk snyk auth # opens browser for GitHub OAuth snyk test # scans dependencies snyk code test # scans source code
7
Add GitHub Actions CI security scan on every PR
This runs on GitHub's servers — your local secrets are never involved. It catches vulnerabilities before code reaches production.
# .github/workflows/security.yml name: Security Scan on: [push, pull_request] jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20 } - run: npm ci - run: npm audit --audit-level=high - uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
8
For the next AI-assisted audit — use this prompt template
Copy-paste this prompt structure. It gets equivalent audit depth without requiring credential access.
# Safe audit prompt template: "I am sharing my Next.js + Supabase codebase for a security audit. NOTE: All .env files are excluded. Credentials are not present. The following env variables EXIST but are not shared: - NEXT_PUBLIC_SUPABASE_URL (public, safe to know) - NEXT_PUBLIC_SUPABASE_ANON_KEY (public, safe to know) - OPENAI_API_KEY (server-only, not in NEXT_PUBLIC_) Please audit the following for: 1. Auth logic and session handling in src/lib/supabase/ 2. Any hardcoded credentials or dev bypasses 3. API route security (src/app/api/) 4. RLS policy recommendations based on the schema 5. RBAC enforcement in middleware and page guards [paste source files here]"
Key rule going forward Never share a real .env.local with any AI tool, collaborator, or audit service. If you must show env variable names, redact all values. Rotate keys immediately if exposure occurs. Use the Supabase CLI local development stack for any hands-on security testing — it creates a complete Supabase environment on your local machine with no production exposure.