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.
uyloerfniphfxjvnuwmn (org foeliowlfikzqfwyxcst, "digitalbeachgirl's Projects" — her paid org). App code at PROJECTS/Virtual Restyle/app/.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.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).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.)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.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).grant_credits keyed on session id, ON CONFLICT DO NOTHING)._shared/billing.ts; client sends only pack10/pack40/pro) — no amount tampering.credits/credit_grants read-own-only no client write; usage has No-direct-insert/update/delete with WITH CHECK (false) + tester-mode trigger guard.grant_credits + refund_generation are service_role-only (lockdown migration applied — verified live).stagings bucket private; CORS pinned to domain (_shared/cors.ts); admin server-gated to srverva@gmail.com.REVOKE EXECUTE ... FROM PUBLIC, anon, authenticated; GRANT ... TO service_role on 4 functions: update_user_plan, read_email_batch, enqueue_email, delete_email. Safe — every real caller (stripe-webhook, check-subscription, auth-email-hook, process-email-queue) uses the service-role key. Re-verify with has_function_privilege after.stage-room server-side.PROJECTS/Virtual Restyle/app/supabase/functions/auth-email-hook/index.ts — CRITICAL: what gets enqueued into auth_emails (magic-link URLs).PROJECTS/Virtual Restyle/app/supabase/migrations/20260704120000_credits_and_compliance.sql + 20260704150000_lock_down_rpcs_and_storage.sql — the credit RPCs + existing revokes (the pattern to copy).PROJECTS/Virtual Restyle/app/supabase/functions/stripe-webhook/index.ts — where the refund handler would go.PROJECTS/Virtual Restyle/app/src/components/DownloadWithPresets.tsx — client-side watermark burn.reference/fable-mode-money-path-audit.md — the audit standard; log new patterns here when fixes ship.mcp__supabase__execute_sql on uyloerfniphfxjvnuwmn: select proname, has_function_privilege('authenticated', p.oid,'EXECUTE') from pg_proc p join pg_namespace n on n.oid=p.pronamespace where n.nspname='public' and proname in ('update_user_plan','read_email_batch','enqueue_email','delete_email'); → all should still be true (the holes) until fixed; after fix, all false.select queue_name from pgmq.list_queues(); → confirms auth_emails / transactional_emails exist.reference/fable-mode-money-path-audit.md Findings Log yet (that doc logs SHIPPED fixes; the new class — "over-granted SECURITY DEFINER infra RPCs leaking auth tokens" — should be added when the revoke ships).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 🤍