Skip to content

fix(gateway): stop shared-main chat.send from inheriting stale external routes#34669

Closed
Sid-Qin wants to merge 1 commit intoopenclaw:mainfrom
Sid-Qin:fix/webchat-channel-metadata-34647
Closed

fix(gateway): stop shared-main chat.send from inheriting stale external routes#34669
Sid-Qin wants to merge 1 commit intoopenclaw:mainfrom
Sid-Qin:fix/webchat-channel-metadata-34647

Conversation

@Sid-Qin
Copy link
Copy Markdown
Contributor

@Sid-Qin Sid-Qin commented Mar 4, 2026

Summary

  • Problem: chat.send on shared-main and other channel-agnostic webchat sessions could inherit stale external delivery metadata from the session store and route replies out to the wrong channel.
  • Why it matters: Control UI replies should stay on webchat unless the session key itself encodes an explicit external target.
  • What changed: rebased onto current main, kept the current route-inheritance logic as the base, extracted the chat.send route decision into a focused helper, and narrowed the fix so channel-scoped sessions still keep explicit external delivery while shared-main/channel-agnostic webchat sessions refuse stale inherited routes.
  • What did NOT change: explicit delivery for channel-scoped sessions, CLI/non-webchat configured-main flows, and the separate Control UI visibility/direct-session fixes already merged in Fix: respect original delivery target for Control UI visibility #36030 and fix(session): keep direct WebChat replies on WebChat #37135.

Linked Issue/PR

User-visible / Behavior Changes

  • Control UI replies on shared-main sessions stay on webchat instead of leaking to the last external channel.
  • Channel-scoped sessions selected for explicit delivery keep routing back to their encoded external channel.
  • Added a changelog entry under 2026.3.3 ### Fixes.

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Dependency changes? No

Verification

  • pnpm vitest run src/gateway/server-methods/chat.directive-tags.test.ts
  • pnpm tsgo
  • pnpm check
  • pnpm audit --prod --audit-level high

Notes

  • The prior Bun CI failure on this PR was infra noise from an upstream Bun download 404, not a repository test failure. The rerun state was refreshed before this rebase push.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR fixes a cross-routing bug (#34647) in the chat.send gateway path. Previously, chat.send parsed the session key and delivery context to decide whether to inherit an external channel's routing metadata (OriginatingChannel, OriginatingTo, AccountId, MessageThreadId). When the session had a stored Slack/Telegram/Feishu delivery context, the reply dispatcher would route responses to that external channel instead of back to the webchat connection.

The fix: Remove the 37-line session-key-parsing and route-inheritance block entirely. OriginatingChannel is now unconditionally set to INTERNAL_MESSAGE_CHANNEL ("webchat"), and OriginatingTo, AccountId, and MessageThreadId are always undefined for the chat.send code path.

Key observations:

  • The two now-unused imports (parseAgentSessionKey, normalizeMessageChannel) are correctly cleaned up.
  • The removed CHANNEL_AGNOSTIC_SESSION_SCOPES and CHANNEL_SCOPED_SESSION_SHAPES constants had no other usages in the file.
  • Five tests are updated from asserting external-channel inheritance to asserting OriginatingChannel: "webchat", which correctly documents the new invariant.
  • Acknowledged breaking change: API/CLI clients that previously relied on chat.send silently inheriting an external channel route (e.g., to forward a message to Slack via webchat) will lose that behaviour. The PR description explicitly calls this out and recommends using the explicit message tool instead. This is a reasonable trade-off given that the previous behaviour was a routing bug from the user's perspective.

Confidence Score: 4/5

  • Safe to merge — the fix is narrow, well-tested, and correctly removes a cross-routing bug; the one acknowledged risk (API/CLI route-inheritance breakage) is a reasonable and documented trade-off.
  • The change is minimal (remove complex conditional routing, replace with four constant assignments), imports are cleaned up correctly, and 13 tests all pass with the updated expectations. The only non-trivial risk — that programmatic callers relying on implicit external-channel routing via chat.send will silently lose that routing — is explicitly documented in the PR description, reducing the likelihood of an unpleasant surprise post-merge.
  • No files require special attention. Both changed files are clean.

Last reviewed commit: c2e4e0f

@vincentkoc
Copy link
Copy Markdown
Member

Quick status pass on the routing cluster: this is still the canonical shared-main fix path, but I’m not merging it yet because the Bun test job is red while the other required checks are green.

Once that failing job is rerun or fixed, this remains the right PR to land for the shared-main Control UI cross-routing issue tracked in #33619.

@vincentkoc
Copy link
Copy Markdown
Member

The red Bun job here is not showing a repository failure. It failed while downloading Bun 1.3.9+cf6cdbbba with a 404 from the upstream release URL.

I’ve rerun the failed jobs so we can separate CI infrastructure noise from an actual code problem before deciding merge readiness.

… channels

chat.send always originates from the webchat/control-UI surface.  Previously,
channel-scoped session keys (e.g. agent:main:slack:direct:U…) caused
OriginatingChannel to inherit the session's stored external route, so the
reply dispatcher would route responses to Slack/Telegram instead of back to
the gateway connection.  Remove the route-inheritance logic from chat.send and
always set OriginatingChannel to INTERNAL_MESSAGE_CHANNEL ("webchat").

Closes openclaw#34647

Made-with: Cursor
@vincentkoc vincentkoc force-pushed the fix/webchat-channel-metadata-34647 branch from c2e4e0f to 15ecd73 Compare March 6, 2026 13:53
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 6, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Denial of Service via unbounded sessionKey.split(":") in chat.send routing resolution

1. 🟠 Denial of Service via unbounded sessionKey.split(":") in chat.send routing resolution

Property Value
Severity High
CWE CWE-400
Location src/gateway/server-methods/chat.ts:142-145

Description

The resolveChatSendOriginatingRoute helper performs unbounded split(":") operations on the client-supplied sessionKey (or its parsed rest). Because sessionKey has no maxLength constraint in the gateway RPC schema, an attacker can send a very large string containing many : delimiters, causing String.prototype.split to allocate a huge array and consume excessive CPU/memory.

Impact:

  • A single crafted chat.send request can trigger large allocations and high CPU usage in the gateway process.
  • This can lead to process slowdown or crash (DoS), especially with payloads near the WebSocket maxPayload limit.

Vulnerable code:

const sessionScopeParts = (parsedSessionKey?.rest ?? params.sessionKey)
  .split(":")
  .filter(Boolean);

Example payloads (illustrative):

  • sessionKey = ":".repeat(10_000_000) (creates an array with ~10M+ elements before filtering)
  • sessionKey = "agent:main:" + ":".repeat(10_000_000)

Even though the gateway has a maxPayload limit (25MB), a 25MB string containing mostly : characters can still create an enormous split array and exhaust memory.

Recommendation

Add strict bounds and avoid unbounded split on attacker-controlled strings.

Recommended defenses (layered):

  1. Enforce a maximum sessionKey length at the protocol/schema level (and ideally also at runtime):
// Example: restrict to a reasonable size
export const ChatSendParamsSchema = Type.Object({
  sessionKey: Type.String({ minLength: 1, maxLength: 256 }),// ...
});
  1. Parse only the small number of tokens you actually need, using the limit argument to split:
const rawScope = parsedSessionKey?.rest ?? params.sessionKey;// only need first 3 tokens (head + 2 shape candidates)
const sessionScopeParts = rawScope.split(":", 4).filter((p) => p.trim().length > 0);
  1. Consider rejecting keys with excessive delimiter counts or total length early:
if (rawScope.length > 256) {
  return { originatingChannel: INTERNAL_MESSAGE_CHANNEL, explicitDeliverRoute: false };
}

These changes prevent pathological sessionKey inputs from causing large intermediate allocations.


Analyzed PR: #34669 at commit 15ecd73

Last updated on: 2026-03-06T14:24:29Z

@vincentkoc vincentkoc changed the title fix(gateway): prevent webchat messages from cross-routing to external channels fix(gateway): stop shared-main chat.send from inheriting stale external routes Mar 6, 2026
@vincentkoc
Copy link
Copy Markdown
Member

Reposting this fix on a maintainer branch as #38418.

The underlying shared-main chat.send stale-route issue is still separate from the fixes that merged in #36030 and #37135, so the narrowed/rebased change continues there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui dedupe:parent Primary canonical item in dedupe cluster gateway Gateway runtime size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: chat.send inherits OriginatingChannel from session delivery context, causing duplicate delivery in dmScope=main sessions

3 participants