Skip to content

fix(security): prevent cross-channel reply routing in shared sessions#24571

Merged
steipete merged 3 commits intoopenclaw:mainfrom
brandonwise:fix/cross-channel-reply-routing
Feb 25, 2026
Merged

fix(security): prevent cross-channel reply routing in shared sessions#24571
steipete merged 3 commits intoopenclaw:mainfrom
brandonwise:fix/cross-channel-reply-routing

Conversation

@brandonwise
Copy link
Contributor

@brandonwise brandonwise commented Feb 23, 2026

Summary

Fixes #24152 — cross-channel reply routing privacy leak in shared sessions.

Problem

When dmScope="main" (the default), all DM channels share a single session. The session's lastChannel/lastTo fields get overwritten by each inbound message, regardless of source channel.

Race condition: If a Slack DM arrives while the agent is generating a WhatsApp reply, lastChannel flips to "slack" and the reply routes to the Slack sender instead of the WhatsApp user — a privacy violation.

Reproduction conditions (from issue):

  • Active WhatsApp conversation in progress
  • Incoming Slack system message (team member DM)
  • Agent generates response → delivered to Slack DM instead of WhatsApp

Fix

Adds optional turnSourceChannel, turnSourceTo, turnSourceAccountId, and turnSourceThreadId parameters to:

  1. resolveSessionDeliveryTarget() in targets.ts
  2. resolveAgentDeliveryPlan() in agent-delivery.ts

When turnSourceChannel is set, it overrides the session-level lastChannel for "last" channel resolution. This ensures the reply always routes back to the channel that originated the turn, regardless of concurrent inbound messages from other channels.

Key design decisions:

  • Backward compatible — all new parameters are optional; existing call sites work unchanged
  • Opt-in — callers pass the originating channel context when they want cross-channel safety
  • No session schema changes — operates at the delivery resolution layer, not storage
  • Follows existing patterns — mirrors the requestedChannel / explicitTo parameter style

Changes

File Change
src/infra/outbound/targets.ts Add turnSource* params to resolveSessionDeliveryTarget()
src/infra/outbound/agent-delivery.ts Add turnSource* params to resolveAgentDeliveryPlan(), pass through to target resolution
src/infra/outbound/targets.test.ts 4 new test cases for cross-channel guard

Test cases

  1. turnSourceChannel overrides session lastChannel when provided
  2. ✅ Falls back to session lastChannel when turnSourceChannel is absent
  3. ✅ Explicit requestedChannel takes priority over turnSourceChannel
  4. ✅ Preserves turnSourceAccountId and turnSourceThreadId

Next steps

Upstream callers (channel monitors, agent turn pipeline) need to pass turnSourceChannel when initiating turns. This PR provides the infrastructure; wiring the callers is a follow-up.


AI-assisted: Claude analyzed the routing code and authored the fix. Human reviewed the approach and design decisions.

Greptile Summary

Adds optional turnSource* parameters to delivery resolution functions to prevent cross-channel reply routing in shared sessions (dmScope="main"). When a turn originates from WhatsApp but a concurrent Slack message updates the session's lastChannel, replies could be misrouted to Slack—this fix ensures replies route back to the originating channel by letting callers override session state with turn-specific context.

Key changes:

  • resolveSessionDeliveryTarget() and resolveAgentDeliveryPlan() accept new optional turnSourceChannel, turnSourceTo, turnSourceAccountId, and turnSourceThreadId parameters
  • When turnSourceChannel is set, it overrides session-level lastChannel for "last" channel resolution
  • Backward compatible—all parameters optional, existing call sites unchanged
  • Test coverage includes cross-channel guard scenarios and priority handling

As noted in PR description: This provides the infrastructure; upstream callers (channel monitors, agent turn pipeline) need updates in follow-up work to pass turn source context.

Confidence Score: 5/5

  • Safe to merge with minimal risk—addresses privacy issue with backward-compatible infrastructure
  • Clean implementation following existing patterns, comprehensive test coverage for new functionality, well-documented with issue references, backward compatible design, and PR explicitly scopes this as infrastructure-only (follow-up needed for caller integration)
  • No files require special attention

