From a997596f67a46dddef2fdfec521be5ac85352b9d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Dec 2025 17:48:48 +0000 Subject: [PATCH] fix(security): resolve biased cryptographic random in short-code generation Use rejection sampling to eliminate modulo bias when generating short codes. Bytes outside the unbiased range [0, maxUsable) are rejected to ensure uniform distribution across the alphabet. Addresses CodeQL alert: js/biased-cryptographic-random --- src/lib/short-code.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/lib/short-code.ts b/src/lib/short-code.ts index cf3fd0a..3079dca 100644 --- a/src/lib/short-code.ts +++ b/src/lib/short-code.ts @@ -9,16 +9,27 @@ import { randomBytes } from 'crypto' const ALPHABET = '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz' /** - * Generate a random short code + * Generate a random short code using unbiased rejection sampling. + * Security: Avoids modulo bias by rejecting bytes outside the usable range. */ export function generateShortCode(length: number = 8): string { - const bytes = randomBytes(length) + const alphabetLength = ALPHABET.length + // Calculate the largest multiple of alphabetLength that fits in a byte (0-255) + // This ensures uniform distribution by rejecting biased values + const maxUsable = Math.floor(256 / alphabetLength) * alphabetLength + let code = '' - for (let i = 0; i < length; i++) { - const byte = bytes[i] - if (byte !== undefined) { - code += ALPHABET[byte % ALPHABET.length] + while (code.length < length) { + // Request more bytes than needed to reduce iterations + const bytes = randomBytes(length - code.length + 10) + + for (let i = 0; i < bytes.length && code.length < length; i++) { + const byte = bytes[i] + // Only use bytes in the unbiased range [0, maxUsable) + if (byte !== undefined && byte < maxUsable) { + code += ALPHABET[byte % alphabetLength] + } } }