From 8f2e520abb50871f3fa82e61ebc22a9a6e7194fa Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 18:03:29 -0400 Subject: [PATCH] fix(apns): cap relay timeout --- src/infra/push-apns.relay.test.ts | 13 +++++++++++++ src/infra/push-apns.relay.ts | 6 ++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/infra/push-apns.relay.test.ts b/src/infra/push-apns.relay.test.ts index 763b05d07e6..b1b93dc49b5 100644 --- a/src/infra/push-apns.relay.test.ts +++ b/src/infra/push-apns.relay.test.ts @@ -1,5 +1,6 @@ import { generateKeyPairSync } from "node:crypto"; import { afterEach, describe, expect, it, vi } from "vitest"; +import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js"; import { deriveDeviceIdFromPublicKey, publicKeyRawBase64UrlFromPem, @@ -122,6 +123,18 @@ describe("push-apns.relay", () => { }); }); + it("caps oversized timeout values before they reach AbortSignal.timeout", () => { + const resolved = resolveApnsRelayConfigFromEnv({ + OPENCLAW_APNS_RELAY_BASE_URL: "https://relay.example.com", + OPENCLAW_APNS_RELAY_TIMEOUT_MS: String(Number.MAX_SAFE_INTEGER), + } as NodeJS.ProcessEnv); + + expectRelayConfig(resolved, { + baseUrl: "https://relay.example.com", + timeoutMs: MAX_TIMER_TIMEOUT_MS, + }); + }); + it("allows loopback http URLs for alternate truthy env values", () => { const resolved = resolveApnsRelayConfigFromEnv({ OPENCLAW_APNS_RELAY_BASE_URL: "http://[::1]:8787", diff --git a/src/infra/push-apns.relay.ts b/src/infra/push-apns.relay.ts index 359c4cbe3c0..5da11c412aa 100644 --- a/src/infra/push-apns.relay.ts +++ b/src/infra/push-apns.relay.ts @@ -1,5 +1,6 @@ import { URL } from "node:url"; import type { GatewayConfig } from "../config/types.gateway.js"; +import { resolveTimerTimeoutMs } from "../shared/number-coercion.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, @@ -71,10 +72,7 @@ function normalizeTimeoutMs(value: string | number | undefined): number { return DEFAULT_APNS_RELAY_TIMEOUT_MS; } const parsed = Number(raw); - if (!Number.isFinite(parsed)) { - return DEFAULT_APNS_RELAY_TIMEOUT_MS; - } - return Math.max(1000, Math.trunc(parsed)); + return resolveTimerTimeoutMs(parsed, DEFAULT_APNS_RELAY_TIMEOUT_MS, 1000); } function readAllowHttp(value: string | undefined): boolean {