diff --git a/CHANGELOG.md b/CHANGELOG.md index e96702cd443..6321961d6a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Slack/interactive replies: preserve `channelData.slack.blocks` through live DM delivery and preview-finalized edits so Block Kit button and select directives render instead of falling back to raw text. Thanks @vincentkoc. -- Inbound policy hardening: tighten callback and webhook sender checks across Mattermost and Google Chat, match Nextcloud Talk rooms by stable room token, treat explicit empty allowlists in Twitch and Tlon as deny-all, defer Tlon cross-channel cite expansion until after authorization, and require `operator.admin` for mutating internal `/acp` actions. Thanks @vincentkoc. +- Inbound policy hardening: tighten callback and webhook sender checks across Mattermost and Google Chat, match Nextcloud Talk rooms by stable room token, and treat explicit empty Twitch allowlists as deny-all. Thanks @vincentkoc. - CI/channel test routing: move the built-in channel suites into `test:channels` and keep them out of `test:extensions`, so extension CI no longer fails after the channel migration while targeted test routing still sends Slack, Signal, and iMessage suites to the right lane. (#46066) Thanks @scoootscooob. - Node/startup: remove leftover debug `console.log("node host PATH: ...")` that printed the resolved PATH on every `openclaw node run` invocation. (#46411) - Control UI/dashboard: preserve structured gateway shutdown reasons across restart disconnects so config-triggered restarts no longer fall back to `disconnected (1006): no reason`. (#46532) Thanks @vincentkoc. diff --git a/extensions/mattermost/src/mattermost/interactions.test.ts b/extensions/mattermost/src/mattermost/interactions.test.ts index 9c7b99c4826..dea16d51e57 100644 --- a/extensions/mattermost/src/mattermost/interactions.test.ts +++ b/extensions/mattermost/src/mattermost/interactions.test.ts @@ -741,6 +741,7 @@ describe("createMattermostInteractionHandler", () => { it("blocks button dispatch when the sender is not allowed for the action", async () => { const { context, token } = createActionContext(); const dispatchButtonClick = vi.fn(); + const handleInteraction = vi.fn(); const handler = createMattermostInteractionHandler({ client: { request: async (_path: string, init?: { method?: string }) => @@ -754,6 +755,7 @@ describe("createMattermostInteractionHandler", () => { ephemeral_text: "blocked", }, }), + handleInteraction, dispatchButtonClick, }); @@ -763,6 +765,7 @@ describe("createMattermostInteractionHandler", () => { expect(res.statusCode).toBe(200); expect(res.body).toContain("blocked"); + expect(handleInteraction).not.toHaveBeenCalled(); expect(dispatchButtonClick).not.toHaveBeenCalled(); }); diff --git a/extensions/mattermost/src/mattermost/interactions.ts b/extensions/mattermost/src/mattermost/interactions.ts index da0fc7a54f9..f4ef06cf1ed 100644 --- a/extensions/mattermost/src/mattermost/interactions.ts +++ b/extensions/mattermost/src/mattermost/interactions.ts @@ -574,32 +574,6 @@ export function createMattermostInteractionHandler(params: { `post=${payload.post_id} channel=${payload.channel_id}`, ); - if (params.handleInteraction) { - try { - const response = await params.handleInteraction({ - payload, - userName, - actionId, - actionName: clickedButtonName, - originalMessage, - context: contextWithoutToken, - post: originalPost, - }); - if (response !== null) { - res.statusCode = 200; - res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify(response)); - return; - } - } catch (err) { - log?.(`mattermost interaction: custom handler failed: ${String(err)}`); - res.statusCode = 500; - res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify({ error: "Interaction handler failed" })); - return; - } - } - if (params.authorizeButtonClick) { try { const authorization = await params.authorizeButtonClick({ @@ -627,6 +601,32 @@ export function createMattermostInteractionHandler(params: { } } + if (params.handleInteraction) { + try { + const response = await params.handleInteraction({ + payload, + userName, + actionId, + actionName: clickedButtonName, + originalMessage, + context: contextWithoutToken, + post: originalPost, + }); + if (response !== null) { + res.statusCode = 200; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify(response)); + return; + } + } catch (err) { + log?.(`mattermost interaction: custom handler failed: ${String(err)}`); + res.statusCode = 500; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ error: "Interaction handler failed" })); + return; + } + } + // Dispatch as system event so the agent can handle it. // Wrapped in try/catch — the post update below must still run even if // system event dispatch fails (e.g. missing sessionKey or channel lookup). diff --git a/extensions/nextcloud-talk/src/inbound.ts b/extensions/nextcloud-talk/src/inbound.ts index 081029782f8..10ecd924fd7 100644 --- a/extensions/nextcloud-talk/src/inbound.ts +++ b/extensions/nextcloud-talk/src/inbound.ts @@ -114,7 +114,6 @@ export async function handleNextcloudTalkInbound(params: { const roomMatch = resolveNextcloudTalkRoomMatch({ rooms: account.config.rooms, roomToken, - roomName, }); const roomConfig = roomMatch.roomConfig; if (isGroup && !roomMatch.allowed) { diff --git a/extensions/nextcloud-talk/src/policy.ts b/extensions/nextcloud-talk/src/policy.ts index 12ee843d718..15e19da84de 100644 --- a/extensions/nextcloud-talk/src/policy.ts +++ b/extensions/nextcloud-talk/src/policy.ts @@ -57,7 +57,6 @@ export type NextcloudTalkRoomMatch = { export function resolveNextcloudTalkRoomMatch(params: { rooms?: Record; roomToken: string; - roomName?: string | null; }): NextcloudTalkRoomMatch { const rooms = params.rooms ?? {}; const allowlistConfigured = Object.keys(rooms).length > 0; @@ -96,11 +95,9 @@ export function resolveNextcloudTalkGroupToolPolicy( if (!roomToken) { return undefined; } - const roomName = params.groupChannel?.trim() || undefined; const match = resolveNextcloudTalkRoomMatch({ rooms: cfg.channels?.["nextcloud-talk"]?.rooms, roomToken, - roomName, }); return match.roomConfig?.tools ?? match.wildcardConfig?.tools; }