Threat model, cryptographic invariants, Communication Agent controls, and known limitations for this deployment.
Email [email protected]. Please do not open public GitHub issues for unpatched vulnerabilities. Acknowledgement target: 72 hours. Fixes are disclosed in the CHANGELOG once a release ships.
For product help (setup, disconnect, billing questions): email [email protected]. Do not use the security address for general support.
| Boundary | Trusted credential / state | Lives |
|---|---|---|
Inbound HTTP (ChatGPT / Claude / MCP clients → /mcp) |
OAuth JWT issued and verified by this server; legacy mode may use api.mctl.ai |
Bearer header, per request |
Outbound MTProto (gotd/td → Telegram) |
Encrypted per-user session blob | telegram_accounts.session_encrypted |
| Communication Agent listener and queue | Per-user listener profile, encrypted message/event rows, queue jobs, conversation and policy state | User-scoped Postgres agent tables; active only for listener-enabled profiles |
Every database lookup for user content is intended to be scoped to the owning user. Compromise of one account should not expose another account unless the service, key derivation, or deployment boundary itself is compromised.
The model prompt and model output are not trusted as a security boundary. The server-side listener, state machine, policy engine, approval flow, and Telegram send gate remain authoritative.
/mctl command is stored and routed. Ordinary notes in Saved Messages are ignored.taken_over. Programmatic text/media sends are correlated by their Telegram message ID so their echo is not mistaken for a human reply.observe mode requires owner approval for replies. guarded mode can allow only allowlisted replies that satisfy every server-side policy, turn budget, and rate constraint; other actions require approval or are denied.executing, that action is not automatically retried, avoiding duplicate messages.Enabling the listener does not bypass the existing ALLOW_SEND, scope, per-account, and per-peer send gates.
incoming_events and conversation_messages may contain third-party Telegram message bodies.agent_actions may contain proposed reply payloads and approval/policy metadata.AGENT_RETENTION_DAYS; default is 30 days. Setting it to 0 keeps rows indefinitely.AUDIT_RETENTION_DAYS defaults to 90 days and audit rows contain no message body.When an account is set to mode='local', its MTProto session lives on the user's machine instead of the hosted server:
session_encrypted is NULL for local-mode accounts.call_path marks local relay calls, while message bodies are excluded from audit rows and stdout.Authorization: Bearer header contentsThis guarantee applies to logs and audit rows. Communication Agent message/action tables intentionally store encrypted content as described above.
AUTH_MODE=shared-hmac-legacy validates JWTs using secret material shared with an external authorization service. This coupling does not apply to the default local-jwt mode.
local-jwt for new deployments.ENCRYPTION_KEY must be 32 random bytes, hex-encoded as 64 characters. Production must set it.On production tg.mctl.ai, ALLOW_SEND=false is the safe default. Real Telegram sends through send_message or send_media require all of:
ALLOW_SEND=true.telegram:messages:send scope.telegram_accounts.send_enabled = true for that operator.Any failed condition returns a dry-run result (sent=false) with a dry_reason; no send RPC reaches Telegram. A denied media call does not fetch its URL or decode its base64 payload.
AUTH_REQUIRED=false is for local development only. Production must use authenticated OAuth mode.AUTH_MODE=local-dev returns a fixed platform-admin identity and must not be reachable from a public production interface.The default per-identity token bucket is 30 requests/minute with the same burst. Write paths additionally use per-peer limits so one recipient cannot be targeted up to the global ceiling. Public and well-known endpoints are limited at the ingress layer.
expires_at is revoked.Audit rows form a SHA-256 hash chain using prev_hash and entry_hash. You can ask the server to verify the chain:
GET /api/account/audit/verify
{
"ok": true,
"verified": 42
}
A non-OK response includes first_bad_id and a reason. Rows created before hash chaining are skipped.
Read tools wrap Telegram message bodies in <telegram-content origin="telegram" peer="redacted" untrusted="true">…</telegram-content> and escape literal closing tags inside message text. This is a signalling boundary for the MCP client, not a substitute for the server-side Communication Agent policy engine.