UNPKG

prisma-zod-generator

Version:

Prisma 2+ generator to emit Zod schemas from your Prisma schema

322 lines 12.5 kB
"use strict"; /** * DoS Protection Utilities * * Comprehensive protection against resource exhaustion and DoS attacks * for PZG Pro features */ Object.defineProperty(exports, "__esModule", { value: true }); exports.dosProtection = exports.DoSProtection = exports.ExecutionTimeoutError = exports.ResourceExhaustionError = exports.DEFAULT_LIMITS = void 0; exports.dosProtect = dosProtect; exports.safeChunker = safeChunker; exports.safeMap = safeMap; exports.safeFilter = safeFilter; exports.safeForEach = safeForEach; const concurrency_1 = require("./concurrency"); exports.DEFAULT_LIMITS = { maxFileSize: 50 * 1024 * 1024, // 50MB maxMemoryUsage: 500 * 1024 * 1024, // 500MB maxExecutionTime: 30000, // 30 seconds maxIterations: 10000, maxRecursionDepth: 100, maxArrayLength: 10000, maxStringLength: 1024 * 1024, // 1MB maxJSONDepth: 20, maxJSONSize: 10 * 1024 * 1024, // 10MB maxConcurrentOperations: 10, }; class ResourceExhaustionError extends Error { constructor(message, resourceType) { super(message); this.resourceType = resourceType; this.name = 'ResourceExhaustionError'; } } exports.ResourceExhaustionError = ResourceExhaustionError; class ExecutionTimeoutError extends Error { constructor(message, timeoutMs) { super(message); this.timeoutMs = timeoutMs; this.name = 'ExecutionTimeoutError'; } } exports.ExecutionTimeoutError = ExecutionTimeoutError; /** * DoS Protection Manager */ class DoSProtection { constructor(limits = {}) { this.activeOperations = 0; this.startTime = Date.now(); this.limits = { ...exports.DEFAULT_LIMITS, ...limits }; this.rateLimiter = new concurrency_1.RateLimiter(this.limits.maxConcurrentOperations, 10); // 10 ops per second } /** * Execute operation with resource limits */ async executeWithLimits(operation, operationName, customLimits) { const limits = { ...this.limits, ...customLimits }; // Rate limiting if (!(await this.rateLimiter.acquire())) { throw new ResourceExhaustionError(`Rate limit exceeded for operation: ${operationName}`, 'rate_limit'); } // Concurrent operations limit if (this.activeOperations >= limits.maxConcurrentOperations) { throw new ResourceExhaustionError(`Too many concurrent operations: ${this.activeOperations}`, 'concurrency'); } this.activeOperations++; try { // Execution time limit const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new ExecutionTimeoutError(`Operation ${operationName} timed out after ${limits.maxExecutionTime}ms`, limits.maxExecutionTime)); }, limits.maxExecutionTime); }); const operationPromise = Promise.resolve(operation()); const result = await Promise.race([operationPromise, timeoutPromise]); // Memory usage check (if available) if (process.memoryUsage) { const memUsage = process.memoryUsage(); if (memUsage.heapUsed > limits.maxMemoryUsage) { throw new ResourceExhaustionError(`Memory usage exceeded: ${Math.round(memUsage.heapUsed / 1024 / 1024)}MB > ${Math.round(limits.maxMemoryUsage / 1024 / 1024)}MB`, 'memory'); } } return result; } finally { this.activeOperations--; } } /** * Safe JSON parsing with limits */ safeJSONParse(jsonString, operationName = 'JSON.parse') { // Size check if (jsonString.length > this.limits.maxJSONSize) { throw new ResourceExhaustionError(`JSON size exceeds limit: ${jsonString.length} > ${this.limits.maxJSONSize}`, 'json_size'); } try { const result = JSON.parse(jsonString); // Depth check const depth = this.getObjectDepth(result); if (depth > this.limits.maxJSONDepth) { throw new ResourceExhaustionError(`JSON depth exceeds limit: ${depth} > ${this.limits.maxJSONDepth}`, 'json_depth'); } return result; } catch (error) { if (error instanceof ResourceExhaustionError) { throw error; } throw new Error(`Failed to parse JSON in ${operationName}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Safe array operation with size limits */ safeArrayOperation(array, operation, operationName) { if (array.length > this.limits.maxArrayLength) { throw new ResourceExhaustionError(`Array length exceeds limit: ${array.length} > ${this.limits.maxArrayLength}`, 'array_length'); } const results = []; let iterations = 0; for (let i = 0; i < array.length; i++) { if (iterations++ > this.limits.maxIterations) { throw new ResourceExhaustionError(`Iteration limit exceeded in ${operationName}: ${iterations} > ${this.limits.maxIterations}`, 'iterations'); } results.push(operation(array[i], i)); } return results; } /** * Safe string operation with length limits */ safeStringOperation(input, _operationName) { if (input.length > this.limits.maxStringLength) { throw new ResourceExhaustionError(`String length exceeds limit: ${input.length} > ${this.limits.maxStringLength}`, 'string_length'); } return input; } /** * Safe regex execution with timeout protection */ safeRegexExec(regex, input, operationName) { // Check for potentially dangerous regex patterns const regexStr = regex.toString(); const dangerousPatterns = [ /\(\.\*\+/, // (.*+ - catastrophic backtracking /\(\.\*\?\+/, // (.*?+ - catastrophic backtracking /\(\.\+\)\+/, // (.+)+ - catastrophic backtracking /\(\.\*\)\*/, // (.*)* - catastrophic backtracking /\(\.\*\)\+/, // (.*)+ - catastrophic backtracking ]; for (const pattern of dangerousPatterns) { if (pattern.test(regexStr)) { throw new ResourceExhaustionError(`Potentially dangerous regex pattern detected in ${operationName}: ${regexStr}`, 'regex_dos'); } } // String length check this.safeStringOperation(input, operationName); try { // Use timeout for regex execution return this.executeWithTimeout(() => regex.exec(input), 5000); // 5 second timeout } catch (error) { throw new ResourceExhaustionError(`Regex execution timed out or failed in ${operationName}: ${error instanceof Error ? error.message : 'Unknown error'}`, 'regex_timeout'); } } /** * Safe file size validation */ validateFileSize(size, _operationName) { if (size > this.limits.maxFileSize) { throw new ResourceExhaustionError(`File size exceeds limit: ${Math.round(size / 1024 / 1024)}MB > ${Math.round(this.limits.maxFileSize / 1024 / 1024)}MB`, 'file_size'); } } /** * Safe loop execution with iteration limits */ safeLoop(iterable, operation, operationName) { return this.executeWithLimits(async () => { let iterations = 0; let index = 0; for (const item of iterable) { if (iterations++ > this.limits.maxIterations) { throw new ResourceExhaustionError(`Loop iteration limit exceeded in ${operationName}: ${iterations} > ${this.limits.maxIterations}`, 'loop_iterations'); } await operation(item, index++); } }, operationName); } /** * Get system resource usage */ getResourceUsage() { const usage = { memory: process.memoryUsage(), uptime: Date.now() - this.startTime, activeOperations: this.activeOperations, }; if (process.cpuUsage) { usage.cpuUsage = process.cpuUsage(); } return usage; } /** * Reset DoS protection state */ reset() { this.activeOperations = 0; this.startTime = Date.now(); } // Private helper methods getObjectDepth(obj, depth = 0) { if (depth > this.limits.maxJSONDepth) { return depth; // Early exit to prevent stack overflow } if (obj === null || typeof obj !== 'object') { return depth; } if (Array.isArray(obj)) { let maxDepth = depth; for (const item of obj) { const childDepth = this.getObjectDepth(item, depth + 1); if (childDepth > maxDepth) { maxDepth = childDepth; if (maxDepth > this.limits.maxJSONDepth) { return maxDepth; } } } return maxDepth; } let maxDepth = depth; for (const value of Object.values(obj)) { const childDepth = this.getObjectDepth(value, depth + 1); if (childDepth > maxDepth) { maxDepth = childDepth; if (maxDepth > this.limits.maxJSONDepth) { return maxDepth; } } } return maxDepth; } executeWithTimeout(operation, timeoutMs) { const start = Date.now(); try { const result = operation(); // Check if operation took too long if (Date.now() - start > timeoutMs) { throw new ResourceExhaustionError(`Operation timed out after ${timeoutMs}ms`, 'operation_timeout'); } return result; } catch (error) { if (Date.now() - start > timeoutMs) { throw new ResourceExhaustionError(`Operation timed out after ${timeoutMs}ms`, 'operation_timeout'); } throw error; } } } exports.DoSProtection = DoSProtection; /** * Global DoS protection instance */ exports.dosProtection = new DoSProtection(); /** * Decorator for protecting methods against DoS */ function dosProtect(operationName, limits) { return function (target, propertyKey, descriptor) { const originalMethod = descriptor.value; const opName = operationName || `${target.constructor.name}.${propertyKey}`; descriptor.value = async function (...args) { return exports.dosProtection.executeWithLimits(() => originalMethod.apply(this, args), opName, limits); }; return descriptor; }; } /** * Safe array chunker to prevent memory exhaustion */ function* safeChunker(array, chunkSize, maxChunks) { if (array.length > exports.DEFAULT_LIMITS.maxArrayLength) { throw new ResourceExhaustionError(`Array too large for chunking: ${array.length} > ${exports.DEFAULT_LIMITS.maxArrayLength}`, 'chunk_array_size'); } let chunksGenerated = 0; const maxAllowedChunks = maxChunks || Math.ceil(exports.DEFAULT_LIMITS.maxArrayLength / chunkSize); for (let i = 0; i < array.length; i += chunkSize) { if (chunksGenerated++ > maxAllowedChunks) { throw new ResourceExhaustionError(`Too many chunks generated: ${chunksGenerated} > ${maxAllowedChunks}`, 'chunk_count'); } yield array.slice(i, i + chunkSize); } } /** * Safe map operation with limits */ function safeMap(array, mapper, operationName = 'safeMap') { return exports.dosProtection.safeArrayOperation(array, mapper, operationName); } /** * Safe filter operation with limits */ function safeFilter(array, predicate, operationName = 'safeFilter') { const results = []; exports.dosProtection.safeArrayOperation(array, (item, index) => { if (predicate(item, index)) { results.push(item); } return true; // Return value not used }, operationName); return results; } /** * Safe forEach operation with limits */ async function safeForEach(array, operation, operationName = 'safeForEach') { await exports.dosProtection.safeLoop(array, operation, operationName); } //# sourceMappingURL=dosProtection.js.map