@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,303 @@
1
+ # S041 - Session Tokens must be invalidated after logout or expiration
2
+
3
+ ## Overview
4
+
5
+ This rule enforces proper session token invalidation after logout or expiration to prevent session hijacking and unauthorized access. It ensures that session data is properly cleared and tokens are invalidated when users log out.
6
+
7
+ ## Description
8
+
9
+ Session token invalidation is a critical security measure that helps prevent:
10
+
11
+ - **Session hijacking** through stolen or leaked tokens
12
+ - **Unauthorized access** using expired or invalidated sessions
13
+ - **Token reuse attacks** where old tokens remain valid
14
+ - **Privilege escalation** through persistent session data
15
+
16
+ ## Rule Details
17
+
18
+ **Rule ID**: S041
19
+ **Category**: Security
20
+ **Severity**: Error
21
+ **Type**: Hybrid Analysis (Symbol-based + Regex-based)
22
+
23
+ ## Examples
24
+
25
+ ### ❌ Violations
26
+
27
+ ```typescript
28
+ // Express.js - Logout without session cleanup
29
+ app.post('/logout', (req, res) => {
30
+ // Missing session cleanup
31
+ res.json({ message: 'Logged out' });
32
+ });
33
+
34
+ // NestJS - Logout handler without token invalidation
35
+ @Post('logout')
36
+ async logout(@Req() req: Request, @Res() res: Response) {
37
+ // Missing token invalidation
38
+ res.redirect('/login');
39
+ }
40
+
41
+ // JWT token signing during logout (wrong approach)
42
+ app.post('/logout', (req, res) => {
43
+ const token = jwt.sign({ userId: req.user.id }, secret);
44
+ res.json({ token }); // Should invalidate, not sign new token
45
+ });
46
+
47
+ // Session method without proper cleanup
48
+ app.post('/logout', (req, res) => {
49
+ req.session.userId = null; // Should use destroy()
50
+ res.json({ message: 'Logged out' });
51
+ });
52
+
53
+ // Logout handler function without session cleanup
54
+ function logoutHandler(req, res) {
55
+ // Missing session cleanup and token invalidation
56
+ res.json({ message: 'Logged out successfully' });
57
+ }
58
+
59
+ // Session storage without token invalidation
60
+ app.post('/logout', (req, res) => {
61
+ sessionStorage.removeItem('userToken');
62
+ // Missing proper session cleanup
63
+ res.json({ message: 'Logged out' });
64
+ });
65
+ ```
66
+
67
+ ### ✅ Correct Usage
68
+
69
+ ```typescript
70
+ // Express.js - Complete logout with session cleanup
71
+ app.post('/logout', (req, res) => {
72
+ req.session.destroy((err) => {
73
+ if (err) {
74
+ return res.status(500).json({ error: 'Logout failed' });
75
+ }
76
+ res.clearCookie('sessionId');
77
+ res.json({ message: 'Logged out successfully' });
78
+ });
79
+ });
80
+
81
+ // NestJS - Logout with token invalidation
82
+ @Post('logout')
83
+ async logout(@Req() req: Request, @Res() res: Response) {
84
+ const token = req.headers.authorization?.split(' ')[1];
85
+ if (token) {
86
+ await this.tokenService.blacklist(token);
87
+ }
88
+
89
+ req.session.destroy();
90
+ res.clearCookie('sessionId');
91
+ res.redirect('/login');
92
+ }
93
+
94
+ // JWT token blacklisting during logout
95
+ app.post('/logout', async (req, res) => {
96
+ const token = req.headers.authorization?.split(' ')[1];
97
+ if (token) {
98
+ await tokenService.blacklist(token);
99
+ }
100
+
101
+ req.session.destroy();
102
+ res.clearCookie('sessionId');
103
+ res.json({ message: 'Logged out successfully' });
104
+ });
105
+
106
+ // Complete logout implementation
107
+ app.post('/logout', async (req, res) => {
108
+ try {
109
+ // 1. Invalidate JWT token
110
+ const token = req.headers.authorization?.split(' ')[1];
111
+ if (token) {
112
+ await this.invalidateToken(token);
113
+ }
114
+
115
+ // 2. Clear session data
116
+ req.session.destroy((err) => {
117
+ if (err) {
118
+ return res.status(500).json({ error: 'Session cleanup failed' });
119
+ }
120
+
121
+ // 3. Clear all cookies
122
+ res.clearCookie('sessionId');
123
+ res.clearCookie('token');
124
+ res.clearCookie('refreshToken');
125
+
126
+ res.json({
127
+ message: 'Logged out successfully',
128
+ clearStorage: true
129
+ });
130
+ });
131
+ } catch (error) {
132
+ res.status(500).json({ error: 'Logout failed' });
133
+ }
134
+ });
135
+
136
+ // Express session with regeneration
137
+ app.post('/secure-logout', (req, res) => {
138
+ req.session.regenerate((err) => {
139
+ if (err) {
140
+ return res.status(500).json({ error: 'Logout failed' });
141
+ }
142
+
143
+ res.clearCookie('connect.sid', {
144
+ httpOnly: true,
145
+ secure: process.env.NODE_ENV === 'production',
146
+ sameSite: 'strict'
147
+ });
148
+
149
+ res.json({ message: 'Securely logged out' });
150
+ });
151
+ });
152
+ ```
153
+
154
+ ## Configuration
155
+
156
+ ### Detected Patterns
157
+
158
+ This rule detects session token invalidation issues in multiple scenarios:
159
+
160
+ ### Logout Methods
161
+
162
+ - `logout()`, `signOut()`, `logOut()` methods without session cleanup
163
+ - `invalidate()`, `revoke()`, `expire()` methods without proper implementation
164
+ - Logout handler functions without token invalidation
165
+
166
+ ### Session Methods
167
+
168
+ - `req.session.destroy()` calls in logout context
169
+ - `req.session.clear()`, `req.session.remove()` without token invalidation
170
+ - `sessionStorage.clear()`, `localStorage.clear()` without proper cleanup
171
+
172
+ ### JWT Token Handling
173
+
174
+ - `jwt.sign()` calls during logout (should invalidate instead)
175
+ - JWT token creation in logout context
176
+ - Missing token blacklisting/revocation
177
+
178
+ ### Session Storage
179
+
180
+ - `sessionStorage.removeItem()`, `localStorage.removeItem()` without token invalidation
181
+ - Session data clearing without proper token cleanup
182
+ - Cookie clearing without session destruction
183
+
184
+ **Logout Method Names:**
185
+
186
+ - `logout`, `signOut`, `logOut`
187
+ - `destroy`, `clear`, `invalidate`
188
+ - `revoke`, `expire`
189
+
190
+ **Session Methods:**
191
+
192
+ - `req.session.destroy`, `req.session.clear`, `req.session.remove`
193
+ - `sessionStorage.clear`, `localStorage.clear`
194
+ - `res.clearCookie`, `res.clearCookie()`
195
+
196
+ **Token Invalidation Patterns:**
197
+
198
+ - `blacklist`, `revoke`, `invalidate`
199
+ - `expire`, `remove.*token`, `delete.*token`
200
+ - `clear.*token`, `destroy.*token`
201
+
202
+ ### Required Session Cleanup Patterns
203
+
204
+ **Session Cleanup:**
205
+
206
+ - `req.session.destroy()`
207
+ - `req.session = null`
208
+ - `req.session = {}`
209
+ - `session.destroy()`
210
+ - `session.clear()`
211
+ - `session.remove()`
212
+
213
+ **Cookie Cleanup:**
214
+
215
+ - `res.clearCookie()`
216
+ - `sessionStorage.clear()`
217
+ - `localStorage.clear()`
218
+
219
+ **Token Invalidation:**
220
+
221
+ - Token blacklisting
222
+ - Token revocation
223
+ - Token expiration
224
+ - Token removal/deletion
225
+
226
+ ## Security Benefits
227
+
228
+ 1. **Session Security**: Prevents unauthorized access through persistent sessions
229
+ 2. **Token Invalidation**: Ensures old tokens cannot be reused
230
+ 3. **Data Cleanup**: Removes sensitive session data from memory
231
+ 4. **Attack Prevention**: Mitigates session hijacking and token reuse attacks
232
+
233
+ ## Analysis Approach
234
+
235
+ ### Symbol-based Analysis (Primary)
236
+
237
+ - Uses TypeScript AST for semantic analysis
238
+ - Detects logout methods and session handling
239
+ - Analyzes function context for proper cleanup
240
+ - Provides precise line/column positions
241
+
242
+ ### Regex-based Analysis (Fallback)
243
+
244
+ - Pattern-based detection for complex cases
245
+ - Handles string-based session methods
246
+ - Covers edge cases missed by AST analysis
247
+ - Maintains line number accuracy
248
+
249
+ ## Best Practices
250
+
251
+ 1. **Always destroy sessions** when users log out
252
+ 2. **Invalidate JWT tokens** by blacklisting or revoking them
253
+ 3. **Clear all cookies** related to authentication
254
+ 4. **Remove session data** from client-side storage
255
+ 5. **Handle errors gracefully** during logout process
256
+ 6. **Use proper session methods** like `destroy()` instead of setting to null
257
+ 7. **Implement token blacklisting** for JWT-based authentication
258
+ 8. **Clear multiple storage types** (sessionStorage, localStorage, cookies)
259
+
260
+ ## Testing
261
+
262
+ Run the rule on test fixtures:
263
+
264
+ ```bash
265
+ # Test violations
266
+ node cli.js --rule=S041 --input=examples/rule-test-fixtures/rules/S041_session_token_invalidation/violations --engine=heuristic
267
+
268
+ # Test clean examples
269
+ node cli.js --rule=S041 --input=examples/rule-test-fixtures/rules/S041_session_token_invalidation/clean --engine=heuristic
270
+
271
+ # Test all examples
272
+ node cli.js --rule=S041 --input=examples/rule-test-fixtures/rules/S041_session_token_invalidation --engine=heuristic
273
+
274
+ # Framework-specific testing
275
+ # Test with ESLint engine (fast)
276
+ node cli.js --rule=S041 --input=path/to/your/files
277
+
278
+ # Test with heuristic engine (comprehensive)
279
+ node cli.js --rule=S041 --input=path/to/your/files --engine=heuristic
280
+ ```
281
+
282
+ ## Framework Compatibility
283
+
284
+ - **Node.js**: All versions
285
+ - **Frameworks**: Express.js, NestJS, Next.js, NextAuth.js
286
+ - **Languages**: JavaScript, TypeScript
287
+ - **Analysis Engines**: ESLint (fast), Heuristic (comprehensive)
288
+
289
+ ## Related Rules
290
+
291
+ - **S031**: Set Secure flag for Session Cookies
292
+ - **S032**: Set HttpOnly attribute for Session Cookies
293
+ - **S033**: Set SameSite attribute for Session Cookies
294
+ - **S034**: Use __Host- prefix for Session Cookies
295
+ - **S049**: Authentication tokens should have short validity periods
296
+
297
+ Together, these rules provide comprehensive session and token security.
298
+
299
+ ## OWASP References
300
+
301
+ - [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
302
+ - [OWASP Session Hijacking Attack](https://owasp.org/www-community/attacks/Session_hijacking_attack)
303
+ - [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
@@ -0,0 +1,242 @@
1
+ /**
2
+ * S041 Main Analyzer - Session Tokens must be invalidated after logout or expiration
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
5
+ * Command: node cli.js --rule=S041 --input=examples/rule-test-fixtures/rules/S041_session_token_invalidation --engine=heuristic
6
+ */
7
+
8
+ const S041SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
+ const S041RegexBasedAnalyzer = require("./regex-based-analyzer.js");
10
+
11
+ class S041Analyzer {
12
+ constructor(options = {}) {
13
+ if (process.env.SUNLINT_DEBUG) {
14
+ console.log(`🔧 [S041] Constructor called with options:`, !!options);
15
+ console.log(
16
+ `🔧 [S041] Options type:`,
17
+ typeof options,
18
+ Object.keys(options || {})
19
+ );
20
+ }
21
+
22
+ this.ruleId = "S041";
23
+ this.ruleName = "Session Tokens must be invalidated after logout or expiration";
24
+ this.description =
25
+ "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.";
26
+ this.semanticEngine = options.semanticEngine || null;
27
+ this.verbose = options.verbose || false;
28
+
29
+ // Configuration
30
+ this.config = {
31
+ useSymbolBased: true, // Primary approach
32
+ fallbackToRegex: true, // Secondary approach
33
+ regexBasedOnly: false, // Can be set to true for pure mode
34
+ };
35
+
36
+ // Initialize analyzers
37
+ try {
38
+ this.symbolAnalyzer = new S041SymbolBasedAnalyzer(this.semanticEngine);
39
+ if (process.env.SUNLINT_DEBUG) {
40
+ console.log(`🔧 [S041] Symbol analyzer created successfully`);
41
+ }
42
+ } catch (error) {
43
+ console.error(`🔧 [S041] Error creating symbol analyzer:`, error);
44
+ }
45
+
46
+ try {
47
+ this.regexAnalyzer = new S041RegexBasedAnalyzer(this.semanticEngine);
48
+ if (process.env.SUNLINT_DEBUG) {
49
+ console.log(`🔧 [S041] Regex analyzer created successfully`);
50
+ }
51
+ } catch (error) {
52
+ console.error(`🔧 [S041] Error creating regex analyzer:`, error);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Initialize analyzer with semantic engine
58
+ */
59
+ async initialize(semanticEngine) {
60
+ this.semanticEngine = semanticEngine;
61
+
62
+ if (process.env.SUNLINT_DEBUG) {
63
+ console.log(`🔧 [S041] Main analyzer initializing...`);
64
+ }
65
+
66
+ // Initialize both analyzers
67
+ if (this.symbolAnalyzer) {
68
+ await this.symbolAnalyzer.initialize?.(semanticEngine);
69
+ }
70
+ if (this.regexAnalyzer) {
71
+ await this.regexAnalyzer.initialize?.(semanticEngine);
72
+ }
73
+
74
+ // Clean up if needed
75
+ if (this.regexAnalyzer) {
76
+ this.regexAnalyzer.cleanup?.();
77
+ }
78
+
79
+ if (process.env.SUNLINT_DEBUG) {
80
+ console.log(`🔧 [S041] Main analyzer initialized successfully`);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Single file analysis method for testing
86
+ */
87
+ analyzeSingle(filePath, options = {}) {
88
+ if (process.env.SUNLINT_DEBUG) {
89
+ console.log(`📊 [S041] analyzeSingle() called for: ${filePath}`);
90
+ }
91
+
92
+ // Return result using same format as analyze method
93
+ return this.analyze([filePath], "typescript", options);
94
+ }
95
+
96
+ async analyze(files, language, options = {}) {
97
+ if (process.env.SUNLINT_DEBUG) {
98
+ console.log(
99
+ `🔧 [S041] analyze() method called with ${files.length} files, language: ${language}`
100
+ );
101
+ }
102
+
103
+ const violations = [];
104
+
105
+ for (const filePath of files) {
106
+ try {
107
+ if (process.env.SUNLINT_DEBUG) {
108
+ console.log(`🔧 [S041] Processing file: ${filePath}`);
109
+ }
110
+
111
+ const fileViolations = await this.analyzeFile(filePath, options);
112
+ violations.push(...fileViolations);
113
+
114
+ if (process.env.SUNLINT_DEBUG) {
115
+ console.log(
116
+ `🔧 [S041] File ${filePath}: Found ${fileViolations.length} violations`
117
+ );
118
+ }
119
+ } catch (error) {
120
+ console.warn(
121
+ `⚠ [S041] Analysis failed for ${filePath}:`,
122
+ error.message
123
+ );
124
+ }
125
+ }
126
+
127
+ if (process.env.SUNLINT_DEBUG) {
128
+ console.log(`🔧 [S041] Total violations found: ${violations.length}`);
129
+ }
130
+
131
+ return violations;
132
+ }
133
+
134
+ async analyzeFile(filePath, options = {}) {
135
+ if (process.env.SUNLINT_DEBUG) {
136
+ console.log(`🔍 [S041] analyzeFile() called for: ${filePath}`);
137
+ }
138
+
139
+ // Create a Map to track unique violations and prevent duplicates
140
+ const violationMap = new Map();
141
+
142
+ // 1. Try Symbol-based analysis first (primary)
143
+ if (
144
+ this.config.useSymbolBased &&
145
+ this.semanticEngine?.project &&
146
+ this.semanticEngine?.initialized
147
+ ) {
148
+ try {
149
+ if (process.env.SUNLINT_DEBUG) {
150
+ console.log(`🔧 [S041] Trying symbol-based analysis...`);
151
+ }
152
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
153
+ if (sourceFile) {
154
+ if (process.env.SUNLINT_DEBUG) {
155
+ console.log(`🔧 [S041] Source file found, analyzing...`);
156
+ }
157
+ const symbolViolations = await this.symbolAnalyzer.analyze(
158
+ sourceFile,
159
+ filePath
160
+ );
161
+
162
+ // Add to violation map with deduplication
163
+ symbolViolations.forEach((violation) => {
164
+ const key = `${violation.line}:${violation.column}:${violation.message}`;
165
+ if (!violationMap.has(key)) {
166
+ violationMap.set(key, violation);
167
+ }
168
+ });
169
+
170
+ if (process.env.SUNLINT_DEBUG) {
171
+ console.log(
172
+ `🔧 [S041] Symbol analysis completed: ${symbolViolations.length} violations`
173
+ );
174
+ }
175
+ } else {
176
+ if (process.env.SUNLINT_DEBUG) {
177
+ console.log(`🔧 [S041] Source file not found, falling back...`);
178
+ }
179
+ }
180
+ } catch (error) {
181
+ console.warn(`⚠ [S041] Symbol analysis failed:`, error.message);
182
+ }
183
+ }
184
+
185
+ // 2. Try Regex-based analysis (fallback or additional)
186
+ if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
187
+ try {
188
+ if (process.env.SUNLINT_DEBUG) {
189
+ console.log(`🔧 [S041] Trying regex-based analysis...`);
190
+ }
191
+ const regexViolations = await this.regexAnalyzer.analyze(filePath);
192
+
193
+ // Add to violation map with deduplication
194
+ regexViolations.forEach((violation) => {
195
+ const key = `${violation.line}:${violation.column}:${violation.message}`;
196
+ if (!violationMap.has(key)) {
197
+ violationMap.set(key, violation);
198
+ }
199
+ });
200
+
201
+ if (process.env.SUNLINT_DEBUG) {
202
+ console.log(
203
+ `🔧 [S041] Regex analysis completed: ${regexViolations.length} violations`
204
+ );
205
+ }
206
+ } catch (error) {
207
+ console.warn(`⚠ [S041] Regex analysis failed:`, error.message);
208
+ }
209
+ }
210
+
211
+ // Convert Map values to array and add filePath to each violation
212
+ const finalViolations = Array.from(violationMap.values()).map(
213
+ (violation) => ({
214
+ ...violation,
215
+ filePath: filePath,
216
+ file: filePath, // Also add 'file' for compatibility
217
+ })
218
+ );
219
+
220
+ if (process.env.SUNLINT_DEBUG) {
221
+ console.log(
222
+ `🔧 [S041] File analysis completed: ${finalViolations.length} unique violations`
223
+ );
224
+ }
225
+
226
+ return finalViolations;
227
+ }
228
+
229
+ /**
230
+ * Clean up resources
231
+ */
232
+ cleanup() {
233
+ if (this.symbolAnalyzer?.cleanup) {
234
+ this.symbolAnalyzer.cleanup();
235
+ }
236
+ if (this.regexAnalyzer?.cleanup) {
237
+ this.regexAnalyzer.cleanup();
238
+ }
239
+ }
240
+ }
241
+
242
+ module.exports = S041Analyzer;