diff --git a/packages/bots/telnyx/src/index.test.ts b/packages/bots/telnyx/src/index.test.ts index 5976ec1d..2068b2a3 100644 --- a/packages/bots/telnyx/src/index.test.ts +++ b/packages/bots/telnyx/src/index.test.ts @@ -185,6 +185,50 @@ describe('bot-telnyx webhook behavior', () => { } }); + it('rejects non-decimal-integer Telnyx webhook timestamps', async () => { + const keys = generateKeyPairSync('ed25519'); + const publicKey = Buffer.from(keys.publicKey.export({ format: 'der', type: 'spki' })).toString('base64'); + const handler = vi.fn(); + const handle = await bot.register(ctx(), [{ match: { type: 'message' }, handle: handler }], { + from: '+15551234567', + webhookPort: 0, + publicKey, + }) as { close(): Promise; port: number }; + + try { + const body = JSON.stringify({ + data: { + event_type: 'message.received', + payload: { from: { phone_number: '+15559876543' }, text: 'hello' }, + }, + }); + const now = Math.floor(Date.now() / 1000); + const invalidTimestamps = [ + `${now}e0`, + `0x${now.toString(16)}`, + `${now}.5`, + ` ${now} `, + `+${now}`, + ]; + + for (const timestamp of invalidTimestamps) { + const response = await fetch(`http://127.0.0.1:${handle.port}/telnyx/messaging`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'telnyx-timestamp': timestamp, + 'telnyx-signature-ed25519': signature(keys.privateKey, timestamp, body), + }, + body, + }); + expect(response.status).toBe(403); + } + expect(handler).not.toHaveBeenCalled(); + } finally { + await handle.close(); + } + }); + it('falls back to the default signature tolerance for invalid config values', async () => { const keys = generateKeyPairSync('ed25519'); const publicKey = Buffer.from(keys.publicKey.export({ format: 'der', type: 'spki' })).toString('base64'); diff --git a/packages/bots/telnyx/src/index.ts b/packages/bots/telnyx/src/index.ts index e7de5913..7c45693f 100644 --- a/packages/bots/telnyx/src/index.ts +++ b/packages/bots/telnyx/src/index.ts @@ -302,8 +302,9 @@ function telnyxPublicKey(value: string): ReturnType { } function validTimestamp(value: string, toleranceSeconds: number): boolean { + if (!/^\d+$/.test(value)) return false; const parsed = Number(value); - if (!Number.isFinite(parsed)) return false; + if (!Number.isSafeInteger(parsed)) return false; return Math.abs(Date.now() / 1000 - parsed) <= toleranceSeconds; }