How we protect your data, your bot tokens, and your subscribers. Last updated: May 17, 2026.
Infrastructure
- Front-end: Vercel edge network, EU primary region.
- Back-end: Railway-managed Python application, EU region (Frankfurt / Amsterdam).
- Database: Railway-managed PostgreSQL with daily automated snapshots, 30-day retention.
- Transport: TLS 1.2+ everywhere, HSTS preload, HTTP/2.
- Headers: strict CSP, X-Frame-Options DENY, X-Content-Type-Options nosniff, Referrer-Policy strict-origin-when-cross-origin.
Authentication
- Email verification required before first login. Verification codes expire after 15 minutes and are single-use.
- Passwords: bcrypt with per-row salt, cost factor 12.
- Session: signed JWT in HttpOnly, Secure, SameSite=Lax cookie, 30-day rolling expiry.
- Per-IP rate limiting on
/api/auth/login and /api/auth/register — repeated failures throttle the IP and the targeted account. - Optional IP allow-list (
WEB_ALLOWED_IPS) for high-security single-tenant deployments.
Authorization & tenant isolation
- Every bot-scoped endpoint resolves the bot from the URL slug, then verifies the authenticated user is the owner or a granted member via
BotPermission. - Database-level isolation: every tenant table carries a
bot_id column and a SQLAlchemy ORM event auto-injects WHERE bot_id = current_bot_id on every SELECT. Mis-routed queries can never leak rows across bots. - Cross-tenant requests return
403 — never 404 — so we don't leak the existence of other bots.
Bot tokens & webhooks
- Bot tokens are encrypted at rest with Fernet (AES-128-CBC + HMAC-SHA256) keyed off the
WEB_SECRET_KEY environment variable. Decrypted only in-memory at send time. - Tokens are validated against Telegram's
getMe before being persisted — invalid tokens are refused. - Every webhook URL carries a per-bot
webhook_secret. We verify the X-Telegram-Bot-Api-Secret-Token header on every inbound request; mismatches return 403 and are not processed. - Setting or rotating a bot token regenerates
webhook_secret automatically so the old secret becomes useless.
Payments
- Card details never reach our servers — Stripe Checkout handles capture and we only persist Stripe customer / subscription IDs.
- Stripe webhook signatures verified with
STRIPE_WEBHOOK_SECRET on every event; mismatches return 400. - Payment-disputes and refund history live in your Stripe Customer Portal — accessible from Account → Subscription → Manage billing.
Concurrency & data integrity
- Broadcasts use a Postgres partial-unique-index lock (
UNIQUE ON broadcasts(bot_id) WHERE status='sending') — two admins clicking Send simultaneously can't double-deliver. Stale ‘sending’ rows older than 10 minutes are auto-released. - Edits use optimistic concurrency: PATCH endpoints accept
if_unmodified_since and return 409 if the row moved on us. Each campaign / auto-message / scheduled broadcast carries an updated_at token. - Scheduler skips read-only bots, bots without tokens, and paused auto-messages — three independent gates against accidental sends.
Audit logging
Every mutating action writes an immutable row in audit_logs: who, what, which resource, IP address, user-agent, full details JSON. Retained for 2 years. Visible to the bot owner under Audit Log.
Monitoring & incident response
- Health endpoint
/healthz exposed for uptime probes. - Application errors stream to Sentry with PII payloads scrubbed.
- Database backups verified by automated restore drill weekly.
- Critical-severity incidents trigger pager rotation; users informed via in-app banner + email.
Responsible disclosure
Found a vulnerability? Please email security@myskua.com. We respond within one business day. Please give us at least 30 days to remediate before public disclosure. We don't currently run a paid bug-bounty programme but we credit researchers in our security hall of fame on request.
What we ask of you
- Use a strong, unique password — a manager helps.
- Don't paste your bot token into public channels. If a token leaks, rotate it from Platform → Edit bot → Telegram token.
- Review the audit log periodically. Anything unfamiliar ⇒ rotate password and email security@myskua.com.
See also: Privacy Policy · DPA · Terms of Service.