fix: bound slack interactive button urls

This commit is contained in:
Peter Steinberger
2026-04-30 03:32:03 +01:00
parent 4329cee0c0
commit 11d8ba96f9
3 changed files with 36 additions and 2 deletions

View File

@@ -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.

View File

@@ -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 } : {}),
},
];

View File

@@ -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: [