#18170: feat(telegram): support local Bot API server via `apiRoot` config
channel: telegram
size: L
Cluster:
Messaging Platform Improvements
Add a per-account `apiRoot` option so all Telegram HTTP calls route
through a self-hosted Bot API server instead of the default
`https://api.telegram.org`/.
Key changes:
- New `src/telegram/api-base.ts` module: `getTelegramApiBase()` resolves
the base URL (explicit config override > default), `isCustomTelegramApi()`
and `isLocalBotApiFilePath()` helpers for downstream branching,
`validateLocalFilePath()` for path-traversal protection.
- Config: `apiRoot` and `localApiDataDir` fields added to
`TelegramAccountConfig` type and Zod schema (`apiRoot` validated as URL).
- Bot, send, audit, and probe modules all thread the per-account
`apiRoot` config through to the grammy API client and
`getTelegramApiBase()`. `bot.ts` and `send.ts` use
`isCustomTelegramApi()` for custom-API detection.
- File downloads (voice notes, media): when a local Bot API server
returns an absolute disk path in `file_path`, the path is validated
against the required `localApiDataDir` config using `realpath` to
block symlink and `..` traversal attacks, then read directly from disk.
- SSRF relaxation is scoped to accounts that explicitly set `apiRoot`
in config (not env vars); accounts without `apiRoot` always use the
default cloud endpoint with full SSRF protection.
Security:
- Disk reads require `localApiDataDir` to be explicitly configured —
there is no default. Without it, absolute file paths returned by the
local server are rejected.
- `realpath` resolves symlinks before the prefix check, preventing
symlink-based escapes.
- A compromised local Bot API server cannot trick the gateway into
reading arbitrary files (e.g. /etc/shadow, ~/.ssh/id_rsa).
Tests:
- `api-base.test.ts`: covers URL resolution priority, normalization,
`isCustomTelegramApi`, `isLocalBotApiFilePath`, and
`validateLocalFilePath` (happy path, `..` traversal, symlink escape,
missing file, missing config).
- `delivery.resolve-media-local-api.test.ts`: covers direct disk read
for absolute paths, path-traversal rejection, HTTP with relaxed SSRF
for relative paths, and verifies SSRF stays strict when `apiRoot` is
not set in config.
Tested with a local Bot API server; confirmed voice-note file downloads
work end-to-end via both the direct disk-read and HTTP paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
Describe the problem and fix in 2–5 bullets:
- Problem: Local bot API server not supported
- Why it matters: Adding support for this increases privacy significantly, and allows enhanced telegram integration.
- What changed: Integrated local bot api server use. SSRF relaxation on for telegram allowing SCOPED local disk access.
- What did NOT change (scope boundary): Other messaging frameworks.
## Change Type (select all)
- [ ] Bug fix
- [X] Feature
- [ ] Refactor
- [ ] Docs
- [X] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [ ] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [X] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Closes #
- Related #
## User-visible / Behavior Changes
List user-visible changes (including defaults/config). config: apiRoot, and localApiDataDir (required for local disk access).
If none, write `None`.
## Security Impact (required)
- New permissions/capabilities? (`Yes`)
- Secrets/tokens handling changed? (`Yes/No`)
- New/changed network calls? (`Yes`)
- Command/tool execution surface changed? (`Yes/No`)
- Data access scope changed? (`Yes`)
- If any `Yes`, explain risk + mitigation: Risk: more disk access triggered by bot api server, mitigation: only allow a configured api data dir, mandatory config, otherwise all disk read fails.
## Repro + Verification
### Environment
- OS: Gentoo
- Runtime/container: Local
- Model/provider: Claude Opus 4.6
- Integration/channel (if any): Telegram
- Relevant config (redacted):
### Steps
1.
2.
3.
### Expected
-
### Actual
-
## Evidence
Attach at least one:
- [ ] Failing test/log before + passing after
- [ ] Trace/log snippets
- [ ] Screenshot/recording
- [ ] Perf numbers (if relevant)
## Human Verification (required)
What you personally verified (not just CI), and how:
I personally verified voice note receipt from Telegram in a direct message with my bot.
- Verified scenarios:
- Edge cases checked: Removed api data dir config, and confirmed local disk access blocked.
- What you did **not** verify: download of other media types (document, file, etc.).
## Compatibility / Migration
- Backward compatible? (`Yes`)
- Config/env changes? (`Yes`)
- Migration needed? (`No`)
- If yes, exact upgrade steps:
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly:
- Files/config to restore: openclaw.json
- Known bad symptoms reviewers should watch for:
## Risks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write `None`.
- Risk: SSRF relaxation for Telegram only, per account, not globally
- Mitigation: Config check, and api data dir scoping.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Adds support for self-hosted local Telegram Bot API servers via per-account `apiRoot` configuration, enabling direct disk reads for media files with comprehensive path-traversal protection.
**Major changes:**
- New `src/telegram/api-base.ts` module with URL resolution, custom API detection, and `validateLocalFilePath()` using `realpath()` to prevent symlink/`..` escapes
- Config fields `apiRoot` (validated as URL) and `localApiDataDir` added to `TelegramAccountConfig`
- SSRF relaxation scoped to accounts with explicit `apiRoot` config (not env vars)
- Direct disk reads require `localApiDataDir` to be configured (no default); root directory explicitly blocked
- Bot, send, audit, and probe modules thread per-account `apiRoot` through to API clients
- Comprehensive test coverage: `api-base.test.ts` (path validation, Windows paths, root blocking) and `delivery.resolve-media-local-api.test.ts` (disk reads, HTTP fallback, SSRF scoping)
**Security measures verified:**
- `realpath()` resolves symlinks before prefix checks
- Root directory (`/`) rejected as `localApiDataDir`
- Path-traversal with `..` blocked
- Windows absolute paths (`C:\...`) properly detected via `nodePath.win32.isAbsolute()`
- Audit and probe correctly receive per-account `apiRoot` config
<h3>Confidence Score: 4/5</h3>
- Safe to merge with minor considerations - security mitigations are comprehensive and well-tested
- All previously identified security issues have been addressed (Windows paths, root directory bypass, per-account config threading). Path validation is robust with `realpath()` + prefix checks. Test coverage is comprehensive. Score not 5 due to complexity of security-sensitive disk access feature and SSRF relaxation, though both are properly scoped and protected.
- No files require special attention - all previous review comments have been addressed
<sub>Last reviewed commit: cda1cee</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#19616: feat(telegram): support custom apiRoot for Telegram Bot API
by CarloCPP · 2026-02-18
85.9%
#19942: feat(telegram): configurable SSRF policy for media fetch
by onewesong · 2026-02-18
77.3%
#9577: feat: Add apiRoot option for custom Telegram Bot API server endpoint
by vfiee · 2026-02-05
77.3%
#21029: Feature/telegram bot avatar clean
by aleonnet · 2026-02-19
76.6%
#13070: feat: Add support for custom Telegram Bot API server URL via apiRoo...
by vfiee · 2026-02-10
76.2%
#19615: fix(discord): include default account when sub-accounts are configured
by prue-starfield · 2026-02-18
75.7%
#21757: Docs/telegram inbound troubleshooting
by alanparesys · 2026-02-20
75.0%
#8310: feat(telegram): Add allowBots support for groups (parity with Disco...
by vishaltandale00 · 2026-02-03
74.9%
#23627: fix(telegram,feishu): pass mediaLocalRoots through channel action a...
by rockkoca · 2026-02-22
74.6%
#14419: feat(extensions): add telegram-files Mini App
by audichuang · 2026-02-12
74.4%