Skip to content

Commit 21559f8

Browse files
committed
🛡️ Sentinel: [MEDIUM] Fix weak random number generation
1 parent 03e9c85 commit 21559f8

3 files changed

Lines changed: 10 additions & 2 deletions

File tree

.jules/sentinel.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,9 @@
6464
**Vulnerability:** Rate limiting on private endpoints relied solely on the client's IP address (`getClientIpAddress(req)`) rather than the authenticated user's API key hash.
6565
**Learning:** Because the rate limiter bucketed by IP address instead of API key, an attacker with a single valid API key could distribute their requests across multiple IP addresses to completely bypass the intended limits, leading to potential Denial of Service (resource exhaustion). Conversely, multiple legitimate users sharing the same NAT IP address would unfairly exhaust each other's quota.
6666
**Prevention:** In API key-authenticated endpoints, `getBucketKey` should utilize the validated API key hash (`req.apiKeyValidation?.keyHash`) as the primary identifier, falling back to the client IP address only for unauthenticated paths.
67+
68+
## 2026-03-19 - Weak Random Number Generation for Nonces and Jitters
69+
70+
**Vulnerability:** `Math.random()` was being used to generate a uniqueness nonce for tracking Hypixel API calls in Redis, and for calculating a jitter offset for retry delays.
71+
**Learning:** `Math.random()` generates a pseudo-random number sequence that is predictable and not cryptographically secure. While the risk of collision or timing attacks on simple jitter delays or Redis set members might be theoretically low, using weak PRNG constructs introduces subtle vulnerabilities that can be exploited by motivated attackers performing timing analysis or forcing predictable nonce sequences.
72+
**Prevention:** Always use cryptographically secure random number generators (CSPRNG), such as `randomBytes` or `randomInt` from `node:crypto`, when calculating jitter, nonces, session identifiers, or hashing salts in backend logic.

backend/src/services/hypixel.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import axios, { type AxiosResponseHeaders, type RawAxiosResponseHeaders } from 'axios';
22
import https from 'node:https';
3+
import { randomInt } from 'node:crypto';
34
import CacheableLookup from 'cacheable-lookup';
45
import {
56
CB_FAILURE_THRESHOLD,
@@ -167,7 +168,7 @@ function parseLastModified(value?: string): number | null {
167168
function jitterDelay(): number {
168169
const min = Math.max(0, HYPIXEL_RETRY_DELAY_MIN_MS);
169170
const max = Math.max(min, HYPIXEL_RETRY_DELAY_MAX_MS);
170-
return min + Math.random() * (max - min);
171+
return max > min ? randomInt(min, max + 1) : min;
171172
}
172173

173174
async function wait(ms: number): Promise<void> {

backend/src/services/hypixelTracker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { pool, ensureInitialized } from './cache';
22
import { sql } from 'kysely';
3+
import { randomBytes } from 'node:crypto';
34
import { DatabaseType, dbType } from './database/db';
45
import { HYPIXEL_API_CALL_WINDOW_MS } from '../config';
56
import { logger } from '../util/logger';
@@ -82,7 +83,7 @@ async function loadWatermarkIfNeeded(): Promise<void> {
8283
}
8384

8485
function buildRedisRollingMember(uuid: string, calledAt: number): string {
85-
const nonce = Math.random().toString(36).slice(2);
86+
const nonce = randomBytes(8).toString('hex');
8687
return `${uuid}:${calledAt}:${nonce}`;
8788
}
8889

0 commit comments

Comments
 (0)