Skip to content

Commit aa75477

Browse files
committed
fix(zalouser): reject unsafe inbound timestamps
1 parent 598e3f8 commit aa75477

2 files changed

Lines changed: 58 additions & 10 deletions

File tree

extensions/zalouser/src/zalo-js.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import fs from "node:fs";
33
import os from "node:os";
44
import path from "node:path";
55
import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
6-
import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
6+
import {
7+
asFiniteNumberInRange,
8+
parseStrictFiniteNumber,
9+
parseStrictNonNegativeInteger,
10+
} from "openclaw/plugin-sdk/number-runtime";
711
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
812
import {
913
privateFileStoreSync,
@@ -53,6 +57,8 @@ const GROUP_CONTEXT_CACHE_TTL_MS = 5 * 60_000;
5357
const GROUP_CONTEXT_CACHE_MAX_ENTRIES = 500;
5458
const LISTENER_WATCHDOG_INTERVAL_MS = 30_000;
5559
const LISTENER_WATCHDOG_MAX_GAP_MS = 35_000;
60+
const ZALO_TIMESTAMP_MS_THRESHOLD = 1_000_000_000_000;
61+
const MAX_SAFE_ZALO_TIMESTAMP_SECONDS = Number.MAX_SAFE_INTEGER / 1000;
5662

5763
const apiByProfile = new Map<string, API>();
5864
const apiInitByProfile = new Map<string, Promise<API>>();
@@ -261,17 +267,27 @@ function normalizeMessageContent(content: unknown): string {
261267
}
262268

263269
function resolveInboundTimestamp(rawTs: unknown): number {
264-
if (typeof rawTs === "number" && Number.isFinite(rawTs)) {
265-
return rawTs > 1_000_000_000_000 ? rawTs : rawTs * 1000;
270+
const parsed =
271+
typeof rawTs === "number"
272+
? rawTs
273+
: typeof rawTs === "string"
274+
? parseStrictFiniteNumber(rawTs)
275+
: undefined;
276+
const timestamp = asFiniteNumberInRange(parsed, {
277+
min: 0,
278+
minExclusive: true,
279+
max: Number.MAX_SAFE_INTEGER,
280+
});
281+
if (timestamp === undefined) {
282+
return Date.now();
266283
}
267-
const parsed = Number.parseInt(
268-
typeof rawTs === "string" ? rawTs : typeof rawTs === "number" ? String(rawTs) : "",
269-
10,
270-
);
271-
if (!Number.isFinite(parsed) || parsed <= 0) {
284+
if (timestamp > ZALO_TIMESTAMP_MS_THRESHOLD) {
285+
return Math.trunc(timestamp);
286+
}
287+
if (timestamp > MAX_SAFE_ZALO_TIMESTAMP_SECONDS) {
272288
return Date.now();
273289
}
274-
return parsed > 1_000_000_000_000 ? parsed : parsed * 1000;
290+
return Math.trunc(timestamp * 1000);
275291
}
276292

277293
function extractMentionIds(rawMentions: unknown): string[] {

extensions/zalouser/src/zalo-quote-metadata.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { describe, expect, it } from "vitest";
1+
import { afterEach, describe, expect, it, vi } from "vitest";
22
import { __testing as zaloTesting } from "./zalo-js.js";
33

4+
afterEach(() => {
5+
vi.useRealTimers();
6+
});
7+
48
describe("Zalo quote metadata extraction (#86851)", () => {
59
it("extracts quote id, owner, and body from zca-js message data", () => {
610
const message = zaloTesting.toInboundMessage(
@@ -43,3 +47,31 @@ describe("Zalo quote metadata extraction (#86851)", () => {
4347
expect(message?.quotedBody).toBeUndefined();
4448
});
4549
});
50+
51+
describe("Zalo inbound timestamp normalization", () => {
52+
function inboundTimestamp(ts: unknown): number | undefined {
53+
return zaloTesting.toInboundMessage({
54+
type: 0,
55+
data: {
56+
uidFrom: "123456789",
57+
idTo: "987654321",
58+
content: "plain message",
59+
ts,
60+
},
61+
} as unknown as Parameters<typeof zaloTesting.toInboundMessage>[0])?.timestampMs;
62+
}
63+
64+
it("normalizes second and millisecond timestamps", () => {
65+
expect(inboundTimestamp(1_764_000_000)).toBe(1_764_000_000_000);
66+
expect(inboundTimestamp("1764000000.5")).toBe(1_764_000_000_500);
67+
expect(inboundTimestamp(1_764_000_000_000)).toBe(1_764_000_000_000);
68+
});
69+
70+
it("falls back for partial or unsafe timestamps", () => {
71+
vi.useFakeTimers();
72+
vi.setSystemTime(1_700_000_000_000);
73+
74+
expect(inboundTimestamp("1764000000abc")).toBe(1_700_000_000_000);
75+
expect(inboundTimestamp("9007199254740993")).toBe(1_700_000_000_000);
76+
});
77+
});

0 commit comments

Comments
 (0)