Last reviewed commit: 53250c8

Copy link
Contributor

@arosstale arosstale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix for a real privacy bug. The turn-source channel override pattern is the right approach — it pins reply routing to the originating channel rather than trusting the mutable session-level lastChannel. The null-coalescing fallbacks (turnSourceTo ?? context?.to) preserve backward compatibility when the caller doesn't set the new fields. Tests cover the cross-channel race. LGTM.

@steipete steipete merged commit 389ccda into openclaw:main Feb 25, 2026
25 checks passed
@steipete
Copy link
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm check && pnpm build && pnpm test && pnpm check:docs (full test run hit unrelated flaky src/gateway/server.auth.test timeout assertion); pnpm test src/infra/outbound/targets.test.ts src/infra/outbound/agent-delivery.test.ts src/commands/agent.delivery.test.ts src/gateway/server-methods/agent.test.ts
  • Land commit: 93dc388
  • Merge commit: 389ccda

Thanks @brandonwise!

margulans pushed a commit to margulans/Neiron-AI-assistant that referenced this pull request Feb 25, 2026
Jackson3195 pushed a commit to Jackson3195/openclaw-with-a-personal-touch that referenced this pull request Feb 25, 2026
brianleach pushed a commit to brianleach/openclaw that referenced this pull request Feb 26, 2026
execute008 pushed a commit to execute008/openclaw that referenced this pull request Feb 27, 2026
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 1, 2026
…@brandonwise)

(cherry picked from commit 885452f)

# Conflicts:
#	src/infra/outbound/agent-delivery.ts
#	src/infra/outbound/targets.test.ts
#	src/infra/outbound/targets.ts
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 3, 2026
…@brandonwise)

(cherry picked from commit 885452f)

# Conflicts:
#	src/infra/outbound/agent-delivery.ts
#	src/infra/outbound/targets.test.ts
#	src/infra/outbound/targets.ts
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…rce routing

This fixes group message routing failures introduced in v2026.2.24 when
PR openclaw#24571 added fail-closed security hardening for shared-session
cross-channel replies.

The issue: `buildEmbeddedContextFromTemplate` was only setting
`messageProvider` but not `messageChannel`. The delivery planner's
`turnSourceChannel` logic reads from `runContext.messageChannel`, so
without it set, the turn-source binding fails and replies to group
messages don't route back to the originating channel.

This affects all plugin-based channels (Mattermost, etc.) and potentially
built-in channels (Telegram, WhatsApp) in group/channel contexts where
the session might be shared across channels.

The fix adds `messageChannel` alongside `messageProvider` using the same
resolved value from `resolveOriginMessageProvider`.

Related issues:
- openclaw#24571 (original security hardening PR)
- openclaw#29637 (Telegram group messages not working)
- openclaw#19509 (WhatsApp group messages not working)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…tibility

The change in v2026.2.24 that made pairing store DM-only broke existing
setups where users relied on DM pairing to access group chats. This restores
the previous behavior where storeAllowFrom is included in effectiveGroupAllowFrom.

Note: Command authorization in groups still requires explicit allowlist
(not pairing store) to maintain security separation.

See: openclaw#24571

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…tibility

The change in v2026.2.24 that made pairing store DM-only broke existing
setups where users relied on DM pairing to access group chats. This restores
the previous behavior where storeAllowFrom is included in effectiveGroupAllowFrom.

Added groupAuthIncludesPairingStore option (defaults to true) for users who
want to opt into the stricter v2026.2.24 behavior.

Note: Command authorization in groups still requires explicit allowlist
(not pairing store) to maintain security separation.

See: openclaw#24571

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…rce routing

This fixes group message routing failures introduced in v2026.2.24 when
PR openclaw#24571 added fail-closed security hardening for shared-session
cross-channel replies.

The issue: `buildEmbeddedContextFromTemplate` was only setting
`messageProvider` but not `messageChannel`. The delivery planner's
`turnSourceChannel` logic reads from `runContext.messageChannel`, so
without it set, the turn-source binding fails and replies to group
messages don't route back to the originating channel.

