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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user