Security

Studio 7 Security Center

Threat model, hardening checklist, RLS patterns, compliance map, incident playbooks — built for your exact stack.

8 critical · 32 total
Client surfaces
9
Sites + apps to defend
Critical items
8
Do these first
Total checklist
32
Hardening tasks
Tools to bolt on
5
~$30–$40/mo at scale

This week — do these five

1Flip Clerk to live keys (pk_live_ / sk_live_) in Vercel envs
2Enable RLS on every Supabase table — pattern below
3npm audit + bump Next.js to latest patched 15.x or 16.x
4Rotate every dev-era secret (Clerk, Supabase service-role, GHL webhooks)
5Drop Cloudflare Turnstile on every public form

Threat model

What specifically wants to hurt this stack — not generic advice, mapped to your Next.js + Supabase + Clerk + GHL + Vercel pipeline.

Cross-tenant data leakage via missing/weak Supabase RLS

Your #1 risk. One bad policy = every tenant sees every other tenant's leads.

Clerk dev keys in production

pk_test_ keys have looser CSRF/cookie rules and shared dev infra. Flip to pk_live_ before any real client logs in.

npm supply-chain attacks via Vercel builds

Sept 2025 chalk/debug, axios RAT injection, Nx token theft — all reached Vercel projects. Postinstall scripts LLM-scan for secrets.

Next.js middleware/RSC CVEs

CVE-2025-29927 bypasses middleware auth via one header. Dec 2025 RSC RCE was CVSS 10.0. Stay on the latest patched release.

GHL webhook URL exposure

If a webhook is hardcoded client-side or echoed in errors, attackers fire fake leads, pollute CRMs, and burn A2P trust.

Bot form spam → A2P 10DLC violation

Fake leads trigger SMS workflows, recipients complain, carrier blocks the campaign. With Ryan + Library already verified, a flood costs the brand registration.

Secret leakage in Git

.env.local committed, service-role key in a client component, GHL webhook in a public repo.

DNS hijacking on Spaceship

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 in
    Critical5 min
  • Enable Clerk bot protection + breached-password detection in dashboard
    High5 min
  • Set Clerk authorizedParties to your exact production domains; enable subdomain allowlist
    High15 min
  • Turn on MFA for: Clerk, Supabase, Vercel, GitHub, Spaceship, GHL
    Critical5 min
  • Require MFA for all portal users with the owner role
    Medium1 hr

Secrets

4 items
  • Confirm .env* files in .gitignore; grep history for leaked keys
    Critical5 min
  • Rotate every key created during dev: Clerk secret, Supabase service-role, GHL webhook URLs, any API keys
    High1 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 scanning
    High1 day

Database (Supabase)

5 items
  • Enable Row-Level Security (RLS) on EVERY table — see pattern below
    Critical1 hr
  • Verify service-role key is only used in server routes, never imported into a client component
    Critical15 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 plan
    Medium1 hr
  • Restrict Supabase dashboard access by IP if you only work from 1–2 locations
    Medium15 min

Network & CORS

4 items
  • Audit every /api/lead.js — replace Access-Control-Allow-Origin: * with exact per-client allowlist
    Critical15 min
  • Move all sites behind Cloudflare proxy; enable WAF managed rules
    High30 min
  • Lock Spaceship registrar with registrar-lock + 2FA + email alerts on DNS changes
    High15 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 GHL
    Critical30 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 spikes
    High30 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 geos
    Medium15 min
  • GitHub Dependabot alerts ON for every repo
    High5 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 release
    Critical1 hr
  • Add overrides in package.json to pin transitive deps; lock package-lock.json and commit it
    High30 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.js
    Medium30 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 CI
    Medium30 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);
