diff --git a/src/channels/direct-dm-guard-policy.ts b/src/channels/direct-dm-guard-policy.ts index 3f816b496972..af0c649d4675 100644 --- a/src/channels/direct-dm-guard-policy.ts +++ b/src/channels/direct-dm-guard-policy.ts @@ -1,3 +1,5 @@ +import { resolveIntegerOption } from "../shared/number-coercion.js"; + export type DirectDmPreCryptoGuardPolicy = { allowedKinds: readonly number[]; maxFutureSkewSec: number; @@ -21,16 +23,43 @@ export type DirectDmPreCryptoGuardPolicyOverrides = Partial< export function createDirectDmPreCryptoGuardPolicy( overrides: DirectDmPreCryptoGuardPolicyOverrides = {}, ): DirectDmPreCryptoGuardPolicy { + const defaultMaxFutureSkewSec = 120; + const defaultMaxCiphertextBytes = 16 * 1024; + const defaultMaxPlaintextBytes = 8 * 1024; + const defaultWindowMs = 60_000; + const defaultMaxPerSenderPerWindow = 20; + const defaultMaxGlobalPerWindow = 200; + const defaultMaxTrackedSenderKeys = 4096; return { allowedKinds: overrides.allowedKinds ?? [4], - maxFutureSkewSec: overrides.maxFutureSkewSec ?? 120, - maxCiphertextBytes: overrides.maxCiphertextBytes ?? 16 * 1024, - maxPlaintextBytes: overrides.maxPlaintextBytes ?? 8 * 1024, + maxFutureSkewSec: resolveIntegerOption(overrides.maxFutureSkewSec, defaultMaxFutureSkewSec, { + min: 0, + }), + maxCiphertextBytes: resolveIntegerOption( + overrides.maxCiphertextBytes, + defaultMaxCiphertextBytes, + { min: 1 }, + ), + maxPlaintextBytes: resolveIntegerOption(overrides.maxPlaintextBytes, defaultMaxPlaintextBytes, { + min: 1, + }), rateLimit: { - windowMs: overrides.rateLimit?.windowMs ?? 60_000, - maxPerSenderPerWindow: overrides.rateLimit?.maxPerSenderPerWindow ?? 20, - maxGlobalPerWindow: overrides.rateLimit?.maxGlobalPerWindow ?? 200, - maxTrackedSenderKeys: overrides.rateLimit?.maxTrackedSenderKeys ?? 4096, + windowMs: resolveIntegerOption(overrides.rateLimit?.windowMs, defaultWindowMs, { min: 1 }), + maxPerSenderPerWindow: resolveIntegerOption( + overrides.rateLimit?.maxPerSenderPerWindow, + defaultMaxPerSenderPerWindow, + { min: 1 }, + ), + maxGlobalPerWindow: resolveIntegerOption( + overrides.rateLimit?.maxGlobalPerWindow, + defaultMaxGlobalPerWindow, + { min: 1 }, + ), + maxTrackedSenderKeys: resolveIntegerOption( + overrides.rateLimit?.maxTrackedSenderKeys, + defaultMaxTrackedSenderKeys, + { min: 1 }, + ), }, }; } diff --git a/src/plugin-sdk/direct-dm.test.ts b/src/plugin-sdk/direct-dm.test.ts index 0457e197796f..cbd89c421863 100644 --- a/src/plugin-sdk/direct-dm.test.ts +++ b/src/plugin-sdk/direct-dm.test.ts @@ -186,6 +186,30 @@ describe("plugin-sdk/direct-dm", () => { expect(policy.rateLimit.maxGlobalPerWindow).toBe(200); }); + it("defaults non-finite shared pre-crypto guard numeric overrides", () => { + const policy = createDirectDmPreCryptoGuardPolicy({ + maxFutureSkewSec: Number.NaN, + maxCiphertextBytes: Number.POSITIVE_INFINITY, + maxPlaintextBytes: Number.NEGATIVE_INFINITY, + rateLimit: { + windowMs: Number.NaN, + maxPerSenderPerWindow: Number.POSITIVE_INFINITY, + maxGlobalPerWindow: Number.NEGATIVE_INFINITY, + maxTrackedSenderKeys: Number.NaN, + }, + }); + + expect(policy.maxFutureSkewSec).toBe(120); + expect(policy.maxCiphertextBytes).toBe(16 * 1024); + expect(policy.maxPlaintextBytes).toBe(8 * 1024); + expect(policy.rateLimit).toEqual({ + windowMs: 60_000, + maxPerSenderPerWindow: 20, + maxGlobalPerWindow: 200, + maxTrackedSenderKeys: 4096, + }); + }); + it("dispatches direct DMs through the standard route/session/reply pipeline", async () => { const { recordInboundSession, dispatchReplyWithBufferedBlockDispatcher, runtime } = createDirectDmRuntime();