@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.
Files changed (28) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/config/presets/all.json +49 -48
  3. package/config/presets/beginner.json +7 -18
  4. package/config/presets/ci.json +63 -27
  5. package/config/presets/maintainability.json +6 -4
  6. package/config/presets/performance.json +4 -3
  7. package/config/presets/quality.json +11 -50
  8. package/config/presets/recommended.json +83 -10
  9. package/config/presets/security.json +20 -19
  10. package/config/presets/strict.json +6 -13
  11. package/config/rules/enhanced-rules-registry.json +64 -7
  12. package/core/config-preset-resolver.js +7 -2
  13. package/package.json +1 -1
  14. package/rules/common/C067_no_hardcoded_config/analyzer.js +95 -0
  15. package/rules/common/C067_no_hardcoded_config/config.json +81 -0
  16. package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +1000 -0
  17. package/rules/security/S024_xpath_xxe_protection/analyzer.js +242 -0
  18. package/rules/security/S024_xpath_xxe_protection/config.json +152 -0
  19. package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +338 -0
  20. package/rules/security/S024_xpath_xxe_protection/symbol-based-analyzer.js +474 -0
  21. package/rules/security/S025_server_side_validation/README.md +179 -0
  22. package/rules/security/S025_server_side_validation/analyzer.js +242 -0
  23. package/rules/security/S025_server_side_validation/config.json +111 -0
  24. package/rules/security/S025_server_side_validation/regex-based-analyzer.js +388 -0
  25. package/rules/security/S025_server_side_validation/symbol-based-analyzer.js +523 -0
  26. package/scripts/README.md +83 -0
  27. package/scripts/analyze-core-rules.js +151 -0
  28. 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