feat(telegram): add allow_all_users/allowed_users to [telegram] config#1297
Conversation
Discord and Slack have had allowed_users in their first-class config
sections for a while, but [telegram] never did - the only way to
restrict who can message a Telegram bot was the shared GATEWAY_*
env vars (GATEWAY_ALLOW_ALL_USERS/GATEWAY_ALLOWED_USERS), which
default to deny-all (per the identity-trust-none ADR) and aren't
Telegram-specific.
Add allow_all_users/allowed_users to TelegramConfig, matching the same
resolution convention already used for every other [telegram] field
and for Discord's allowed_users: config value wins, falls back to a
TELEGRAM_* env var (TELEGRAM_ALLOW_ALL_USERS / TELEGRAM_ALLOWED_USERS,
comma-separated), then auto-detects allow_all_users from whether the
resolved list is empty (same convention as [discord]/[slack]).
Wire the resolved values into main.rs's PlatformTrustConfigs registry
as a Telegram-specific override, mirroring exactly how Discord's own
block already overrides the shared GATEWAY_* L3 defaults - added
right after the existing Discord override.
Now a bot operator can write:
[telegram]
bot_token = "${TELEGRAM_BOT_TOKEN}"
allowed_users = ["176096071"]
instead of routing a plain user ID through spec.secrets/Secrets
Manager (oabctl's manifest has no plain env var field) or opening the
bot to allow_all_users=true just to admit one person.
Testing: cargo build/clippy -D warnings/test clean on macmini for the
whole workspace (openab-core: 48 tests incl. 5 new allowed_users
scenarios in telegram_resolve_all_scenarios; openab binary: 15 tests).
No regressions. Fixed one now-incomplete TelegramConfig struct literal
in an existing test (missing the two new fields).
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Add the new [telegram] user access control fields to: - Unified mode env vars table - Full example config block - Field precedence table - Gateway Environment Variables table - New 'User Access Control' section with auto-detect logic explanation
7c45cce to
e6dc557
Compare
This comment has been minimized.
This comment has been minimized.
Align [telegram] user gating with the identity-trust-none ADR: - Empty allowed_users + no explicit flag → deny all (was: allow all) - Users must now explicitly set allow_all_users = true or list specific IDs in allowed_users to admit senders - Matches the shared GATEWAY_ALLOW_ALL_USERS default (false) This is not a breaking change for existing deployments because: - [telegram] config section is brand new (introduced in this PR) - Existing env-only deployments use GATEWAY_ALLOW_ALL_USERS which already defaults to deny-all Added Scenario 12 test: explicit allow_all_users = true opt-in.
This comment has been minimized.
This comment has been minimized.
…verride - F1: Resolve Telegram trust override even when [telegram] section is absent but TELEGRAM_ALLOWED_USERS / TELEGRAM_ALLOW_ALL_USERS env vars are set. Previously, env-only deployments (no config.toml section) silently ignored these env vars and fell back to GATEWAY_* defaults. - F2: Change TelegramConfig.allowed_users from Vec<String> (with #[serde(default)]) to Option<Vec<String>>. This lets serde distinguish omitted (None → fall back to env) from explicitly empty (Some([]) → deny all, config-authoritative). Fixes the case where allowed_users=[] in config.toml could not override a non-empty TELEGRAM_ALLOWED_USERS env var. - Add Scenario 13 test: explicit empty list overrides env var. Addresses review findings from 法師團隊 on PR #1297.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
CHANGES REQUESTED What This PR DoesAdds How It Works
Review Panel Verdict
Consensus: 2/3 LGTM, 1 CHANGES REQUESTED on regression risk + docs. Findings
Finding Details🟡 F1: Regression — shared GATEWAY_* silently overriddenProblem: When config.toml has a Impact: Fail-closed regression (bot unexpectedly rejects users). Severity is 🟡 not 🔴 because security posture becomes more restrictive, not less. Suggested fix: Only insert the override when Telegram-specific trust is actually configured: let has_telegram_trust_config = cfg.telegram.as_ref()
.map_or(false, |t| t.allow_all_users.is_some() || t.allowed_users.is_some())
|| std::env::var("TELEGRAM_ALLOW_ALL_USERS").is_ok()
|| std::env::var("TELEGRAM_ALLOWED_USERS").is_ok();
if has_telegram_trust_config {
let r = cfg.telegram.as_ref()
.map(|t| t.resolve())
.unwrap_or_else(|| config::TelegramConfig::default().resolve());
reg.insert("telegram", TrustConfig::new(
Some(allow_all_channels),
allowed_channels.clone(),
None,
Some(r.allow_all_users),
r.allowed_users,
));
}🟡 F2: PR description / implementation mismatchPR body states "empty list defaults to allow-all (same convention as [discord]/[slack])" but implementation correctly defaults to deny-all. Code is correct per identity-trust-none ADR; PR description should be updated. 🟡 F3: Cross-platform semantics divergenceDiscord uses 🟡 F4: Silent deny-all UXA fresh deployment with default config gets deny-all — bot silently ignores all messages. Docs should mention what the user experience looks like when denied and where to look for logs. 🟡 F5: Missing integration testThe Baseline Check
What's Good (🟢)
|
- TELEGRAM_ALLOW_ALL_USERS="" now resolves to false (deny-all) instead of true. Adds .filter(|v| !v.is_empty()) before parsing to prevent accidental fail-open from empty env var. - Added doc comment clarifying that allow_all_users=true bypasses the allowed_users list entirely. - Marked TELEGRAM_ALLOWED_USERS/TELEGRAM_ALLOW_ALL_USERS as "Unified binary only" in the Gateway env vars table — standalone gateway uses [gateway].allowed_users instead. - Added Scenario 14 test: empty-string env var must resolve to false.
|
LGTM ✅ — Clean, well-tested addition of per-platform What This PR DoesAdds first-class How It Works
Findings
Baseline Check
What's Good (🟢)
|
|
LGTM ✅ — All review findings addressed in What This PR DoesAdds How It Works
Findings
Finding Details🟡 F1: Empty-string env var fail-open (FIXED)
Fix: Added 🟡 F2: Supersede behavior undocumented (FIXED)When Fix: Added doc comment: "When this resolves to 🟡 F3: Gateway docs scope confusion (FIXED)The "Environment Variables (Gateway)" table included Fix: Added "Unified binary only — standalone gateway uses Baseline Check
Architecture Note: Platform Gating BoundaryOnce a What's Good (🟢)
|
Found while testing PR #1297 (telegram allowed_users): triggering PR Preview Build with variant=default builds openab with its default Cargo features (discord/slack/secrets-aws/agentcore/config-s3/ pre-seed) - telegram/line/feishu/wecom/googlechat/teams are NOT in that list, they only compile in via the 'unified' feature. The resulting preview image ran fine but had no webhook server at all for any of those platforms - every request returned 503 with no logs, since the whole #[cfg(feature = "telegram")]-gated code path was absent from the binary. Wasted a full deploy+test cycle before tracing it back to the Dockerfile's BUILD_MODE default. Add 'unified' as a variant choice, resolved to the same base Dockerfile as 'default' but with BUILD_MODE=unified passed as a docker build-arg, matching what the real release pipeline (build-operator.yml) uses via a different (Dockerfile.unified) mechanism for the multi-agent-variant builds - this workflow only supports the legacy single Dockerfile, so BUILD_MODE=unified is the correct way to get the same Cargo-feature set here. Produces the same ghcr.io/openabdev/openab:pr<N> tag as 'default' (same image name/suffix, different feature set baked in) - no new tag pattern introduced. Not run through CI (workflow_dispatch-only, can't be exercised by 'changes'/'check' path-filter jobs) - verified by re-triggering it for PR #1297 with variant=unified immediately after this commit and confirming the resulting image actually starts the telegram webhook server (see PR #1297 conversation for the live verification). Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
Add a [line] config section for L3 identity trust, replacing the uniform GATEWAY_ALLOW_ALL_USERS/GATEWAY_ALLOWED_USERS env vars for LINE. First slice of #1355, mirroring the [telegram] pattern (#1297). - config.rs: LineConfig { allow_all_users, allowed_users } with LINE_ALLOW_ALL_USERS / LINE_ALLOWED_USERS env fallbacks and deny-all default (identity-trust-none ADR). Trust-only by design — channel credentials stay on LINE_CHANNEL_SECRET / LINE_CHANNEL_ACCESS_TOKEN. - main.rs: [line] (or LINE_* env) overrides the uniform GATEWAY_* seed in the trust registry, exactly like the telegram override. When LINE is active and still driven by the legacy GATEWAY_* env, log a Phase 1 deprecation warning (becomes an error in Phase 2, #1356). - docs: config.toml.example, config-reference.md ([line] section), line.md (User Trust section + env table). - tests: TOML parse + all env resolution scenarios in one test fn (env-race safety, same pattern as telegram_resolve_all_scenarios). Group policy (open/members) and Reply-API deny-echo are follow-ups on the issue. Refs #1355, umbrella #1356, ADR #1291. Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
Summary
[discord]/[slack]have hadallowed_usersin their first-class configsections for a while;
[telegram]never did. The only way to restrict whocan message a Telegram bot today is the shared
GATEWAY_ALLOW_ALL_USERS/GATEWAY_ALLOWED_USERSenv vars — which default to deny-all (per theidentity-trust-none ADR) and aren't Telegram-specific in name or intent.
Came up directly from deploying a real bot via
oabctl— the manifestschema has no plain (non-secret) env var field, only
spec.secrets, so therewas no clean way to admit a single Telegram user ID without routing it
through Secrets Manager or opening the bot to everyone.
Change
Adds
allow_all_users/allowed_usersto[telegram], matching the exactresolution convention already used for every other
[telegram]field andfor
[discord].allowed_users:TELEGRAM_ALLOW_ALL_USERS/TELEGRAM_ALLOWED_USERS(comma-separated) env vars → auto-detectsallow_all_usersfrom whether the resolved list is empty (same conventionas
[discord]/[slack]: non-empty list defaults to deny-all-except-list,empty list defaults to allow-all).
main.rs'sPlatformTrustConfigsregistry as aTelegram-specific override, right after the existing Discord override —
same pattern, same place.
Testing done
5 new scenarios added to
telegram_resolve_all_scenarios(the existingconsolidated env-var test, kept serialized to avoid
std::envraceconditions under parallel test execution — matches the existing test's own
convention): config list wins over env, empty-list auto-detect → allow-all,
non-empty-list auto-detect → deny-all-except-list,
TELEGRAM_ALLOWED_USERSenv fallback (comma-separated + trimmed), explicit
allow_all_users = falseoverride.
Also fixed one now-incomplete
TelegramConfigstruct literal in an existingtest (missing the two new fields, caught immediately by the compiler).
Verified on macmini per the project's build-offloading convention, not run
locally.