@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,242 @@
1
+ /**
2
+ * S025 Main Analyzer - Always validate client-side data on the server
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
5
+ * Command: node cli.js --rule=S025 --input=examples/rule-test-fixtures/rules/S025_server_side_validation --engine=heuristic
6
+ */
7
+
8
+ const S025SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
+ const S025RegexBasedAnalyzer = require("./regex-based-analyzer.js");
10
+
11
+ class S025Analyzer {
12
+ constructor(options = {}) {
13
+ if (process.env.SUNLINT_DEBUG) {
14
+ console.log(`🔧 [S025] Constructor called with options:`, !!options);
15
+ console.log(
16
+ `🔧 [S025] Options type:`,
17
+ typeof options,
18
+ Object.keys(options || {})
19
+ );
20
+ }
21
+
22
+ this.ruleId = "S025";
23
+ this.ruleName = "Always validate client-side data on the server";
24
+ this.description =
25
+ "Ensure all client-side data is validated on the server. Client-side validation is not sufficient for security as it can be bypassed by attackers. Server-side validation is mandatory for data integrity and security.";
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 S025SymbolBasedAnalyzer(this.semanticEngine);
39
+ if (process.env.SUNLINT_DEBUG) {
40
+ console.log(`🔧 [S025] Symbol analyzer created successfully`);
41
+ }
42
+ } catch (error) {
43
+ console.error(`🔧 [S025] Error creating symbol analyzer:`, error);
44
+ }
45
+
46
+ try {
47
+ this.regexAnalyzer = new S025RegexBasedAnalyzer(this.semanticEngine);
48
+ if (process.env.SUNLINT_DEBUG) {
49
+ console.log(`🔧 [S025] Regex analyzer created successfully`);
50
+ }
51
+ } catch (error) {
52
+ console.error(`🔧 [S025] 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(`🔧 [S025] 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(`🔧 [S025] 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(`🔧 [S025] 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
+ `🔧 [S025] 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(`🔧 [S025] 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
+ `🔧 [S025] File ${filePath}: Found ${fileViolations.length} violations`
117
+ );
118
+ }
119
+ } catch (error) {
120
+ console.warn(
121
+ `⚠ [S025] Analysis failed for ${filePath}:`,
122
+ error.message
123
+ );
124
+ }
125
+ }
126
+
127
+ if (process.env.SUNLINT_DEBUG) {
128
+ console.log(`🔧 [S025] 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(`🔍 [S025] 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(`🔧 [S025] 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(`🔧 [S025] 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
+ `🔧 [S025] Symbol analysis completed: ${symbolViolations.length} violations`
173
+ );
174
+ }
175
+ } else {
176
+ if (process.env.SUNLINT_DEBUG) {
177
+ console.log(`🔧 [S025] Source file not found, falling back...`);
178
+ }
179
+ }
180
+ } catch (error) {
181
+ console.warn(`⚠ [S025] 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(`🔧 [S025] 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
+ `🔧 [S025] Regex analysis completed: ${regexViolations.length} violations`
204
+ );
205
+ }
206
+ } catch (error) {
207
+ console.warn(`⚠ [S025] 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
+ `🔧 [S025] 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 = S025Analyzer;
@@ -0,0 +1,111 @@
1
+ {
2
+ "ruleId": "S025",
3
+ "name": "Always validate client-side data on the server",
4
+ "description": "Ensure all client-side data is validated on the server. Client-side validation is not sufficient for security as it can be bypassed by attackers. Server-side validation is mandatory for data integrity and security.",
5
+ "category": "security",
6
+ "severity": "error",
7
+ "languages": ["typescript", "javascript"],
8
+ "tags": ["security", "validation", "server-side", "owasp", "input-validation"],
9
+
10
+ "patterns": {
11
+ "nestjs": {
12
+ "missingValidationPipe": {
13
+ "description": "NestJS routes missing ValidationPipe or DTO validation",
14
+ "severity": "error"
15
+ },
16
+ "unsafeBodyUsage": {
17
+ "description": "Using @Body() with 'any' type instead of validated DTO",
18
+ "severity": "error"
19
+ }
20
+ },
21
+ "express": {
22
+ "directReqUsage": {
23
+ "description": "Direct use of req.body/req.query/req.params without validation",
24
+ "severity": "error"
25
+ },
26
+ "missingMiddleware": {
27
+ "description": "Express routes missing validation middleware",
28
+ "severity": "error"
29
+ }
30
+ },
31
+ "sensitiveFields": {
32
+ "clientTrusted": {
33
+ "description": "Sensitive fields (userId, role, price, isAdmin) trusted from client",
34
+ "severity": "error",
35
+ "fields": ["userId", "user_id", "id", "role", "roles", "permissions", "price", "amount", "total", "cost", "isAdmin", "is_admin", "admin", "discount", "balance", "credits", "isActive", "is_active", "enabled", "status", "state"]
36
+ }
37
+ },
38
+ "sqlInjection": {
39
+ "stringConcatenation": {
40
+ "description": "SQL queries using string concatenation or template literals with user input",
41
+ "severity": "error"
42
+ }
43
+ },
44
+ "fileUpload": {
45
+ "missingValidation": {
46
+ "description": "File uploads missing server-side validation (type, size, content)",
47
+ "severity": "error"
48
+ }
49
+ }
50
+ },
51
+
52
+ "validationIndicators": [
53
+ "ValidationPipe",
54
+ "class-validator",
55
+ "IsString", "IsInt", "IsEmail", "IsUUID", "IsOptional", "IsArray",
56
+ "validateOrReject", "plainToClass",
57
+ "joi.validate", "yup.validate", "zod.parse",
58
+ "fileFilter", "mimetype", "file.size"
59
+ ],
60
+
61
+ "frameworkSupport": {
62
+ "nestjs": {
63
+ "patterns": ["@Controller", "@Post", "@Get", "@Put", "@Delete", "@Body()", "@nestjs/"],
64
+ "validationMethods": ["ValidationPipe", "class-validator", "DTO"]
65
+ },
66
+ "express": {
67
+ "patterns": ["express", "req.body", "req.query", "req.params", "app.post", "app.get"],
68
+ "validationMethods": ["joi", "yup", "express-validator", "middleware"]
69
+ }
70
+ },
71
+
72
+ "examples": {
73
+ "violations": [
74
+ {
75
+ "code": "@Post('/checkout') checkout(@Body() body: any) { return this.service.checkout(body); }",
76
+ "issue": "Using @Body() with 'any' type - vulnerable to parameter tampering"
77
+ },
78
+ {
79
+ "code": "const { userId, discount } = req.body; const order = await this.service.create(userId, discount);",
80
+ "issue": "Trusting sensitive fields (userId, discount) from client without validation"
81
+ },
82
+ {
83
+ "code": "const query = `SELECT * FROM users WHERE id = ${req.params.id}`;",
84
+ "issue": "SQL injection via string concatenation with user input"
85
+ }
86
+ ],
87
+ "fixes": [
88
+ {
89
+ "code": "app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));",
90
+ "description": "Configure ValidationPipe globally"
91
+ },
92
+ {
93
+ "code": "@Post('/checkout') checkout(@Body() dto: CheckoutDto, @Req() req) { const userId = req.user.sub; }",
94
+ "description": "Use DTO validation and get userId from authenticated session"
95
+ },
96
+ {
97
+ "code": "const query = 'SELECT * FROM users WHERE id = ?'; await db.query(query, [req.params.id]);",
98
+ "description": "Use parameterized queries"
99
+ }
100
+ ]
101
+ },
102
+
103
+ "owaspMapping": {
104
+ "category": "A03:2021 – Injection",
105
+ "subcategories": [
106
+ "A04:2021 – Insecure Design",
107
+ "A07:2021 – Identification and Authentication Failures"
108
+ ],
109
+ "description": "Validates that all user input is properly validated server-side to prevent injection attacks and parameter tampering"
110
+ }
111
+ }