@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
JSON
{
"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"
}
}