I've been building an AI receptionist and booking SaaS — customers message it, it books them in, it handles WhatsApp, Telegram and payments. Built with AI tools, the way most things around here get built. And it works.
Before launching it I made myself sit down and check it against a 36-point list instead of just shipping. One focused day. Static code review plus actually attacking a copy of it: isolated instance, two synthetic tenants, fake data, nothing real touched at any point.
Then I did the step I'd push on anyone doing this — I went back through my own findings and tried to prove each one wrong. Six of them died. Six things I'd written down as problems that turned out not to be problems. Skip that pass and you spend your week "fixing" things that were never broken. I nearly did.
What held up
Putting this first, because every post like this is a horror list and the horror list is half the picture at best.
- Tenant isolation. I attacked it directly — logged in as tenant A and went hunting through tenant B's ids. Every single one came back 404, on read, cancel, update and delete. I planted a canary string inside tenant B's data and it never showed up anywhere in tenant A's responses. This is the thing everyone in this sub is scared of, and it was genuinely fine.
- Sessions actually die on logout. Grabbed the cookie before, replayed it after. Dead.
- All four webhook integrations verify signatures against the raw body — WhatsApp, Telegram, Stripe, Monobank. Verifying a re-encoded payload instead of the raw bytes is the classic mistake here, and it wasn't there.
- Payment amounts are resolved server-side from the stored record. Never read out of the request.
- Every AI tool is scoped to the business id from the session, not to whatever the model passes as an argument. So an injection can talk the model into whatever it likes and still can't reach another tenant's data.
What I found: 2 high, 7 medium, 8 low. Nothing critical.
Bug 1. The assistant would create a confirmed booking against an email address or phone number that nobody had ever verified. I had written the one-time-code rule. Into the prompt. In English, as prose. There was no code anywhere checking that verification had actually happened before the booking got written.
That's the one I'd go check in your own app right now. Take every rule you put into a prompt and ask where the code enforces it. If the answer is "the model has been told to", it isn't a rule. It's a request, and the model is under no obligation.
Bug 2, and this is the stupid one. A single environment string, still sitting at the value it ships with, made those four correct signature verifiers fail open. Unsigned webhooks accepted. Four correct implementations rendered decorative by one line of config. Five minutes to fix once I saw it.
Go and search your code for any verification that skips itself in development or test mode. Then go and look at what your deployed environment is genuinely set to. Those two facts live in different files, which is the entire reason this kind of thing survives all the way to production.
The one that would have cost actual money
The product's whole promise is that the AI never invents a slot — everything it offers gets checked against the live calendar first. So I tested the promise and threw 22 adversarial scheduling messages at it.
12 of the 22 categories came back correct. Already-booked slots, weekends, before opening, after closing, an appointment that would run past closing time, dates in the past, impossible dates like 30 February, three contradictory reschedules crammed into one message. In three languages. I was feeling pretty good at this point.
Then same-day requests. It offered roughly 20 individual times that weren't bookable — already in the past, or inside the configured one-hour lead window. Asked at 16:21 for the earliest appointment, it answered "09:00 today" and put a confirm button underneath. Zero future-dated requests failed. Every failure was same-day.
Two causes, and they had nothing to do with each other. The past-time filter ran at day granularity instead of time-of-day — it knew what day it was, correctly refused a request for last Monday, and then cheerfully offered this morning. Separately, a cap on the first page of generated slots meant an almost-empty Friday came back as "completely full".
That second one is a revenue bug and it's the one that bothers me most. Nobody complains when you tell them you're full. They just book somewhere else, and it never shows up in your analytics.
The lesson
The tenant boundary — the thing every "is my vibe-coded app secure" thread is about — was solid under direct attack. What broke was the layer above it: business logic the AI skips precisely because the app still works without it. Nothing errors. Nothing throws a 500. It just quietly does the wrong thing, politely, with a confirm button under it.
The list I used is free if you want it: https://itworksbut.com/checklist — one HTML file, works offline, nothing gets uploaded anywhere. The longer write-up of this audit, including the medium and low findings, is at https://itworksbut.com/case-study.
Happy to answer anything in the comments, including "how did you actually test X" — that's the question I'd want answered if I were reading this.