Enable RLS on EVERY table — including join tables and audit logs. One un-RLS'd table is a hole through the whole tenancy model.
Service-role key bypasses RLS — only use it in server-only code paths (/api/admin/*), never in code that ships to the browser.
Add a regression test that signs in as Tenant A and tries to read Tenant B's row — assert 0 rows back.
Pre-auth lookups (slug, custom_domain) live in a tenants_public view with only non-sensitive columns.

Per-client security posture

ClientSurfacesAuthStorageA2P / SMSNotes
K Pro Roofing2 sitesnonenone0 security-adjacent items
AJ Commercial Group2 sitesnoneSupabase · verify RLSneeds review0 security-adjacent items
Glimpse Vision1 sitenonenone0 security-adjacent items
Island Optique0 sitesnonenone0 security-adjacent items
Captain's Choice1 sitenonenone0 security-adjacent items
Library Historic Hotel1 sitenonenoneneeds review0 security-adjacent items
Dwan Concrete Coatings1 sitenonenone2 security-adjacent items
Ryan Heagney LLC1 siteClerk · dev keysSupabase · verify RLSneeds review1 security-adjacent items

Compliance landscape

A2P 10DLC + FCC one-to-one consent (Jan 2026)
Yes — Ryan + Library Hotel verified

Every form needs explicit consent naming THAT specific business. Shared multi-client language is now non-compliant. TCPA fines: $500–$1,500/text.

CCPA
Likely not (revenue/record threshold)

Adopt basics anyway: privacy policy, Do Not Sell link, honor Global Privacy Control headers — clients with CA customers may be on the hook.

GDPR
Risk-adjacent (one EU resident submitting triggers it)

US-local clients are low risk; geo-block EU IPs at the form layer for clean hands.

Illinois BIPA
Only if biometric data collected

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.

FTC marketing rules
Yes for all email/SMS

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

  1. 1Rotate the key immediately in the source dashboard — don't wait to delete the commit. Old key is already scraped.
  2. 2Update value in Vercel env vars; redeploy all affected projects.
  3. 3Use BFG or git filter-repo to scrub history, force-push.
  4. 4Check Supabase audit logs / Clerk activity / GHL message logs for unauthorized use in the window.
  5. 5If service-role key leaked, assume DB compromise — run the breach playbook.

Suspected database breach

  1. 1Rotate Supabase service-role and anon keys; revoke all sessions in Clerk.
  2. 2Snapshot the database for forensics BEFORE changing anything else.
  3. 3Pull access logs from Supabase + Vercel for the window; identify scope.
  4. 4Notify affected clients within 72 hours (GDPR clock). If 500+ CA residents, notify CA AG.
  5. 5Post-mortem: RLS gap, leaked key, or CVE? Patch root cause before restoring service.

Defaced client site

  1. 1Roll back Vercel deployment to last known-good (Deployments → Promote).
  2. 2Rotate GitHub repo's deploy keys + your PAT.
  3. 3git log since last clean deploy — find bad commit, identify if it's compromised account or a dep.
  4. 4If supply chain: check npm advisory feed, pin/replace the package.
  5. 5Notify client; if customer data was on the page, trigger DB breach playbook.

SMS compliance complaint / carrier block

  1. 1Pause the affected GHL workflow immediately.
  2. 2Pull consent record for the complaining number — when, what form, what disclosure.
  3. 3Honor opt-out; scrub the number from ALL client lists, not just the complaining one.
  4. 4If carrier blocked: re-register with corrected use case + sample messages via TCR in GHL.
  5. 5Audit other clients' consent flows for the same gap before it cascades.

Top tools to bolt on

ToolSolvesCost
Cloudflare TurnstileBot/spam on every form, no CAPTCHA UX taxFree up to 1M verifications/mo
Upstash RatelimitPer-IP rate limits on /api/lead + auth routesFree 500K commands/mo; $10/mo after
GitGuardianReal-time secret scanning across GitHub org + historical commitsFree for solo (<25 contributors)
SentryError tracking — surfaces auth failures, RLS denials, broken webhooks before clients noticeFree 5K errors/mo; $26/mo Team
Cloudflare proxy + WAFDDoS, bot fight mode, auto-mitigates new Next.js/React CVEs via managed rulesFree plan covers everything you need

Sources & advisories