Skip to content

fix: prevent Telegram DM sandbox bypass in resolveTelegramConversationBaseSessionKey#29

Open
EronFan wants to merge 2646 commits into
mainfrom
fix/telegram-dm-sandbox-bypass
Open

fix: prevent Telegram DM sandbox bypass in resolveTelegramConversationBaseSessionKey#29
EronFan wants to merge 2646 commits into
mainfrom
fix/telegram-dm-sandbox-bypass

Conversation

@EronFan

@EronFan EronFan commented Apr 23, 2026

Copy link
Copy Markdown
Owner

Fix: prevent Telegram DM sandbox bypass in resolveTelegramConversationBaseSessionKey

Problem

Telegram direct-message (DM) sessions for the default account returned agent:main:main from resolveTelegramConversationBaseSessionKey(). Since this matches the agent's main session key, shouldSandboxSession() returned false (sandbox mode=all), causing Telegram DM executions to bypass the sandbox entirely.

Root Cause

The previous implementation only applied a peer-scoped session key (e.g. agent:main:telegram:default:direct:<senderId>) for named account DMs (when routeAccountId !== defaultAccountId). Default-account DMs fell through to return params.route.sessionKey directly, which was agent:main:main (the route's default session key for the main agent on the default account).

Fix

resolveTelegramConversationBaseSessionKey() now returns a peer-scoped session key for all direct (non-group) chats that are NOT explicitly bound, ensuring the key differs from the main session key and sandboxing is active.

Changes

  • extensions/telegram/src/conversation-route.ts: Rewrote resolveTelegramConversationBaseSessionKey() to:

    1. Return route.sessionKey immediately for explicitly bound sessions (matchedBy === 'binding.channel') or group chats (isGroup === true)
    2. For all other direct chats, build a peer-scoped key using buildAgentSessionKey() with dmScope from config (falling back to 'per-account-channel-peer' if dmScope === 'main' or unset) to ensure sandboxing is active
  • extensions/telegram/src/conversation-route.base-session-key.test.ts: Updated two tests that previously expected agent:main:main for default-account DMs to now expect sandboxed peer-scoped keys

Test Results

  • pnpm test -- --run extensions/telegram/src/conversation-route.base-session-key.test.ts5 tests passed
  • pnpm test -- --run src/agents/sandbox/tool-policy.test.ts10 tests passed

Closes openclaw#70342

steipete and others added 30 commits April 23, 2026 02:46
…ve sends

Reconnect drain (drainPendingDeliveries) matches fresh pending entries by
design to preserve crash-replay, but the live delivery path in
deliverOutboundPayloads held no in-memory claim while the send was running.
A reconnect firing mid-send therefore re-drove the same queue entry and
produced duplicate outbound messages (e.g. WhatsApp cron sends going out
7-12x when the 30-minute inbound-silence watchdog fired during delivery).

Claim the queueId against the existing entriesInProgress set right after
enqueueDelivery and release it in the finally branch around ack/fail. Drain
already skips claimed ids via claimRecoveryEntry, so no drain-side change is
needed. The claim is process-local on purpose: a crashed owner leaves no
claim behind, so startup recovery still reclaims orphaned entries.

Fixes openclaw#70386.

Made-with: Cursor
If a reconnect/startup drain observes the newly enqueued queue entry and
calls claimRecoveryEntry before the live delivery path reaches
tryClaimActiveDelivery, tryClaimActiveDelivery returns false. Previously
the live path still proceeded to deliverOutboundPayloadsCore and then
ack/fail, which would race the drain's own delivery and ack/fail for the
same entry id and produce duplicate outbound messages.

Treat a failed claim acquisition as "another in-process owner is already
handling this queue entry" and bail out with an empty result array, leaving
the queue entry in place for the drain to deliver and clean up. This closes
the narrow residual race called out by the Aisle security review on
openclaw#70428.

Made-with: Cursor
steipete and others added 30 commits April 23, 2026 09:03
… (openclaw#49588)

updateLastRoute() used mergeSessionEntry which bumps updatedAt to
Date.now() on every inbound message. This prevented session idle
and daily reset from ever firing, since evaluateSessionFreshness()
always saw a fresh updatedAt.

The fix from openclaw#32379 patched recordSessionMetaFromInbound to use
mergeSessionEntryPreserveActivity, but missed updateLastRoute() in
the same inbound pipeline.

Changes:
- Remove explicit updatedAt from updateLastRoute basePatch
- Switch from mergeSessionEntry to mergeSessionEntryPreserveActivity
- Add regression test verifying updatedAt is preserved
- Update existing test assertion to match corrected behavior

Fixes openclaw#49515
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.

[Bug]: Telegram-routed direct chat can bypass sandboxing while TUI and heartbeat/non-main runs use the sandbox