Studio 7 Security Center
Threat model, hardening checklist, RLS patterns, compliance map, incident playbooks — built for your exact stack.
This week — do these five
Threat model
What specifically wants to hurt this stack — not generic advice, mapped to your Next.js + Supabase + Clerk + GHL + Vercel pipeline.
Your #1 risk. One bad policy = every tenant sees every other tenant's leads.
pk_test_ keys have looser CSRF/cookie rules and shared dev infra. Flip to pk_live_ before any real client logs in.
Sept 2025 chalk/debug, axios RAT injection, Nx token theft — all reached Vercel projects. Postinstall scripts LLM-scan for secrets.
CVE-2025-29927 bypasses middleware auth via one header. Dec 2025 RSC RCE was CVSS 10.0. Stay on the latest patched release.
If a webhook is hardcoded client-side or echoed in errors, attackers fire fake leads, pollute CRMs, and burn A2P trust.
Fake leads trigger SMS workflows, recipients complain, carrier blocks the campaign. With Ryan + Library already verified, a flood costs the brand registration.
.env.local committed, service-role key in a client component, GHL webhook in a public repo.
Single-factor login on the DNS registrar = total takeover of every client domain.
Hardening checklist
32 concrete actions. Effort estimate per item.
Identity & Auth
5 items- Flip Clerk from dev (pk_test_) to live (pk_live_) keys before any real client signs inCritical5 min
- Enable Clerk bot protection + breached-password detection in dashboardHigh5 min
- Set Clerk authorizedParties to your exact production domains; enable subdomain allowlistHigh15 min
- Turn on MFA for: Clerk, Supabase, Vercel, GitHub, Spaceship, GHLCritical5 min
- Require MFA for all portal users with the owner roleMedium1 hr
Secrets
4 items- Confirm .env* files in .gitignore; grep history for leaked keysCritical5 min
- Rotate every key created during dev: Clerk secret, Supabase service-role, GHL webhook URLs, any API keysHigh1 hr
- Move all secrets to Vercel env vars scoped to Production. Never NEXT_PUBLIC_ prefix anything sensitive.High1 hr
- Install GitGuardian on dillonai369 GitHub org (free for solo) for historical + ongoing scanningHigh1 day
Database (Supabase)
5 items- Enable Row-Level Security (RLS) on EVERY table — see pattern belowCritical1 hr
- Verify service-role key is only used in server routes, never imported into a client componentCritical15 min
- Add indexes on every tenant_id / org_id column referenced by RLS (perf killer otherwise)High30 min
- Enable Point-in-Time Recovery on the paid Supabase planMedium1 hr
- Restrict Supabase dashboard access by IP if you only work from 1–2 locationsMedium15 min
Network & CORS
4 items- Audit every /api/lead.js — replace Access-Control-Allow-Origin: * with exact per-client allowlistCritical15 min
- Move all sites behind Cloudflare proxy; enable WAF managed rulesHigh30 min
- Lock Spaceship registrar with registrar-lock + 2FA + email alerts on DNS changesHigh15 min
- Inventory all *.momentumarketing.io CNAMEs; delete any that don't point to a live Vercel project (subdomain takeover risk)Medium30 min
Forms
4 items- Cloudflare Turnstile on every public form; verify token server-side in /api/lead.js before relaying to GHLCritical30 min
- Honeypot field + minimum-time-on-page check (kills 80% of bots Turnstile misses)High30 min
- Per-IP rate limit on /api/lead.js (Upstash Ratelimit, 5/min/IP)High30 min
- Reject non-US country codes on phone for US-only clients (A2P-scoped traffic)Medium15 min
Monitoring
4 items- Sentry on the Next.js portal; alert on error spikesHigh30 min
- Vercel Log Drain → email/Slack alert on /api/lead.js 5xx rate > 1%Medium15 min
- Monthly Supabase auth log review — look for unusual sign-in geosMedium15 min
- GitHub Dependabot alerts ON for every repoHigh5 min
Code & Dependencies
4 items- Pin Next.js to latest patched 15.x (or 16.x); CVE-2025-29927 + Dec 2025 RSC RCE + May 2026 releaseCritical1 hr
- Add overrides in package.json to pin transitive deps; lock package-lock.json and commit itHigh30 min
- In .npmrc: ignore-scripts=true for production CI (kills postinstall exfil class — Nx, axios, chalk attacks)High15 min
- Add Content-Security-Policy, X-Frame-Options: DENY, Strict-Transport-Security headers via next.config.jsMedium30 min
Infra
2 items- Vercel: enable Deployment Protection on preview deployments (no public preview URLs leaking unreleased copy)High15 min
- Separate GitHub org owner from daily dev account, or use a scoped PAT for CIMedium30 min
Supabase RLS — the policies you MUST have
RLS is the only thing stopping tenant A from SELECTing tenant B's leads. The SQL editor bypasses RLS — always test policies from the client SDK.
-- 1. Force RLS on (deny by default)
alter table leads enable row level security;
alter table leads force row level security;
-- 2. Helper to read tenant_id from Clerk JWT
create or replace function auth.tenant_id() returns uuid
language sql stable as $$
select nullif(current_setting('request.jwt.claims', true)::json->>'tenant_id','')::uuid;
$$;
-- 3. SELECT — only your tenant's rows
create policy "tenant_read_own_leads"
on leads for select
using (tenant_id = auth.tenant_id());
-- 4. INSERT — cannot write for another tenant
create policy "tenant_insert_own_leads"
on leads for insert
with check (tenant_id = auth.tenant_id());
-- 5. UPDATE/DELETE — constrain old and new row
create policy "tenant_modify_own_leads"
on leads for update
using (tenant_id = auth.tenant_id())
with check (tenant_id = auth.tenant_id());
-- 6. Index — RLS without this crawls at scale
create index on leads (tenant_id);Per-client security posture
| Client | Surfaces | Auth | Storage | A2P / SMS | Notes |
|---|---|---|---|---|---|
| K Pro Roofing | 2 sites | none | none | — | 0 security-adjacent items |
| AJ Commercial Group | 2 sites | none | Supabase · verify RLS | needs review | 0 security-adjacent items |
| Glimpse Vision | 1 site | none | none | — | 0 security-adjacent items |
| Island Optique | 0 sites | none | none | — | 0 security-adjacent items |
| Captain's Choice | 1 site | none | none | — | 0 security-adjacent items |
| Library Historic Hotel | 1 site | none | none | needs review | 0 security-adjacent items |
| Dwan Concrete Coatings | 1 site | none | none | — | 2 security-adjacent items |
| Ryan Heagney LLC | 1 site | Clerk · dev keys | Supabase · verify RLS | needs review | 1 security-adjacent items |
Compliance landscape
Every form needs explicit consent naming THAT specific business. Shared multi-client language is now non-compliant. TCPA fines: $500–$1,500/text.
Adopt basics anyway: privacy policy, Do Not Sell link, honor Global Privacy Control headers — clients with CA customers may be on the hook.
US-local clients are low risk; geo-block EU IPs at the form layer for clean hands.
AJCG (Chicagoland) — standard contact form is fine. If you add face/voice recognition, written consent + retention policy required BEFORE collection. $1k–$5k per violation, retroactive.
Truthful claims, clear disclosures, honor opt-outs within 10 days. Virginia SB 1339: store STOP requests forever (10 years minimum).
Incident response playbooks
Leaked secret in Git
- 1Rotate the key immediately in the source dashboard — don't wait to delete the commit. Old key is already scraped.
- 2Update value in Vercel env vars; redeploy all affected projects.
- 3Use BFG or git filter-repo to scrub history, force-push.
- 4Check Supabase audit logs / Clerk activity / GHL message logs for unauthorized use in the window.
- 5If service-role key leaked, assume DB compromise — run the breach playbook.
Suspected database breach
- 1Rotate Supabase service-role and anon keys; revoke all sessions in Clerk.
- 2Snapshot the database for forensics BEFORE changing anything else.
- 3Pull access logs from Supabase + Vercel for the window; identify scope.
- 4Notify affected clients within 72 hours (GDPR clock). If 500+ CA residents, notify CA AG.
- 5Post-mortem: RLS gap, leaked key, or CVE? Patch root cause before restoring service.
Defaced client site
- 1Roll back Vercel deployment to last known-good (Deployments → Promote).
- 2Rotate GitHub repo's deploy keys + your PAT.
- 3git log since last clean deploy — find bad commit, identify if it's compromised account or a dep.
- 4If supply chain: check npm advisory feed, pin/replace the package.
- 5Notify client; if customer data was on the page, trigger DB breach playbook.
SMS compliance complaint / carrier block
- 1Pause the affected GHL workflow immediately.
- 2Pull consent record for the complaining number — when, what form, what disclosure.
- 3Honor opt-out; scrub the number from ALL client lists, not just the complaining one.
- 4If carrier blocked: re-register with corrected use case + sample messages via TCR in GHL.
- 5Audit other clients' consent flows for the same gap before it cascades.
Top tools to bolt on
| Tool | Solves | Cost |
|---|---|---|
| Cloudflare Turnstile | Bot/spam on every form, no CAPTCHA UX tax | Free up to 1M verifications/mo |
| Upstash Ratelimit | Per-IP rate limits on /api/lead + auth routes | Free 500K commands/mo; $10/mo after |
| GitGuardian | Real-time secret scanning across GitHub org + historical commits | Free for solo (<25 contributors) |
| Sentry | Error tracking — surfaces auth failures, RLS denials, broken webhooks before clients notice | Free 5K errors/mo; $26/mo Team |
| Cloudflare proxy + WAF | DDoS, bot fight mode, auto-mitigates new Next.js/React CVEs via managed rules | Free plan covers everything you need |
Sources & advisories
- · Supabase RLS Best Practices
- · Clerk Production Deployment
- · Vercel npm Attack Response (Sept 2025)
- · Axios Package Compromise
- · Next.js Dec 2025 Security Advisory
- · Next.js May 2026 Security Release
- · CVE-2025-29927 Next.js Middleware Bypass
- · Cloudflare React/Next.js CVE Mitigation
- · A2P 10DLC 2026 Compliance Guide
- · GHL A2P 10DLC Best Practices
- · CCPA 2026 Compliance Guide
- · Illinois BIPA Apr 2026 Ruling