@sun-asterisk/sunlint 1.3.4 → 1.3.5
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/CHANGELOG.md +32 -0
- package/config/presets/all.json +49 -48
- package/config/presets/beginner.json +7 -18
- package/config/presets/ci.json +63 -27
- package/config/presets/maintainability.json +6 -4
- package/config/presets/performance.json +4 -3
- package/config/presets/quality.json +11 -50
- package/config/presets/recommended.json +83 -10
- package/config/presets/security.json +20 -19
- package/config/presets/strict.json +6 -13
- package/config/rules/enhanced-rules-registry.json +64 -7
- package/core/config-preset-resolver.js +7 -2
- package/package.json +1 -1
- package/rules/common/C067_no_hardcoded_config/analyzer.js +95 -0
- package/rules/common/C067_no_hardcoded_config/config.json +81 -0
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +1000 -0
- package/rules/security/S024_xpath_xxe_protection/analyzer.js +242 -0
- package/rules/security/S024_xpath_xxe_protection/config.json +152 -0
- package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +338 -0
- package/rules/security/S024_xpath_xxe_protection/symbol-based-analyzer.js +474 -0
- package/rules/security/S025_server_side_validation/README.md +179 -0
- package/rules/security/S025_server_side_validation/analyzer.js +242 -0
- package/rules/security/S025_server_side_validation/config.json +111 -0
- package/rules/security/S025_server_side_validation/regex-based-analyzer.js +388 -0
- package/rules/security/S025_server_side_validation/symbol-based-analyzer.js +523 -0
- package/scripts/README.md +83 -0
- package/scripts/analyze-core-rules.js +151 -0
- package/scripts/generate-presets.js +202 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S025 Symbol-Based Analyzer - Always validate client-side data on the server
|
|
3
|
+
* Uses TypeScript compiler API for semantic analysis
|
|
4
|
+
*
|
|
5
|
+
* Detects patterns where client data is used without server-side validation:
|
|
6
|
+
* 1. Using @Body() without ValidationPipe or DTO validation
|
|
7
|
+
* 2. Trusting sensitive fields from client (userId, role, price, isAdmin)
|
|
8
|
+
* 3. Direct use of req.body, req.query, req.params without validation
|
|
9
|
+
* 4. Missing ValidationPipe configuration
|
|
10
|
+
* 5. SQL injection via string concatenation
|
|
11
|
+
* 6. File upload without server-side validation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const ts = require("typescript");
|
|
15
|
+
|
|
16
|
+
class S025SymbolBasedAnalyzer {
|
|
17
|
+
constructor(semanticEngine = null) {
|
|
18
|
+
this.semanticEngine = semanticEngine;
|
|
19
|
+
this.ruleId = "S025";
|
|
20
|
+
this.category = "security";
|
|
21
|
+
|
|
22
|
+
// Sensitive field patterns that should not come from client
|
|
23
|
+
this.sensitiveFields = [
|
|
24
|
+
"userId", "user_id", "id",
|
|
25
|
+
"role", "roles", "permissions",
|
|
26
|
+
"price", "amount", "total", "cost",
|
|
27
|
+
"isAdmin", "is_admin", "admin",
|
|
28
|
+
"discount", "balance", "credits",
|
|
29
|
+
"isActive", "is_active", "enabled",
|
|
30
|
+
"status", "state"
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Client data sources that need validation
|
|
34
|
+
this.clientDataSources = [
|
|
35
|
+
"req.body", "request.body",
|
|
36
|
+
"req.query", "request.query",
|
|
37
|
+
"req.params", "request.params",
|
|
38
|
+
"ctx.request.body", "ctx.query", "ctx.params",
|
|
39
|
+
"@Body()", "@Query()", "@Param()"
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Validation indicators
|
|
43
|
+
this.validationIndicators = [
|
|
44
|
+
"ValidationPipe", "validate", "validator",
|
|
45
|
+
"class-validator", "joi", "yup", "zod",
|
|
46
|
+
"IsString", "IsInt", "IsEmail", "IsUUID",
|
|
47
|
+
"validateOrReject", "plainToClass"
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// SQL query patterns
|
|
51
|
+
this.sqlQueryPatterns = [
|
|
52
|
+
"query", "exec", "execute", "find", "findOne",
|
|
53
|
+
"createQueryBuilder", "getRepository"
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialize analyzer with semantic engine
|
|
59
|
+
*/
|
|
60
|
+
async initialize(semanticEngine) {
|
|
61
|
+
this.semanticEngine = semanticEngine;
|
|
62
|
+
if (this.verbose) {
|
|
63
|
+
console.log(`🔍 [${this.ruleId}] Symbol: Semantic engine initialized`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async analyze(filePath) {
|
|
68
|
+
if (this.verbose) {
|
|
69
|
+
console.log(
|
|
70
|
+
`🔍 [${this.ruleId}] Symbol: Starting analysis for ${filePath}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!this.semanticEngine) {
|
|
75
|
+
if (this.verbose) {
|
|
76
|
+
console.log(
|
|
77
|
+
`🔍 [${this.ruleId}] Symbol: No semantic engine available, skipping`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const sourceFile = this.semanticEngine.getSourceFile(filePath);
|
|
85
|
+
if (!sourceFile) {
|
|
86
|
+
if (this.verbose) {
|
|
87
|
+
console.log(
|
|
88
|
+
`🔍 [${this.ruleId}] Symbol: No source file found, trying ts-morph fallback`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return await this.analyzeTsMorph(filePath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (this.verbose) {
|
|
95
|
+
console.log(`🔧 [${this.ruleId}] Source file found, analyzing...`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return await this.analyzeSourceFile(sourceFile, filePath);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (this.verbose) {
|
|
101
|
+
console.log(
|
|
102
|
+
`🔍 [${this.ruleId}] Symbol: Error in analysis:`,
|
|
103
|
+
error.message
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async analyzeTsMorph(filePath) {
|
|
111
|
+
try {
|
|
112
|
+
if (this.verbose) {
|
|
113
|
+
console.log(`🔍 [${this.ruleId}] Symbol: Starting ts-morph analysis`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const { Project } = require("ts-morph");
|
|
117
|
+
const project = new Project();
|
|
118
|
+
const sourceFile = project.addSourceFileAtPath(filePath);
|
|
119
|
+
|
|
120
|
+
return await this.analyzeSourceFile(sourceFile, filePath);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (this.verbose) {
|
|
123
|
+
console.log(
|
|
124
|
+
`🔍 [${this.ruleId}] Symbol: ts-morph analysis failed:`,
|
|
125
|
+
error.message
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async analyzeSourceFile(sourceFile, filePath) {
|
|
133
|
+
const violations = [];
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const fileContent = sourceFile.getFullText();
|
|
137
|
+
|
|
138
|
+
// Check for NestJS specific patterns
|
|
139
|
+
const isNestJSFile = this.isNestJSFile(fileContent);
|
|
140
|
+
const isExpressFile = this.isExpressFile(fileContent);
|
|
141
|
+
|
|
142
|
+
if (this.verbose) {
|
|
143
|
+
console.log(`🔍 [${this.ruleId}] Symbol: Framework detection - NestJS: ${isNestJSFile}, Express: ${isExpressFile}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 1. Check for missing ValidationPipe in NestJS
|
|
147
|
+
if (isNestJSFile) {
|
|
148
|
+
violations.push(...this.checkValidationPipeUsage(sourceFile));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 2. Check for unsafe @Body() usage without DTO
|
|
152
|
+
violations.push(...this.checkUnsafeBodyUsage(sourceFile));
|
|
153
|
+
|
|
154
|
+
// 3. Check for sensitive field trusting
|
|
155
|
+
violations.push(...this.checkSensitiveFieldTrusting(sourceFile));
|
|
156
|
+
|
|
157
|
+
// 4. Check for SQL injection patterns
|
|
158
|
+
violations.push(...this.checkSQLInjectionPatterns(sourceFile));
|
|
159
|
+
|
|
160
|
+
// 5. Check for file upload validation
|
|
161
|
+
violations.push(...this.checkFileUploadValidation(sourceFile));
|
|
162
|
+
|
|
163
|
+
// 6. Check for Express req usage without validation
|
|
164
|
+
if (isExpressFile) {
|
|
165
|
+
violations.push(...this.checkExpressReqUsage(sourceFile));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (this.verbose) {
|
|
169
|
+
console.log(
|
|
170
|
+
`🔍 [${this.ruleId}] Symbol: Analysis completed. Found ${violations.length} violations`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return violations;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (this.verbose) {
|
|
177
|
+
console.log(
|
|
178
|
+
`🔍 [${this.ruleId}] Symbol: Error in source file analysis:`,
|
|
179
|
+
error.message
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
isNestJSFile(content) {
|
|
187
|
+
return content.includes("@nestjs/") ||
|
|
188
|
+
content.includes("@Controller") ||
|
|
189
|
+
content.includes("@Post") ||
|
|
190
|
+
content.includes("@Get") ||
|
|
191
|
+
content.includes("@Body()");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
isExpressFile(content) {
|
|
195
|
+
return content.includes("express") ||
|
|
196
|
+
content.includes("req.body") ||
|
|
197
|
+
content.includes("req.query") ||
|
|
198
|
+
content.includes("res.") ||
|
|
199
|
+
content.includes("app.post") ||
|
|
200
|
+
content.includes("app.get");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
checkValidationPipeUsage(sourceFile) {
|
|
204
|
+
const violations = [];
|
|
205
|
+
const content = sourceFile.getFullText();
|
|
206
|
+
|
|
207
|
+
// Check if ValidationPipe is configured globally
|
|
208
|
+
const hasGlobalValidationPipe = content.includes("useGlobalPipes") &&
|
|
209
|
+
content.includes("ValidationPipe");
|
|
210
|
+
|
|
211
|
+
// If no global ValidationPipe, check individual routes
|
|
212
|
+
if (!hasGlobalValidationPipe) {
|
|
213
|
+
const decorators = sourceFile.getDescendantsOfKind?.(
|
|
214
|
+
require("typescript").SyntaxKind.Decorator
|
|
215
|
+
) || [];
|
|
216
|
+
|
|
217
|
+
for (const decorator of decorators) {
|
|
218
|
+
try {
|
|
219
|
+
const decoratorText = decorator.getText();
|
|
220
|
+
if (decoratorText.includes("@Post") ||
|
|
221
|
+
decoratorText.includes("@Put") ||
|
|
222
|
+
decoratorText.includes("@Patch")) {
|
|
223
|
+
|
|
224
|
+
// Find the method this decorator is attached to
|
|
225
|
+
const method = decorator.getParent();
|
|
226
|
+
if (method) {
|
|
227
|
+
const methodText = method.getText();
|
|
228
|
+
|
|
229
|
+
// Check if method uses @Body() without proper validation
|
|
230
|
+
if (methodText.includes("@Body()") &&
|
|
231
|
+
!this.hasValidationInMethod(methodText)) {
|
|
232
|
+
|
|
233
|
+
const lineNumber = sourceFile.getLineAndColumnAtPos(decorator.getStart()).line;
|
|
234
|
+
violations.push(this.createViolation(
|
|
235
|
+
sourceFile,
|
|
236
|
+
decorator,
|
|
237
|
+
`Route missing ValidationPipe or DTO validation`
|
|
238
|
+
));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (this.verbose) {
|
|
244
|
+
console.log(`🔍 [${this.ruleId}] Symbol: Error checking decorator:`, error.message);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return violations;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
hasValidationInMethod(methodText) {
|
|
254
|
+
return this.validationIndicators.some(indicator =>
|
|
255
|
+
methodText.includes(indicator)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
checkUnsafeBodyUsage(sourceFile) {
|
|
260
|
+
const violations = [];
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const content = sourceFile.getFullText();
|
|
264
|
+
|
|
265
|
+
// Look for @Body() any or @Body() without DTO
|
|
266
|
+
const bodyUsagePatterns = [
|
|
267
|
+
/@Body\(\)\s+\w+:\s*any/g,
|
|
268
|
+
/@Body\(\)\s+\w+:\s*Record<string,\s*any>/g,
|
|
269
|
+
/@Body\(\)\s+\{[^}]*\}:\s*any/g
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
for (const pattern of bodyUsagePatterns) {
|
|
273
|
+
let match;
|
|
274
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
275
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
276
|
+
violations.push({
|
|
277
|
+
rule: this.ruleId,
|
|
278
|
+
source: sourceFile.getFilePath(),
|
|
279
|
+
category: this.category,
|
|
280
|
+
line: lineNumber,
|
|
281
|
+
column: 1,
|
|
282
|
+
message: `Unsafe @Body() usage without proper DTO validation`,
|
|
283
|
+
severity: "error",
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (this.verbose) {
|
|
290
|
+
console.log(`🔍 [${this.ruleId}] Symbol: Error checking body usage:`, error.message);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return violations;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
checkSensitiveFieldTrusting(sourceFile) {
|
|
298
|
+
const violations = [];
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
const content = sourceFile.getFullText();
|
|
302
|
+
|
|
303
|
+
// Check for destructuring sensitive fields from client data
|
|
304
|
+
for (const sensitiveField of this.sensitiveFields) {
|
|
305
|
+
const patterns = [
|
|
306
|
+
new RegExp(`\\{[^}]*${sensitiveField}[^}]*\\}\\s*=\\s*req\\.body`, 'g'),
|
|
307
|
+
new RegExp(`\\{[^}]*${sensitiveField}[^}]*\\}\\s*=\\s*@Body\\(\\)`, 'g'),
|
|
308
|
+
new RegExp(`const\\s+${sensitiveField}\\s*=\\s*req\\.body\\.${sensitiveField}`, 'g'),
|
|
309
|
+
new RegExp(`let\\s+${sensitiveField}\\s*=\\s*req\\.body\\.${sensitiveField}`, 'g')
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
for (const pattern of patterns) {
|
|
313
|
+
let match;
|
|
314
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
315
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
316
|
+
violations.push({
|
|
317
|
+
rule: this.ruleId,
|
|
318
|
+
source: sourceFile.getFilePath(),
|
|
319
|
+
category: this.category,
|
|
320
|
+
line: lineNumber,
|
|
321
|
+
column: 1,
|
|
322
|
+
message: `Sensitive field "${sensitiveField}" should not be trusted from client data`,
|
|
323
|
+
severity: "error",
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
} catch (error) {
|
|
330
|
+
if (this.verbose) {
|
|
331
|
+
console.log(`🔍 [${this.ruleId}] Symbol: Error checking sensitive fields:`, error.message);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return violations;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
checkSQLInjectionPatterns(sourceFile) {
|
|
339
|
+
const violations = [];
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const content = sourceFile.getFullText();
|
|
343
|
+
|
|
344
|
+
// Check for string concatenation in SQL queries
|
|
345
|
+
const sqlInjectionPatterns = [
|
|
346
|
+
/\.query\s*\(\s*[`"'][^`"']*\$\{[^}]+\}[^`"']*[`"']/g,
|
|
347
|
+
/\.query\s*\(\s*[`"'][^`"']*\+[^`"']*[`"']/g,
|
|
348
|
+
/SELECT\s+[^"'`]*\$\{[^}]+\}/gi,
|
|
349
|
+
/WHERE\s+[^"'`]*\$\{[^}]+\}/gi,
|
|
350
|
+
/ORDER\s+BY\s+[^"'`]*\$\{[^}]+\}/gi
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
for (const pattern of sqlInjectionPatterns) {
|
|
354
|
+
let match;
|
|
355
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
356
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
357
|
+
violations.push({
|
|
358
|
+
rule: this.ruleId,
|
|
359
|
+
source: sourceFile.getFilePath(),
|
|
360
|
+
category: this.category,
|
|
361
|
+
line: lineNumber,
|
|
362
|
+
column: 1,
|
|
363
|
+
message: `Potential SQL injection: use parameterized queries instead of string concatenation`,
|
|
364
|
+
severity: "error",
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
} catch (error) {
|
|
370
|
+
if (this.verbose) {
|
|
371
|
+
console.log(`🔍 [${this.ruleId}] Symbol: Error checking SQL patterns:`, error.message);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return violations;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
checkFileUploadValidation(sourceFile) {
|
|
379
|
+
const violations = [];
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const content = sourceFile.getFullText();
|
|
383
|
+
|
|
384
|
+
// Check for file upload without validation
|
|
385
|
+
if (content.includes("@UseInterceptors(FileInterceptor") ||
|
|
386
|
+
content.includes("@UploadedFile()")) {
|
|
387
|
+
|
|
388
|
+
// Check if there's proper file validation
|
|
389
|
+
const hasFileValidation = content.includes("fileFilter") ||
|
|
390
|
+
content.includes("file.mimetype") ||
|
|
391
|
+
content.includes("file.size") ||
|
|
392
|
+
content.includes("multer");
|
|
393
|
+
|
|
394
|
+
if (!hasFileValidation) {
|
|
395
|
+
const uploadMatch = content.match(/@UploadedFile\(\)/);
|
|
396
|
+
if (uploadMatch) {
|
|
397
|
+
const lineNumber = this.getLineNumber(content, uploadMatch.index);
|
|
398
|
+
violations.push({
|
|
399
|
+
rule: this.ruleId,
|
|
400
|
+
source: sourceFile.getFilePath(),
|
|
401
|
+
category: this.category,
|
|
402
|
+
line: lineNumber,
|
|
403
|
+
column: 1,
|
|
404
|
+
message: `File upload missing server-side validation (type, size, content)`,
|
|
405
|
+
severity: "error",
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (this.verbose) {
|
|
413
|
+
console.log(`🔍 [${this.ruleId}] Symbol: Error checking file upload:`, error.message);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return violations;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
checkExpressReqUsage(sourceFile) {
|
|
421
|
+
const violations = [];
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
const content = sourceFile.getFullText();
|
|
425
|
+
|
|
426
|
+
// Check for direct req.body usage without validation
|
|
427
|
+
const patterns = [
|
|
428
|
+
/req\.body\.\w+/g,
|
|
429
|
+
/req\.query\.\w+/g,
|
|
430
|
+
/req\.params\.\w+/g
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
for (const pattern of patterns) {
|
|
434
|
+
let match;
|
|
435
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
436
|
+
// Check if validation is present in the same function
|
|
437
|
+
const functionStart = this.findFunctionStart(content, match.index);
|
|
438
|
+
const functionEnd = this.findFunctionEnd(content, match.index);
|
|
439
|
+
const functionBody = content.substring(functionStart, functionEnd);
|
|
440
|
+
|
|
441
|
+
const hasValidation = this.validationIndicators.some(indicator =>
|
|
442
|
+
functionBody.includes(indicator)
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
if (!hasValidation) {
|
|
446
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
447
|
+
violations.push({
|
|
448
|
+
rule: this.ruleId,
|
|
449
|
+
source: sourceFile.getFilePath(),
|
|
450
|
+
category: this.category,
|
|
451
|
+
line: lineNumber,
|
|
452
|
+
column: 1,
|
|
453
|
+
message: `Direct use of ${match[0]} without server-side validation`,
|
|
454
|
+
severity: "error",
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (this.verbose) {
|
|
462
|
+
console.log(`🔍 [${this.ruleId}] Symbol: Error checking Express req usage:`, error.message);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return violations;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
findFunctionStart(content, index) {
|
|
470
|
+
// Simple heuristic: find the previous function declaration
|
|
471
|
+
const beforeIndex = content.lastIndexOf("function", index);
|
|
472
|
+
const beforeArrow = content.lastIndexOf("=>", index);
|
|
473
|
+
const beforeAsync = content.lastIndexOf("async", index);
|
|
474
|
+
|
|
475
|
+
return Math.max(beforeIndex, beforeArrow, beforeAsync, 0);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
findFunctionEnd(content, index) {
|
|
479
|
+
// Simple heuristic: find the next function or end of file
|
|
480
|
+
const afterFunction = content.indexOf("function", index + 1);
|
|
481
|
+
const afterArrow = content.indexOf("=>", index + 1);
|
|
482
|
+
|
|
483
|
+
if (afterFunction === -1 && afterArrow === -1) {
|
|
484
|
+
return content.length;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (afterFunction === -1) return afterArrow;
|
|
488
|
+
if (afterArrow === -1) return afterFunction;
|
|
489
|
+
|
|
490
|
+
return Math.min(afterFunction, afterArrow);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
createViolation(sourceFile, node, message) {
|
|
494
|
+
try {
|
|
495
|
+
const start = node.getStart();
|
|
496
|
+
const lineAndChar = sourceFile.getLineAndColumnAtPos(start);
|
|
497
|
+
|
|
498
|
+
return {
|
|
499
|
+
rule: this.ruleId,
|
|
500
|
+
source: sourceFile.getFilePath(),
|
|
501
|
+
category: this.category,
|
|
502
|
+
line: lineAndChar.line,
|
|
503
|
+
column: lineAndChar.column,
|
|
504
|
+
message: `Server-side validation missing: ${message}`,
|
|
505
|
+
severity: "error",
|
|
506
|
+
};
|
|
507
|
+
} catch (error) {
|
|
508
|
+
if (this.verbose) {
|
|
509
|
+
console.log(
|
|
510
|
+
`🔍 [${this.ruleId}] Symbol: Error creating violation:`,
|
|
511
|
+
error.message
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
getLineNumber(content, index) {
|
|
519
|
+
return content.substring(0, index).split("\n").length;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
module.exports = S025SymbolBasedAnalyzer;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# SunLint Scripts Directory
|
|
2
|
+
|
|
3
|
+
## 📋 Script Categories
|
|
4
|
+
|
|
5
|
+
### 🔧 Core Generation Scripts
|
|
6
|
+
- **`generate-presets.js`** - Generate preset configurations from rules
|
|
7
|
+
- **`generate-rules-registry.js`** - Generate unified rules registry from origin-rules
|
|
8
|
+
- **`generate_insights.js`** - Generate insights and analysis of rule implementations
|
|
9
|
+
|
|
10
|
+
### 🔍 Analysis Scripts
|
|
11
|
+
- **`analyze-core-rules.js`** - Analyze common and security rules from markdown files
|
|
12
|
+
- **`validate-rule-structure.js`** - Validate rule structure and consistency
|
|
13
|
+
- **`validate-system.js`** - System-wide validation
|
|
14
|
+
|
|
15
|
+
### 🚀 Build & Release Scripts
|
|
16
|
+
- **`build-release.sh`** - Build release packages
|
|
17
|
+
- **`prepare-release.sh`** - Prepare release artifacts
|
|
18
|
+
- **`manual-release.sh`** - Manual release process
|
|
19
|
+
- **`trigger-release.sh`** - Trigger automated release
|
|
20
|
+
- **`pre-release-test.sh`** - Pre-release testing
|
|
21
|
+
- **`verify-install.sh`** - Verify installation
|
|
22
|
+
|
|
23
|
+
### ⚡ Performance & Testing Scripts
|
|
24
|
+
- **`performance-test.js`** - Performance benchmarking
|
|
25
|
+
- **`quick-performance-test.js`** - Quick performance check
|
|
26
|
+
- **`ci-report.js`** - CI reporting
|
|
27
|
+
|
|
28
|
+
### 🔄 Migration & Maintenance Scripts
|
|
29
|
+
- **`migrate-rule-registry.js`** - Migrate rule registry data
|
|
30
|
+
- **`consolidate-config.js`** - Consolidate configuration files
|
|
31
|
+
- **`copy-rules.js`** - Copy rules between locations
|
|
32
|
+
- **`category-manager.js`** - Manage rule categories
|
|
33
|
+
|
|
34
|
+
### 📦 Setup & Install Scripts
|
|
35
|
+
- **`install.sh`** - Installation script
|
|
36
|
+
- **`setup-github-registry.sh`** - Setup GitHub package registry
|
|
37
|
+
|
|
38
|
+
### 🎯 Demo & Example Scripts
|
|
39
|
+
- **`batch-processing-demo.js`** - Batch processing demonstration
|
|
40
|
+
|
|
41
|
+
## 🔄 Script Relationships
|
|
42
|
+
|
|
43
|
+
### Potential Consolidation Opportunities:
|
|
44
|
+
1. **Analysis Scripts**: `analyze-core-rules.js` and `generate_insights.js` have overlapping functionality
|
|
45
|
+
2. **Generation Scripts**: Multiple scripts parse rules - could be unified under common utilities
|
|
46
|
+
|
|
47
|
+
### Dependencies:
|
|
48
|
+
- Most scripts depend on `SimpleRuleParser` from `../rules/parser/rule-parser-simple`
|
|
49
|
+
- Rule source files in `../origin-rules/`
|
|
50
|
+
- Configuration files in `../config/`
|
|
51
|
+
|
|
52
|
+
## 🚀 Usage Guidelines
|
|
53
|
+
|
|
54
|
+
### For Preset Management:
|
|
55
|
+
```bash
|
|
56
|
+
# Generate new presets from rule sources
|
|
57
|
+
node scripts/generate-presets.js
|
|
58
|
+
|
|
59
|
+
# Analyze current rule status
|
|
60
|
+
node scripts/analyze-core-rules.js
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### For Rule Registry:
|
|
64
|
+
```bash
|
|
65
|
+
# Generate unified registry
|
|
66
|
+
node scripts/generate-rules-registry.js
|
|
67
|
+
|
|
68
|
+
# Get implementation insights
|
|
69
|
+
node scripts/generate_insights.js
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### For Release:
|
|
73
|
+
```bash
|
|
74
|
+
# Full release process
|
|
75
|
+
./scripts/prepare-release.sh
|
|
76
|
+
./scripts/build-release.sh
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 📝 Maintenance Notes
|
|
80
|
+
|
|
81
|
+
- Scripts marked with `#!/usr/bin/node` are executable
|
|
82
|
+
- Path references updated for scripts/ subdirectory location
|
|
83
|
+
- Consider consolidating overlapping analysis functionality
|