This affects all plugin-based channels (Mattermost, etc.) and potentially
built-in channels (Telegram, WhatsApp) in group/channel contexts where
the session might be shared across channels.

The fix adds `messageChannel` alongside `messageProvider` using the same
resolved value from `resolveOriginMessageProvider`.

Related issues:
- openclaw#24571 (original security hardening PR)
- openclaw#29637 (Telegram group messages not working)
- openclaw#19509 (WhatsApp group messages not working)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…tibility

The change in v2026.2.24 that made pairing store DM-only broke existing
setups where users relied on DM pairing to access group chats. This restores
the previous behavior where storeAllowFrom is included in effectiveGroupAllowFrom.

Added groupAuthIncludesPairingStore option (defaults to true) for users who
want to opt into the stricter v2026.2.24 behavior.

Note: Command authorization in groups still requires explicit allowlist
(not pairing store) to maintain security separation.

See: openclaw#24571

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…rce routing

This fixes group message routing failures introduced in v2026.2.24 when
PR openclaw#24571 added fail-closed security hardening for shared-session
cross-channel replies.

The issue: `buildEmbeddedContextFromTemplate` was only setting
`messageProvider` but not `messageChannel`. The delivery planner's
`turnSourceChannel` logic reads from `runContext.messageChannel`, so
without it set, the turn-source binding fails and replies to group
messages don't route back to the originating channel.

This affects all plugin-based channels (Mattermost, etc.) and potentially
built-in channels (Telegram, WhatsApp) in group/channel contexts where
the session might be shared across channels.

The fix adds `messageChannel` alongside `messageProvider` using the same
resolved value from `resolveOriginMessageProvider`.

Related issues:
- openclaw#24571 (original security hardening PR)
- openclaw#29637 (Telegram group messages not working)
- openclaw#19509 (WhatsApp group messages not working)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…tibility

The change in v2026.2.24 that made pairing store DM-only broke existing
setups where users relied on DM pairing to access group chats. This restores
the previous behavior where storeAllowFrom is included in effectiveGroupAllowFrom.

Added groupAuthIncludesPairingStore option (defaults to true) for users who
want to opt into the stricter v2026.2.24 behavior.

Note: Command authorization in groups still requires explicit allowlist
(not pairing store) to maintain security separation.

See: openclaw#24571

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…rce routing

This fixes group message routing failures introduced in v2026.2.24 when
PR openclaw#24571 added fail-closed security hardening for shared-session
cross-channel replies.

The issue: `buildEmbeddedContextFromTemplate` was only setting
`messageProvider` but not `messageChannel`. The delivery planner's
`turnSourceChannel` logic reads from `runContext.messageChannel`, so
without it set, the turn-source binding fails and replies to group
messages don't route back to the originating channel.

This affects all plugin-based channels (Mattermost, etc.) and potentially
built-in channels (Telegram, WhatsApp) in group/channel contexts where
the session might be shared across channels.

The fix adds `messageChannel` alongside `messageProvider` using the same
resolved value from `resolveOriginMessageProvider`.

Related issues:
- openclaw#24571 (original security hardening PR)
- openclaw#29637 (Telegram group messages not working)
- openclaw#19509 (WhatsApp group messages not working)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…tibility

The change in v2026.2.24 that made pairing store DM-only broke existing
setups where users relied on DM pairing to access group chats. This restores
the previous behavior where storeAllowFrom is included in effectiveGroupAllowFrom.

Added groupAuthIncludesPairingStore option (defaults to true) for users who
want to opt into the stricter v2026.2.24 behavior.

Note: Command authorization in groups still requires explicit allowlist
(not pairing store) to maintain security separation.

See: openclaw#24571

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…rce routing

This fixes group message routing failures introduced in v2026.2.24 when
PR openclaw#24571 added fail-closed security hardening for shared-session
cross-channel replies.

The issue: `buildEmbeddedContextFromTemplate` was only setting
`messageProvider` but not `messageChannel`. The delivery planner's
`turnSourceChannel` logic reads from `runContext.messageChannel`, so
without it set, the turn-source binding fails and replies to group
messages don't route back to the originating channel.

