Skip to content

fix(chat): require per-channel sender allowlist for inbound commands - #1

Merged
jtalborough merged 2 commits into
mainfrom
telegram-sender-allowlist
Jun 26, 2026
Merged

fix(chat): require per-channel sender allowlist for inbound commands#1
jtalborough merged 2 commits into
mainfrom
telegram-sender-allowlist

Conversation

@jtalborough

Copy link
Copy Markdown
Owner

Why

Chat-channel commands (/task, /folder, /approve, …) spawn and drive agents with the host's full privileges, but dispatch_command routed purely on the command verb — sender_id was logged and used for context yet never authorized. Anyone who could message the bot could run arbitrary tasks (RCE), and /approve always disabled the human-in-the-loop. This is acute for an always-on server reachable via Telegram.

What

  • Fail-closed authorization gate at the top of dispatch_command (covers Telegram, Lark, and WeChat in one place): only sender ids in the channel's config_json.allowed_senders may drive the bot. Empty/unset list ⇒ nobody is authorized.
  • A blocked sender is replied with their own sender id so the operator can add it.
  • chat_channel/authz.rs — pure is_sender_allowed() with unit tests (6, passing).
  • i18n.rs — unauthorized reply in all 10 languages.
  • add/edit channel dialogs — Allowed Sender IDs management field.

Validation

  • cargo test --no-default-features --bin codeg-server --lib chat_channel::authz → 6 passed
  • cargo clippy --no-default-features --bin codeg-server --lib -- -D warnings → clean
  • eslint on changed components → clean

Follow-up (separate)

Defense-in-depth: scope inbound Telegram getUpdates to the configured chat_id (backends/telegram.rs).

Chat-channel commands (/task, /folder, /approve, …) spawn and drive agents
with the host's full privileges, but the dispatcher routed purely on the
command verb — sender_id was logged and used for context yet never authorized.
Any user who could message the bot could run arbitrary tasks (RCE) and
/approve always disabled the human-in-the-loop. This is acute for an always-on
server reachable via Telegram.

Add a fail-closed authorization gate at the top of dispatch_command (covers
Telegram, Lark, and WeChat in one place): only sender ids listed in the
channel's config_json `allowed_senders` may drive the bot. An empty/unset list
authorizes no one; a blocked sender is told their own id so the operator can
add it. Surfaced in the add/edit channel dialogs as an "Allowed Sender IDs"
field.

- chat_channel/authz.rs: pure is_sender_allowed() + unit tests
- command_dispatcher.rs: gate before any command, incl. follow-ups
- i18n.rs: unauthorized reply (10 languages)
- add/edit-chat-channel-dialog.tsx: allowlist management UI

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jtalborough jtalborough reopened this Jun 26, 2026
`pnpm build` (strict tsc) flagged the `.map((s) => …)` callback as implicit
`any`: in the edit dialog `config` comes from `JSON.parse` (any), so the
`allowedSenders` state inferred `any`. Type the state as `string` and annotate
the map parameter. eslint doesn't run the type-checker so it passed locally;
validated now with `pnpm build` + `pnpm test` (1747 passing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jtalborough
jtalborough merged commit 8f33fdc into main Jun 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant