mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(gateway): stop webchat route inheritance on channel sessions (#39175, thanks @widingmarcus-cyber)
Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
This commit is contained in:
@@ -288,6 +288,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/polling conflict recovery: reset the polling `webhookCleared` latch on `getUpdates` 409 conflicts so webhook cleanup re-runs on restart cycles and polling avoids infinite conflict loops. (#39205) Thanks @amittell.
|
||||
- Heartbeat/requests-in-flight scheduling: stop advancing `nextDueMs` and avoid immediate `scheduleNext()` timer overrides on requests-in-flight skips, so wake-layer retry cooldowns are honored and heartbeat cadence no longer drifts under sustained contention. (#39182) Thanks @MumuTW.
|
||||
- Memory/SQLite contention resilience: re-apply `PRAGMA busy_timeout` on every sync-store and QMD connection open so process restarts/reopens no longer revert to immediate `SQLITE_BUSY` failures under lock contention. (#39183) Thanks @MumuTW.
|
||||
- Gateway/webchat route safety: block webchat/control-ui clients from inheriting stored external delivery routes on channel-scoped sessions (while preserving route inheritance for UI/TUI clients), preventing cross-channel leakage from scoped chats. (#39175) Thanks @widingmarcus-cyber.
|
||||
|
||||
## 2026.3.2
|
||||
|
||||
|
||||
@@ -797,4 +797,92 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("chat.send does not inherit external routes for webchat clients on channel-scoped sessions", async () => {
|
||||
createTranscriptFixture("openclaw-chat-send-webchat-channel-scoped-no-inherit-");
|
||||
mockState.finalText = "ok";
|
||||
mockState.sessionEntry = {
|
||||
deliveryContext: {
|
||||
channel: "imessage",
|
||||
to: "+8619800001234",
|
||||
accountId: "default",
|
||||
},
|
||||
lastChannel: "imessage",
|
||||
lastTo: "+8619800001234",
|
||||
lastAccountId: "default",
|
||||
};
|
||||
const respond = vi.fn();
|
||||
const context = createChatContext();
|
||||
|
||||
// Webchat client accessing an iMessage channel-scoped session should NOT
|
||||
// inherit the external delivery route. Fixes #38957.
|
||||
await runNonStreamingChatSend({
|
||||
context,
|
||||
respond,
|
||||
idempotencyKey: "idem-webchat-channel-scoped-no-inherit",
|
||||
client: {
|
||||
connect: {
|
||||
client: {
|
||||
mode: GATEWAY_CLIENT_MODES.WEBCHAT,
|
||||
id: "openclaw-webchat",
|
||||
},
|
||||
},
|
||||
} as unknown,
|
||||
sessionKey: "agent:main:imessage:direct:+8619800001234",
|
||||
deliver: true,
|
||||
expectBroadcast: false,
|
||||
});
|
||||
|
||||
expect(mockState.lastDispatchCtx).toEqual(
|
||||
expect.objectContaining({
|
||||
OriginatingChannel: "webchat",
|
||||
OriginatingTo: undefined,
|
||||
ExplicitDeliverRoute: false,
|
||||
AccountId: undefined,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("chat.send still inherits external routes for UI clients on channel-scoped sessions", async () => {
|
||||
createTranscriptFixture("openclaw-chat-send-ui-channel-scoped-inherit-");
|
||||
mockState.finalText = "ok";
|
||||
mockState.sessionEntry = {
|
||||
deliveryContext: {
|
||||
channel: "imessage",
|
||||
to: "+8619800001234",
|
||||
accountId: "default",
|
||||
},
|
||||
lastChannel: "imessage",
|
||||
lastTo: "+8619800001234",
|
||||
lastAccountId: "default",
|
||||
};
|
||||
const respond = vi.fn();
|
||||
const context = createChatContext();
|
||||
|
||||
await runNonStreamingChatSend({
|
||||
context,
|
||||
respond,
|
||||
idempotencyKey: "idem-ui-channel-scoped-inherit",
|
||||
client: {
|
||||
connect: {
|
||||
client: {
|
||||
mode: GATEWAY_CLIENT_MODES.UI,
|
||||
id: "openclaw-tui",
|
||||
},
|
||||
},
|
||||
} as unknown,
|
||||
sessionKey: "agent:main:imessage:direct:+8619800001234",
|
||||
deliver: true,
|
||||
expectBroadcast: false,
|
||||
});
|
||||
|
||||
expect(mockState.lastDispatchCtx).toEqual(
|
||||
expect.objectContaining({
|
||||
OriginatingChannel: "imessage",
|
||||
OriginatingTo: "+8619800001234",
|
||||
ExplicitDeliverRoute: true,
|
||||
AccountId: "default",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,11 +32,7 @@ import {
|
||||
} from "../chat-abort.js";
|
||||
import { type ChatImageContent, parseMessageWithAttachments } from "../chat-attachments.js";
|
||||
import { stripEnvelopeFromMessage, stripEnvelopeFromMessages } from "../chat-sanitize.js";
|
||||
import {
|
||||
GATEWAY_CLIENT_CAPS,
|
||||
GATEWAY_CLIENT_MODES,
|
||||
hasGatewayClientCap,
|
||||
} from "../protocol/client-info.js";
|
||||
import { GATEWAY_CLIENT_CAPS, hasGatewayClientCap } from "../protocol/client-info.js";
|
||||
import {
|
||||
ErrorCodes,
|
||||
errorShape,
|
||||
@@ -168,22 +164,22 @@ function resolveChatSendOriginatingRoute(params: {
|
||||
!isChannelScopedSession &&
|
||||
typeof sessionScopeParts[1] === "string" &&
|
||||
sessionChannelHint === routeChannelCandidate;
|
||||
const isFromWebchatClient =
|
||||
isWebchatClient(params.client) || params.client?.mode === GATEWAY_CLIENT_MODES.UI;
|
||||
const isFromWebchatClient = isWebchatClient(params.client);
|
||||
const configuredMainKey = (params.mainKey ?? "main").trim().toLowerCase();
|
||||
const isConfiguredMainSessionScope =
|
||||
normalizedSessionScopeHead.length > 0 && normalizedSessionScopeHead === configuredMainKey;
|
||||
|
||||
// Keep explicit delivery for channel-scoped sessions, but refuse to inherit
|
||||
// stale external routes for shared-main and other channel-agnostic webchat/UI
|
||||
// turns where the session key does not encode the user's current target.
|
||||
// Webchat/Control UI clients never inherit external delivery routes, even when
|
||||
// accessing channel-scoped sessions. External routes are only for non-webchat
|
||||
// clients where the session key explicitly encodes an external target.
|
||||
// Preserve the old configured-main contract: any connected non-webchat client
|
||||
// may inherit the last external route even when client metadata is absent.
|
||||
const canInheritDeliverableRoute = Boolean(
|
||||
!isFromWebchatClient &&
|
||||
sessionChannelHint &&
|
||||
sessionChannelHint !== INTERNAL_MESSAGE_CHANNEL &&
|
||||
((!isChannelAgnosticSessionScope && (isChannelScopedSession || hasLegacyChannelPeerShape)) ||
|
||||
(isConfiguredMainSessionScope && params.hasConnectedClient && !isFromWebchatClient)),
|
||||
(isConfiguredMainSessionScope && params.hasConnectedClient)),
|
||||
);
|
||||
const hasDeliverableRoute =
|
||||
canInheritDeliverableRoute &&
|
||||
|
||||
Reference in New Issue
Block a user