This affects all plugin-based channels (Mattermost, etc.) and potentially
built-in channels (Telegram, WhatsApp) in group/channel contexts where
the session might be shared across channels.

The fix adds `messageChannel` alongside `messageProvider` using the same
resolved value from `resolveOriginMessageProvider`.

Related issues:
- openclaw#24571 (original security hardening PR)
- openclaw#29637 (Telegram group messages not working)
- openclaw#19509 (WhatsApp group messages not working)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…tibility

The change in v2026.2.24 that made pairing store DM-only broke existing
setups where users relied on DM pairing to access group chats. This restores
the previous behavior where storeAllowFrom is included in effectiveGroupAllowFrom.

Added groupAuthIncludesPairingStore option (defaults to true) for users who
want to opt into the stricter v2026.2.24 behavior.

Note: Command authorization in groups still requires explicit allowlist
(not pairing store) to maintain security separation.

See: openclaw#24571

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…rce routing

This fixes group message routing failures introduced in v2026.2.24 when
PR openclaw#24571 added fail-closed security hardening for shared-session
cross-channel replies.

The issue: `buildEmbeddedContextFromTemplate` was only setting
`messageProvider` but not `messageChannel`. The delivery planner's
`turnSourceChannel` logic reads from `runContext.messageChannel`, so
without it set, the turn-source binding fails and replies to group
messages don't route back to the originating channel.

This affects all plugin-based channels (Mattermost, etc.) and potentially
built-in channels (Telegram, WhatsApp) in group/channel contexts where
the session might be shared across channels.

The fix adds `messageChannel` alongside `messageProvider` using the same
resolved value from `resolveOriginMessageProvider`.

Related issues:
- openclaw#24571 (original security hardening PR)
- openclaw#29637 (Telegram group messages not working)
- openclaw#19509 (WhatsApp group messages not working)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 3, 2026
…tibility

The change in v2026.2.24 that made pairing store DM-only broke existing
setups where users relied on DM pairing to access group chats. This restores
the previous behavior where storeAllowFrom is included in effectiveGroupAllowFrom.

Added groupAuthIncludesPairingStore option (defaults to true) for users who
want to opt into the stricter v2026.2.24 behavior.

Note: Command authorization in groups still requires explicit allowlist
(not pairing store) to maintain security separation.

See: openclaw#24571

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 4, 2026
…rce routing

This fixes group message routing failures introduced in v2026.2.24 when
PR openclaw#24571 added fail-closed security hardening for shared-session
cross-channel replies.

The issue: `buildEmbeddedContextFromTemplate` was only setting
`messageProvider` but not `messageChannel`. The delivery planner's
`turnSourceChannel` logic reads from `runContext.messageChannel`, so
without it set, the turn-source binding fails and replies to group
messages don't route back to the originating channel.

This affects all plugin-based channels (Mattermost, etc.) and potentially
built-in channels (Telegram, WhatsApp) in group/channel contexts where
the session might be shared across channels.

The fix adds `messageChannel` alongside `messageProvider` using the same
resolved value from `resolveOriginMessageProvider`.

Related issues:
- openclaw#24571 (original security hardening PR)
- openclaw#29637 (Telegram group messages not working)
- openclaw#19509 (WhatsApp group messages not working)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GixGosu pushed a commit to GixGosu/openclaw that referenced this pull request Mar 4, 2026
…tibility

The change in v2026.2.24 that made pairing store DM-only broke existing
setups where users relied on DM pairing to access group chats. This restores
the previous behavior where storeAllowFrom is included in effectiveGroupAllowFrom.

Added groupAuthIncludesPairingStore option (defaults to true) for users who
want to opt into the stricter v2026.2.24 behavior.

Note: Command authorization in groups still requires explicit allowlist
(not pairing store) to maintain security separation.

See: openclaw#24571

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
thebenjaminlee pushed a commit to escape-velocity-ventures/openclaw that referenced this pull request Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Security / Privacy / Session Routing

3 participants