fix(slack): validate inbound timestamp parsing

This commit is contained in:
Peter Steinberger
2026-05-29 08:40:37 -04:00
parent 93e15abdf6
commit 6235720c8a
2 changed files with 26 additions and 4 deletions

View File

@@ -707,6 +707,18 @@ describe("slack prepareSlackMessage inbound contract", () => {
expect(prepared?.ackReactionPromise).toBeNull();
});
it("does not coerce malformed Slack timestamps into inbound event times", async () => {
const prepared = await prepareWithDefaultCtx(
createSlackMessage({
ts: "0x10",
}),
);
assertPrepared(prepared);
expect(prepared.ctxPayload.Timestamp).toBeUndefined();
expect(prepared.ctxPayload.MessageSid).toBe("0x10");
});
it("primes Slack status reactions when channel replies are message-tool-only", async () => {
const slackCtx = createInboundSlackCtx({
cfg: {

View File

@@ -83,6 +83,7 @@ const SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS = 4;
const SLACK_HISTORY_MEDIA_MAX_BYTES = 10 * 1024 * 1024;
const SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS = 1_000;
const SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS = 3_000;
const SLACK_TIMESTAMP_RE = /^\d+(?:\.\d+)?$/;
function recordString(
record: Record<string, unknown> | undefined,
@@ -104,6 +105,15 @@ function recordNullableString(
return normalizeOptionalString(record[key]);
}
function resolveSlackTimestampMs(ts: string | undefined): number | undefined {
const trimmed = ts?.trim();
if (!trimmed || !SLACK_TIMESTAMP_RE.test(trimmed)) {
return undefined;
}
const parsed = Number(trimmed);
return Number.isFinite(parsed) ? Math.round(parsed * 1000) : undefined;
}
function mergeSlackAssistantThreadContext(
primary: Omit<SlackAssistantThreadContext, "updatedAt"> | undefined,
fallback: Omit<SlackAssistantThreadContext, "updatedAt"> | undefined,
@@ -969,7 +979,7 @@ export async function prepareSlackMessage(params: {
client: ctx.app.client,
})
: null;
const timestamp = message.ts ? Math.round(Number(message.ts) * 1000) : undefined;
const timestamp = resolveSlackTimestampMs(message.ts);
const senderName = pendingBody ? await resolveSenderName() : undefined;
await recordDroppedChannelInboundHistory({
input: {
@@ -1145,7 +1155,7 @@ export async function prepareSlackMessage(params: {
const body = formatInboundEnvelope({
channel: "Slack",
from: envelopeFrom,
timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
timestamp: resolveSlackTimestampMs(message.ts),
body: textWithId,
chatType,
sender: { name: senderName, id: senderId },
@@ -1238,7 +1248,7 @@ export async function prepareSlackMessage(params: {
channel: "slack",
accountId: route.accountId,
messageId: message.ts,
timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
timestamp: resolveSlackTimestampMs(message.ts),
from: slackFrom,
sender: {
id: senderId,
@@ -1335,7 +1345,7 @@ export async function prepareSlackMessage(params: {
entry: {
sender: senderName,
body: rawBody,
timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
timestamp: resolveSlackTimestampMs(message.ts),
messageId: message.ts,
},
});