From e658ac1e64359958e0ab76f494dd927090717993 Mon Sep 17 00:00:00 2001 From: gonzaloriestra <14979109+gonzaloriestra@users.noreply.github.com> Date: Sun, 21 Jun 2026 00:34:01 +0000 Subject: [PATCH 1/2] [Security] use secure PRNG for random array selection Replace Math.random() with globalThis.crypto.getRandomValues() in takeRandomFromArray to ensure cryptographically secure random selection. --- .../cli-kit/src/public/common/array.test.ts | 26 ++++++++++++++++++- packages/cli-kit/src/public/common/array.ts | 4 ++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/cli-kit/src/public/common/array.test.ts b/packages/cli-kit/src/public/common/array.test.ts index 720becbac0b..02e8e906f58 100644 --- a/packages/cli-kit/src/public/common/array.test.ts +++ b/packages/cli-kit/src/public/common/array.test.ts @@ -1,6 +1,30 @@ -import {difference, uniq, uniqBy} from './array.js' +import {difference, takeRandomFromArray, uniq, uniqBy} from './array.js' import {describe, test, expect} from 'vitest' +describe('takeRandomFromArray', () => { + test('returns an element from the array', () => { + // Given + const array = [1, 2, 3, 4, 5] + + // When + const got = takeRandomFromArray(array) + + // Then + expect(array).toContain(got) + }) + + test('handles arrays of various sizes', () => { + // Given + const arrays = [[1], [1, 2], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]] + + // When/Then + arrays.forEach((array) => { + const got = takeRandomFromArray(array) + expect(array).toContain(got) + }) + }) +}) + describe('uniqBy', () => { test('removes duplicates', () => { // When diff --git a/packages/cli-kit/src/public/common/array.ts b/packages/cli-kit/src/public/common/array.ts index 8b22c0b7b93..803fbb42294 100644 --- a/packages/cli-kit/src/public/common/array.ts +++ b/packages/cli-kit/src/public/common/array.ts @@ -9,7 +9,9 @@ import type {List, ValueIteratee} from 'lodash' * @returns A random element from the array. */ export function takeRandomFromArray(array: T[]): T { - return array[Math.floor(Math.random() * array.length)]! + // We use a cryptographically secure PRNG to ensure the selection is non-predictable. + const randomIndex = globalThis.crypto.getRandomValues(new Uint32Array(1))[0]! % array.length + return array[randomIndex]! } /** From 96d0ddb1eb1b68b8758653718729016686e3d8a3 Mon Sep 17 00:00:00 2001 From: gonzaloriestra <14979109+gonzaloriestra@users.noreply.github.com> Date: Sun, 21 Jun 2026 00:42:53 +0000 Subject: [PATCH 2/2] [Security] use secure PRNG and fix modulo bias for random array selection Replace Math.random() with globalThis.crypto.getRandomValues() in takeRandomFromArray to ensure cryptographically secure random selection. Implemented rejection sampling to eliminate modulo bias. --- packages/cli-kit/src/public/common/array.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/cli-kit/src/public/common/array.ts b/packages/cli-kit/src/public/common/array.ts index 803fbb42294..18e2da7b960 100644 --- a/packages/cli-kit/src/public/common/array.ts +++ b/packages/cli-kit/src/public/common/array.ts @@ -9,9 +9,22 @@ import type {List, ValueIteratee} from 'lodash' * @returns A random element from the array. */ export function takeRandomFromArray(array: T[]): T { + if (array.length === 0) return undefined as T + // We use a cryptographically secure PRNG to ensure the selection is non-predictable. - const randomIndex = globalThis.crypto.getRandomValues(new Uint32Array(1))[0]! % array.length - return array[randomIndex]! + // We use rejection sampling to eliminate modulo bias. + const arrayLength = array.length + const range = 4294967296 + const limit = range - (range % arrayLength) + + const buffer = new Uint32Array(1) + let randomIndex + do { + globalThis.crypto.getRandomValues(buffer) + randomIndex = buffer[0]! + } while (randomIndex >= limit) + + return array[randomIndex % arrayLength]! } /**