@sun-asterisk/sunlint 1.3.16 → 1.3.17

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 (50) hide show
  1. package/config/rule-analysis-strategies.js +3 -3
  2. package/config/rules/enhanced-rules-registry.json +40 -20
  3. package/core/cli-action-handler.js +2 -2
  4. package/core/config-merger.js +28 -6
  5. package/core/constants/defaults.js +1 -1
  6. package/core/file-targeting-service.js +72 -4
  7. package/core/output-service.js +21 -4
  8. package/engines/heuristic-engine.js +5 -0
  9. package/package.json +1 -1
  10. package/rules/common/C002_no_duplicate_code/README.md +115 -0
  11. package/rules/common/C002_no_duplicate_code/analyzer.js +615 -219
  12. package/rules/common/C002_no_duplicate_code/test-cases/api-handlers.ts +64 -0
  13. package/rules/common/C002_no_duplicate_code/test-cases/data-processor.ts +46 -0
  14. package/rules/common/C002_no_duplicate_code/test-cases/good-example.tsx +40 -0
  15. package/rules/common/C002_no_duplicate_code/test-cases/product-service.ts +57 -0
  16. package/rules/common/C002_no_duplicate_code/test-cases/user-service.ts +49 -0
  17. package/rules/common/C008/analyzer.js +40 -0
  18. package/rules/common/C008/config.json +20 -0
  19. package/rules/common/C008/ts-morph-analyzer.js +1067 -0
  20. package/rules/common/C018_no_throw_generic_error/analyzer.js +1 -1
  21. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +27 -3
  22. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +504 -162
  23. package/rules/common/C029_catch_block_logging/analyzer.js +499 -89
  24. package/rules/common/C033_separate_service_repository/README.md +131 -20
  25. package/rules/common/C033_separate_service_repository/analyzer.js +1 -1
  26. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +417 -274
  27. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +144 -254
  28. package/rules/common/C041_no_sensitive_hardcode/config.json +50 -0
  29. package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +575 -0
  30. package/rules/common/C067_no_hardcoded_config/analyzer.js +17 -16
  31. package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +3477 -659
  32. package/rules/docs/C002_no_duplicate_code.md +276 -11
  33. package/rules/index.js +5 -1
  34. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +266 -88
  35. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +805 -0
  36. package/rules/security/S010_no_insecure_encryption/README.md +78 -0
  37. package/rules/security/S010_no_insecure_encryption/analyzer.js +463 -398
  38. package/rules/security/S013_tls_enforcement/README.md +51 -0
  39. package/rules/security/S013_tls_enforcement/analyzer.js +99 -0
  40. package/rules/security/S013_tls_enforcement/config.json +41 -0
  41. package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +339 -0
  42. package/rules/security/S014_tls_version_enforcement/README.md +354 -0
  43. package/rules/security/S014_tls_version_enforcement/analyzer.js +118 -0
  44. package/rules/security/S014_tls_version_enforcement/config.json +56 -0
  45. package/rules/security/S014_tls_version_enforcement/symbol-based-analyzer.js +194 -0
  46. package/rules/security/S055_content_type_validation/analyzer.js +121 -279
  47. package/rules/security/S055_content_type_validation/symbol-based-analyzer.js +346 -0
  48. package/rules/tests/C002_no_duplicate_code.test.js +111 -22
  49. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +0 -755
  50. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +0 -296
@@ -1,311 +1,153 @@
1
1
  /**
2
- * Heuristic analyzer for S055 - Content-Type Validation in REST Services
2
+ * S055 Main Analyzer - Content-Type Validation in REST Services
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
3
5
  * Purpose: Detect REST endpoints that process request body without validating Content-Type
4
- * Based on OWASP ASVS 13.2.5 - Input Validation
6
+ * Command: node cli.js --rule=S055 --input=examples/rule-test-fixtures/rules/S055_content_type_validation --engine=heuristic --verbose
5
7
  */
6
8
 
