@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,329 @@
1
+ /**
2
+ * S044 Regex-Based Analyzer - Re-authentication Required for Sensitive Operations
3
+ * Fallback analyzer using regex patterns for cases where symbol-based analysis is not available
4
+ */
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+
9
+ class S044RegexBasedAnalyzer {
10
+ constructor(semanticEngine = null) {
11
+ this.semanticEngine = semanticEngine;
12
+ this.ruleId = "S044";
13
+ this.category = "security";
14
+
15
+ // Sensitive operations patterns
16
+ this.sensitiveOperationPatterns = [
17
+ /changePassword|updatePassword|resetPassword/gi,
18
+ /changeEmail|updateEmail/gi,
19
+ /changeProfile|updateProfile/gi,
20
+ /deleteAccount|deactivateAccount/gi,
21
+ /changePhoneNumber|updatePhoneNumber/gi,
22
+ /changeSecurityQuestion|updateSecurityQuestion/gi,
23
+ /changeTwoFactorSettings|updateTwoFactorSettings/gi,
24
+ /changeBillingInfo|updateBillingInfo/gi,
25
+ /changePaymentMethod|updatePaymentMethod/gi
26
+ ];
27
+
28
+ // Re-authentication patterns
29
+ this.reAuthPatterns = [
30
+ /verifyPassword|confirmPassword|reAuthenticate/gi,
31
+ /verifyCurrentPassword|validatePassword/gi,
32
+ /checkPassword|authenticateUser/gi,
33
+ /verifyIdentity/gi
34
+ ];
35
+
36
+ // Re-authentication middleware patterns
37
+ this.reAuthMiddlewarePatterns = [
38
+ /requireReAuth|requireReAuthentication/gi,
39
+ /verifyReAuth|checkReAuth/gi,
40
+ /validateReAuth/gi,
41
+ /ReAuthGuard|ReAuthenticationGuard/gi,
42
+ /VerifyReAuthGuard/gi
43
+ ];
44
+
45
+ // NestJS decorator patterns
46
+ this.nestjsDecoratorPatterns = [
47
+ /@RequireReAuth|@ReAuthenticationRequired/gi,
48
+ /@VerifyReAuth/gi,
49
+ /@UseGuards\s*\(\s*ReAuthGuard/gi,
50
+ /@UseGuards\s*\(\s*ReAuthenticationGuard/gi,
51
+ /@UseGuards\s*\(\s*VerifyReAuthGuard/gi
52
+ ];
53
+
54
+ // Express route patterns
55
+ this.expressRoutePatterns = [
56
+ /\.(get|post|put|patch|delete)\s*\(\s*['"`]\/change-password/gi,
57
+ /\.(get|post|put|patch|delete)\s*\(\s*['"`]\/update-password/gi,
58
+ /\.(get|post|put|patch|delete)\s*\(\s*['"`]\/change-email/gi,
59
+ /\.(get|post|put|patch|delete)\s*\(\s*['"`]\/update-email/gi,
60
+ /\.(get|post|put|patch|delete)\s*\(\s*['"`]\/change-profile/gi,
61
+ /\.(get|post|put|patch|delete)\s*\(\s*['"`]\/update-profile/gi,
62
+ /\.(get|post|put|patch|delete)\s*\(\s*['"`]\/delete-account/gi,
63
+ /\.(get|post|put|patch|delete)\s*\(\s*['"`]\/deactivate-account/gi
64
+ ];
65
+
66
+ // Exclude patterns
67
+ this.excludePatterns = [
68
+ /login|register|logout/gi,
69
+ /forgot-password|reset-password-request/gi,
70
+ /signin|signup|signout/gi
71
+ ];
72
+ }
73
+
74
+ /**
75
+ * Initialize analyzer with semantic engine
76
+ */
77
+ async initialize(semanticEngine) {
78
+ this.semanticEngine = semanticEngine;
79
+ if (this.verbose) {
80
+ console.log(`🔍 [${this.ruleId}] Regex: Semantic engine initialized`);
81
+ }
82
+ }
83
+
84
+ async analyze(filePath) {
85
+ if (this.verbose) {
86
+ console.log(
87
+ `🔍 [${this.ruleId}] Regex: Starting analysis for ${filePath}`
88
+ );
89
+ }
90
+
91
+ try {
92
+ const content = fs.readFileSync(filePath, "utf8");
93
+ const lines = content.split("\n");
94
+ const violations = [];
95
+ let inControllerClass = false;
96
+
97
+ for (let i = 0; i < lines.length; i++) {
98
+ const line = lines[i];
99
+ const lineNumber = i + 1;
100
+
101
+ // Track if we're inside a controller class
102
+ if (this.isControllerClassDeclaration(line)) {
103
+ inControllerClass = true;
104
+ } else if (this.isClassDeclaration(line) && !this.isControllerClassDeclaration(line)) {
105
+ inControllerClass = false;
106
+ }
107
+
108
+ // Only check methods inside controller classes
109
+ if (inControllerClass) {
110
+ const violation = this.analyzeLine(line, lineNumber, filePath, lines, i);
111
+ if (violation) {
112
+ violations.push(violation);
113
+ }
114
+ }
115
+ }
116
+
117
+ if (this.verbose) {
118
+ console.log(
119
+ `🔍 [${this.ruleId}] Regex: Analysis completed. Found ${violations.length} violations`
120
+ );
121
+ }
122
+
123
+ return violations;
124
+ } catch (error) {
125
+ if (this.verbose) {
126
+ console.log(
127
+ `🔍 [${this.ruleId}] Regex: Error in analysis:`,
128
+ error.message
129
+ );
130
+ }
131
+ return [];
132
+ }
133
+ }
134
+
135
+ analyzeLine(line, lineNumber, filePath, lines, currentIndex) {
136
+ try {
137
+ // Skip comments and empty lines
138
+ if (this.isCommentOrEmpty(line)) {
139
+ return null;
140
+ }
141
+
142
+ // Check for sensitive operations
143
+ const sensitiveOperation = this.findSensitiveOperation(line);
144
+ if (!sensitiveOperation) {
145
+ return null;
146
+ }
147
+
148
+ // Check if this is an excluded pattern
149
+ if (this.isExcludedPattern(line)) {
150
+ return null;
151
+ }
152
+
153
+ // Check if re-authentication is present in current line or previous lines
154
+ if (this.hasReAuthentication(line) || this.hasReAuthenticationInContext(lines, currentIndex)) {
155
+ return null; // Properly protected
156
+ }
157
+
158
+ // Only check methods in controller classes, not service classes
159
+ if (this.isControllerMethod(line)) {
160
+ return this.createViolation(
161
+ filePath,
162
+ lineNumber,
163
+ line.indexOf(sensitiveOperation),
164
+ `Sensitive operation '${sensitiveOperation}' requires re-authentication before execution`
165
+ );
166
+ }
167
+
168
+ // Check if this is an Express route
169
+ if (this.isExpressRoute(line)) {
170
+ return this.createViolation(
171
+ filePath,
172
+ lineNumber,
173
+ line.indexOf(sensitiveOperation),
174
+ `Sensitive route requires re-authentication middleware`
175
+ );
176
+ }
177
+
178
+ return null;
179
+ } catch (error) {
180
+ if (this.verbose) {
181
+ console.log(
182
+ `🔍 [${this.ruleId}] Regex: Error analyzing line ${lineNumber}:`,
183
+ error.message
184
+ );
185
+ }
186
+ return null;
187
+ }
188
+ }
189
+
190
+ isCommentOrEmpty(line) {
191
+ const trimmed = line.trim();
192
+ return (
193
+ trimmed === "" ||
194
+ trimmed.startsWith("//") ||
195
+ trimmed.startsWith("/*") ||
196
+ trimmed.startsWith("*") ||
197
+ trimmed.startsWith("#")
198
+ );
199
+ }
200
+
201
+ findSensitiveOperation(line) {
202
+ for (const pattern of this.sensitiveOperationPatterns) {
203
+ const match = line.match(pattern);
204
+ if (match) {
205
+ return match[0];
206
+ }
207
+ }
208
+ return null;
209
+ }
210
+
211
+ isExcludedPattern(line) {
212
+ return this.excludePatterns.some(pattern => pattern.test(line));
213
+ }
214
+
215
+ hasReAuthentication(line) {
216
+ // Check for re-authentication methods
217
+ if (this.reAuthPatterns.some(pattern => pattern.test(line))) {
218
+ return true;
219
+ }
220
+
221
+ // Check for re-authentication middleware
222
+ if (this.reAuthMiddlewarePatterns.some(pattern => pattern.test(line))) {
223
+ return true;
224
+ }
225
+
226
+ // Check for NestJS decorators
227
+ if (this.nestjsDecoratorPatterns.some(pattern => pattern.test(line))) {
228
+ return true;
229
+ }
230
+
231
+ // Check for @UseGuards decorators
232
+ if (/@UseGuards\s*\(\s*ReAuth/gi.test(line)) {
233
+ return true;
234
+ }
235
+
236
+ return false;
237
+ }
238
+
239
+ isControllerClassDeclaration(line) {
240
+ return /class\s+\w*Controller\s*{/gi.test(line) ||
241
+ /@Controller/gi.test(line);
242
+ }
243
+
244
+ isClassDeclaration(line) {
245
+ return /class\s+\w+\s*{/gi.test(line);
246
+ }
247
+
248
+ isControllerMethod(line) {
249
+ // Check if this is a method in a controller class
250
+ // Look for @Controller decorator or controller class pattern
251
+ return (
252
+ /^\s*@\w+.*\s+(async\s+)?(public\s+|private\s+|protected\s+)?(static\s+)?(async\s+)?\w+\s*\(/gi.test(line) ||
253
+ /^\s*(async\s+)?(public\s+|private\s+|protected\s+)?(static\s+)?(async\s+)?\w+\s*\(/gi.test(line)
254
+ );
255
+ }
256
+
257
+ hasReAuthenticationInContext(lines, currentIndex) {
258
+ // Check previous lines for re-authentication decorators
259
+ for (let i = Math.max(0, currentIndex - 5); i < currentIndex; i++) {
260
+ if (this.hasReAuthentication(lines[i])) {
261
+ return true;
262
+ }
263
+ }
264
+
265
+ // Check if method calls re-authentication methods
266
+ const methodBody = this.getMethodBody(lines, currentIndex);
267
+ if (methodBody && this.hasReAuthMethodCall(methodBody)) {
268
+ return true;
269
+ }
270
+
271
+ return false;
272
+ }
273
+
274
+ getMethodBody(lines, currentIndex) {
275
+ // Get the method body by looking for the opening brace
276
+ let braceCount = 0;
277
+ let methodBody = [];
278
+ let foundOpeningBrace = false;
279
+
280
+ for (let i = currentIndex; i < lines.length && i < currentIndex + 20; i++) {
281
+ const line = lines[i];
282
+ methodBody.push(line);
283
+
284
+ // Count braces to find method body
285
+ for (const char of line) {
286
+ if (char === '{') {
287
+ braceCount++;
288
+ foundOpeningBrace = true;
289
+ } else if (char === '}') {
290
+ braceCount--;
291
+ if (foundOpeningBrace && braceCount === 0) {
292
+ return methodBody.join('\n');
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ return null;
299
+ }
300
+
301
+ hasReAuthMethodCall(methodBody) {
302
+ return this.reAuthPatterns.some(pattern => pattern.test(methodBody));
303
+ }
304
+
305
+ isExpressRoute(line) {
306
+ return this.expressRoutePatterns.some(pattern => pattern.test(line));
307
+ }
308
+
309
+ createViolation(filePath, line, column, message) {
310
+ return {
311
+ rule: this.ruleId,
312
+ source: filePath,
313
+ category: this.category,
314
+ line: line,
315
+ column: column,
316
+ message: message,
317
+ severity: "error",
318
+ };
319
+ }
320
+
321
+ /**
322
+ * Clean up resources
323
+ */
324
+ cleanup() {
325
+ // No cleanup needed for regex-based analyzer
326
+ }
327
+ }
328
+
329
+ module.exports = S044RegexBasedAnalyzer;