diff --git a/packages/types/src/requestId.ts b/packages/types/src/requestId.ts index 74713fc..e20b0d5 100644 --- a/packages/types/src/requestId.ts +++ b/packages/types/src/requestId.ts @@ -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 { 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();