From e0d6f21fad137be6613744896a1bc4718e3fe8a9 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Sun, 12 Jul 2026 16:25:59 -0400 Subject: [PATCH 1/2] fix(randId): use crypto.getRandomValues for connection IDs Math.random() gives ~30 bits of entropy and no cryptographic guarantees. Switch to crypto.getRandomValues over a Uint32 buffer so device / saved- connection IDs get full 32-bit range and don't collide across concurrent transports. Closes #1273 --- apps/web/src/core/utils/randId.test.ts | 37 +++++++------------------- apps/web/src/core/utils/randId.ts | 6 +++-- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/apps/web/src/core/utils/randId.test.ts b/apps/web/src/core/utils/randId.test.ts index 7cf40e65b..ed15b27b9 100644 --- a/apps/web/src/core/utils/randId.test.ts +++ b/apps/web/src/core/utils/randId.test.ts @@ -1,44 +1,25 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it } from "vitest"; import { randId } from "./randId.ts"; describe("randId", () => { - beforeEach(() => { - vi.restoreAllMocks(); - }); - - it("returns a number", () => { - const result = randId(); - expect(typeof result).toBe("number"); - }); - it("returns an integer", () => { const result = randId(); + expect(typeof result).toBe("number"); expect(Number.isInteger(result)).toBe(true); }); - it("uses Math.random to generate the number", () => { - const mockRandom = vi.spyOn(Math, "random").mockReturnValue(0.5); - const result = randId(); - - expect(mockRandom).toHaveBeenCalled(); - expect(result).toBe(Math.floor(0.5 * 1e9)); - }); - - it("returns a value between 0 and 1e9 (exclusive)", () => { + it("returns a value within the uint32 range", () => { const result = randId(); expect(result).toBeGreaterThanOrEqual(0); - expect(result).toBeLessThan(1e9); + expect(result).toBeLessThanOrEqual(0xffffffff); }); - it("returns different values on subsequent calls", () => { - vi.spyOn(Math, "random").mockRestore(); - - const results = new Set(); - - for (let i = 0; i < 100; i++) { + it("returns unique values across many calls", () => { + const results = new Set(); + for (let i = 0; i < 1000; i++) { results.add(randId()); } - - expect(results.size).toBeGreaterThan(95); + // With 32 bits of entropy over 1000 samples, collisions are vanishingly rare. + expect(results.size).toBe(1000); }); }); diff --git a/apps/web/src/core/utils/randId.ts b/apps/web/src/core/utils/randId.ts index 2d2a5939b..3fae03dd3 100644 --- a/apps/web/src/core/utils/randId.ts +++ b/apps/web/src/core/utils/randId.ts @@ -1,3 +1,5 @@ -export const randId = () => { - return Math.floor(Math.random() * 1e9); +export const randId = (): number => { + const buf = new Uint32Array(1); + crypto.getRandomValues(buf); + return buf[0]; }; From 9c98066c30a7046d80ba541ba802f5fcc25dc85e Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 15 Jul 2026 20:42:49 -0400 Subject: [PATCH 2/2] fix: assert Uint32Array index for strict TS7 compat Under strict + noUncheckedIndexedAccess (TS7 default), buf[0] is number | undefined. Uint32Array(1) guarantees length 1, so assert non-null to satisfy the number return type. --- apps/web/src/core/utils/randId.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/core/utils/randId.ts b/apps/web/src/core/utils/randId.ts index 3fae03dd3..3df88e02b 100644 --- a/apps/web/src/core/utils/randId.ts +++ b/apps/web/src/core/utils/randId.ts @@ -1,5 +1,5 @@ export const randId = (): number => { const buf = new Uint32Array(1); crypto.getRandomValues(buf); - return buf[0]; + return buf[0]!; };