If you find a security issue in kimi-to-im, please do not open a public GitHub issue.
Instead, open a private security advisory at https://github.com/hah23255/kimi-to-im/security/advisories/new, or email the maintainer with the subject prefix [security]. Include:
- A clear description of the issue.
- Reproduction steps or a proof-of-concept.
- The affected commit SHA.
- The impact (information disclosure, RCE, auth bypass, etc.).
You should expect an initial acknowledgement within five working days.
In scope:
- The Python source under
src/. - The systemd unit template (
systemd/kimi-telegram-bridge.service.template). - The plugin manifest (
plugin.json) and its declaredbridgetool entry point (src/control.py). - The installer (
install.sh).
Out of scope (report upstream):
- The Kimi CLI itself (https://github.com/MoonshotAI/kimi-cli).
- Telegram's API, BotFather, or the Telegram client.
- The
httpxlibrary. - The host operating system, systemd, or
uv.
kimi-to-im runs as a single-user daemon under systemctl --user. It has access to:
- A Telegram bot token (in
config.json, mode 0600). - A long-lived OAuth token belonging to the Kimi CLI (in
~/.kimi/credentials/). - Whatever filesystem and shell access the invoking user has.
Assumed trust boundaries:
| Trusted | Untrusted |
|---|---|
| The host OS and the local user account | Inbound Telegram messages (filtered by allowed_user_ids) |
The kimi CLI binary on PATH |
Telegram's network path (mitigated by TLS) |
config.json on disk (mode 0600) |
Anything that fails token validation |
- The user keeps
config.jsonprivate and gitignored. The repository's.gitignoreexcludes it; commits are checked in CI. - The user does not invite the bot into untrusted Telegram groups. The default config sets
allowed_chat_ids: [](any chat from an allowed user); operators are encouraged to set an explicit chat-id whitelist. - Backups of
~/.kimi/are encrypted at rest by the user.
The systemd unit ships with:
NoNewPrivileges=truePrivateTmp=trueProtectKernelTunables,ProtectKernelModules,ProtectControlGroupsRestrictAddressFamilies=AF_UNIX AF_INET AF_INET6LockPersonality,RestrictNamespaces,RestrictRealtime,RestrictSUIDSGIDSystemCallArchitectures=native,SystemCallFilter=@system-service ~@privileged ~@resourcesUMask=0077
The bridge:
- Defaults to deny — empty
allowed_user_idscauses the loader to refuse to start. - Validates that
allowed_user_idsentries are integers (and rejects bools, which Python would otherwise accept asint). - Validates persisted session ids against the uuid4-hex format on load, so a tampered
state.jsoncannot inject argv flags into thekimisubprocess. - Suppresses
httpx/httpcoreINFO-level logging in both daemon and plugin-tool entry points so the bot token (which appears in URL paths) never reaches the log file. - Bounds every
kimiinvocation with a 900-second timeout so a hung subprocess cannot stall the bridge indefinitely (raised from 300s; aligns with the 15-minute Kimi OAuth JWT TTL — the natural upper bound). - Surfaces a friendly user-facing message on timeout (exit code 124) instead of leaking the raw subprocess
stderr. - Runs a concurrent heartbeat task that refreshes the Telegram "typing" indicator and posts progress notices at 250s and 600s, so the user knows the bot is still working rather than hung.
A pre-publication audit was performed against commit a12aeeb. The findings and remediation status are documented in docs/security-scan.md.
Security fixes will be tagged on main and noted in the release notes. There is no LTS branch; users are expected to track main.