@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.
Files changed (34) hide show
  1. package/config/rules/enhanced-rules-registry.json +77 -18
  2. package/core/cli-program.js +2 -1
  3. package/core/github-annotate-service.js +89 -0
  4. package/core/output-service.js +25 -0
  5. package/core/summary-report-service.js +30 -30
  6. package/package.json +3 -2
  7. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +392 -280
  8. package/rules/common/C017_constructor_logic/analyzer.js +137 -503
  9. package/rules/common/C017_constructor_logic/config.json +50 -0
  10. package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +463 -0
  11. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +463 -21
  12. package/rules/security/S011_secure_guid_generation/README.md +255 -0
  13. package/rules/security/S011_secure_guid_generation/analyzer.js +135 -0
  14. package/rules/security/S011_secure_guid_generation/config.json +56 -0
  15. package/rules/security/S011_secure_guid_generation/symbol-based-analyzer.js +609 -0
  16. package/rules/security/S028_file_upload_size_limits/README.md +537 -0
  17. package/rules/security/S028_file_upload_size_limits/analyzer.js +202 -0
  18. package/rules/security/S028_file_upload_size_limits/config.json +186 -0
  19. package/rules/security/S028_file_upload_size_limits/symbol-based-analyzer.js +530 -0
  20. package/rules/security/S041_session_token_invalidation/README.md +303 -0
  21. package/rules/security/S041_session_token_invalidation/analyzer.js +242 -0
  22. package/rules/security/S041_session_token_invalidation/config.json +175 -0
  23. package/rules/security/S041_session_token_invalidation/regex-based-analyzer.js +411 -0
  24. package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +674 -0
  25. package/rules/security/S044_re_authentication_required/README.md +136 -0
  26. package/rules/security/S044_re_authentication_required/analyzer.js +242 -0
  27. package/rules/security/S044_re_authentication_required/config.json +161 -0
  28. package/rules/security/S044_re_authentication_required/regex-based-analyzer.js +329 -0
  29. package/rules/security/S044_re_authentication_required/symbol-based-analyzer.js +537 -0
  30. package/rules/security/S045_brute_force_protection/README.md +345 -0
  31. package/rules/security/S045_brute_force_protection/analyzer.js +336 -0
  32. package/rules/security/S045_brute_force_protection/config.json +139 -0
  33. package/rules/security/S045_brute_force_protection/symbol-based-analyzer.js +646 -0
  34. 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;