We have checked bots and Mini Apps on audits and on our own pilots. The order below follows how often we find each one. The most common item here is also the dullest.
1. The initData signature is not verified
A Mini App receives data about the user in an initData string signed with the
bot token. Verifying it takes fifteen lines of code. It is skipped in roughly
half the cases—because in development everything works without it.
The consequence: anyone with curl can place an order, spend loyalty points or
read the history under an arbitrary user_id. No hacking involved, just an HTTP
request.
Verification belongs on the server, on every request. Doing it once when the
application opens is not enough. And look at auth_date separately: a signed
string from a week ago is a valid signature and an invalid session.
2. Direct access to other people’s objects
The OWASP classic at number one, and in bots it appears in almost unchanged
form. An endpoint like /api/orders/1042 returns the order by id, and there is
no check that “this order belongs to this user”.
The identifiers are sequential, so dumping the entire order database is a ten-line loop. We have pulled delivery addresses, phone numbers and amounts that way.
The cure is not obfuscating identifiers (UUIDs barely help here) but an
ownership check in the data access layer: the query always carries
where user_id = ..., and it is not forgotten because otherwise it does not
compile.
3. The bot token and acquiring keys in the repository
.env in git, keys in docker-compose, the token in the source “just while
debugging”. A private repository is not protection: contractors change, forks
remain, and history is not deleted along with the file.
The bot token lets anyone read every message and write as the bot to everyone who has ever talked to it. An acquiring key lets them initiate refunds.
What to do: secrets only in environment variables, an .env.example with no
values in the repository, a secret scanner in CI. If a token has ever been in
the history, revoke it. Deleting the file achieves nothing.
4. No rate limiting
The bot takes enquiries; each enquiry sends a Telegram notification and writes a row to the database. There are no limits. One script creates ten thousand enquiries in a minute, the manager receives ten thousand notifications, the database grows and real enquiries are lost in the noise.
No data leaks here, but the business process stops, and clearing up takes longer than the attack did.
The minimum: a limit per user id and per IP, a honeypot field in the forms, a check on the minimum time taken to fill a form in. A captcha is a last resort; it costs conversion.
5. A payment webhook with no signature check
The provider sends a notification that a payment succeeded. The handler trusts the request body and moves the order into the paid state. The webhook address can be read from the frontend or guessed—it is rarely secret.
After that anyone at all sends a POST and gets the goods for free.
What has to be verified is the signature of the notification, and on top of that you request the payment status from the provider’s API before shipping anything. The notification is a “go and check” signal.
What is not on this list
No exotica: chains of three vulnerabilities, race conditions, supply chain attacks. They exist, we find them occasionally, but they are not what happens to eight bots out of ten.
Eight out of ten break on the things listed above, and all five are closed in about a day of work. At the start that is a day. After an incident it is a week and a conversation with your customers.
How to check your own bot in an hour
- Send a request to the bot’s API without
initData—does it answer. - Take your own order id, subtract one, request it—see what comes back.
- Search the repository history for the strings
bot,token,secret,key. - Send a hundred requests in a row—does anything push back.
- Send a payment webhook by hand with a valid body and a wrong signature.
If even one of them goes through, the vulnerability is already there. You are unlikely to be the first person to find it.