mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-21 06:32:16 +03:00
Release v3.8.32 (#4418)
Release v3.8.32 — see CHANGELOG.md [3.8.32] for the full list. Merged via --admin over documented non-blocking checks: CodeQL alerts ratchet (#665 fixed by #4457/#4462, auto-closes on main rescan), Integration Tests (env-flaky batch-upstream), SonarCloud/SonarQube (advisory new-code).
This commit is contained in:
committed by
GitHub
parent
d0396c200d
commit
bfaf459f3c
66
src/shared/utils/secureRandom.ts
Normal file
66
src/shared/utils/secureRandom.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user