From 11d8ba96f9b14a7e5ade80af33d90ee936b76478 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 30 Apr 2026 03:32:03 +0100 Subject: [PATCH] fix: bound slack interactive button urls --- CHANGELOG.md | 1 + extensions/slack/src/blocks-render.ts | 9 ++++-- .../slack/src/shared-interactive.test.ts | 28 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efa329a8216..099566e77d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Docs: https://docs.openclaw.ai - Slack/commands: keep native command argument menus on select controls for encoded choice values up to Slack's option limit and truncate fallback button labels to Slack's button-text limit, so long valid choices no longer render invalid Slack blocks. Thanks @slackapi. - Agents/Codex: flush accepted debounced steering messages before normal app-server turn cleanup, so inbound follow-ups acknowledged as queued are not dropped when the turn completes before the debounce fires. Thanks @vincentkoc. - Slack/interactive replies: keep rendered buttons and selects within Slack Block Kit value and count limits, and align command argument select values with Slack's option limit, so overlong agent-authored choices no longer make Slack reject the whole block payload. Thanks @slackapi. +- Slack/interactive replies: drop overlong Block Kit button URLs while preserving valid callback values, so malformed link buttons no longer make Slack reject the whole interactive reply. Thanks @slackapi. - CLI/update: scope packaged Node compile caches by OpenClaw version and install metadata, so global installs no longer reuse stale compiled chunks after package updates. Thanks @pashpashpash. - Channels/Voice call: keep pre-auth webhook in-flight limiting active when socket remote address metadata is missing, so slow-body requests from stripped-IP proxy paths still share the fallback bucket. (#74453) Thanks @davidangularme. - Plugin SDK/testing: lazy-load TypeScript from the plugin test-contract runtime and add release checks for critical SDK contract entrypoint imports and bundle size, so published packages fail preflight before shipping ESM-incompatible or oversized contract helpers. Thanks @vincentkoc. diff --git a/extensions/slack/src/blocks-render.ts b/extensions/slack/src/blocks-render.ts index f7fb5344d37..37b161a732f 100644 --- a/extensions/slack/src/blocks-render.ts +++ b/extensions/slack/src/blocks-render.ts @@ -15,6 +15,7 @@ const SLACK_SECTION_TEXT_MAX = 3000; const SLACK_PLAIN_TEXT_MAX = 75; const SLACK_OPTION_VALUE_MAX = 75; const SLACK_BUTTON_VALUE_MAX = 2000; +const SLACK_BUTTON_URL_MAX = 3000; const SLACK_STATIC_SELECT_OPTIONS_MAX = 100; const SLACK_ACTION_BLOCK_ELEMENTS_MAX = 25; @@ -72,7 +73,11 @@ export function buildSlackInteractiveBlocks(interactive?: InteractiveReply): Sla button.value && isWithinSlackLimit(button.value, SLACK_BUTTON_VALUE_MAX) ? button.value : undefined; - if (!value && !button.url) { + const url = + button.url && isWithinSlackLimit(button.url, SLACK_BUTTON_URL_MAX) + ? button.url + : undefined; + if (!value && !url) { return []; } const style = resolveSlackButtonStyle(button.style); @@ -86,7 +91,7 @@ export function buildSlackInteractiveBlocks(interactive?: InteractiveReply): Sla emoji: true, }, ...(value ? { value } : {}), - ...(button.url ? { url: button.url } : {}), + ...(url ? { url } : {}), ...(style ? { style } : {}), }, ]; diff --git a/extensions/slack/src/shared-interactive.test.ts b/extensions/slack/src/shared-interactive.test.ts index e8af5d69015..abc1d764cb5 100644 --- a/extensions/slack/src/shared-interactive.test.ts +++ b/extensions/slack/src/shared-interactive.test.ts @@ -164,6 +164,34 @@ describe("buildSlackInteractiveBlocks", () => { expect(buttonBlock.elements?.[1]).not.toHaveProperty("value"); }); + it("drops Slack button URLs beyond Block Kit limits", () => { + const validUrl = `https://example.com/${"a".repeat(2980)}`; + const longUrl = `https://example.com/${"b".repeat(2981)}`; + const blocks = buildSlackInteractiveBlocks({ + blocks: [ + { + type: "buttons", + buttons: [ + { label: "Allowed", url: validUrl }, + { label: "Too long", url: longUrl }, + { label: "Fallback action", value: "fallback", url: longUrl }, + ], + }, + ], + }); + + const buttonBlock = blocks[0] as { + elements?: Array<{ value?: string; url?: string }>; + }; + + expect(validUrl).toHaveLength(3000); + expect(longUrl).toHaveLength(3001); + expect(buttonBlock.elements).toHaveLength(2); + expect(buttonBlock.elements?.[0]?.url).toBe(validUrl); + expect(buttonBlock.elements?.[1]?.value).toBe("fallback"); + expect(buttonBlock.elements?.[1]).not.toHaveProperty("url"); + }); + it("caps Slack actions blocks at the Block Kit element limit", () => { const blocks = buildSlackInteractiveBlocks({ blocks: [