fix: validate imessage action integers

This commit is contained in:
Peter Steinberger
2026-05-28 19:14:41 -04:00
parent 9dd8ffd767
commit 8a8767dd1e
2 changed files with 54 additions and 7 deletions
+46
View File
@@ -304,6 +304,52 @@ describe("imessage message actions", () => {
]);
});
it("rejects fractional chatId params before resolving chat GUIDs", async () => {
probeMock.getCachedIMessagePrivateApiStatus.mockReturnValue({
available: true,
v2Ready: true,
selectors: {},
});
await expect(
imessageMessageActions.handleAction?.({
action: "react",
cfg: cfg(),
params: {
chatId: 42.5,
messageId: "message-guid",
emoji: "👍",
},
} as never),
).rejects.toThrow("chatId must be a positive integer");
expect(runtimeMock.resolveChatGuidForTarget).not.toHaveBeenCalled();
expect(runtimeMock.sendReaction).not.toHaveBeenCalled();
});
it("rejects fractional partIndex values before invoking bridge actions", async () => {
probeMock.getCachedIMessagePrivateApiStatus.mockReturnValue({
available: true,
v2Ready: true,
selectors: {},
});
await expect(
imessageMessageActions.handleAction?.({
action: "react",
cfg: cfg(),
params: {
chatGuid: "iMessage;+;chat0000",
messageId: "message-guid",
emoji: "👍",
partIndex: 1.5,
},
} as never),
).rejects.toThrow("partIndex must be a non-negative integer");
expect(runtimeMock.sendReaction).not.toHaveBeenCalled();
});
it("resolves short message ids before invoking bridge actions", async () => {
probeMock.getCachedIMessagePrivateApiStatus.mockReturnValue({
available: true,
+8 -7
View File
@@ -2,7 +2,8 @@ import { readBooleanParam } from "openclaw/plugin-sdk/boolean-param";
import {
createActionGate,
jsonResult,
readNumberParam,
readNonNegativeIntegerParam,
readPositiveIntegerParam,
readReactionParams,
readStringParam,
} from "openclaw/plugin-sdk/channel-actions";
@@ -103,7 +104,7 @@ async function resolveChatGuid(params: {
if (explicitChatGuid) {
return explicitChatGuid;
}
const explicitChatId = readNumberParam(params.actionParams, "chatId", { integer: true });
const explicitChatId = readPositiveIntegerParam(params.actionParams, "chatId");
if (typeof explicitChatId === "number") {
const resolved = await params.runtime.resolveChatGuidForTarget({
target: { kind: "chat_id", chatId: explicitChatId },
@@ -198,7 +199,7 @@ function buildChatContextFromActionParams(params: {
}): IMessageChatContext {
const explicitChatGuid = readStringParam(params.actionParams, "chatGuid")?.trim();
const explicitChatIdentifier = readStringParam(params.actionParams, "chatIdentifier")?.trim();
const explicitChatId = readNumberParam(params.actionParams, "chatId", { integer: true });
const explicitChatId = readPositiveIntegerParam(params.actionParams, "chatId");
// Trim before the truthy check so a whitespace-only currentChannelId can't
// reach parseIMessageTarget (which throws on empty/whitespace input and
// would abort the whole action with a confusing "target is required").
@@ -486,7 +487,7 @@ export const imessageMessageActions: ChannelMessageActionAdapter = {
);
}
const resolvedMessageId = messageId();
const partIndex = readNumberParam(params, "partIndex", { integer: true });
const partIndex = readNonNegativeIntegerParam(params, "partIndex");
const resolvedChatGuid = await chatGuid();
const reactionsToSend = remove && !reaction ? [...TAPBACK_KINDS] : reaction ? [reaction] : [];
for (const kind of reactionsToSend) {
@@ -512,7 +513,7 @@ export const imessageMessageActions: ChannelMessageActionAdapter = {
if (!text) {
throw new Error("iMessage edit requires text, newText, or message.");
}
const partIndex = readNumberParam(params, "partIndex", { integer: true });
const partIndex = readNonNegativeIntegerParam(params, "partIndex");
const backwardsCompatMessage = readStringParam(params, "backwardsCompatMessage");
const resolvedChatGuid = await chatGuid();
await runtime.editMessage({
@@ -529,7 +530,7 @@ export const imessageMessageActions: ChannelMessageActionAdapter = {
if (action === "unsend") {
await assertPrivateApiEnabled();
const resolvedMessageId = messageId({ requireFromMe: true });
const partIndex = readNumberParam(params, "partIndex", { integer: true });
const partIndex = readNonNegativeIntegerParam(params, "partIndex");
const resolvedChatGuid = await chatGuid();
await runtime.unsendMessage({
chatGuid: resolvedChatGuid,
@@ -569,7 +570,7 @@ export const imessageMessageActions: ChannelMessageActionAdapter = {
);
}
}
const partIndex = readNumberParam(params, "partIndex", { integer: true });
const partIndex = readNonNegativeIntegerParam(params, "partIndex");
const resolvedChatGuid = await chatGuid();
const result = await runtime.sendRichMessage({
chatGuid: resolvedChatGuid,