If you find a security issue, please do not open a public GitHub issue.
Email the maintainer directly at security@mcp-telegram.com (also reachable via the contact listed at mcp-telegram.com). Self-hosters should replace this address with their own operator contact before publishing a fork.
Please include:
- A clear description of the issue and its impact.
- Steps to reproduce (PoC welcome, but not required).
- Affected version / commit SHA.
- Your disclosure timeline expectations.
We aim to acknowledge reports within 72 hours and to ship a fix or mitigation within 14 days for high-severity issues. Coordinated disclosure is appreciated — we will credit you in the release notes unless you prefer to stay anonymous.
In scope:
src/(runtime code): OAuth flow, MCP handler, session manager, rate limiter, admin endpoints, logging.Dockerfile,docker-compose.example.yml,.github/workflows/*.yml(supply chain)..env.exampledefaults that could lead to insecure production deploys.
Out of scope:
- Upstream
@overpod/mcp-telegram— report those on the upstream repo. - gram.js / telegram MTProto library.
- Third-party MCP clients (Claude.ai, ChatGPT, etc.).
- Social-engineering or physical attacks.
- DoS via legitimate traffic volume (see Rate limiting below — open a regular issue if you have an improvement).
This service holds live Telegram MTProto sessions on behalf of its users. A breach of the session database means a full-account compromise equivalent to an attacker cloning the victim's Telegram app. Treat the database as the crown jewel.
- Admin endpoint abuse —
ADMIN_TOKENis compared withtimingSafeEqualto prevent timing oracles. - Rainbow-table lookup of user IDs in logs — IDs can be replaced with
HMAC hashes (
LOG_USER_IDS=false+LOG_HASH_SALT). - Unbounded OAuth abuse — per-IP rate limit on
/oauth/*(seeOAUTH_RATE_LIMIT). - Secret leaks in git history — pre-commit
gitleakshook. - XSS in user-controlled metadata — JSON-LD output is escaped; Hono JSX auto-escapes by default.
- Log bloat / indefinite retention —
USAGE_LOG_RETENTION_DAYSpurges old rows daily (default 90d).
Outbound telemetry (logs/metrics/traces to SigNoz) is gated by the
MCP_TELEGRAM_TELEMETRY master switch:
| Mode | SQLite usage_log |
Console / docker logs | OTLP logs → SigNoz | OTLP metrics → SigNoz | In-memory ring (errors + metrics) |
|---|---|---|---|---|---|
local-only (default) |
✅ | ✅ | ❌ | ❌ | ✅ |
on |
✅ | ✅ | ✅ (if SIGNOZ_ENDPOINT set) |
✅ (if SIGNOZ_ENDPOINT set) |
✅ |
off |
✅ | ❌ | ❌ | ❌ | ✅ |
Allowed in any mode (recorded in usage_log + log attrs):
- Tool name (e.g.
telegram-list-chats) - MCP client classification (
claude,chatgpt,browser,bot,script,other) - HTTP method, route template, status class, duration
- Component / event labels we author (
component=oauth,event=token.issued) - HMAC-hashed user id (10-hex-char prefix; HMAC key
LOG_HASH_SALT) - Aggregate counts (DAU, calls/day)
Never recorded (two-layer guard — compile-time + runtime — both run in CI):
- Compile-time (
src/telemetry/log-fields.ts):LogFieldsis a closed whitelist of allowed attribute keys. Anylogger.*orrecordError(...)call that passes a key not on this list is a TypeScript error and failspnpm typecheck. This stops PII categories that haven't been blacklisted yet (e.g. a freshemailHash,ipAddr, etc.). - Runtime (
pnpm check-telemetryin.github/workflows/build.yml): greps the source tree for the BLACKLIST of known-bad keys (userId,peer,messageText, …) insidelogger/recordErrorattrs blocks. Catches escape hatches (// telemetry-allow) and any future caller that bypasses the type system.
What is blocked:
- Raw Telegram user id, peer id, chat id, message id, message text
- Phone, email, first/last name, username
- MTProto session string, auth key, API hash
- OAuth
code,state, access tokens, refresh tokens - HTTP query strings (only the route path)
AuthorizationandCookieheaders- Any Telegram update payload bodies
For self-hosters: the default local-only mode means a fresh docker run
emits zero outbound network traffic for telemetry. The
/api/observability page renders DAU, by-tool, by-client, recent errors,
and process metrics (HTTP/tool latency p50/p95/p99, OAuth flow counters,
active sessions, pending upload bytes) straight from SQLite + an in-memory
ring buffer (last 500 ERRORs, lost on restart) + in-process counters/
histograms/gauges (cumulative since boot, lost on restart). Set
MCP_TELEGRAM_TELEMETRY=on and SIGNOZ_ENDPOINT=... only when you operate
your own SigNoz and want centralized dashboards. If your SigNoz ingest is
exposed publicly behind an HTTP Basic auth gateway (e.g. Traefik), set
SIGNOZ_AUTH="user:password" so the exporter attaches the correct
Authorization header. The credentials are not logged or persisted.
- Plaintext session storage — the SQLite DB contains MTProto session
strings in plain text. File-system access = account takeover. Mitigations
are a hosting concern, see
docs/self-hosting.md. - Malicious admin — anyone with
ADMIN_TOKENcan read all users' usage stats and import sessions. Rotate it aggressively and keep it out of untrusted environments. - Compromised host — root access to the server = full compromise. No cryptographic separation between the web process and session storage.
- Dependencies — we bump deps aggressively but do not audit every
transitive package. Run
pnpm auditin your pipeline.
Security fixes land on main and are shipped in the next Docker image
tag. We do not maintain long-lived support branches. Self-hosters
should track main or pin to a tagged release and upgrade promptly when a
security advisory is posted.
See docs/self-hosting.md for the full
operations guide. The short version:
- Generate a strong
ADMIN_TOKENandLOG_HASH_SALT(32 bytes hex). - Leave
LOG_USER_IDSunset (default = HMAC-hashed). Only setLOG_USER_IDS=truefor short local debugging — never in production. - Leave
MCP_TELEGRAM_TELEMETRYunset (default =local-only, zero outbound). Set toononly if you run your own SigNoz. - Put the service behind TLS — never expose
:3000directly. - Restrict the database volume to the service user (
0600). - Back up the DB encrypted — it contains live sessions.
- Firewall
/api/stats,/api/observability, and/api/import-sessionto trusted IPs if possible. - Subscribe to this repo's releases for security advisories.