diff --git a/IMPLEMENTATION_SUMMARY_#320.md b/IMPLEMENTATION_SUMMARY_#320.md deleted file mode 100644 index 26c04aa..0000000 --- a/IMPLEMENTATION_SUMMARY_#320.md +++ /dev/null @@ -1,326 +0,0 @@ -# Request Body Size Limit Middleware - Implementation Summary - -## Overview - -A comprehensive request body size limiting system has been implemented to prevent Denial-of-Service (DoS) attacks and protect the server from resource exhaustion caused by large incoming payloads. - -## Issue Resolution - -**GitHub Issue**: #320 - Request Body Size Limit Middleware for DoS Prevention - -### Status: ✅ RESOLVED - -All acceptance criteria have been met: -- ✅ Requests exceeding size limits rejected early (before full read) -- ✅ 413 status code returned with clear size limit information -- ✅ Memory usage protected from large payload attacks -- ✅ Different endpoints have appropriate size limits -- ✅ File uploads handle large files via streaming -- ✅ Size limit headers included in error responses -- ✅ No false positives for legitimate large uploads -- ✅ Configuration via environment variables -- ✅ Protection against zip bomb and decompression attacks -- ✅ Multipart boundaries properly validated - -## Files Created - -### Core Middleware Components - -1. **request-size-limit.config.ts** - - Configuration constants for size limits - - Content-type to limit mapping - - Configurable via environment variables - -2. **request-size-limit.middleware.ts** - - NestJS middleware for request size validation - - Monitors incoming request data chunks - - Logs oversized request attempts - - Gracefully rejects requests exceeding limits - -3. **size-limit.decorator.ts** - - `@CustomSizeLimit(bytes)` - Set custom byte limit - - `@SizeLimitConfig(config)` - Use predefined sizes - - Allows per-endpoint override of default limits - -4. **size-limit.guard.ts** - - Guard to apply custom size limits - - Integrates with decorator metadata - - Runs before request body parsing - -### Error Handling - -5. **payload-too-large.filter.ts** - - Exception filter for 413 errors - - Formats error responses consistently - - Logs payload violations - -### Monitoring & Logging - -6. **request-size-logging.interceptor.ts** - - Logs request sizes for security monitoring - - Warns on large requests (>5MB) - - Tracks content-length headers - -### Documentation - -7. **REQUEST_SIZE_LIMIT_README.md** - - Feature overview and usage - - Size limits by endpoint type - - Security considerations - - Configuration options - -8. **REQUEST_SIZE_LIMIT_EXAMPLES.md** - - Real-world usage examples - - Code samples for common scenarios - - Testing examples - - Error handling patterns - -9. **REQUEST_SIZE_LIMIT_CONFIG.md** - - Environment variable documentation - - Per-endpoint configuration guide - - Performance tuning tips - - Troubleshooting guide - -### Testing - -10. **request-size-limit.e2e-spec.ts** - - End-to-end tests for all size limits - - Unit tests for utility functions - - Error response validation - - Custom decorator testing - -## Modified Files - -### main.ts -- Added Express body parser middleware with size limits -- Configured JSON limit: 1MB -- Configured URL-encoded limit: 10MB -- Configured raw binary limit: 100MB -- Added custom error handler for payload too large -- Imported RequestSizeLoggingInterceptor - -### app.module.ts -- Imported RequestSizeLoggingInterceptor -- Registered global logging interceptor -- Imported APP_INTERCEPTOR token - -## Default Size Limits - -| Type | Limit | Content Type | -|------|-------|--------------| -| JSON | 1 MB | application/json | -| Text | 100 KB | text/plain, text/html | -| Form Data | 10 MB | multipart/form-data, application/x-www-form-urlencoded | -| Images | 50 MB | image/jpeg, image/png, image/gif, image/webp | -| Documents | 100 MB | application/pdf, application/msword, application/vnd.* | -| Raw Binary | 100 MB | application/octet-stream | - -## How It Works - -### 1. Request Processing Flow -``` -Request arrives - ↓ -Express body parser checks size - ↓ -If exceeds limit → 413 error - ↓ -If within limit → Continue to middleware - ↓ -RequestSizeLoggingInterceptor logs size - ↓ -Custom size limit guard applies (if decorator used) - ↓ -Controller receives request -``` - -### 2. Size Limit Application - -**Default Behavior**: -- Automatically applies based on Content-Type header -- JSON: 1MB, Form: 10MB, Binary: 100MB - -**Custom Behavior**: -- Use `@CustomSizeLimit(bytes)` for precise control -- Use `@SizeLimitConfig({ type })` for predefined sizes - -### 3. Error Handling - -When size exceeded: -``` -1. Body parser detects oversized payload -2. Halts reading (prevents memory exhaustion) -3. Returns HTTP 413 -4. Custom error handler formats response -5. Logging interceptor records violation -``` - -## Security Features - -### DoS Prevention -- **Early Rejection**: Stops reading before full body received -- **Memory Protection**: Prevents heap exhaustion -- **Slow Request Defense**: Works with Express timeouts - -### Attack Mitigation -- **Zip Bomb Prevention**: Raw limit prevents decompression attacks -- **Slowloris Protection**: Inherent in Express timeout handling -- **Boundary Validation**: Enforced in multipart parsing - -## Usage Examples - -### Basic (Default Behavior) -```typescript -@Post('create') -createPuzzle(@Body() dto: CreatePuzzleDto) { - // Uses default 1MB JSON limit -} -``` - -### Custom Byte Size -```typescript -@Post('upload') -@CustomSizeLimit(100 * 1024 * 1024) -uploadFile(@Body() file: Buffer) { - // 100MB limit -} -``` - -### Predefined Config -```typescript -@Post('profile-picture') -@SizeLimitConfig({ type: 'profilePictureUpload' }) -uploadPicture(@Body() file: Buffer) { - // 5MB limit -} -``` - -## Error Response Format - -```json -{ - "statusCode": 413, - "errorCode": "PAYLOAD_TOO_LARGE", - "message": "Request body exceeds maximum allowed size", - "timestamp": "2026-03-26T10:15:30.123Z", - "path": "/api/endpoint" -} -``` - -## Configuration - -### Environment Variables -```env -REQUEST_SIZE_LIMIT_ENABLED=true -LOG_OVERSIZED_REQUESTS=true -ENFORCE_ON_SIZE_LIMIT_ERROR=false -NODE_OPTIONS="--max-old-space-size=4096" -``` - -### Per-Endpoint Override -```typescript -@CustomSizeLimit(50 * 1024 * 1024) -``` - -## Testing - -### Run Tests -```bash -npm test -- request-size-limit -npm run test:e2e -- request-size-limit.e2e-spec.ts -``` - -### Test Oversized Request -```bash -curl -X POST http://localhost:3000/api/test \ - -H "Content-Type: application/json" \ - -d "$(python3 -c 'print("{\"data\":\"" + "x" * 2000000 + "\"}")')" -``` - -Expected response: HTTP 413 with error details - -## Performance Impact - -- **Minimal overhead**: Size checking adds <1ms per request -- **Memory efficient**: Data chunks don't accumulate -- **CPU impact**: Negligible - -## Monitoring & Logging - -### View Violations -```bash -# Oversized request attempts -grep "PAYLOAD_TOO_LARGE" logs/app.log - -# Large request warnings (>5MB) -grep "Large request detected" logs/app.log - -# Debug request sizes -grep "Request size:" logs/app.log -``` - -## Integration Notes - -### Works With -- ✅ JWT Authentication -- ✅ API Key validation -- ✅ Rate limiting -- ✅ File uploads (with streaming) -- ✅ Form processing -- ✅ Multipart handling - -### Doesn't Interfere With -- ✅ CORS handling -- ✅ Compression middleware -- ✅ Validation pipes -- ✅ Custom guards/interceptors - -## Future Enhancements - -Potential improvements: -- Dynamic limits based on user tier -- Per-IP rate limiting on oversized requests -- Machine learning anomaly detection -- Metrics dashboard for size violations -- S3/blob storage streaming for large files - -## Support & Troubleshooting - -See documentation files: -- `REQUEST_SIZE_LIMIT_README.md` - Overview and features -- `REQUEST_SIZE_LIMIT_EXAMPLES.md` - Code examples -- `REQUEST_SIZE_LIMIT_CONFIG.md` - Configuration guide - -## Acceptance Criteria Verification - -| Criterion | Status | Details | -|-----------|--------|---------| -| Early rejection | ✅ | Express body parser rejects before full read | -| 413 status | ✅ | Custom error handler returns proper status | -| Memory protection | ✅ | Limits prevent heap exhaustion | -| Different limits | ✅ | Content-type based + custom decorators | -| File streaming | ✅ | Configured in main.ts | -| Size headers | ✅ | Error response includes maxSize | -| No false positives | ✅ | Decorator allows custom limits | -| Config via env | ✅ | Environment variables supported | -| Zip bomb protection | ✅ | Raw limit prevents decompression | -| Boundary validation | ✅ | Express multipart handler enforces | - -## Build & Deployment - -### Build -```bash -npm run build -``` - -### Deploy -```bash -# Build will include all new middleware files -npm run build -# dist/ will contain compiled middleware - -# Start application -npm start -``` - -All middleware is automatically active on deployment with default configuration. \ No newline at end of file diff --git a/REQUEST_SIZE_LIMIT_COMPLETE_SUMMARY.md b/REQUEST_SIZE_LIMIT_COMPLETE_SUMMARY.md deleted file mode 100644 index 67d536c..0000000 --- a/REQUEST_SIZE_LIMIT_COMPLETE_SUMMARY.md +++ /dev/null @@ -1,311 +0,0 @@ -# Request Body Size Limit Middleware - Complete Implementation - -## 🎯 Issue Resolution - -**GitHub Issue**: #320 - Request Body Size Limit Middleware for DoS Prevention -**Status**: ✅ **FULLY IMPLEMENTED & TESTED** - -## 📋 Summary - -A production-ready request body size limiting system has been implemented to prevent Denial-of-Service (DoS) attacks and protect the MindBlock API from resource exhaustion caused by malicious or accidental large payload submissions. - -## ✨ Key Features - -✅ **Early Request Rejection** - Oversized requests rejected before full body is read -✅ **Memory Protection** - Prevents heap exhaustion from large payloads -✅ **Content-Type Based Limits** - Different limits for JSON, forms, files, etc. -✅ **Per-Endpoint Overrides** - Custom decorators for specific routes -✅ **Security Logging** - Monitors and logs all size limit violations -✅ **Streaming Support** - Handles large file uploads efficiently -✅ **Zero Configuration** - Works out of the box with sensible defaults -✅ **Error Handling** - Clear 413 responses with detailed information -✅ **DoS Attack Prevention** - Protects against zip bombs and decompression attacks -✅ **Production Ready** - Fully tested and documented - -## 📦 Files Created - -### Core Middleware (5 files) -``` -src/common/middleware/ -├── request-size-limit.config.ts # Size limit configurations -├── request-size-limit.middleware.ts # Main middleware implementation -└── REQUEST_SIZE_LIMIT_README.md # Comprehensive documentation - -src/common/decorators/ -└── size-limit.decorator.ts # @CustomSizeLimit & @SizeLimitConfig - -src/common/guards/ -└── size-limit.guard.ts # Guard for applying custom limits - -src/common/filters/ -└── payload-too-large.filter.ts # 413 error handler - -src/common/interceptors/ -└── request-size-logging.interceptor.ts # Security monitoring & logging -``` - -### Monitoring & Logging -- Request size tracking interceptor (registers globally) -- Oversized request warnings (>5MB) -- Security audit logs for violations - -### Documentation (4 comprehensive guides) -``` -REQUEST_SIZE_LIMIT_README.md # Feature overview -REQUEST_SIZE_LIMIT_EXAMPLES.md # Code examples & patterns -REQUEST_SIZE_LIMIT_CONFIG.md # Configuration guide -IMPLEMENTATION_SUMMARY_#320.md # This implementation summary -``` - -### Testing -``` -test/ -└── request-size-limit.e2e-spec.ts # E2E & unit tests -``` - -## 🚀 Default Configuration - -| Type | Limit | Content-Type | -|------|-------|---| -| Standard JSON API | 1 MB | `application/json` | -| Text Content | 100 KB | `text/plain`, `text/html` | -| Form Data | 10 MB | `application/x-www-form-urlencoded`, `multipart/form-data` | -| Image Uploads | 50 MB | `image/*` (jpeg, png, gif, webp) | -| Document Uploads | 100 MB | `application/pdf`, `application/msword`, etc. | -| Raw Binary | 100 MB | `application/octet-stream` | - -## 💻 Usage Examples - -### Automatic (No Code Changes Required) -```typescript -@Post('create') -createPuzzle(@Body() dto: CreatePuzzleDto) { - // Automatically uses 1MB JSON limit -} -``` - -### Custom Size Limit -```typescript -@Post('upload-document') -@CustomSizeLimit(100 * 1024 * 1024) // 100MB -uploadDocument(@Body() file: Buffer) { - // Custom size limit applied -} -``` - -### Predefined Configuration -```typescript -@Post('profile-picture') -@SizeLimitConfig({ type: 'profilePictureUpload' }) // 5MB -uploadProfilePicture(@Body() file: Buffer) { - // Uses predefined 5MB limit -} -``` - -## 🔒 Security Features - -### DoS Prevention -- **Request Size Validation** - Rejects oversized payloads early -- **Memory Exhaustion Protection** - Limits prevent heap overflow -- **Rate Limit Integration** - Works with existing rate limiting - -### Attack Mitigation -- **Zip Bomb Prevention** - Raw binary limit prevents decompression attacks -- **Slowloris Protection** - Express timeouts prevent slow request attacks -- **Multipart Validation** - Enforces proper boundary validation - -### Monitoring -- **Violation Logging** - All oversized requests logged with IP -- **Large Request Warnings** - Alerts on >5MB requests -- **Security Audit Trail** - Complete request tracking - -## 📊 Error Response - -When a request exceeds the size limit: - -```json -HTTP/1.1 413 Payload Too Large -Content-Type: application/json - -{ - "statusCode": 413, - "errorCode": "PAYLOAD_TOO_LARGE", - "message": "Request body exceeds maximum allowed size", - "timestamp": "2026-03-26T10:15:30.123Z", - "path": "/api/endpoint" -} -``` - -## ⚙️ Configuration - -### Environment Variables -```env -# Enable/disable request size limiting (default: true) -REQUEST_SIZE_LIMIT_ENABLED=true - -# Log oversized requests (default: true) -LOG_OVERSIZED_REQUESTS=true - -# Memory optimization for large payloads -NODE_OPTIONS="--max-old-space-size=4096" -``` - -### Per-Endpoint Override -```typescript -@SizeLimitConfig({ bytes: 250 * 1024 * 1024 }) // 250MB custom -@SizeLimitConfig({ type: 'bulkOperations' }) // 20MB predefined -``` - -## 🧪 Testing - -### Run Tests -```bash -npm test -- request-size-limit -npm run test:e2e -- request-size-limit.e2e-spec.ts -``` - -### Test Oversized Request -```bash -curl -X POST http://localhost:3000/api/test \ - -H "Content-Type: application/json" \ - -d @large-file.json -``` - -Expected: HTTP 413 with error details - -## 📈 Implementation Checklist - -All acceptance criteria met: - -- [x] Requests exceeding size limits rejected early -- [x] 413 status code returned with clear message -- [x] Memory usage protected from large attacks -- [x] Different endpoints have appropriate limits -- [x] File uploads support streaming -- [x] Size limit information in error responses -- [x] No false positives for legitimate uploads -- [x] Configuration via environment variables -- [x] Protection against zip bomb attacks -- [x] Multipart boundaries properly validated -- [x] Oversized request logging for security -- [x] Clear documentation and examples -- [x] Complete test coverage -- [x] Production-ready implementation - -## 🔧 Integration Points - -### Works With -✅ JWT Authentication guards -✅ API Key validation system -✅ Rate limiting middleware -✅ CORS handling -✅ File upload processing -✅ Form data handling -✅ Multipart form parsing -✅ Compression middleware - -### Modified Files -- `main.ts` - Added express body parser middleware with limits -- `app.module.ts` - Registered global interceptor for logging - -### Build Status -✅ Compiles successfully -✅ All TypeScript checks pass -✅ Distribution files generated -✅ Ready for deployment - -## 📚 Documentation - -Comprehensive documentation provided: - -1. **REQUEST_SIZE_LIMIT_README.md** - - Feature overview - - Security considerations - - Configuration options - - Troubleshooting guide - -2. **REQUEST_SIZE_LIMIT_EXAMPLES.md** - - Real-world code examples - - Common use cases - - Error handling patterns - - Testing examples - -3. **REQUEST_SIZE_LIMIT_CONFIG.md** - - Environment variables - - Per-endpoint configuration - - Performance tuning - - Compatibility notes - -4. **IMPLEMENTATION_SUMMARY_#320.md** - - Technical implementation details - - Architecture overview - - File descriptions - - Integration guide - -## 🚢 Deployment - -1. Build succeeds: `npm run build` -2. All middleware included in dist -3. Interceptor globally registered -4. Express body parsers configured -5. Custom error handler in place -6. Ready for immediate deployment - -No additional setup required - works automatically on application start. - -## 🎓 For Developers - -### Quick Start -1. Default limits apply automatically -2. For custom limits, use `@CustomSizeLimit()` or `@SizeLimitConfig()` -3. Error responses follow standard format -4. Check logs for security violations - -### Common Tasks - -**Increase limit for specific endpoint:** -```typescript -@CustomSizeLimit(200 * 1024 * 1024) -``` - -**Use predefined limit:** -```typescript -@SizeLimitConfig({ type: 'bulkOperations' }) -``` - -**Monitor violations:** -```bash -grep "PAYLOAD_TOO_LARGE" logs/app.log -``` - -## 📞 Support - -For issues or questions: -1. Check `REQUEST_SIZE_LIMIT_README.md` - Features & overview -2. Check `REQUEST_SIZE_LIMIT_EXAMPLES.md` - Code examples -3. Check `REQUEST_SIZE_LIMIT_CONFIG.md` - Configuration help -4. Review test files for implementation patterns - -## ✅ Quality Assurance - -- **Compilation**: ✅ Zero errors, all files compile -- **Testing**: ✅ E2E tests included -- **Documentation**: ✅ 4 comprehensive guides -- **Security**: ✅ DoS attack prevention verified -- **Performance**: ✅ Minimal overhead (<1ms per request) -- **Compatibility**: ✅ Works with all existing features - -## 🎉 Next Steps - -1. **Deploy** - Run `npm run build` and deploy -2. **Monitor** - Watch logs for size violations -3. **Tune** - Adjust limits based on actual usage -4. **Document API** - Update API docs with size limits - ---- - -**Implementation Date**: March 26, 2026 -**Status**: ✅ Complete and Ready for Production -**Build**: ✅ Success -**Tests**: ✅ Passing -**Documentation**: ✅ Comprehensive \ No newline at end of file diff --git a/backend/REQUEST_SIZE_LIMIT_CONFIG.md b/backend/REQUEST_SIZE_LIMIT_CONFIG.md deleted file mode 100644 index 225dffb..0000000 --- a/backend/REQUEST_SIZE_LIMIT_CONFIG.md +++ /dev/null @@ -1,167 +0,0 @@ -# Request Size Limit Configuration - -## Overview -This document describes the configuration options for the request body size limit middleware. - -## Environment Variables - -### REQUEST_SIZE_LIMIT_ENABLED -- **Type**: Boolean -- **Default**: `true` -- **Description**: Enable or disable request body size limiting globally -- **Example**: `REQUEST_SIZE_LIMIT_ENABLED=true` - -### LOG_OVERSIZED_REQUESTS -- **Type**: Boolean -- **Default**: `true` -- **Description**: Log all requests that exceed size limits for security monitoring -- **Example**: `LOG_OVERSIZED_REQUESTS=true` - -### ENFORCE_ON_SIZE_LIMIT_ERROR -- **Type**: Boolean -- **Default**: `false` -- **Description**: Whether to halt processing on size limit errors -- **Example**: `ENFORCE_ON_SIZE_LIMIT_ERROR=false` - -## Size Limits by Content Type - -### Default Configuration - -The middleware automatically applies size limits based on `Content-Type` header: - -``` -JSON (application/json): 1 MB -Form Data (multipart/form-data): 10 MB -URL-encoded (application/x-www-form-urlencoded): 10 MB -Text (text/plain, text/html): 100 KB -Images (image/*): 50 MB -Documents (application/pdf, application/msword): 100 MB -Raw Binary: 100 MB -``` - -## Per-Endpoint Configuration - -Use the `@CustomSizeLimit()` or `@SizeLimitConfig()` decorators to override defaults: - -### Example 1: Custom Byte Size -```typescript -@Post('upload') -@CustomSizeLimit(50 * 1024 * 1024) // 50 MB -uploadFile(@Body() data: any) { - // ... -} -``` - -### Example 2: Predefined Config -```typescript -@Post('profile-picture') -@SizeLimitConfig({ type: 'profilePictureUpload' }) // 5 MB -uploadProfilePicture(@Body() data: any) { - // ... -} -``` - -## Available Predefined Configs - -| Type | Size | Description | -|------|------|-------------| -| `json` | 1 MB | Standard JSON API requests | -| `form` | 10 MB | Form submissions | -| `text` | 100 KB | Text content | -| `imageUpload` | 50 MB | Image files | -| `documentUpload` | 100 MB | Document files | -| `profilePictureUpload` | 5 MB | Avatar images | -| `puzzleCreation` | 10 MB | Puzzles with content | -| `bulkOperations` | 20 MB | Bulk data operations | -| `webhookPayloads` | 5 MB | Webhook data | - -## Express Middleware Configuration - -The following Express middleware is configured in `main.ts`: - -```typescript -app.use(express.json({ limit: '1mb' })); -app.use(express.urlencoded({ limit: '10mb', extended: true })); -app.use(express.raw({ limit: '100mb', type: 'application/octet-stream' })); -``` - -## Error Response - -When a request exceeds the configured limit, the server responds with HTTP 413: - -```json -{ - "statusCode": 413, - "errorCode": "PAYLOAD_TOO_LARGE", - "message": "Request body exceeds maximum allowed size", - "timestamp": "2026-03-26T10:15:30.123Z", - "path": "/api/endpoint" -} -``` - -## Security Considerations - -### DoS Prevention -- Requests are rejected **before** full body is read -- Prevents memory exhaustion -- Works with rate limiting for comprehensive protection - -### Attack Mitigation -- **Zip Bomb Protection**: Raw limit prevents decompression attacks -- **Slowloris Protection**: Inherent in Express timeout settings -- **Multipart Boundary Validation**: Enforced by Express - -## Logging - -The system logs: - -1. **Oversized Request Attempts** - - Level: WARN - - Format: `Request body exceeds size limit: {bytes} > {limit} - {method} {path} from {ip}` - -2. **Large Request Monitoring** (>5MB) - - Level: WARN - - Format: `Large request detected: {size} - {method} {path} from {ip}` - -3. **Request Size Metrics** - - Level: DEBUG - - Format: `Request size: {size} - {method} {path}` - -## Performance Tuning - -### For High-Volume Uploads -```typescript -// Increase Node.js memory -NODE_OPTIONS="--max-old-space-size=8192" - -// Use streaming for files > 100MB -// See main.ts for streaming configuration -``` - -### For Restricted Networks -```typescript -// Reduce limits for security -// In decorator: @CustomSizeLimit(1024 * 512) // 512KB -``` - -## Compatibility - -### Supported Express Versions -- Express 4.x and above -- NestJS 8.x and above - -### Supported Node.js Versions -- Node.js 14.x and above -- Node.js 16.x (recommended) -- Node.js 18.x - -## Troubleshooting - -### Issue: Legitimate uploads rejected -**Solution**: Use `@CustomSizeLimit()` decorator on the endpoint - -### Issue: Memory usage spikes -**Solution**: Enable streaming for large files or reduce global limit - -### Issue: False positives on image uploads -**Solution**: Verify Content-Type header matches actual content \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index bb04cf7..daa2f36 100644 --- a/backend/package.json +++ b/backend/package.json @@ -55,7 +55,7 @@ "sqlite3": "^5.1.7", "stellar-sdk": "^13.3.0", "swagger-ui-express": "^5.0.1", - "typeorm": "^0.3.28" + "typeorm": "^0.3.21" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", diff --git a/backend/src/api-keys/api-key-logging.interceptor.ts b/backend/src/api-keys/api-key-logging.interceptor.ts index 8de3aef..f949111 100644 --- a/backend/src/api-keys/api-key-logging.interceptor.ts +++ b/backend/src/api-keys/api-key-logging.interceptor.ts @@ -12,24 +12,21 @@ import { RequestWithApiKey } from './api-key.middleware'; export class ApiKeyLoggingInterceptor implements NestInterceptor { private readonly logger = new Logger(ApiKeyLoggingInterceptor.name); - async intercept( - context: ExecutionContext, - next: CallHandler, - ): Promise { + intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); const response = context.switchToHttp().getResponse(); if (request.apiKey) { const startTime = Date.now(); - const result = await next.handle().toPromise(); - const duration = Date.now() - startTime; - - this.logger.log( - `API Key Usage: ${request.apiKey.id} - ${request.method} ${request.url} - ${response.statusCode} - ${duration}ms`, + return next.handle().pipe( + tap(() => { + const duration = Date.now() - startTime; + this.logger.log( + `API Key Usage: ${request.apiKey.id} - ${request.method} ${request.url} - ${response.statusCode} - ${duration}ms`, + ); + }), ); - - return result; } return next.handle(); diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index a856184..6c6210f 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -2,7 +2,6 @@ import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/c import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { EventEmitterModule } from '@nestjs/event-emitter'; -import { APP_INTERCEPTOR } from '@nestjs/core'; import { RedisModule } from './redis/redis.module'; import { AuthModule } from './auth/auth.module'; import appConfig from './config/app.config'; @@ -24,7 +23,6 @@ import { UsersService } from './users/providers/users.service'; import { GeolocationMiddleware } from './common/middleware/geolocation.middleware'; import { HealthModule } from './health/health.module'; import { ApiKeyModule } from './api-keys/api-key.module'; -import { RequestSizeLoggingInterceptor } from './common/interceptors/request-size-logging.interceptor'; // const ENV = process.env.NODE_ENV; // console.log('NODE_ENV:', process.env.NODE_ENV); @@ -108,13 +106,7 @@ import { RequestSizeLoggingInterceptor } from './common/interceptors/request-siz ApiKeyModule, ], controllers: [AppController], - providers: [ - AppService, - { - provide: APP_INTERCEPTOR, - useClass: RequestSizeLoggingInterceptor, - }, - ], + providers: [AppService], }) export class AppModule implements NestModule { /** diff --git a/backend/src/common/decorators/size-limit.decorator.ts b/backend/src/common/decorators/size-limit.decorator.ts deleted file mode 100644 index 3091f79..0000000 --- a/backend/src/common/decorators/size-limit.decorator.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; - -export const CUSTOM_SIZE_LIMIT_KEY = 'custom_size_limit'; - -/** - * Decorator to set a custom request body size limit for a specific route - * @param sizeInBytes Maximum size in bytes (can use helper like 50 * 1024 * 1024 for 50MB) - */ -export function CustomSizeLimit(sizeInBytes: number) { - return SetMetadata(CUSTOM_SIZE_LIMIT_KEY, sizeInBytes); -} - -/** - * Decorator to set size limit using predefined sizes - */ -export function SizeLimitConfig(config: { - type?: - | 'json' - | 'form' - | 'text' - | 'imageUpload' - | 'documentUpload' - | 'profilePictureUpload' - | 'puzzleCreation' - | 'bulkOperations' - | 'webhookPayloads'; - bytes?: number; -}) { - if (config.bytes !== undefined) { - return SetMetadata(CUSTOM_SIZE_LIMIT_KEY, config.bytes); - } - - const sizeMap = { - json: 1024 * 1024, - form: 10 * 1024 * 1024, - text: 100 * 1024, - imageUpload: 50 * 1024 * 1024, - documentUpload: 100 * 1024 * 1024, - profilePictureUpload: 5 * 1024 * 1024, - puzzleCreation: 10 * 1024 * 1024, - bulkOperations: 20 * 1024 * 1024, - webhookPayloads: 5 * 1024 * 1024, - }; - - const size = config.type ? sizeMap[config.type] : sizeMap.json; - return SetMetadata(CUSTOM_SIZE_LIMIT_KEY, size); -} \ No newline at end of file diff --git a/backend/src/common/filters/payload-too-large.filter.ts b/backend/src/common/filters/payload-too-large.filter.ts deleted file mode 100644 index 90bb960..0000000 --- a/backend/src/common/filters/payload-too-large.filter.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ExceptionFilter, Catch, ArgumentsHost, HttpException, Logger } from '@nestjs/common'; -import { Response } from 'express'; - -@Catch() -export class PayloadTooLargeFilter implements ExceptionFilter { - private readonly logger = new Logger(PayloadTooLargeFilter.name); - - catch(exception: any, host: ArgumentsHost) { - const ctx = host.switchToHttp(); - const response = ctx.getResponse(); - const request = ctx.getRequest(); - - // Check for payload too large errors - if ( - exception.statusCode === 413 || - exception.message?.includes('PAYLOAD_TOO_LARGE') || - exception.code === 'PAYLOAD_TOO_LARGE' - ) { - const status = 413; - const errorResponse = { - statusCode: status, - errorCode: 'PAYLOAD_TOO_LARGE', - message: - exception.message || 'Request body exceeds maximum allowed size', - maxSize: exception.maxSize, - receivedSize: exception.receivedSize, - timestamp: new Date().toISOString(), - path: request.url, - }; - - this.logger.warn( - `Payload too large: ${exception.receivedSize} bytes > ${exception.maxSize} bytes from ${request.ip}`, - ); - - return response.status(status).json(errorResponse); - } - - // Let other exceptions pass through - throw exception; - } -} \ No newline at end of file diff --git a/backend/src/common/guards/size-limit.guard.ts b/backend/src/common/guards/size-limit.guard.ts deleted file mode 100644 index 7515f27..0000000 --- a/backend/src/common/guards/size-limit.guard.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable, CanActivate, ExecutionContext, Logger } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { Request } from 'express'; -import { CUSTOM_SIZE_LIMIT_KEY } from '../decorators/size-limit.decorator'; - -@Injectable() -export class SizeLimitGuard implements CanActivate { - private readonly logger = new Logger(SizeLimitGuard.name); - - constructor(private readonly reflector: Reflector) {} - - canActivate(context: ExecutionContext): boolean { - const customSizeLimit = this.reflector.get( - CUSTOM_SIZE_LIMIT_KEY, - context.getHandler(), - ); - - if (customSizeLimit) { - const request = context.switchToHttp().getRequest(); - (request as any)._customSizeLimit = customSizeLimit; - - this.logger.debug( - `Custom size limit set to ${customSizeLimit} bytes for ${request.method} ${request.path}`, - ); - } - - return true; - } -} \ No newline at end of file diff --git a/backend/src/common/interceptors/request-size-logging.interceptor.ts b/backend/src/common/interceptors/request-size-logging.interceptor.ts deleted file mode 100644 index d21ddd6..0000000 --- a/backend/src/common/interceptors/request-size-logging.interceptor.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common'; -import { Request } from 'express'; - -@Injectable() -export class RequestSizeLoggingInterceptor implements NestInterceptor { - private readonly logger = new Logger('RequestSizeLogging'); - - async intercept(context: ExecutionContext, next: CallHandler): Promise { - const request = context.switchToHttp().getRequest(); - - // Get content length if available - const contentLength = request.headers['content-length'] - ? parseInt(request.headers['content-length'] as string, 10) - : 0; - - if (contentLength > 0) { - // Log large requests for monitoring - if (contentLength > 5 * 1024 * 1024) { - // 5MB - this.logger.warn( - `Large request detected: ${this.formatBytes(contentLength)} - ${request.method} ${request.path} from ${request.ip}`, - ); - } else { - this.logger.debug( - `Request size: ${this.formatBytes(contentLength)} - ${request.method} ${request.path}`, - ); - } - } - - return next.handle(); - } - - private formatBytes(bytes: number): string { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; - } -} \ No newline at end of file diff --git a/backend/src/common/middleware/REQUEST_SIZE_LIMIT_EXAMPLES.md b/backend/src/common/middleware/REQUEST_SIZE_LIMIT_EXAMPLES.md deleted file mode 100644 index 25fedba..0000000 --- a/backend/src/common/middleware/REQUEST_SIZE_LIMIT_EXAMPLES.md +++ /dev/null @@ -1,319 +0,0 @@ -# Request Size Limit Usage Examples - -## Basic Usage - -The request size limit middleware is applied automatically to all routes. No configuration is needed for default behavior. - -### Default Limits Apply Automatically - -```typescript -// This endpoint using default JSON limit (1MB) -@Post('create') -@Controller('api/puzzles') -export class PuzzleController { - @Post() - createPuzzle(@Body() dto: CreatePuzzleDto) { - // Max 1MB JSON payload - return this.puzzleService.create(dto); - } -} -``` - -## Custom Size Limits - -### Using CustomSizeLimit Decorator - -```typescript -import { CustomSizeLimit } from '@common/decorators/size-limit.decorator'; - -@Post('upload-document') -@CustomSizeLimit(100 * 1024 * 1024) // 100 MB -uploadDocument(@Body() file: Buffer) { - // Now accepts up to 100MB - return this.fileService.process(file); -} -``` - -### Using SizeLimitConfig Decorator - -```typescript -import { SizeLimitConfig } from '@common/decorators/size-limit.decorator'; - -@Post('profile-picture') -@SizeLimitConfig({ type: 'profilePictureUpload' }) // 5MB -uploadProfilePicture(@Body() file: Buffer) { - // Uses predefined 5MB limit - return this.userService.updateProfilePicture(file); -} - -@Post('bulk-import') -@SizeLimitConfig({ type: 'bulkOperations' }) // 20MB -bulkImport(@Body() data: any[]) { - // Uses predefined 20MB limit for bulk operations - return this.importService.processBulk(data); -} -``` - -## Real-World Examples - -### Example 1: Puzzle Creation with Images - -```typescript -import { Controller, Post, Body, UseGuards } from '@nestjs/common'; -import { SizeLimitConfig } from '@common/decorators/size-limit.decorator'; -import { AuthGuard } from '@nestjs/passport'; - -@Controller('api/puzzles') -export class PuzzleController { - constructor(private readonly puzzleService: PuzzleService) {} - - @Post() - @UseGuards(AuthGuard('jwt')) - @SizeLimitConfig({ type: 'puzzleCreation' }) // 10MB for puzzles with images - async createPuzzleWithImage( - @Body() createPuzzleDto: CreatePuzzleWithImageDto, - ) { - return this.puzzleService.createWithImage(createPuzzleDto); - } -} -``` - -### Example 2: Large File Upload - -```typescript -@Controller('api/files') -export class FileController { - constructor(private readonly fileService: FileService) {} - - @Post('upload') - @UseGuards(AuthGuard('jwt')) - @CustomSizeLimit(100 * 1024 * 1024) // 100MB for custom large files - async uploadFile( - @Body() file: Buffer, - @Headers('content-type') contentType: string, - ) { - return this.fileService.store(file, contentType); - } - - @Post('document') - @UseGuards(AuthGuard('jwt')) - @SizeLimitConfig({ type: 'documentUpload' }) // 100MB for documents - async uploadDocument(@Body() document: Buffer) { - return this.fileService.processDocument(document); - } -} -``` - -### Example 3: Bulk Operations - -```typescript -@Controller('api/bulk') -export class BulkController { - constructor(private readonly bulkService: BulkService) {} - - @Post('import-users') - @UseGuards(AuthGuard('jwt')) - @SizeLimitConfig({ type: 'bulkOperations' }) // 20MB limit - async importUsers(@Body() users: ImportUserDto[]) { - return this.bulkService.importUsers(users); - } - - @Post('update-scores') - @UseGuards(AuthGuard('jwt')) - @SizeLimitConfig({ type: 'bulkOperations' }) // 20MB limit - async updateScores(@Body() updates: ScoreUpdateDto[]) { - return this.bulkService.updateScores(updates); - } -} -``` - -### Example 4: Webhook Receivers - -```typescript -@Controller('api/webhooks') -export class WebhookController { - constructor(private readonly webhookService: WebhookService) {} - - @Post('stripe') - @SizeLimitConfig({ type: 'webhookPayloads' }) // 5MB for webhooks - async handleStripeWebhook(@Body() event: any) { - return this.webhookService.processStripe(event); - } - - @Post('github') - @SizeLimitConfig({ type: 'webhookPayloads' }) // 5MB for webhooks - async handleGithubWebhook(@Body() event: any) { - return this.webhookService.processGithub(event); - } -} -``` - -## Error Handling - -### Expected Error Response - -When a request exceeds the size limit: - -```javascript -// Request -POST /api/puzzles HTTP/1.1 -Content-Type: application/json -Content-Length: 2097152 - -{/* 2MB of data */} - -// Response -HTTP/1.1 413 Payload Too Large -Content-Type: application/json - -{ - "statusCode": 413, - "errorCode": "PAYLOAD_TOO_LARGE", - "message": "Request body exceeds maximum allowed size", - "timestamp": "2026-03-26T10:15:30.123Z", - "path": "/api/puzzles" -} -``` - -### Client-Side Handling - -```typescript -// Angular/TypeScript Service Example -uploadFile(file: File): Observable { - const maxSize = 100 * 1024 * 1024; // 100MB - - if (file.size > maxSize) { - return throwError(() => new Error(`File exceeds maximum size of 100MB`)); - } - - return this.http.post('/api/files/upload', file).pipe( - catchError((error) => { - if (error.status === 413) { - return throwError( - () => new Error('File is too large. Maximum size is 100MB.'), - ); - } - return throwError(() => error); - }), - ); -} -``` - -## Testing - -### Unit Test Example - -```typescript -describe('FileController with Custom Size Limit', () => { - let controller: FileController; - - beforeEach(async () => { - const module = await Test.createTestingModule({ - controllers: [FileController], - providers: [FileService], - }).compile(); - - controller = module.get(FileController); - }); - - it('should accept files under custom limit', async () => { - const smallFile = Buffer.alloc(50 * 1024 * 1024); // 50MB - const result = await controller.uploadFile(smallFile, 'application/pdf'); - expect(result).toBeDefined(); - }); - - it('should reject files exceeding custom limit', async () => { - const largeFile = Buffer.alloc(150 * 1024 * 1024); // 150MB - exceeds 100MB limit - await expect( - controller.uploadFile(largeFile, 'application/pdf'), - ).rejects.toThrow(); - }); -}); -``` - -### E2E Test Example - -```typescript -describe('File Upload Endpoints (e2e)', () => { - let app: INestApplication; - - beforeAll(async () => { - const moduleFixture = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it('POST /api/files/upload should reject > 100MB', async () => { - const largePayload = Buffer.alloc(150 * 1024 * 1024); - - await request(app.getHttpServer()) - .post('/api/files/upload') - .set('Authorization', `Bearer ${token}`) - .send(largePayload) - .expect(413) - .expect((res) => { - expect(res.body.errorCode).toBe('PAYLOAD_TOO_LARGE'); - }); - }); - - afterAll(async () => { - await app.close(); - }); -}); -``` - -## Configuration Tips - -### For High-Volume Servers - -```typescript -// In environment variables -NODE_OPTIONS="--max-old-space-size=8192" // 8GB heap - -// For specific endpoint -@Post('large-import') -@CustomSizeLimit(500 * 1024 * 1024) // 500MB for special cases -async importLargeDataset(@Body() data: any[]): Promise { - // Handle large dataset -} -``` - -### For Restricted Networks - -```typescript -// Reduce default limits by modifying main.ts -app.use(express.json({ limit: '512kb' })); // Reduce from 1MB -app.use(express.urlencoded({ limit: '5mb', extended: true })); // Reduce from 10MB -``` - -## Monitoring - -### View Size Limit Violations - -```bash -# Filter application logs for oversized requests -grep "PAYLOAD_TOO_LARGE" logs/app.log -grep "Large request detected" logs/app.log - -# Monitor specific endpoint -grep "POST /api/puzzles.*PAYLOAD_TOO_LARGE" logs/app.log -``` - -### Metrics Collection - -```typescript -// Service to track size limit violations -@Injectable() -export class SizeLimitMetricsService { - incrementOversizedRequests(endpoint: string, size: number): void { - // Track in monitoring system (e.g., Prometheus) - } - - logViolation(endpoint: string, method: string, ip: string): void { - // Log for security analysis - } -} -``` \ No newline at end of file diff --git a/backend/src/common/middleware/REQUEST_SIZE_LIMIT_README.md b/backend/src/common/middleware/REQUEST_SIZE_LIMIT_README.md deleted file mode 100644 index 9bfca78..0000000 --- a/backend/src/common/middleware/REQUEST_SIZE_LIMIT_README.md +++ /dev/null @@ -1,231 +0,0 @@ -# Request Body Size Limit Middleware - -## Overview - -This middleware prevents Denial-of-Service (DoS) attacks by limiting the size of incoming request bodies. Different endpoints have different size limits based on their content type and purpose. - -## Default Size Limits - -| Type | Limit | Use Case | -|------|-------|----------| -| JSON API requests | 1 MB | Standard API calls | -| Text content | 100 KB | Text-based submissions | -| Form data | 10 MB | Form submissions | -| Image uploads | 50 MB | Image file uploads | -| Document uploads | 100 MB | PDF, Word, Excel files | -| Profile pictures | 5 MB | Avatar/profile images | -| Puzzle creation | 10 MB | Puzzles with images | -| Bulk operations | 20 MB | Batch processing | -| Webhook payloads | 5 MB | Webhook receivers | - -## How It Works - -The request body size limiting is implemented through multiple layers: - -### 1. Express Middleware (main.ts) -- **JSON**: 1MB limit -- **URL-encoded**: 10MB limit -- **Raw/Binary**: 100MB limit -- Returns `413 Payload Too Large` on violation - -### 2. Custom Size Limit Decorator -- Override default limits on specific routes -- Applied at the controller method level - -### 3. Security Logging -- Logs oversized requests (>5MB) for security monitoring -- Tracks IP addresses and request details - -## Usage Examples - -### Default Behavior - -```typescript -@Post('create') -createPuzzle(@Body() dto: CreatePuzzleDto) { - // Uses default JSON limit: 1MB -} -``` - -### Custom Size Limits - -```typescript -import { CustomSizeLimit, SizeLimitConfig } from '@common/decorators/size-limit.decorator'; - -// Using custom byte size -@Post('upload-document') -@CustomSizeLimit(100 * 1024 * 1024) // 100MB -uploadDocument(@Body() file: any) { - // Uses custom 100MB limit -} - -// Using predefined configurations -@Post('upload-profile-picture') -@SizeLimitConfig({ type: 'profilePictureUpload' }) // 5MB -uploadProfilePicture(@Body() file: any) { - // Uses predefined 5MB profile picture limit -} - -// Puzzle creation with images -@Post('puzzles') -@SizeLimitConfig({ type: 'puzzleCreation' }) // 10MB -createPuzzleWithImage(@Body() dto: CreatePuzzleDto) { - // Uses 10MB limit for puzzles -} -``` - -## Error Response - -When a request exceeds the size limit: - -```json -{ - "statusCode": 413, - "errorCode": "PAYLOAD_TOO_LARGE", - "message": "Request body exceeds maximum allowed size", - "timestamp": "2026-03-26T10:15:30.123Z", - "path": "/api/puzzles" -} -``` - -## Security Features - -### DoS Prevention -- **Early Rejection**: Oversized requests are rejected before being fully read -- **Memory Protection**: Prevents large payloads from exhausting server memory -- **Rate-based Limiting**: Works in conjunction with rate limiting middleware - -### Attack Prevention -- **Slowloris Protection**: Uses timeouts on request bodies -- **Compression Bomb Protection**: Raw body limit prevents decompression attacks -- **Multipart Validation**: Enforces boundaries on multipart form data - -## Configuration - -### Environment Variables - -```env -# Enable/disable request size limiting (default: true) -REQUEST_SIZE_LIMIT_ENABLED=true - -# Log oversized requests for monitoring (default: true) -LOG_OVERSIZED_REQUESTS=true - -# Enforce custom size limits on error (default: false) -ENFORCE_ON_SIZE_LIMIT_ERROR=false -``` - -## Implementation Details - -### Main.ts middleware order: -1. **Express body parsers** - Apply size limits before processing -2. **Error handler** - Catch 413 errors from body parsers -3. **Validation pipes** - Validate structured data -4. **Correlation ID** - Track requests -5. **Exception filters** - Handle all errors uniformly - -### Supported Content Types and Limits - -```typescript -{ - 'application/json': 1MB, - 'application/x-www-form-urlencoded': 10MB, - 'multipart/form-data': 10MB, - 'text/plain': 100KB, - 'text/html': 100KB, - 'image/jpeg': 50MB, - 'image/png': 50MB, - 'image/gif': 50MB, - 'image/webp': 50MB, - 'application/pdf': 100MB, - 'application/msword': 100MB, - // ... additional MIME types -} -``` - -## Streaming for Large Files - -For applications that need to handle files larger than configured limits, streaming should be used: - -```typescript -@Post('large-file-upload') -@UseInterceptors(FileInterceptor('file')) -async uploadLargeFile(@UploadedFile() file: Express.Multer.File) { - // Use streaming to handle large files - return this.fileService.processStream(file.stream); -} -``` - -## Monitoring - -The system logs: -- All requests exceeding size limits (with IP address) -- All requests over 5MB (for security monitoring) -- Request size metrics for performance analysis - -View logs: -```bash -# Filter for oversized requests -grep "PAYLOAD_TOO_LARGE" logs/application.log - -# Monitor large requests -grep "Large request detected" logs/application.log -``` - -## Testing - -### Test Oversized JSON Request - -```bash -# Should fail with 413 -curl -X POST http://localhost:3000/api/data \ - -H "Content-Type: application/json" \ - -d "$(python3 -c 'print("[" + "x" * 2000000 + "]")')" -``` - -### Test Custom Size Limit - -```bash -# Create endpoint with custom 50MB limit -@Post('upload') -@CustomSizeLimit(50 * 1024 * 1024) -upload(@Body() data: any) { } - -# Should succeed with file < 50MB -curl -X POST http://localhost:3000/api/upload \ - -H "Content-Type: application/octet-stream" \ - --data-binary @large-file.bin -``` - -## Troubleshooting - -### "Payload Too Large" on legitimate uploads -- Increase the custom size limit for that route -- Use `@CustomSizeLimit()` decorator -- Verify content-type header is correct - -### Memory issues with uploads -- Enable streaming where possible -- Increase Node.js heap size: `NODE_OPTIONS="--max-old-space-size=4096"` -- Increase specific endpoint limit incrementally - -### False positives on large JSON payloads -- Check if JSON structure is necessary -- Consider pagination for bulk operations -- Use binary/streaming endpoints for large data - -## Best Practices - -1. **Set appropriate limits** - Match limits to actual use cases -2. **Monitor violations** - Regular review of 413 errors -3. **Inform clients** - Document limits in API documentation -4. **Use streaming** - For file uploads larger than 100MB -5. **Test limits** - Verify size limits work as intended -6. **Log monitoring** - Alert on suspicious patterns - -## Related Features - -- **Rate Limiting**: Prevents request flooding -- **API Key Validation**: Tracks usage per key -- **CORS**: Handles cross-origin requests -- **Compression**: Gzip middleware (before size check) \ No newline at end of file diff --git a/backend/src/common/middleware/request-size-limit.config.ts b/backend/src/common/middleware/request-size-limit.config.ts deleted file mode 100644 index 7ba24bb..0000000 --- a/backend/src/common/middleware/request-size-limit.config.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; - -export const DEFAULT_SIZE_LIMITS = { - // Standard API requests (JSON) - json: 1024 * 1024, // 1MB - - // Text content - text: 100 * 1024, // 100KB - - // Form data - form: 10 * 1024 * 1024, // 10MB - - // File uploads - imageUpload: 50 * 1024 * 1024, // 50MB - documentUpload: 100 * 1024 * 1024, // 100MB - profilePictureUpload: 5 * 1024 * 1024, // 5MB - - // Puzzle creation (with images) - puzzleCreation: 10 * 1024 * 1024, // 10MB - - // Bulk operations - bulkOperations: 20 * 1024 * 1024, // 20MB - - // Webhook payloads - webhookPayloads: 5 * 1024 * 1024, // 5MB -}; - -export const CONTENT_TYPE_LIMITS = { - 'application/json': DEFAULT_SIZE_LIMITS.json, - 'application/x-www-form-urlencoded': DEFAULT_SIZE_LIMITS.form, - 'multipart/form-data': DEFAULT_SIZE_LIMITS.form, - 'text/plain': DEFAULT_SIZE_LIMITS.text, - 'text/html': DEFAULT_SIZE_LIMITS.text, - 'image/jpeg': DEFAULT_SIZE_LIMITS.imageUpload, - 'image/png': DEFAULT_SIZE_LIMITS.imageUpload, - 'image/gif': DEFAULT_SIZE_LIMITS.imageUpload, - 'image/webp': DEFAULT_SIZE_LIMITS.imageUpload, - 'application/pdf': DEFAULT_SIZE_LIMITS.documentUpload, - 'application/msword': DEFAULT_SIZE_LIMITS.documentUpload, - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': - DEFAULT_SIZE_LIMITS.documentUpload, - 'application/vnd.ms-excel': DEFAULT_SIZE_LIMITS.documentUpload, - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': - DEFAULT_SIZE_LIMITS.documentUpload, -}; - -export interface RequestSizeLimitConfig { - enabled: boolean; - logOversizedRequests: boolean; - enforceOnError: boolean; -} - -@Injectable() -export class RequestSizeLimitConfig { - enabled = process.env.REQUEST_SIZE_LIMIT_ENABLED !== 'false'; - logOversizedRequests = process.env.LOG_OVERSIZED_REQUESTS !== 'false'; - enforceOnError = process.env.ENFORCE_ON_SIZE_LIMIT_ERROR === 'true'; -} \ No newline at end of file diff --git a/backend/src/common/middleware/request-size-limit.middleware.ts b/backend/src/common/middleware/request-size-limit.middleware.ts deleted file mode 100644 index b2a42e4..0000000 --- a/backend/src/common/middleware/request-size-limit.middleware.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Injectable, NestMiddleware, Logger } from '@nestjs/common'; -import { Request, Response, NextFunction } from 'express'; -import { - DEFAULT_SIZE_LIMITS, - CONTENT_TYPE_LIMITS, -} from './request-size-limit.config'; - -interface RequestWithSizeData extends Request { - _sizeCheckPassed?: boolean; - _receivedSize?: number; -} - -@Injectable() -export class RequestSizeLimitMiddleware implements NestMiddleware { - private readonly logger = new Logger(RequestSizeLimitMiddleware.name); - - async use(req: RequestWithSizeData, res: Response, next: NextFunction) { - // Get content-type - const contentType = req.headers['content-type'] as string; - const baseContentType = this.getBaseContentType(contentType); - - // Determine size limit based on content type - const sizeLimit = this.getSizeLimitForContentType(baseContentType); - - // Override size check if custom limit is set - const customLimit = (req as any)._customSizeLimit; - const finalLimit = customLimit || sizeLimit; - - let receivedSize = 0; - let sizeLimitExceeded = false; - - // Monitor data chunks - req.on('data', (chunk) => { - receivedSize += chunk.length; - - if (receivedSize > finalLimit && !sizeLimitExceeded) { - sizeLimitExceeded = true; - req.pause(); - - this.logger.warn( - `Request body exceeds size limit: ${receivedSize} bytes > ${finalLimit} bytes - ${req.method} ${req.path} from ${req.ip}`, - ); - - const error: any = new Error('PAYLOAD_TOO_LARGE'); - error.statusCode = 413; - error.errorCode = 'PAYLOAD_TOO_LARGE'; - error.maxSize = finalLimit; - error.receivedSize = receivedSize; - - req.emit('error', error); - } - }); - - // Handle errors - const originalError = res.on.bind(res); - req.once('error', (err: any) => { - if (err && err.statusCode === 413) { - res.status(413).json({ - statusCode: 413, - errorCode: 'PAYLOAD_TOO_LARGE', - message: `Request body exceeds maximum size of ${this.formatBytes(finalLimit)}`, - maxSize: finalLimit, - receivedSize: err.receivedSize, - timestamp: new Date().toISOString(), - }); - } - }); - - // Store size info for later use - req._sizeCheckPassed = true; - req._receivedSize = receivedSize; - - next(); - } - - private getSizeLimitForContentType(contentType: string): number { - // Check for exact match first - if (CONTENT_TYPE_LIMITS[contentType]) { - return CONTENT_TYPE_LIMITS[contentType]; - } - - // Check for partial match - for (const [type, limit] of Object.entries(CONTENT_TYPE_LIMITS)) { - if (contentType.includes(type)) { - return limit; - } - } - - // Default to JSON limit - return DEFAULT_SIZE_LIMITS.json; - } - - private getBaseContentType(contentTypeHeader: string): string { - if (!contentTypeHeader) { - return 'application/json'; // Default to JSON - } - - // Remove charset and other parameters - return contentTypeHeader.split(';')[0].trim().toLowerCase(); - } - - private formatBytes(bytes: number): string { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; - } -} \ No newline at end of file diff --git a/backend/src/health/health.service.spec.ts b/backend/src/health/health.service.spec.ts index 3dab414..6bb450c 100644 --- a/backend/src/health/health.service.spec.ts +++ b/backend/src/health/health.service.spec.ts @@ -95,13 +95,12 @@ describe('HealthService', () => { const result = await service.getReadinessHealth(); - // Database and Redis should be healthy (these are the critical checks) + expect(result.status).toBe('healthy'); + expect(result.checks).toBeDefined(); expect(result.checks!.database.status).toBe('healthy'); expect(result.checks!.redis.status).toBe('healthy'); - - // Memory and filesystem status may vary by environment, just check they exist - expect(result.checks!.memory).toBeDefined(); - expect(result.checks!.filesystem).toBeDefined(); + expect(result.checks!.memory.status).toBe('healthy'); + expect(result.checks!.filesystem.status).toBe('healthy'); }); it('should return unhealthy when database fails', async () => { @@ -236,7 +235,7 @@ describe('HealthService', () => { await service.getDetailedHealth(); // Second call with skip cache - await service.getDetailedHealthSkipCache(); + await service.getDetailedHealth(); // Should call dependencies twice expect(mockConnection.query).toHaveBeenCalledTimes(2); diff --git a/backend/src/health/health.service.ts b/backend/src/health/health.service.ts index 52d48ab..ba1f344 100644 --- a/backend/src/health/health.service.ts +++ b/backend/src/health/health.service.ts @@ -92,38 +92,6 @@ export class HealthService { }; } - async getDetailedHealthSkipCache(): Promise { - const options: HealthCheckOptions = { - includeDetails: true, - timeout: HEALTH_CHECK_TIMEOUT, - skipCache: true - }; - - const status = await this.performHealthChecks(options); - - // Determine overall status - const statuses = Object.values(status).map((check: HealthCheck) => check.status); - const hasUnhealthy = statuses.includes('unhealthy'); - const hasDegraded = statuses.includes('degraded'); - - let overallStatus: 'healthy' | 'degraded' | 'unhealthy'; - if (hasUnhealthy) { - overallStatus = 'unhealthy'; - } else if (hasDegraded) { - overallStatus = 'degraded'; - } else { - overallStatus = 'healthy'; - } - - return { - status: overallStatus, - version: this.version, - uptime: Math.floor((Date.now() - this.startTime) / 1000), - timestamp: new Date().toISOString(), - checks: status as unknown as Record, - }; - } - private async performHealthChecks(options: HealthCheckOptions): Promise { const cacheKey = `health-checks-${JSON.stringify(options)}`; @@ -267,9 +235,7 @@ export class HealthService { try { const fs = require('fs').promises; - // Use a cross-platform temp directory check - const tempDir = process.env.TEMP || process.env.TMP || '.'; - await fs.access(tempDir, fs.constants.W_OK); + await fs.access('/tmp', fs.constants.W_OK); const responseTime = Date.now() - startTime; @@ -278,7 +244,6 @@ export class HealthService { responseTime, details: { writable: true, - path: tempDir, responseTime: `${responseTime}ms`, }, }; diff --git a/backend/src/main.ts b/backend/src/main.ts index a8a5516..ec9a1de 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,7 +1,6 @@ import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import * as express from 'express'; import { AllExceptionsFilter } from './common/filters/http-exception.filter'; import { CorrelationIdMiddleware } from './common/middleware/correlation-id.middleware'; import { AppModule } from './app.module'; @@ -10,42 +9,6 @@ import { HealthService } from './health/health.service'; async function bootstrap() { const app = await NestFactory.create(AppModule); - // Configure request body size limits to prevent DoS attacks - // Apply body parsing middleware with size limits BEFORE other middleware - app.use(express.json({ limit: '1mb' })); - app.use(express.urlencoded({ limit: '10mb', extended: true })); - app.use( - express.raw({ - limit: '100mb', - type: 'application/octet-stream', - }), - ); - - // Custom error handler for payload too large errors from body parser - app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { - if (err.status === 413 || err.code === 'PAYLOAD_TOO_LARGE') { - return res.status(413).json({ - statusCode: 413, - errorCode: 'PAYLOAD_TOO_LARGE', - message: `Request body exceeds maximum allowed size`, - timestamp: new Date().toISOString(), - path: req.url, - }); - } - - if (err.type === 'entity.too.large') { - return res.status(413).json({ - statusCode: 413, - errorCode: 'PAYLOAD_TOO_LARGE', - message: `Request body exceeds maximum allowed size`, - timestamp: new Date().toISOString(), - path: req.url, - }); - } - - next(err); - }); - // Enable global validation app.useGlobalPipes( new ValidationPipe({ diff --git a/backend/test/request-size-limit.e2e-spec.ts b/backend/test/request-size-limit.e2e-spec.ts deleted file mode 100644 index 6b3d83e..0000000 --- a/backend/test/request-size-limit.e2e-spec.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from '../src/app.module'; - -describe('Request Size Limit (e2e)', () => { - let app: INestApplication; - - beforeAll(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - afterAll(async () => { - await app.close(); - }); - - describe('POST /api/test - Default JSON limit (1MB)', () => { - it('should accept requests under 1MB', async () => { - const smallPayload = { data: 'x'.repeat(500 * 1024) }; // 500KB - - const response = await request(app.getHttpServer()) - .post('/api/test') - .send(smallPayload) - .expect((res) => { - // Should not return 413 - expect(res.status).not.toBe(413); - }); - }); - - it('should reject requests exceeding 1MB', async () => { - const largePayload = { data: 'x'.repeat(2 * 1024 * 1024) }; // 2MB - - await request(app.getHttpServer()) - .post('/api/test') - .send(largePayload) - .expect(413) - .expect((res) => { - expect(res.body.errorCode).toBe('PAYLOAD_TOO_LARGE'); - expect(res.body.statusCode).toBe(413); - }); - }); - }); - - describe('POST /api/form - Form data limit (10MB)', () => { - it('should accept form payloads under 10MB', async () => { - const formData = new FormData(); - formData.append('field', 'x'.repeat(5 * 1024 * 1024)); // 5MB - - await request(app.getHttpServer()) - .post('/api/form') - .send(formData) - .expect((res) => { - expect(res.status).not.toBe(413); - }); - }); - - it('should reject form payloads exceeding 10MB', async () => { - const formData = new FormData(); - formData.append('field', 'x'.repeat(15 * 1024 * 1024)); // 15MB - - await request(app.getHttpServer()) - .post('/api/form') - .send(formData) - .expect(413); - }); - }); - - describe('Content-Type specific limits', () => { - it('should apply JSON limit to application/json', async () => { - const payload = { data: 'x'.repeat(2 * 1024 * 1024) }; // 2MB - - await request(app.getHttpServer()) - .post('/api/test') - .set('Content-Type', 'application/json') - .send(JSON.stringify(payload)) - .expect(413); - }); - - it('should apply text limit to text/plain', async () => { - const payload = 'x'.repeat(200 * 1024); // 200KB - - await request(app.getHttpServer()) - .post('/api/text') - .set('Content-Type', 'text/plain') - .send(payload) - .expect(413); - }); - }); - - describe('Error Response Format', () => { - it('should return proper 413 error response', async () => { - const payload = { data: 'x'.repeat(2 * 1024 * 1024) }; // 2MB - - await request(app.getHttpServer()) - .post('/api/test') - .send(payload) - .expect(413) - .expect((res) => { - expect(res.body).toHaveProperty('statusCode', 413); - expect(res.body).toHaveProperty('errorCode', 'PAYLOAD_TOO_LARGE'); - expect(res.body).toHaveProperty('message'); - expect(res.body).toHaveProperty('timestamp'); - expect(res.body).toHaveProperty('path'); - }); - }); - }); - - describe('Custom Size Limit Decorator', () => { - it('should apply custom size limits when decorator is used', async () => { - // This would require a test endpoint with @CustomSizeLimit(50 * 1024 * 1024) - // The test demonstrates the concept - const payload = { data: 'x'.repeat(30 * 1024 * 1024) }; // 30MB - - // Assuming endpoint at /api/custom-upload with 50MB limit - const response = await request(app.getHttpServer()) - .post('/api/custom-upload') - .send(payload); - - // Should succeed (not 413) with custom 50MB limit - expect(response.status).not.toBe(413); - }); - }); -}); - -// Unit tests for size limit utilities -describe('Request Size Utilities', () => { - describe('formatBytes', () => { - const testCases = [ - { bytes: 0, expected: '0 Bytes' }, - { bytes: 1024, expected: '1 KB' }, - { bytes: 1024 * 1024, expected: '1 MB' }, - { bytes: 1024 * 1024 * 1024, expected: '1 GB' }, - { bytes: 500 * 1024, expected: '500 KB' }, - ]; - - testCases.forEach(({ bytes, expected }) => { - it(`should format ${bytes} bytes as ${expected}`, () => { - // Test the formatBytes function logic - const formatted = formatBytes(bytes); - expect(formatted).toBe(expected); - }); - }); - }); - - describe('getBaseContentType', () => { - const testCases = [ - { input: 'application/json', expected: 'application/json' }, - { input: 'application/json; charset=utf-8', expected: 'application/json' }, - { input: 'multipart/form-data; boundary=----', expected: 'multipart/form-data' }, - { input: 'text/plain; charset=utf-8', expected: 'text/plain' }, - ]; - - testCases.forEach(({ input, expected }) => { - it(`should extract base content type from "${input}"`, () => { - // Test the getBaseContentType function logic - const base = getBaseContentType(input); - expect(base).toBe(expected); - }); - }); - }); - - describe('getSizeLimitForContentType', () => { - const testCases = [ - { contentType: 'application/json', expected: 1024 * 1024 }, // 1MB - { contentType: 'multipart/form-data', expected: 10 * 1024 * 1024 }, // 10MB - { contentType: 'image/jpeg', expected: 50 * 1024 * 1024 }, // 50MB - { contentType: 'application/pdf', expected: 100 * 1024 * 1024 }, // 100MB - ]; - - testCases.forEach(({ contentType, expected }) => { - it(`should return ${expected} bytes for ${contentType}`, () => { - // Test the getSizeLimitForContentType function logic - const limit = getSizeLimitForContentType(contentType); - expect(limit).toBe(expected); - }); - }); - }); -}); - -// Helper functions for testing (would be imported from actual modules) -function formatBytes(bytes: number): string { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; -} - -function getBaseContentType(contentTypeHeader: string): string { - if (!contentTypeHeader) return 'application/json'; - return contentTypeHeader.split(';')[0].trim().toLowerCase(); -} - -function getSizeLimitForContentType(contentType: string): number { - const limits: { [key: string]: number } = { - 'application/json': 1024 * 1024, - 'multipart/form-data': 10 * 1024 * 1024, - 'image/jpeg': 50 * 1024 * 1024, - 'application/pdf': 100 * 1024 * 1024, - }; - - return limits[contentType] || 1024 * 1024; // Default to 1MB -} \ No newline at end of file diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 0af50da..f8fbf2b 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -19,6 +19,5 @@ "noImplicitAny": false, "strictBindCallApply": false, "noFallthroughCasesInSwitch": false - }, - "include": ["src/**/*", "test/**/*"] + } } diff --git a/package-lock.json b/package-lock.json index f65b30e..693741f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,7 +67,7 @@ "sqlite3": "^5.1.7", "stellar-sdk": "^13.3.0", "swagger-ui-express": "^5.0.1", - "typeorm": "^0.3.28" + "typeorm": "^0.3.21" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -195,8 +195,6 @@ }, "backend/node_modules/@nestjs/typeorm": { "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", - "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", "license": "MIT", "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", @@ -329,18 +327,6 @@ "balanced-match": "^1.0.0" } }, - "backend/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "backend/node_modules/eslint-scope": { "version": "5.1.1", "dev": true, @@ -374,8 +360,6 @@ }, "backend/node_modules/jackspeak": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -396,8 +380,6 @@ }, "backend/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, "backend/node_modules/magic-string": { @@ -425,8 +407,6 @@ }, "backend/node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -542,23 +522,20 @@ } }, "backend/node_modules/typeorm": { - "version": "0.3.28", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz", - "integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==", + "version": "0.3.26", "license": "MIT", "dependencies": { "@sqltools/formatter": "^1.2.5", - "ansis": "^4.2.0", + "ansis": "^3.17.0", "app-root-path": "^3.1.0", "buffer": "^6.0.3", - "dayjs": "^1.11.19", - "debug": "^4.4.3", - "dedent": "^1.7.0", - "dotenv": "^16.6.1", - "glob": "^10.5.0", - "reflect-metadata": "^0.2.2", - "sha.js": "^2.4.12", - "sql-highlight": "^6.1.0", + "dayjs": "^1.11.13", + "debug": "^4.4.0", + "dedent": "^1.6.0", + "dotenv": "^16.4.7", + "glob": "^10.4.5", + "sha.js": "^2.4.11", + "sql-highlight": "^6.0.0", "tslib": "^2.8.1", "uuid": "^11.1.0", "yargs": "^17.7.2" @@ -575,18 +552,19 @@ "url": "https://opencollective.com/typeorm" }, "peerDependencies": { - "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0", "@sap/hana-client": "^2.14.22", "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", "ioredis": "^5.0.4", "mongodb": "^5.8.0 || ^6.0.0", - "mssql": "^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0", + "mssql": "^9.1.1 || ^10.0.1 || ^11.0.1", "mysql2": "^2.2.5 || ^3.0.1", "oracledb": "^6.3.0", "pg": "^8.5.1", "pg-native": "^3.0.0", "pg-query-stream": "^4.0.0", "redis": "^3.1.1 || ^4.0.0 || ^5.0.14", + "reflect-metadata": "^0.1.14 || ^0.2.0", "sql.js": "^1.4.0", "sqlite3": "^5.0.3", "ts-node": "^10.7.0", @@ -644,19 +622,14 @@ } }, "backend/node_modules/typeorm/node_modules/ansis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", - "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "version": "3.17.0", "license": "ISC", "engines": { "node": ">=14" } }, "backend/node_modules/typeorm/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "version": "10.4.5", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -4086,16 +4059,6 @@ "@noble/hashes": "^1.1.5" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",