Skip to content

Commit 6235720

Browse files
committed
fix(slack): validate inbound timestamp parsing
1 parent 93e15ab commit 6235720

2 files changed

Lines changed: 26 additions & 4 deletions

File tree

extensions/slack/src/monitor/message-handler/prepare.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,18 @@ describe("slack prepareSlackMessage inbound contract", () => {
707707
expect(prepared?.ackReactionPromise).toBeNull();
708708
});
709709

710+
it("does not coerce malformed Slack timestamps into inbound event times", async () => {
711+
const prepared = await prepareWithDefaultCtx(
712+
createSlackMessage({
713+
ts: "0x10",
714+
}),
715+
);
716+
717+
assertPrepared(prepared);
718+
expect(prepared.ctxPayload.Timestamp).toBeUndefined();
719+
expect(prepared.ctxPayload.MessageSid).toBe("0x10");
720+
});
721+
710722
it("primes Slack status reactions when channel replies are message-tool-only", async () => {
711723
const slackCtx = createInboundSlackCtx({
712724
cfg: {

extensions/slack/src/monitor/message-handler/prepare.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS = 4;
8383
const SLACK_HISTORY_MEDIA_MAX_BYTES = 10 * 1024 * 1024;
8484
const SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS = 1_000;
8585
const SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS = 3_000;
86+
const SLACK_TIMESTAMP_RE = /^\d+(?:\.\d+)?$/;
8687

8788
function recordString(
8889
record: Record<string, unknown> | undefined,
@@ -104,6 +105,15 @@ function recordNullableString(
104105
return normalizeOptionalString(record[key]);
105106
}
106107

108+
function resolveSlackTimestampMs(ts: string | undefined): number | undefined {
109+
const trimmed = ts?.trim();
110+
if (!trimmed || !SLACK_TIMESTAMP_RE.test(trimmed)) {
111+
return undefined;
112+
}
113+
const parsed = Number(trimmed);
114+
return Number.isFinite(parsed) ? Math.round(parsed * 1000) : undefined;
115+
}
116+
107117
function mergeSlackAssistantThreadContext(
108118
primary: Omit<SlackAssistantThreadContext, "updatedAt"> | undefined,
109119
fallback: Omit<SlackAssistantThreadContext, "updatedAt"> | undefined,
@@ -969,7 +979,7 @@ export async function prepareSlackMessage(params: {
969979
client: ctx.app.client,
970980
})
971981
: null;
972-
const timestamp = message.ts ? Math.round(Number(message.ts) * 1000) : undefined;
982+
const timestamp = resolveSlackTimestampMs(message.ts);
973983
const senderName = pendingBody ? await resolveSenderName() : undefined;
974984
await recordDroppedChannelInboundHistory({
975985
input: {
@@ -1145,7 +1155,7 @@ export async function prepareSlackMessage(params: {
11451155
const body = formatInboundEnvelope({
11461156
channel: "Slack",
11471157
from: envelopeFrom,
1148-
timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
1158+
timestamp: resolveSlackTimestampMs(message.ts),
11491159
body: textWithId,
11501160
chatType,
11511161
sender: { name: senderName, id: senderId },
@@ -1238,7 +1248,7 @@ export async function prepareSlackMessage(params: {
12381248
channel: "slack",
12391249
accountId: route.accountId,
12401250
messageId: message.ts,
1241-
timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
1251+
timestamp: resolveSlackTimestampMs(message.ts),
12421252
from: slackFrom,
12431253
sender: {
12441254
id: senderId,
@@ -1335,7 +1345,7 @@ export async function prepareSlackMessage(params: {
13351345
entry: {
13361346
sender: senderName,
13371347
body: rawBody,
1338-
timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
1348+
timestamp: resolveSlackTimestampMs(message.ts),
13391349
messageId: message.ts,
13401350
},
13411351
});

0 commit comments

Comments
 (0)