@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,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;
|