9
+ const S055SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
10
+
7
11
  class S055Analyzer {
8
- constructor() {
9
- this.ruleId = 'S055';
10
- this.ruleName = 'Content-Type Validation in REST Services';
11
- this.description = 'Verify that REST services explicitly check the incoming Content-Type';
12
-
13
- // HTTP methods that typically have request bodies
14
- this.httpMethodsWithBody = [
15
- 'post', 'put', 'patch', 'delete'
16
- ];
17
-
18
- // Patterns that indicate request body usage
19
- this.requestBodyPatterns = [
20
- // Express.js patterns
21
- /req\.body/i,
22
- /request\.body/i,
23
-
24
- // NestJS patterns
25
- /@Body\(\)/i,
26
- /@Body\([^)]*\)/i,
27
-
28
- // Generic body access patterns
29
- /\.body\s*[;\.,\]\}]/i,
30
- /body\s*[:=]/i,
31
- ];
32
-
33
- // Patterns that indicate Content-Type validation
34
- this.contentTypeValidationPatterns = [
35
- // Express.js validation methods
36
- /req\.is\s*\(\s*['"`][^'"`]*application\/[^'"`]*['"`]\s*\)/i,
37
- /request\.is\s*\(\s*['"`][^'"`]*application\/[^'"`]*['"`]\s*\)/i,
38
-
39
- // Direct header checks
40
- /req\.headers\s*\[\s*['"`]content-type['"`]\s*\]/i,
41
- /request\.headers\s*\[\s*['"`]content-type['"`]\s*\]/i,
42
- /req\.get\s*\(\s*['"`]content-type['"`]\s*\)/i,
43
- /request\.get\s*\(\s*['"`]content-type['"`]\s*\)/i,
44
-
45
- // Content-Type comparison
46
- /content-type\s*[=!]==?\s*['"`]application\//i,
47
- /['"`]application\/[^'"`]*['"`]\s*[=!]==?\s*.*content-type/i,
48
-
49
- // Middleware patterns
50
- /express\.json\s*\(/i,
51
- /bodyParser\.json\s*\(/i,
52
- /app\.use\s*\([^)]*json[^)]*\)/i,
53
-
54
- // NestJS decorators
55
- /@Header\s*\(\s*['"`]Content-Type['"`]/i,
56
- /@UseInterceptors\s*\([^)]*ContentType[^)]*\)/i,
57
-
58
- // Custom validation functions
59
- /validateContentType/i,
60
- /checkContentType/i,
61
- /verifyContentType/i,
62
- ];
63
-
64
- // Patterns that indicate HTTP method handlers
65
- this.httpHandlerPatterns = [
66
- // Express.js route definitions
67
- /app\.(post|put|patch|delete)\s*\(/i,
68
- /router\.(post|put|patch|delete)\s*\(/i,
69
- /express\(\)\.(post|put|patch|delete)\s*\(/i,
70
-
71
- // NestJS decorators
72
- /@(Post|Put|Patch|Delete)\s*\(/i,
73
-
74
- // Generic handler patterns
75
- /(post|put|patch|delete)\s*:\s*(async\s+)?function/i,
76
- /(post|put|patch|delete)\s*:\s*\(/i,
77
-
78
- // Function names indicating HTTP handlers
79
- /function\s+(handle|process)?(Post|Put|Patch|Delete)/i,
80
- /const\s+\w*(post|put|patch|delete)\w*\s*=/i,
81
- /let\s+\w*(post|put|patch|delete)\w*\s*=/i,
82
- ];
83
-
84
- // Safe patterns to exclude from violations
85
- this.safePatterns = [
86
- // Comments and documentation
87
- /\/\/|\/\*|\*\/|@param|@return|@example/,
88
-
89
- // Import/export statements
90
- /import|export|require|module\.exports/i,
91
-
92
- // Type definitions
93
- /interface|type|enum|declare/i,
94
-
95
- // Test files patterns
96
- /describe\s*\(|it\s*\(|test\s*\(|expect\s*\(/i,
97
-
98
- // Configuration and constants
99
- /const\s+\w+\s*=\s*['"`]/i,
100
-
101
- // Logging and debugging
102
- /console\.|logger\.|log\(/i,
103
-
104
- // Middleware already handling Content-Type
105
- /express\.json|bodyParser\.json|multer\(/i,
106
- ];
107
-
108
- // Patterns indicating secure implementations
109
- this.secureImplementationPatterns = [
110
- // Middleware usage that handles Content-Type
111
- /app\.use\s*\([^)]*express\.json[^)]*\)/i,
112
- /app\.use\s*\([^)]*bodyParser\.json[^)]*\)/i,
113
-
114
- // Global Content-Type validation
115
- /app\.use\s*\([^)]*validateContentType[^)]*\)/i,
116
- /app\.use\s*\([^)]*checkContentType[^)]*\)/i,
117
- ];
118
- }
12
+ constructor(options = {}) {
13
+ if (process.env.SUNLINT_DEBUG) {
14
+ console.log(`🔧 [S055] Constructor called with options:`, !!options);
15
+ console.log(
16
+ `🔧 [S055] Options type:`,
17
+ typeof options,
18
+ Object.keys(options || {})
19
+ );
20
+ }
119
21
 
120
- async analyze(files, language, options = {}) {
121
- const violations = [];
122
-
123
- for (const filePath of files) {
124
- // Skip test files, build directories, and node_modules
125
- if (this.shouldSkipFile(filePath)) {
126
- continue;
127
- }
128
-
129
- try {
130
- const content = require('fs').readFileSync(filePath, 'utf8');
131
- const fileViolations = this.analyzeFile(content, filePath, options);
132
- violations.push(...fileViolations);
133
- } catch (error) {
134
- if (options.verbose) {
135
- console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
136
- }
137
- }
22
+ this.ruleId = "S055";
23
+ this.ruleName = "Do not expose version information in response headers";
24
+ this.description =
25
+ "Detect REST endpoints that process request body without validating Content-Type";
26
+ this.semanticEngine = options.semanticEngine || null;
27
+ this.verbose = options.verbose || false;
28
+
29
+ this.config = {
30
+ useSymbolBased: true,
31
+ fallbackToRegex: false,
32
+ regexBasedOnly: false,
33
+ prioritizeSymbolic: true, // Prefer symbol-based when available
34
+ fallbackToSymbol: true, // Allow symbol analysis even without semantic engine
35
+ };
36
+
37
+ try {
38
+ this.symbolAnalyzer = new S055SymbolBasedAnalyzer(this.semanticEngine);
39
+ if (process.env.SUNLINT_DEBUG)
40
+ console.log(`🔧 [S055] Symbol analyzer created successfully`);
41
+ } catch (error) {
42
+ console.error(`🔧 [S055] Error creating symbol analyzer:`, error);
138
43
  }
139
-
140
- return violations;
141
44
  }
142
45
 
143
- shouldSkipFile(filePath) {
144
- const skipPatterns = [
145
- 'test/', 'tests/', '__tests__/', '.test.', '.spec.',
146
- 'node_modules/', 'build/', 'dist/', '.next/', 'coverage/',
147
- 'vendor/', 'mocks/', '.mock.',
148
- // Config files
149
- 'config/', 'configs/', '.config.',
150
- // Static assets
151
- 'public/', 'static/', 'assets/',
152
- ];
153
-
154
- return skipPatterns.some(pattern => filePath.includes(pattern));
155
- }
46
+ async initialize(semanticEngine = null) {
47
+ if (semanticEngine) {
48
+ this.semanticEngine = semanticEngine;
49
+ }
50
+ this.verbose = semanticEngine?.verbose || false;
156
51
 
157
- analyzeFile(content, filePath, options = {}) {
158
- const violations = [];
159
- const lines = content.split('\n');
160
-
161
- // First, check if file has global Content-Type validation (middleware)
162
- const hasGlobalValidation = this.hasGlobalContentTypeValidation(content);
163
-
164
- lines.forEach((line, index) => {
165
- const lineNumber = index + 1;
166
- const trimmedLine = line.trim();
167
-
168
- // Skip comments, imports, and empty lines
169
- if (this.shouldSkipLine(trimmedLine)) {
170
- return;
171
- }
172
-
173
- // Check for potential Content-Type validation violations
174
- const violation = this.checkForContentTypeViolation(
175
- line,
176
- lineNumber,
177
- filePath,
178
- content,
179
- hasGlobalValidation
180
- );
181
- if (violation) {
182
- violations.push(violation);
183
- }
184
- });
185
-
186
- return violations;
187
- }
52
+ // Initialize both analyzers
53
+ await this.symbolAnalyzer.initialize(semanticEngine);
54
+
55
+ // Ensure verbose flag is propagated
56
+ this.symbolAnalyzer.verbose = this.verbose;
188
57
 
189
- shouldSkipLine(line) {
190
- return (
191
- line.length === 0 ||
192
- this.safePatterns.some(pattern => pattern.test(line))
193
- );
58
+ if (this.verbose) {
59
+ console.log(`🔧 [S055 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
60
+ }
194
61
  }
195
62
 
196
- hasGlobalContentTypeValidation(content) {
197
- return this.secureImplementationPatterns.some(pattern => pattern.test(content));
63
+ analyzeSingle(filePath, options = {}) {
64
+ if (process.env.SUNLINT_DEBUG)
65
+ console.log(`🔍 [S055] analyzeSingle() called for: ${filePath}`);
66
+ return this.analyze([filePath], "typescript", options);
198
67
  }
199
68
 
200
- checkForContentTypeViolation(line, lineNumber, filePath, fullContent, hasGlobalValidation) {
201
- // Check if line contains request body usage
202
- const hasRequestBodyUsage = this.requestBodyPatterns.some(pattern => pattern.test(line));
203
-
204
- if (!hasRequestBodyUsage) {
205
- return null;
206
- }
207
-
208
- // Check if this line is part of an HTTP handler
209
- const isInHttpHandler = this.isInHttpHandlerContext(line, lineNumber, fullContent);
210
-
211
- if (!isInHttpHandler) {
212
- return null;
213
- }
214
-
215
- // Skip if there's global validation
216
- if (hasGlobalValidation) {
217
- return null;
218
- }
219
-
220
- // Check if there's local Content-Type validation in the same function/handler
221
- const hasLocalValidation = this.hasLocalContentTypeValidation(lineNumber, fullContent);
222
-
223
- if (hasLocalValidation) {
224
- return null;
225
- }
226
-
227
- // Check if this is a NestJS handler with proper decorators
228
- if (this.isSecureNestJSHandler(lineNumber, fullContent)) {
229
- return null;
69
+ async analyze(files, language, options = {}) {
70
+ if (process.env.SUNLINT_DEBUG) {
71
+ console.log(`🔧 [S055] analyze() method called with ${files.length} files, language: ${language}`);
230
72
  }
231
-
232
- return {
233
- ruleId: this.ruleId,
234
- severity: 'error',
235
- message: 'REST endpoint processes request body without validating Content-Type header. This can lead to security vulnerabilities.',
236
- line: lineNumber,
237
- column: this.findPatternColumn(line, this.requestBodyPatterns),
238
- filePath: filePath,
239
- type: 'missing_content_type_validation',
240
- details: 'Consider adding Content-Type validation using req.is("application/json") or checking req.headers["content-type"] before processing request body.'
241
- };
242
- }
243
73
 
244
- isInHttpHandlerContext(line, lineNumber, fullContent) {
245
- const lines = fullContent.split('\n');
246
-
247
- // Check previous lines for HTTP handler patterns
248
- const contextRange = Math.max(0, lineNumber - 10); // Check up to 10 lines back
249
-
250
- for (let i = contextRange; i < lineNumber; i++) {
251
- const contextLine = lines[i];
252
- if (this.httpHandlerPatterns.some(pattern => pattern.test(contextLine))) {
253
- return true;
74
+ const violations = [];
75
+
76
+ for (const filePath of files) {
77
+ try {
78
+ if (process.env.SUNLINT_DEBUG) {
79
+ console.log(`🔧 [S055] Processing file: ${filePath}`);
80
+ }
81
+
82
+ const fileViolations = await this.analyzeFile(filePath, options);
83
+ violations.push(...fileViolations);
84
+
85
+ if (process.env.SUNLINT_DEBUG) {
86
+ console.log(`🔧 [S055] File ${filePath}: Found ${fileViolations.length} violations`);
87
+ }
88
+ } catch (error) {
89
+ console.warn(`❌ [S055] Analysis failed for ${filePath}:`, error.message);
254
90
  }
255
91
  }
256
-
257
- // Check current line
258
- if (this.httpHandlerPatterns.some(pattern => pattern.test(line))) {
259
- return true;
92
+
93
+ if (process.env.SUNLINT_DEBUG) {
94
+ console.log(`🔧 [S055] Total violations found: ${violations.length}`);
260
95
  }
261
-
262
- return false;
96
+
97
+ return violations;
263
98
  }
264
99
 
265
- hasLocalContentTypeValidation(lineNumber, fullContent) {
266
- const lines = fullContent.split('\n');
267
-
268
- // Check surrounding lines for Content-Type validation
269
- const startLine = Math.max(0, lineNumber - 15);
270
- const endLine = Math.min(lines.length, lineNumber + 10);
271
-
272
- for (let i = startLine; i < endLine; i++) {
273
- const checkLine = lines[i];
274
- if (this.contentTypeValidationPatterns.some(pattern => pattern.test(checkLine))) {
275
- return true;
276
- }
100
+ async analyzeFile(filePath, options = {}) {
101
+ if (process.env.SUNLINT_DEBUG) {
102
+ console.log(`🔧 [S055] analyzeFile() called for: ${filePath}`);
277
103
  }
278
-
279
- return false;
280
- }
281
104
 
282
- isSecureNestJSHandler(lineNumber, fullContent) {
283
- const lines = fullContent.split('\n');
284
-
285
- // Check previous lines for NestJS decorators that handle Content-Type
286
- const contextRange = Math.max(0, lineNumber - 5);
287
-
288
- for (let i = contextRange; i < lineNumber; i++) {
289
- const line = lines[i];
290
- if (/@Header\s*\(\s*['"`]Content-Type['"`]/i.test(line)) {
291
- return true;
105
+ // 1. Try Symbol-based analysis first (primary)
106
+ if (this.config.useSymbolBased &&
107
+ this.semanticEngine?.project &&
108
+ this.semanticEngine?.initialized) {
109
+ try {
110
+ if (process.env.SUNLINT_DEBUG) {
111
+ console.log(`🔧 [S055] Trying symbol-based analysis...`);
112
+ }
113
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
114
+ if (sourceFile) {
115
+ if (process.env.SUNLINT_DEBUG) {
116
+ console.log(`🔧 [S055] Source file found, analyzing with symbol-based...`);
117
+ }
118
+ const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
119
+
120
+ // Mark violations with analysis strategy
121
+ violations.forEach(v => v.analysisStrategy = 'symbol-based');
122
+
123
+ if (process.env.SUNLINT_DEBUG) {
124
+ console.log(`✅ [S055] Symbol-based analysis: ${violations.length} violations`);
125
+ }
126
+ return violations; // Return even if 0 violations - symbol analysis completed successfully
127
+ } else {
128
+ if (process.env.SUNLINT_DEBUG) {
129
+ console.log(`⚠️ [S055] Source file not found in project`);
130
+ }
131
+ }
132
+ } catch (error) {
133
+ console.warn(`⚠️ [S055] Symbol analysis failed: ${error.message}`);
134
+ // Continue to fallback
292
135
  }
293
- if (/@UseInterceptors\s*\([^)]*ContentType[^)]*\)/i.test(line)) {
294
- return true;
136
+ } else {
137
+ if (process.env.SUNLINT_DEBUG) {
138
+ console.log(`🔄 [S055] Symbol analysis conditions check:`);
139
+ console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
140
+ console.log(` - semanticEngine: ${!!this.semanticEngine}`);
141
+ console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
142
+ console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
143
+ console.log(`🔄 [S055] Symbol analysis unavailable, using regex fallback`);
295
144
  }
296
145
  }
297
-
298
- return false;
299
- }
300
146
 
301
- findPatternColumn(line, patterns) {
302
- for (const pattern of patterns) {
303
- const match = pattern.exec(line);
304
- if (match) {
305
- return match.index + 1;
306
- }
147
+ if (options?.verbose) {
148
+ console.log(`🔧 [S055] No analysis methods succeeded, returning empty`);
307
149
  }
308
- return 1;
150
+ return [];
309
151
  }
310
152
  }
311
153