Add request ID validation and CSPRNG fallback (FRE-4516)

- Max-length guard (256 chars) on incoming request IDs to prevent log bloat
- Format whitelist (alphanumeric, hyphen, underscore) to prevent log injection
- Replace Math.random() with crypto.randomBytes in fallback for CSPRNG
This commit is contained in:
2026-05-02 09:43:13 -04:00
parent fe754761d9
commit 8687868632

View File

@@ -1,36 +1,60 @@
import nodeCrypto from "crypto";
const MAX_REQUEST_ID_LENGTH = 256;
const REQUEST_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
/**
* Validates a request ID for safe logging: enforces max length
* and whitelist of safe characters to prevent log injection.
*/
function isValidRequestId(id: string): boolean {
if (id.length > MAX_REQUEST_ID_LENGTH) return false;
if (!REQUEST_ID_PATTERN.test(id)) return false;
return true;
}
/**
* Generates a unique request ID for distributed tracing.
* Uses crypto.randomUUID when available, falls back to a
* timestamp-based UUID v4 format.
* Node.js crypto.randomBytes-based UUID v4 format.
*/
export function generateRequestId(): string {
if (typeof crypto !== "undefined" && crypto.randomUUID) {
return crypto.randomUUID();
}
const bytes = nodeCrypto.randomBytes(16);
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const hex = bytes.toString("hex");
return (
[0, 0, 0, 0, 0].map((_, i) => {
const segment = i === 3 ? 8 : 4;
return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
.toString(16)
.padEnd(i === 4 ? 12 : 4, "0")
.slice(0, i === 4 ? 12 : 4);
}).join("-")
hex.slice(0, 8) + "-" +
hex.slice(8, 12) + "-" +
hex.slice(12, 16) + "-" +
hex.slice(16, 20) + "-" +
hex.slice(20, 32)
);
}
/**
* Extracts an existing request ID from headers or generates a new one.
* Checks standard headers: X-Request-Id, X-Correlation-Id, X-Trace-Id.
* Validates extracted IDs for safe logging (max 256 chars, alphanumeric/hyphen/underscore).
*/
export function extractOrGenerateRequestId(headers: Record<string, string | string[] | undefined>): string {
const candidates = ["x-request-id", "x-correlation-id", "x-trace-id"];
for (const key of candidates) {
const value = headers[key];
if (typeof value === "string" && value.trim()) {
return value.trim();
if (typeof value === "string") {
const trimmed = value.trim();
if (trimmed && isValidRequestId(trimmed)) {
return trimmed;
}
}
if (Array.isArray(value) && value[0]) {
return value[0].trim();
const trimmed = value[0].trim();
if (isValidRequestId(trimmed)) {
return trimmed;
}
}
}
return generateRequestId();