@sun-asterisk/sunlint
Version:
☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards
128 lines (127 loc) • 4.36 kB
JSON
{
"rule": {
"id": "S029",
"name": "CSRF Protection Required",
"description": "Require CSRF (Cross-Site Request Forgery) protection for state-changing operations. All POST, PUT, DELETE, and PATCH endpoints must implement CSRF token validation to prevent unauthorized requests from malicious sites.",
"category": "security",
"severity": "error",
"languages": ["typescript", "javascript"],
"frameworks": ["express", "nestjs", "node"],
"version": "1.0.0",
"status": "stable",
"tags": ["security", "csrf", "xsrf", "authentication", "owasp"],
"references": [
"https://owasp.org/www-community/attacks/csrf",
"https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html",
"https://portswigger.net/web-security/csrf",
"https://cwe.mitre.org/data/definitions/352.html"
]
},
"configuration": {
"checkStateChangingMethods": ["POST", "PUT", "DELETE", "PATCH"],
"csrfProtectionPatterns": [
"csurf()",
"csrfProtection",
"verifyCsrfToken",
"checkCsrf",
"csrf-token",
"_csrf",
"req.csrfToken",
"csrf.verify",
"validateCSRF",
"x-csrf-token",
"x-xsrf-token"
],
"globalMiddlewarePatterns": [
"app.use(csurf())",
"app.use(csrfProtection)",
"router.use(csurf())"
],
"routeHandlerPatterns": [
"app.post",
"app.put",
"app.delete",
"app.patch",
"router.post",
"router.put",
"router.delete",
"router.patch"
],
"excludePatterns": [
"test",
"spec",
"mock",
"__tests__"
]
},
"examples": {
"violations": [
{
"description": "POST endpoint without CSRF protection",
"code": "app.post('/api/transfer', (req, res) => {\n transferMoney(req.body.amount);\n});"
},
{
"description": "DELETE endpoint without CSRF middleware",
"code": "router.delete('/api/user/:id', (req, res) => {\n deleteUser(req.params.id);\n});"
},
{
"description": "PUT endpoint without token validation",
"code": "app.put('/api/profile', (req, res) => {\n updateProfile(req.body);\n});"
}
],
"fixes": [
{
"description": "Apply CSRF protection globally",
"code": "const csrf = require('csurf');\nconst csrfProtection = csrf({ cookie: true });\napp.use(csrfProtection);\n\napp.post('/api/transfer', (req, res) => {\n // CSRF token is automatically validated\n transferMoney(req.body.amount);\n});"
},
{
"description": "Apply CSRF protection per route",
"code": "const csrfProtection = csrf({ cookie: true });\n\napp.post('/api/transfer', csrfProtection, (req, res) => {\n transferMoney(req.body.amount);\n});"
},
{
"description": "Manual CSRF token validation",
"code": "app.post('/api/transfer', (req, res) => {\n const token = req.body._csrf || req.headers['x-csrf-token'];\n if (!validateCsrfToken(token, req.session)) {\n return res.status(403).send('Invalid CSRF token');\n }\n transferMoney(req.body.amount);\n});"
}
]
},
"testing": {
"testCases": [
{
"name": "post_without_csrf",
"type": "violation",
"description": "POST endpoint without CSRF protection"
},
{
"name": "put_without_csrf",
"type": "violation",
"description": "PUT endpoint without CSRF protection"
},
{
"name": "delete_without_csrf",
"type": "violation",
"description": "DELETE endpoint without CSRF protection"
},
{
"name": "global_csrf_protection",
"type": "clean",
"description": "Global CSRF middleware applied"
},
{
"name": "route_specific_csrf",
"type": "clean",
"description": "Route-specific CSRF middleware"
}
]
},
"performance": {
"complexity": "O(n)",
"description": "Linear complexity based on number of route handlers in the source code"
},
"owaspMapping": {
"category": "A01:2021 – Broken Access Control",
"subcategories": [
"A04:2021 – Insecure Design"
],
"description": "Validates that all state-changing endpoints have proper CSRF protection to prevent unauthorized cross-site requests"
}
}