The Instagram "10 ways your vibecoded app is getting sued" checklist, run for real against our repo, our live site, and our AWS account. Six parallel investigation agents, evidence at file-and-line level. 2026-08-13.
| # | Claim from the reel | Verdict | One-line reality |
|---|---|---|---|
| 1 | No privacy policy | ✅ Clean | Live at /privacy, footer-linked, sitemap'd, updated 2026-05-07 |
| 2 | No "we collect data" disclosure | ✅ Clean | §2 enumerates contact, SMS/call, usage, IP — CCPA-specific wording could be tighter |
| 3 | No AI mention in privacy policy | ✅ Clean | Clara, Anthropic + ElevenLabs named; no ADMT section yet (regs phase in through 2027) |
| 4 | No third-party processors listed | ⚠️ Partial | 3 of ~9 named; AppFolio, AWS, Stripe missing; promised subprocessor list never published |
| 5 | Not deleting user uploads | ❌ Gap | We publicly promise "tenancy + 12 months" retention and deletion-on-request — neither is enforced by anything |
| 6 | Storage bucket = public | ✅ Clean | All 8 buckets + account-level block verified; anonymous probe → 403; everything presigned |
| 7 | Fake testimonials | ⚠️ Partial | Site deliberately has zero testimonials (exemplary) — but blog posts claim production data we can't back |
| 8 | Cancelling harder than signup | ❌ Gap | Self-serve cancel path EXISTS but a wrong auth gate 403s real customers — the button silently does nothing |
| 9 | Auto-renew without reminder | ⚠️ Partial | Monthly B2B = low legal risk, but no reminder exists and the Terms have zero billing language |
| 10 | AI with no self-harm response | ⚠️ Partial | Likely outside SB 243 (customer-service exclusion) — but Clara has zero crisis handling today |
The scary Instagram number was fake, but the exercise was worth it: we found one actual production bug (paying customers cannot cancel online — the button fails silently), one promise our privacy policy makes that our code doesn't keep (deleting old data), and one blog post claiming results from customers we don't have. Everything else is in better shape than most.
What's right: propflowai.co/privacy is live, conspicuous, current (eff. 2025-03-01, upd. 2026-05-07), and matches the repo exactly. §2 discloses collection (contact info, SMS + call content and metadata, usage, IP, visit logs). §1 and §6 disclose Clara, Anthropic, ElevenLabs and Twilio by name. §6 states we don't sell personal information. §7 states retention windows. Terms §5 discloses AI-generated content limits.
Gaps:
/security promises a subprocessor list on the Trust Center, and /trust-center doesn't have one — a promise pointing at a page that doesn't fulfill it.The problem isn't that we lack a mechanism. It's that we publish promises no code enforces:
/data-protection says "Delete requests are actioned on request." There is no subject-erasure path for a tenant/prospect — no deletePerson, IdentityClaims are append-only, no purge script (the only one built, purge-traffic-subject.ts, covers web-visit logs only).deletePhoto() exists in the S3 client with zero callers. The code comments promise a 30-day inbox-photo cleanup cron that was never built. Bucket lifecycle rules cover executed-leases and eval runs — nothing covers photos/./data-protection page references a DPA with "exact windows by data type" — no DPA or retention schedule exists in docs/.What IS enforced: visit logs really do delete after 90 days (TTL + purge script) — the one published claim with a mechanism behind it.
Our privacy page tells tenants "we delete your stuff after you move out, and sooner if you ask." Right now neither is true — nothing in the system ever deletes a photo, a conversation, or a call recording. A written promise you don't keep is worse than no promise: it's the first exhibit in a complaint.
All 8 buckets in account 194925971150 have the full four-flag Public Access Block, AND the account-level block is on — so even a future misconfigured bucket policy can't open public access. Anonymous probe of propflow-photos → HTTP 403 (verified live, not just config). Every photo serve path is auth-gated + presigned (1h expiry); a test explicitly pins that no signed URL leaks to the client. Zero public object URLs in product code. Nothing to fix.
The site's posture is exemplary — testimonials and case studies were consciously withheld until we have real, consented, attributed customers, and that policy is written into the code and the live case-studies page ("We'd rather ship one real case study than four anonymized ones").
The exposure is confined to blog editorial (src/app/(public)/blog/posts.ts, live at /blog/*):
Mockup personas (Sarah Martinez / Mike Torres) are fine — product-UI demos, never quoted as customers — though a small "simulated conversation" caption would harden them. NOI ranges on the marketing pages are properly labeled estimates with disclosed assumptions.
Designed right, broken in prod. Signup: self-serve Stripe Checkout from /settings ($10/unit/mo, recurring amount disclosed at consent). Cancel: a "Manage Subscription" button on the same page opens the Stripe Billing Portal — on paper, exactly as easy as signup.
But POST /api/billing/portal is gated by requireAdmin, which in production only accepts PropFlow staff. A paying customer clicking "Manage Subscription" gets a 403 — and the client only acts on data.url, so the button silently does nothing. Effective cancel path today: email us. That's the exact ROSCA "harder to cancel than sign up" shape. Undetected because our live relationships are hand-managed.
Secondary: the route trusts a client-supplied customerId — must be resolved server-side when the gate is fixed, or any user could open another customer's portal.
Fix: swap requireAdmin → requireUser, resolve the Stripe customer from the caller's email server-side, surface non-OK responses in the client. Also verify the Stripe Dashboard portal config has cancellation enabled.
Monthly B2B billing = the low-risk quadrant (CA ARL is consumer-scoped and bites on annual terms; the FTC click-to-cancel rule was vacated July 2025, ROSCA baseline still applies). Settings shows the renewal date; checkout discloses the recurring amount. Gaps: no pre-renewal reminder anywhere (no invoice.upcoming handler), and the Terms page has zero mention of subscription, billing, fees, auto-renewal, or cancellation — the recurring charge has no contractual disclosure outside the Stripe Checkout screen. If an annual plan ever ships, a 15–45-day reminder becomes mandatory before launch.
Legal scope: likely OUT — SB 243 targets companion chatbots "meeting a user's social needs" (Replika/Character.AI class) and carves out customer-service bots, which is squarely what Clara is. But the private right of action ($1,000/violation) means plaintiffs, not regulators, test that exclusion — and "anthropomorphic named persona + months-long tenant threads + collections conversations" is the fact pattern a complaint would lead with. Worth a one-time counsel confirmation in writing.
Factual finding: Clara has zero crisis/self-harm handling. Repo-wide grep for self-harm / suicide / crisis / 988 / hotline: no hits. What exists is physical life-safety only (gas, fire, "anyone hurt or trapped" → 911 + escalate), which is genuinely good — but a tenant texting "I can't take this anymore" mid-collections gets whatever the base model improvises, unmeasured, on a channel where the escalation email may not be read until morning. No eval case simulates it.
Recommendation (independent of legal scope): a crisis playbook is ~a day of work — prompt block (recognize ideation → share 988 → escalate_to_human high urgency → stop the business flow) + adversarial eval cases (SMS + voice, including a mid-collections shape).
P0 — this week
requireAdmin → requireUser + server-side customer resolution + surface errors client-side. ~5-line diff, closes the one live ROSCA-shaped gap.posts.ts (tie to real Camellia data with consent, or mark illustrative).P1 — this month
deletePhoto, EL recording retention pinned) or amend the policy text to match reality. A published-but-unenforced promise is the worst of both.purge-person-subject.ts mirroring the existing traffic-purge shape (dry-run default, deletion receipt): personId → claims, role rows, conversations, S3 photos, eval-corpus copies.P2 — opportunistic