UNPKG

@sun-asterisk/sunlint

Version:

☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards

112 lines (106 loc) 4.25 kB
{ "ruleId": "S025", "name": "Always validate client-side data on the server", "description": "Ensure all client-side data is validated on the server. Client-side validation is not sufficient for security as it can be bypassed by attackers. Server-side validation is mandatory for data integrity and security.", "category": "security", "severity": "error", "languages": ["typescript", "javascript"], "tags": ["security", "validation", "server-side", "owasp", "input-validation"], "patterns": { "nestjs": { "missingValidationPipe": { "description": "NestJS routes missing ValidationPipe or DTO validation", "severity": "error" }, "unsafeBodyUsage": { "description": "Using @Body() with 'any' type instead of validated DTO", "severity": "error" } }, "express": { "directReqUsage": { "description": "Direct use of req.body/req.query/req.params without validation", "severity": "error" }, "missingMiddleware": { "description": "Express routes missing validation middleware", "severity": "error" } }, "sensitiveFields": { "clientTrusted": { "description": "Sensitive fields (userId, role, price, isAdmin) trusted from client", "severity": "error", "fields": ["userId", "user_id", "id", "role", "roles", "permissions", "price", "amount", "total", "cost", "isAdmin", "is_admin", "admin", "discount", "balance", "credits", "isActive", "is_active", "enabled", "status", "state"] } }, "sqlInjection": { "stringConcatenation": { "description": "SQL queries using string concatenation or template literals with user input", "severity": "error" } }, "fileUpload": { "missingValidation": { "description": "File uploads missing server-side validation (type, size, content)", "severity": "error" } } }, "validationIndicators": [ "ValidationPipe", "class-validator", "IsString", "IsInt", "IsEmail", "IsUUID", "IsOptional", "IsArray", "validateOrReject", "plainToClass", "joi.validate", "yup.validate", "zod.parse", "fileFilter", "mimetype", "file.size" ], "frameworkSupport": { "nestjs": { "patterns": ["@Controller", "@Post", "@Get", "@Put", "@Delete", "@Body()", "@nestjs/"], "validationMethods": ["ValidationPipe", "class-validator", "DTO"] }, "express": { "patterns": ["express", "req.body", "req.query", "req.params", "app.post", "app.get"], "validationMethods": ["joi", "yup", "express-validator", "middleware"] } }, "examples": { "violations": [ { "code": "@Post('/checkout') checkout(@Body() body: any) { return this.service.checkout(body); }", "issue": "Using @Body() with 'any' type - vulnerable to parameter tampering" }, { "code": "const { userId, discount } = req.body; const order = await this.service.create(userId, discount);", "issue": "Trusting sensitive fields (userId, discount) from client without validation" }, { "code": "const query = `SELECT * FROM users WHERE id = ${req.params.id}`;", "issue": "SQL injection via string concatenation with user input" } ], "fixes": [ { "code": "app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));", "description": "Configure ValidationPipe globally" }, { "code": "@Post('/checkout') checkout(@Body() dto: CheckoutDto, @Req() req) { const userId = req.user.sub; }", "description": "Use DTO validation and get userId from authenticated session" }, { "code": "const query = 'SELECT * FROM users WHERE id = ?'; await db.query(query, [req.params.id]);", "description": "Use parameterized queries" } ] }, "owaspMapping": { "category": "A03:2021 – Injection", "subcategories": [ "A04:2021 – Insecure Design", "A07:2021 – Identification and Authentication Failures" ], "description": "Validates that all user input is properly validated server-side to prevent injection attacks and parameter tampering" } }