← All projects
Session Handoff · Virtual Restyle

Session Handoff — Virtual Restyle Fable-Mode money-path audit (findings only, no fixes shipped)

Saved 2026-07-10

Paste it into a fresh Claude Code session and say “resume from this handoff.” It's briefing only — Claude will wait for your next instruction.

Session Handoff — Virtual Restyle Fable-Mode money-path audit (findings only, no fixes shipped)

Where it started

After /prime, Sondra asked for a full Fable-mode money-path audit of Virtual Restyle (live app, virtualrestyle.com, Stripe LIVE), same standard as today's BookifAI / Pain-2-Profit / CEO Dashboard audits. Framing: read-only audit — find the holes, report ranked, STOP for her go before touching the live money app. All verdicts verified against the LIVE Supabase DB (uyloerfniphfxjvnuwmn), not just the repo.

Decisions locked + what shipped

Findings (ranked, most severe first — all verified live)

  1. CRITICAL — account takeover via email queue. Three SECURITY DEFINER pgmq-wrapper functions are executable by authenticated (should be service_role only): read_email_batch, enqueue_email, delete_email. The auth_emails queue holds fully-rendered auth emails whose HTML contains the real magic-link / password-reset / signup confirmationUrl with token (see supabase/functions/auth-email-hook/index.ts:246-261). Exploit: any signed-in user runs supabase.rpc('read_email_batch',{queue_name:'auth_emails',batch_size:100,vt:0}) → reads other users' login/reset links → takes over accounts. enqueue_email = send phishing from her verified domain on her Resend bill; delete_email = suppress others' auth emails. Verified: queues auth_emails/transactional_emails exist; all 3 fns has_function_privilege('authenticated', ..., 'EXECUTE') = true.
  2. HIGH — free Pro plan. update_user_plan(p_user_id, p_plan, ...) is SECURITY DEFINER, executable by authenticated, and has NO auth.uid() guard (live body: bare UPDATE public.usage SET plan=p_plan WHERE user_id=p_user_id). Exploit: supabase.rpc('update_user_plan',{p_user_id:MY_UID,p_plan:'pro'})spend_generation then grants 50 clean stagings/mo (source:'pro', watermark:false), renewing. Sibling toggle_tester_mode IS correctly guarded (good model).
  3. MEDIUM — no refund/chargeback clawback. stripe-webhook/index.ts:47 only handles checkout.session.completed; no charge.refunded / charge.dispute.created handler → buyer refunds a credit pack and keeps the credits. (Same hole BookifAI + P2P closed today with a clawback RPC.)
  4. MEDIUM — client-side watermark (client-side paywall class). stage-room returns the full CLEAN image + a boolean isWatermarked; watermark burned in-browser at src/components/DownloadWithPresets.tsx:170 (burnComplianceWatermark). Free user reads clean image off the network response / flips the flag → clean unmarked stagings (bounded ~3/mo per free signup) + defeats the MLS "virtually staged" legal marker.
  5. LOW — no per-account daily rate cap on stage-room/edit-room (auth-gated + server-side credit spend are fine, but no daily cap; structure-check refund path at stage-room/index.ts:298 is a theoretical refund-abuse loop — harder to force than P2P's).

Verified SAFE (checks confirmed working, live)

The fix (proposed, NOT applied)

Key files for next session

Running state

Verification — how to confirm findings still hold

Deferred + open questions

Pick up here

Get Sondra's go, then apply the 4-function REVOKE ... FROM authenticated; GRANT ... TO service_role migration to uyloerfniphfxjvnuwmn (closes the CRITICAL email-queue account-takeover + HIGH free-Pro bypass in one shot), re-verify has_function_privilege = false live, then log it in reference/fable-mode-money-path-audit.md.

Generated for the CEO Dashboard · source: PROJECTS/Virtual Restyle/handoffs/money-path-audit-2026-07-10.md 🤍