ai_api / src /shared /utils /secureRandom.ts
Yogesh
initial deploy
cd8bd0a
Raw
History Blame Contribute Delete
3.23 kB
/**
* Cryptographically-secure RNG helpers for load-balancing / routing selection.
*
* OmniRoute's combo target selection (weighted / random / power-of-two-choices), the
* credential-deck rotation, and shadow-routing sampling pick among upstream
* providers/connections. CodeQL's `js/insecure-randomness` flags `Math.random()` in these
* paths as "randomness in a security context" β€” a false positive (provider load-balancing
* is not a secret, token, nonce or session id). Routing these few, non-hot-path selections
* through `node:crypto` removes the finding at negligible cost. These are drop-in
* replacements with identical ranges and semantics:
*
* secureRandomInt(n) === Math.floor(Math.random() * n) // integer in [0, n)
* secureRandomFloat() === Math.random() // float in [0, 1)
*
* The integer helper uses `crypto.randomInt` (unbiased rejection sampling) rather than
* `Math.floor(cryptoFloat() * n)` β€” dividing/rounding a crypto value introduces modulo bias
* (CodeQL `js/biased-cryptographic-random`).
*/
import { randomBytes, randomInt } from "node:crypto";
/** Default source: uniform float in [0, 1) from 48 bits of crypto entropy. */
function cryptoRandomFloat(): number {
const buf = randomBytes(6);
let value = 0;
for (let i = 0; i < buf.length; i++) {
value = value * 256 + buf[i];
}
return value / 2 ** 48;
}
// Test-only deterministic float source β€” `null` in production (the crypto sources above are
// used directly). Tests inject a fixed sequence via _setSecureRandomFloatSource (mirrors the
// `_resetAllDecks` test-only export). When injected, secureRandomInt derives the index from
// the same float so the deterministic selection tests keep identical assertions; production
// never takes that path, so no crypto value is divided/rounded into a biased integer.
let testFloatSource: (() => number) | null = null;
/** Uniform float in [0, 1) β€” drop-in for `Math.random()`. */
export function secureRandomFloat(): number {
return testFloatSource ? testFloatSource() : cryptoRandomFloat();
}
/**
* Uniform integer in [0, maxExclusive) β€” drop-in for `Math.floor(Math.random() * maxExclusive)`.
* Returns 0 for any `maxExclusive <= 1` (matching `Math.floor(Math.random() * {0,1})`), so
* single-element / empty selections behave exactly as before. Production uses the unbiased
* `crypto.randomInt`; only the test path (deterministic injected source) scales a float.
*/
export function secureRandomInt(maxExclusive: number): number {
if (!Number.isFinite(maxExclusive) || maxExclusive <= 1) return 0;
const max = Math.floor(maxExclusive);
if (testFloatSource) {
// Deterministic test path β€” clamp defends the probability-0 case of a source returning ~1.
return Math.min(max - 1, Math.floor(testFloatSource() * max));
}
return randomInt(max);
}
/**
* TEST ONLY β€” replace the underlying RNG with a deterministic source so selection logic can
* be asserted; pass `null` to restore the crypto source. Mirrors the `_resetAllDecks`
* test-only export convention in shuffleDeck.ts.
*/
export function _setSecureRandomFloatSource(source: (() => number) | null): void {
testFloatSource = source ?? null;
}