@sun-asterisk/sunlint 1.3.18 → 1.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/rules/enhanced-rules-registry.json +77 -18
- package/core/cli-program.js +2 -1
- package/core/github-annotate-service.js +89 -0
- package/core/output-service.js +25 -0
- package/core/summary-report-service.js +30 -30
- package/package.json +3 -2
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +392 -280
- package/rules/common/C017_constructor_logic/analyzer.js +137 -503
- package/rules/common/C017_constructor_logic/config.json +50 -0
- package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +463 -0
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +463 -21
- package/rules/security/S011_secure_guid_generation/README.md +255 -0
- package/rules/security/S011_secure_guid_generation/analyzer.js +135 -0
- package/rules/security/S011_secure_guid_generation/config.json +56 -0
- package/rules/security/S011_secure_guid_generation/symbol-based-analyzer.js +609 -0
- package/rules/security/S028_file_upload_size_limits/README.md +537 -0
- package/rules/security/S028_file_upload_size_limits/analyzer.js +202 -0
- package/rules/security/S028_file_upload_size_limits/config.json +186 -0
- package/rules/security/S028_file_upload_size_limits/symbol-based-analyzer.js +530 -0
- package/rules/security/S041_session_token_invalidation/README.md +303 -0
- package/rules/security/S041_session_token_invalidation/analyzer.js +242 -0
- package/rules/security/S041_session_token_invalidation/config.json +175 -0
- package/rules/security/S041_session_token_invalidation/regex-based-analyzer.js +411 -0
- package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +674 -0
- package/rules/security/S044_re_authentication_required/README.md +136 -0
- package/rules/security/S044_re_authentication_required/analyzer.js +242 -0
- package/rules/security/S044_re_authentication_required/config.json +161 -0
- package/rules/security/S044_re_authentication_required/regex-based-analyzer.js +329 -0
- package/rules/security/S044_re_authentication_required/symbol-based-analyzer.js +537 -0
- package/rules/security/S045_brute_force_protection/README.md +345 -0
- package/rules/security/S045_brute_force_protection/analyzer.js +336 -0
- package/rules/security/S045_brute_force_protection/config.json +139 -0
- package/rules/security/S045_brute_force_protection/symbol-based-analyzer.js +646 -0
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +0 -340
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rule": {
|
|
3
|
+
"id": "S041",
|
|
4
|
+
"name": "Session Tokens must be invalidated after logout or expiration",
|
|
5
|
+
"description": "Session tokens must be properly invalidated after logout or expiration to prevent session hijacking and unauthorized access. This includes clearing session data, invalidating JWT tokens, and ensuring proper session cleanup.",
|
|
6
|
+
"category": "security",
|
|
7
|
+
"severity": "error",
|
|
8
|
+
"languages": ["typescript", "javascript"],
|
|
9
|
+
"frameworks": ["express", "nestjs", "node"],
|
|
10
|
+
"version": "1.0.0",
|
|
11
|
+
"status": "stable",
|
|
12
|
+
"tags": ["security", "session", "token", "logout", "invalidation", "owasp"],
|
|
13
|
+
"references": [
|
|
14
|
+
"https://owasp.org/www-community/controls/Session_Management_Cheat_Sheet",
|
|
15
|
+
"https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html",
|
|
16
|
+
"https://owasp.org/www-community/attacks/Session_hijacking_attack",
|
|
17
|
+
"https://portswigger.net/web-security/authentication/password-based/lab-session-fixation"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"configuration": {
|
|
21
|
+
"enableLogoutDetection": true,
|
|
22
|
+
"enableSessionCleanupDetection": true,
|
|
23
|
+
"enableTokenInvalidationDetection": true,
|
|
24
|
+
"enableJWTHandlingDetection": true,
|
|
25
|
+
"checkLogoutMethods": [
|
|
26
|
+
"logout",
|
|
27
|
+
"signOut",
|
|
28
|
+
"logOut",
|
|
29
|
+
"destroy",
|
|
30
|
+
"clear",
|
|
31
|
+
"invalidate",
|
|
32
|
+
"revoke",
|
|
33
|
+
"expire"
|
|
34
|
+
],
|
|
35
|
+
"checkSessionMethods": [
|
|
36
|
+
"removeItem",
|
|
37
|
+
"clear",
|
|
38
|
+
"destroy",
|
|
39
|
+
"delete"
|
|
40
|
+
],
|
|
41
|
+
"checkExpressSessionMethods": [
|
|
42
|
+
"destroy",
|
|
43
|
+
"regenerate",
|
|
44
|
+
"reload",
|
|
45
|
+
"save"
|
|
46
|
+
],
|
|
47
|
+
"checkJWTMethods": [
|
|
48
|
+
"sign",
|
|
49
|
+
"verify",
|
|
50
|
+
"decode",
|
|
51
|
+
"invalidate",
|
|
52
|
+
"blacklist"
|
|
53
|
+
],
|
|
54
|
+
"checkTokenMethods": [
|
|
55
|
+
"removeToken",
|
|
56
|
+
"clearToken",
|
|
57
|
+
"invalidateToken",
|
|
58
|
+
"revokeToken",
|
|
59
|
+
"deleteToken",
|
|
60
|
+
"destroyToken"
|
|
61
|
+
],
|
|
62
|
+
"sessionCleanupPatterns": [
|
|
63
|
+
"req.session.destroy",
|
|
64
|
+
"req.session = null",
|
|
65
|
+
"req.session = {}",
|
|
66
|
+
"session.destroy",
|
|
67
|
+
"session.clear",
|
|
68
|
+
"session.remove",
|
|
69
|
+
".removeItem(",
|
|
70
|
+
".clear(",
|
|
71
|
+
".delete("
|
|
72
|
+
],
|
|
73
|
+
"tokenInvalidationPatterns": [
|
|
74
|
+
"blacklist",
|
|
75
|
+
"revoke",
|
|
76
|
+
"invalidate",
|
|
77
|
+
"expire",
|
|
78
|
+
"remove.*token",
|
|
79
|
+
"delete.*token",
|
|
80
|
+
"clear.*token",
|
|
81
|
+
"destroy.*token"
|
|
82
|
+
],
|
|
83
|
+
"logoutHandlerPatterns": [
|
|
84
|
+
"function.*logout",
|
|
85
|
+
"const.*logout.*=",
|
|
86
|
+
"let.*logout.*=",
|
|
87
|
+
"var.*logout.*=",
|
|
88
|
+
"logout.*:.*function",
|
|
89
|
+
"logout.*:.*("
|
|
90
|
+
],
|
|
91
|
+
"sessionStoragePatterns": [
|
|
92
|
+
"sessionStorage",
|
|
93
|
+
"localStorage",
|
|
94
|
+
"req.session",
|
|
95
|
+
"session["
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
"examples": {
|
|
99
|
+
"violations": [
|
|
100
|
+
{
|
|
101
|
+
"description": "Logout method without session cleanup",
|
|
102
|
+
"code": "app.post('/logout', (req, res) => {\n // Missing session cleanup\n res.json({ message: 'Logged out' });\n});"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"description": "Logout handler without token invalidation",
|
|
106
|
+
"code": "function logout(req, res) {\n // Missing token invalidation\n res.redirect('/login');\n}"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"description": "JWT token signing during logout",
|
|
110
|
+
"code": "app.post('/logout', (req, res) => {\n const token = jwt.sign({ userId: req.user.id }, secret);\n res.json({ token });\n});"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"description": "Session method without proper cleanup",
|
|
114
|
+
"code": "app.post('/logout', (req, res) => {\n req.session.userId = null; // Should use destroy()\n res.json({ message: 'Logged out' });\n});"
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"fixes": [
|
|
118
|
+
{
|
|
119
|
+
"description": "Proper session cleanup in logout",
|
|
120
|
+
"code": "app.post('/logout', (req, res) => {\n req.session.destroy((err) => {\n if (err) {\n return res.status(500).json({ error: 'Logout failed' });\n }\n res.clearCookie('sessionId');\n res.json({ message: 'Logged out successfully' });\n });\n});"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"description": "JWT token blacklisting during logout",
|
|
124
|
+
"code": "app.post('/logout', (req, res) => {\n const token = req.headers.authorization?.split(' ')[1];\n if (token) {\n // Add token to blacklist\n blacklist.add(token);\n }\n res.json({ message: 'Logged out successfully' });\n});"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"description": "Complete session and token cleanup",
|
|
128
|
+
"code": "app.post('/logout', async (req, res) => {\n try {\n // Invalidate JWT token\n const token = req.headers.authorization?.split(' ')[1];\n if (token) {\n await tokenService.blacklist(token);\n }\n \n // Clear session\n req.session.destroy();\n \n // Clear cookies\n res.clearCookie('sessionId');\n res.clearCookie('token');\n \n res.json({ message: 'Logged out successfully' });\n } catch (error) {\n res.status(500).json({ error: 'Logout failed' });\n }\n});"
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"testing": {
|
|
133
|
+
"testCases": [
|
|
134
|
+
{
|
|
135
|
+
"name": "logout_without_session_cleanup",
|
|
136
|
+
"type": "violation",
|
|
137
|
+
"description": "Logout method without proper session cleanup"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "logout_handler_without_token_invalidation",
|
|
141
|
+
"type": "violation",
|
|
142
|
+
"description": "Logout handler without token invalidation"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "jwt_sign_during_logout",
|
|
146
|
+
"type": "violation",
|
|
147
|
+
"description": "JWT token signing during logout process"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "session_method_without_cleanup",
|
|
151
|
+
"type": "violation",
|
|
152
|
+
"description": "Session method without proper cleanup"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "secure_logout_with_session_cleanup",
|
|
156
|
+
"type": "clean",
|
|
157
|
+
"description": "Logout with proper session cleanup"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "secure_logout_with_token_invalidation",
|
|
161
|
+
"type": "clean",
|
|
162
|
+
"description": "Logout with proper token invalidation"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "complete_logout_implementation",
|
|
166
|
+
"type": "clean",
|
|
167
|
+
"description": "Complete logout implementation with all security measures"
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
"performance": {
|
|
172
|
+
"complexity": "O(n)",
|
|
173
|
+
"description": "Linear complexity based on number of function calls and expressions in the source code"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S041 Regex-Based Analyzer - Session Tokens must be invalidated after logout or expiration
|
|
3
|
+
* Uses regex patterns for analysis when symbol-based analysis is not available
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
|
|
9
|
+
class S041RegexBasedAnalyzer {
|
|
10
|
+
constructor(semanticEngine = null) {
|
|
11
|
+
this.semanticEngine = semanticEngine;
|
|
12
|
+
this.ruleId = "S041";
|
|
13
|
+
this.category = "security";
|
|
14
|
+
|
|
15
|
+
// Logout method patterns
|
|
16
|
+
this.logoutPatterns = [
|
|
17
|
+
/logout\s*\(/gi,
|
|
18
|
+
/signOut\s*\(/gi,
|
|
19
|
+
/logOut\s*\(/gi,
|
|
20
|
+
/destroy\s*\(/gi,
|
|
21
|
+
/clear\s*\(/gi,
|
|
22
|
+
/invalidate\s*\(/gi,
|
|
23
|
+
/revoke\s*\(/gi,
|
|
24
|
+
/expire\s*\(/gi,
|
|
25
|
+
/endSession\s*\(/gi,
|
|
26
|
+
/end-session\s*\(/gi,
|
|
27
|
+
/clear-session\s*\(/gi,
|
|
28
|
+
/destroy-session\s*\(/gi,
|
|
29
|
+
/remove-session\s*\(/gi
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// Session cleanup patterns (what should be present)
|
|
33
|
+
this.sessionCleanupPatterns = [
|
|
34
|
+
/req\.session\.destroy/gi,
|
|
35
|
+
/req\.session\s*=\s*null/gi,
|
|
36
|
+
/req\.session\s*=\s*\{\}/gi,
|
|
37
|
+
/session\.destroy/gi,
|
|
38
|
+
/session\.clear/gi,
|
|
39
|
+
/session\.remove/gi,
|
|
40
|
+
/\.removeItem\s*\(/gi,
|
|
41
|
+
/\.clear\s*\(/gi,
|
|
42
|
+
/\.delete\s*\(/gi,
|
|
43
|
+
/res\.clearCookie/gi,
|
|
44
|
+
/sessionStorage\.clear/gi,
|
|
45
|
+
/localStorage\.clear/gi,
|
|
46
|
+
/req\.session\.regenerate/gi
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Token invalidation patterns (what should be present)
|
|
50
|
+
this.tokenInvalidationPatterns = [
|
|
51
|
+
/blacklist/gi,
|
|
52
|
+
/revoke/gi,
|
|
53
|
+
/invalidate/gi,
|
|
54
|
+
/expire/gi,
|
|
55
|
+
/remove.*token/gi,
|
|
56
|
+
/delete.*token/gi,
|
|
57
|
+
/clear.*token/gi,
|
|
58
|
+
/destroy.*token/gi
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// JWT token patterns
|
|
62
|
+
this.jwtPatterns = [
|
|
63
|
+
/jwt\.sign/gi,
|
|
64
|
+
/jwt\.verify/gi,
|
|
65
|
+
/jwt\.decode/gi,
|
|
66
|
+
/jsonwebtoken/gi
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// Session storage patterns
|
|
70
|
+
this.sessionStoragePatterns = [
|
|
71
|
+
/sessionStorage/gi,
|
|
72
|
+
/localStorage/gi,
|
|
73
|
+
/req\.session/gi,
|
|
74
|
+
/session\[/gi
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// Logout handler function patterns
|
|
78
|
+
this.logoutHandlerPatterns = [
|
|
79
|
+
/function\s+\w*logout\w*\s*\(/gi,
|
|
80
|
+
/const\s+\w*logout\w*\s*=/gi,
|
|
81
|
+
/let\s+\w*logout\w*\s*=/gi,
|
|
82
|
+
/var\s+\w*logout\w*\s*=/gi,
|
|
83
|
+
/logout\s*:\s*function/gi,
|
|
84
|
+
/logout\s*:\s*\(/gi,
|
|
85
|
+
/async\s+\w*logout\w*\s*\(/gi,
|
|
86
|
+
/async\s+\w*signout\w*\s*\(/gi,
|
|
87
|
+
/async\s+\w*endSession\w*\s*\(/gi,
|
|
88
|
+
/@Post\s*\(\s*['"]logout['"]\s*\)/gi,
|
|
89
|
+
/@Post\s*\(\s*['"]signout['"]\s*\)/gi,
|
|
90
|
+
/@Post\s*\(\s*['"]end-session['"]\s*\)/gi
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Initialize analyzer with semantic engine
|
|
96
|
+
*/
|
|
97
|
+
async initialize(semanticEngine) {
|
|
98
|
+
this.semanticEngine = semanticEngine;
|
|
99
|
+
if (this.verbose) {
|
|
100
|
+
console.log(`🔍 [${this.ruleId}] Regex: Semantic engine initialized`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async analyze(filePath) {
|
|
105
|
+
if (this.verbose) {
|
|
106
|
+
console.log(
|
|
107
|
+
`🔍 [${this.ruleId}] Regex: Starting analysis for ${filePath}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
113
|
+
return await this.analyzeContent(content, filePath);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (this.verbose) {
|
|
116
|
+
console.log(
|
|
117
|
+
`🔍 [${this.ruleId}] Regex: Error reading file:`,
|
|
118
|
+
error.message
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async analyzeContent(content, filePath) {
|
|
126
|
+
const violations = [];
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
if (this.verbose) {
|
|
130
|
+
console.log(`🔍 [${this.ruleId}] Regex: Starting regex-based analysis`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Split content into lines for line number tracking
|
|
134
|
+
const lines = content.split("\n");
|
|
135
|
+
|
|
136
|
+
// 1. Check for logout methods without proper session cleanup
|
|
137
|
+
const logoutViolations = this.findLogoutViolations(lines, filePath);
|
|
138
|
+
violations.push(...logoutViolations);
|
|
139
|
+
|
|
140
|
+
// 2. Check for logout handlers without session cleanup
|
|
141
|
+
const handlerViolations = this.findLogoutHandlerViolations(lines, filePath);
|
|
142
|
+
violations.push(...handlerViolations);
|
|
143
|
+
|
|
144
|
+
// 3. Check for JWT token handling in logout context
|
|
145
|
+
const jwtViolations = this.findJWTViolations(lines, filePath);
|
|
146
|
+
violations.push(...jwtViolations);
|
|
147
|
+
|
|
148
|
+
// 4. Check for session methods without token invalidation
|
|
149
|
+
const sessionViolations = this.findSessionViolations(lines, filePath);
|
|
150
|
+
violations.push(...sessionViolations);
|
|
151
|
+
|
|
152
|
+
if (this.verbose) {
|
|
153
|
+
console.log(
|
|
154
|
+
`🔍 [${this.ruleId}] Regex: Analysis completed. Found ${violations.length} violations`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return violations;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (this.verbose) {
|
|
161
|
+
console.log(
|
|
162
|
+
`🔍 [${this.ruleId}] Regex: Error in content analysis:`,
|
|
163
|
+
error.message
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
findLogoutViolations(lines, filePath) {
|
|
171
|
+
const violations = [];
|
|
172
|
+
|
|
173
|
+
for (let i = 0; i < lines.length; i++) {
|
|
174
|
+
const line = lines[i];
|
|
175
|
+
const lineNumber = i + 1;
|
|
176
|
+
|
|
177
|
+
// Check if line contains logout method
|
|
178
|
+
const hasLogoutMethod = this.logoutPatterns.some(pattern =>
|
|
179
|
+
pattern.test(line)
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (hasLogoutMethod) {
|
|
183
|
+
// Check if session cleanup is present in the same function
|
|
184
|
+
const hasSessionCleanup = this.hasSessionCleanupInFunction(lines, i);
|
|
185
|
+
|
|
186
|
+
if (!hasSessionCleanup) {
|
|
187
|
+
violations.push({
|
|
188
|
+
rule: this.ruleId,
|
|
189
|
+
source: filePath,
|
|
190
|
+
category: this.category,
|
|
191
|
+
line: lineNumber,
|
|
192
|
+
column: 1,
|
|
193
|
+
message: "Session token invalidation vulnerability: Logout method should invalidate session tokens and clear session data",
|
|
194
|
+
severity: "error",
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return violations;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
findLogoutHandlerViolations(lines, filePath) {
|
|
204
|
+
const violations = [];
|
|
205
|
+
|
|
206
|
+
for (let i = 0; i < lines.length; i++) {
|
|
207
|
+
const line = lines[i];
|
|
208
|
+
const lineNumber = i + 1;
|
|
209
|
+
|
|
210
|
+
// Check if line contains logout handler function
|
|
211
|
+
const hasLogoutHandler = this.logoutHandlerPatterns.some(pattern =>
|
|
212
|
+
pattern.test(line)
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (hasLogoutHandler) {
|
|
216
|
+
// Check if session cleanup is present in the function
|
|
217
|
+
const hasSessionCleanup = this.hasSessionCleanupInFunction(lines, i);
|
|
218
|
+
|
|
219
|
+
if (!hasSessionCleanup) {
|
|
220
|
+
violations.push({
|
|
221
|
+
rule: this.ruleId,
|
|
222
|
+
source: filePath,
|
|
223
|
+
category: this.category,
|
|
224
|
+
line: lineNumber,
|
|
225
|
+
column: 1,
|
|
226
|
+
message: "Session token invalidation vulnerability: Logout handler should clear session data and invalidate tokens",
|
|
227
|
+
severity: "error",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return violations;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
findJWTViolations(lines, filePath) {
|
|
237
|
+
const violations = [];
|
|
238
|
+
|
|
239
|
+
for (let i = 0; i < lines.length; i++) {
|
|
240
|
+
const line = lines[i];
|
|
241
|
+
const lineNumber = i + 1;
|
|
242
|
+
|
|
243
|
+
// Check if line contains JWT sign method
|
|
244
|
+
const hasJWTSign = /jwt\.sign/gi.test(line);
|
|
245
|
+
|
|
246
|
+
if (hasJWTSign) {
|
|
247
|
+
// Check if it's in a logout context
|
|
248
|
+
const isInLogoutContext = this.isInLogoutContext(lines, i);
|
|
249
|
+
|
|
250
|
+
if (isInLogoutContext) {
|
|
251
|
+
violations.push({
|
|
252
|
+
rule: this.ruleId,
|
|
253
|
+
source: filePath,
|
|
254
|
+
category: this.category,
|
|
255
|
+
line: lineNumber,
|
|
256
|
+
column: 1,
|
|
257
|
+
message: "Session token invalidation vulnerability: JWT token should be invalidated/blacklisted during logout, not signed",
|
|
258
|
+
severity: "error",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return violations;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
findSessionViolations(lines, filePath) {
|
|
268
|
+
const violations = [];
|
|
269
|
+
|
|
270
|
+
for (let i = 0; i < lines.length; i++) {
|
|
271
|
+
const line = lines[i];
|
|
272
|
+
const lineNumber = i + 1;
|
|
273
|
+
|
|
274
|
+
// Don't detect session cleanup methods as violations
|
|
275
|
+
const sessionCleanupMethods = [
|
|
276
|
+
'req.session.destroy',
|
|
277
|
+
'session.destroy',
|
|
278
|
+
'res.clearCookie',
|
|
279
|
+
'sessionStorage.clear',
|
|
280
|
+
'localStorage.clear',
|
|
281
|
+
'req.session.reload',
|
|
282
|
+
'req.session.regenerate'
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
if (sessionCleanupMethods.some(method => line.includes(method))) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Check if line contains session storage methods
|
|
290
|
+
const hasSessionMethod = this.sessionStoragePatterns.some(pattern =>
|
|
291
|
+
pattern.test(line)
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
if (hasSessionMethod) {
|
|
295
|
+
// Check if it's in a logout context and if token invalidation is present
|
|
296
|
+
const isInLogoutContext = this.isInLogoutContext(lines, i);
|
|
297
|
+
const hasTokenInvalidation = this.hasTokenInvalidationInFunction(lines, i);
|
|
298
|
+
|
|
299
|
+
// Only report if it's in logout context and missing token invalidation
|
|
300
|
+
if (isInLogoutContext && !hasTokenInvalidation) {
|
|
301
|
+
violations.push({
|
|
302
|
+
rule: this.ruleId,
|
|
303
|
+
source: filePath,
|
|
304
|
+
category: this.category,
|
|
305
|
+
line: lineNumber,
|
|
306
|
+
column: 1,
|
|
307
|
+
message: "Session token invalidation vulnerability: Session method should invalidate tokens during logout",
|
|
308
|
+
severity: "error",
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return violations;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
hasSessionCleanupInFunction(lines, startIndex) {
|
|
318
|
+
// Look for session cleanup patterns in the current function scope
|
|
319
|
+
const functionEnd = this.findFunctionEnd(lines, startIndex);
|
|
320
|
+
|
|
321
|
+
for (let i = startIndex; i <= functionEnd && i < lines.length; i++) {
|
|
322
|
+
const line = lines[i];
|
|
323
|
+
const hasCleanup = this.sessionCleanupPatterns.some(pattern =>
|
|
324
|
+
pattern.test(line)
|
|
325
|
+
);
|
|
326
|
+
if (hasCleanup) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
hasTokenInvalidationInFunction(lines, startIndex) {
|
|
335
|
+
// Look for token invalidation patterns in the current function scope
|
|
336
|
+
const functionEnd = this.findFunctionEnd(lines, startIndex);
|
|
337
|
+
|
|
338
|
+
for (let i = startIndex; i <= functionEnd && i < lines.length; i++) {
|
|
339
|
+
const line = lines[i];
|
|
340
|
+
const hasInvalidation = this.tokenInvalidationPatterns.some(pattern =>
|
|
341
|
+
pattern.test(line)
|
|
342
|
+
);
|
|
343
|
+
if (hasInvalidation) {
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
isInLogoutContext(lines, startIndex) {
|
|
352
|
+
// Look for logout-related patterns in the current function scope
|
|
353
|
+
const functionEnd = this.findFunctionEnd(lines, startIndex);
|
|
354
|
+
|
|
355
|
+
for (let i = startIndex; i <= functionEnd && i < lines.length; i++) {
|
|
356
|
+
const line = lines[i];
|
|
357
|
+
|
|
358
|
+
// Check for actual logout method calls or function names
|
|
359
|
+
const hasLogoutMethod = this.logoutPatterns.some(pattern =>
|
|
360
|
+
pattern.test(line)
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
// Check for logout handler function patterns
|
|
364
|
+
const hasLogoutHandler = this.logoutHandlerPatterns.some(pattern =>
|
|
365
|
+
pattern.test(line)
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
if (hasLogoutMethod || hasLogoutHandler) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
findFunctionEnd(lines, startIndex) {
|
|
377
|
+
let braceCount = 0;
|
|
378
|
+
let inFunction = false;
|
|
379
|
+
|
|
380
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
381
|
+
const line = lines[i];
|
|
382
|
+
|
|
383
|
+
// Count opening and closing braces
|
|
384
|
+
const openBraces = (line.match(/\{/g) || []).length;
|
|
385
|
+
const closeBraces = (line.match(/\}/g) || []).length;
|
|
386
|
+
|
|
387
|
+
if (openBraces > 0) {
|
|
388
|
+
inFunction = true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (inFunction) {
|
|
392
|
+
braceCount += openBraces - closeBraces;
|
|
393
|
+
|
|
394
|
+
if (braceCount === 0 && closeBraces > 0) {
|
|
395
|
+
return i;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return lines.length - 1;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Clean up resources
|
|
405
|
+
*/
|
|
406
|
+
cleanup() {
|
|
407
|
+
// No cleanup needed for regex-based analyzer
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
module.exports = S041RegexBasedAnalyzer;
|