Security model
LinkBrain is single-user. Two independent gates in the Worker’s fetch() entry
ensure only you can store or read links.
Gate 1 — webhook secret (blocks the open internet)
Section titled “Gate 1 — webhook secret (blocks the open internet)”Every POST must carry an X-Telegram-Bot-Api-Secret-Token header equal to
your WEBHOOK_SECRET (a 128-bit random value). A request with a wrong or
missing secret is rejected with 403 before anything else runs. Only Telegram
sends that header, because you registered it via setWebhook. Forging a request
means guessing a 16-byte random secret — infeasible.
Gate 2 — chat-id allowlist (blocks other Telegram users)
Section titled “Gate 2 — chat-id allowlist (blocks other Telegram users)”Even a request that has the correct secret is ignored unless the message’s
chat.id matches your ALLOWED_CHAT_ID. Anything else is silently dropped — no
reply, nothing stored. So only your chat can actually use the bot.
What this does and doesn’t stop
Section titled “What this does and doesn’t stop”- ✅ A stranger cannot POST directly to the Worker (Gate 1).
- ✅ Another Telegram user cannot save or read links, and gets no response at all (Gate 2) — the bot appears dead to them.
- ⚠️ The bot is still messageable. Anyone who knows its
@usernamecan send it a message, and Telegram forwards it to the Worker; Gate 2 just makes it a no-op. You can’t make a Telegram bot un-messageable — the defense is to ignore outsiders, which is exactly what happens. Keeping the username private is the practical extra step.
Keeping it secure
Section titled “Keeping it secure”WEBHOOK_SECRETlives only as a Cloudflare secret and was passed once tosetWebhook— it is not in the repository. Don’t share it.- If it ever leaks, rotate:
wrangler secret put WEBHOOK_SECRETwith a new value, then callsetWebhookagain with the matchingsecret_token. - The Worker also serves these public docs on
GETrequests at every path except/webhook. That’s intentional and contains no secrets. The webhook itself —POST /webhook— stays behind both gates;run_worker_firstroutes that path to the Worker so a static asset can never shadow it.