prisma-zod-generator
Version:
Prisma 2+ generator to emit Zod schemas from your Prisma schema
322 lines • 12.5 kB
JavaScript
;
/**
* 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