@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,296 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- class C041ASTAnalyzer {
5
- constructor() {
6
- this.ruleId = 'C041';
7
- this.ruleName = 'No Hardcoded Sensitive Information (AST-Enhanced)';
8
- this.description = 'AST-based detection of hardcoded sensitive information - superior to regex approach';
9
- }
10
-
11
- async analyze(files, language, options = {}) {
12
- const violations = [];
13
-
14
- for (const filePath of files) {
15
- if (options.verbose) {
16
- console.log(`🎯 Running C041 AST analysis on ${path.basename(filePath)}`);
17
- }
18
-
19
- try {
20
- const content = fs.readFileSync(filePath, 'utf8');
21
- const fileViolations = await this.analyzeFile(filePath, content, language, options);
22
- violations.push(...fileViolations);
23
- } catch (error) {
24
- console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
25
- }
26
- }
27
-
28
- return violations;
29
- }
30
-
31
- async analyzeFile(filePath, content, language, config) {
32
- switch (language) {
33
- case 'typescript':
34
- case 'javascript':
35
- return this.analyzeJSTS(filePath, content, config);
36
- default:
37
- return [];
38
- }
39
- }
40
-
41
- async analyzeJSTS(filePath, content, config) {
42
- const violations = [];
43
-
44
- try {
45
- // Try AST analysis first (like ESLint approach)
46
- const astViolations = await this.analyzeWithAST(filePath, content, config);
47
- if (astViolations.length > 0) {
48
- violations.push(...astViolations);
49
- }
50
- } catch (astError) {
51
- if (config.verbose) {
52
- console.log(`⚠️ AST analysis failed for ${path.basename(filePath)}, falling back to regex`);
53
- }
54
-
55
- // Fallback to regex-based analysis
56
- const regexViolations = await this.analyzeWithRegex(filePath, content, config);
57
- violations.push(...regexViolations);
58
- }
59
-
60
- return violations;
61
- }
62
-
63
- async analyzeWithAST(filePath, content, config) {
64
- const violations = [];
65
-
66
- // Import AST modules dynamically
67
- let astModules;
68
- try {
69
- astModules = require('../../../core/ast-modules');
70
- } catch (error) {
71
- throw new Error('AST modules not available');
72
- }
73
-
74
- // Try to parse with AST
75
- let ast;
76
- try {
77
- // Use the registry's parseCode method
78
- ast = await astModules.parseCode(content, 'javascript', filePath);
79
- if (!ast) {
80
- throw new Error('AST parsing returned null');
81
- }
82
- } catch (parseError) {
83
- throw new Error(`Parse error: ${parseError.message}`);
84
- }
85
-
86
- // Traverse AST to find sensitive information - mimicking ESLint's approach
87
- const rootNode = ast.program || ast; // Handle both Babel and ESLint formats
88
- this.traverseAST(rootNode, (node) => {
89
- if (this.isLiteralNode(node)) {
90
- const violation = this.checkLiteralForSensitiveInfo(node, filePath, content);
91
- if (violation) {
92
- violations.push(violation);
93
- }
94
- }
95
-
96
- if (this.isTemplateLiteralNode(node)) {
97
- const violation = this.checkTemplateLiteralForSensitiveInfo(node, filePath, content);
98
- if (violation) {
99
- violations.push(violation);
100
- }
101
- }
102
- });
103
-
104
- return violations;
105
- }
106
-
107
- traverseAST(node, callback) {
108
- if (!node || typeof node !== 'object') return;
109
-
110
- callback(node);
111
-
112
- for (const key in node) {
113
- if (key === 'parent' || key === 'leadingComments' || key === 'trailingComments') continue;
114
-
115
- const child = node[key];
116
- if (Array.isArray(child)) {
117
- child.forEach(item => this.traverseAST(item, callback));
118
- } else if (child && typeof child === 'object') {
119
- this.traverseAST(child, callback);
120
- }
121
- }
122
- }
123
-
124
- isLiteralNode(node) {
125
- // Support both ESLint format (Literal) and Babel format (StringLiteral)
126
- return node && (node.type === 'Literal' || node.type === 'StringLiteral') && typeof node.value === 'string';
127
- }
128
-
129
- isTemplateLiteralNode(node) {
130
- return node && node.type === 'TemplateLiteral' &&
131
- node.quasis && node.quasis.length === 1 && // No variable interpolation
132
- node.expressions && node.expressions.length === 0;
133
- }
134
-
135
- checkLiteralForSensitiveInfo(node, filePath, content) {
136
- const value = node.value;
137
- if (!value || value.length < 4) return null;
138
-
139
- const lines = content.split('\n');
140
- const lineNumber = node.loc.start.line;
141
- const lineText = lines[lineNumber - 1] || '';
142
-
143
- // Skip if it's in UI/component context - same as ESLint
144
- if (this.isFalsePositive(value, lineText)) {
145
- return null;
146
- }
147
-
148
- // Check against sensitive patterns - enhanced version of ESLint patterns
149
- const sensitivePattern = this.detectSensitivePattern(value, lineText);
150
- if (sensitivePattern) {
151
- return {
152
- ruleId: this.ruleId,
153
- file: filePath,
154
- line: lineNumber,
155
- column: node.loc.start.column + 1,
156
- message: sensitivePattern.message,
157
- severity: 'warning', // Match ESLint severity
158
- code: lineText.trim(),
159
- type: sensitivePattern.type,
160
- confidence: sensitivePattern.confidence,
161
- suggestion: sensitivePattern.suggestion
162
- };
163
- }
164
-
165
- return null;
166
- }
167
-
168
- checkTemplateLiteralForSensitiveInfo(node, filePath, content) {
169
- if (!node.quasis || node.quasis.length !== 1) return null;
170
-
171
- const value = node.quasis[0].value.raw;
172
- if (!value || value.length < 4) return null;
173
-
174
- // Create a mock literal node for consistent processing
175
- const mockNode = {
176
- ...node,
177
- value: value,
178
- loc: node.loc
179
- };
180
-
181
- return this.checkLiteralForSensitiveInfo(mockNode, filePath, content);
182
- }
183
-
184
- detectSensitivePattern(value, lineText) {
185
- const lowerValue = value.toLowerCase();
186
- const lowerLine = lineText.toLowerCase();
187
-
188
- // Enhanced patterns based on ESLint rule but with better detection
189
- const sensitivePatterns = [
190
- {
191
- type: 'password',
192
- condition: () => /password/i.test(lineText) && value.length >= 4,
193
- message: 'Potential hardcoded sensitive information detected. Move sensitive values to environment variables or secure config files.',
194
- confidence: 0.8,
195
- suggestion: 'Move sensitive values to environment variables or secure config files'
196
- },
197
- {
198
- type: 'secret',
199
- condition: () => /secret/i.test(lineText) && value.length >= 6,
200
- message: 'Potential hardcoded sensitive information detected. Move sensitive values to environment variables or secure config files.',
201
- confidence: 0.8,
202
- suggestion: 'Use environment variables for secrets'
203
- },
204
- {
205
- type: 'api_key',
206
- condition: () => /api[_-]?key/i.test(lineText) && value.length >= 10,
207
- message: 'Potential hardcoded sensitive information detected. Move sensitive values to environment variables or secure config files.',
208
- confidence: 0.9,
209
- suggestion: 'Use environment variables for API keys'
210
- },
211
- {
212
- type: 'auth_token',
213
- condition: () => /auth[_-]?token/i.test(lineText) && value.length >= 16,
214
- message: 'Potential hardcoded sensitive information detected. Move sensitive values to environment variables or secure config files.',
215
- confidence: 0.9,
216
- suggestion: 'Store tokens in secure storage'
217
- },
218
- {
219
- type: 'access_token',
220
- condition: () => /access[_-]?token/i.test(lineText) && value.length >= 16,
221
- message: 'Potential hardcoded sensitive information detected. Move sensitive values to environment variables or secure config files.',
222
- confidence: 0.9,
223
- suggestion: 'Store tokens in secure storage'
224
- },
225
- {
226
- type: 'database_url',
227
- condition: () => /(mongodb|mysql|postgres|redis):\/\//i.test(value) && value.length >= 10,
228
- message: 'Potential hardcoded sensitive information detected. Move sensitive values to environment variables or secure config files.',
229
- confidence: 0.95,
230
- suggestion: 'Use environment variables for database connections'
231
- }
232
- ];
233
-
234
- for (const pattern of sensitivePatterns) {
235
- if (pattern.condition()) {
236
- return pattern;
237
- }
238
- }
239
-
240
- return null;
241
- }
242
-
243
- isFalsePositive(value, sourceCode) {
244
- const lowerValue = value.toLowerCase();
245
- const lowerLine = sourceCode.toLowerCase();
246
-
247
- // Global false positive indicators - same as ESLint
248
- const globalFalsePositives = [
249
- 'test', 'mock', 'example', 'demo', 'sample', 'placeholder', 'dummy', 'fake',
250
- 'xmlns', 'namespace', 'schema', 'w3.org', 'google.com', 'googleapis.com',
251
- 'error', 'message', 'missing', 'invalid', 'failed', 'localhost', '127.0.0.1'
252
- ];
253
-
254
- // Check global false positives
255
- if (globalFalsePositives.some(pattern => lowerValue.includes(pattern))) {
256
- return true;
257
- }
258
-
259
- // Check if line context suggests UI/component usage - same as ESLint
260
- if (this.isConfigOrUIContext(lowerLine)) {
261
- return true;
262
- }
263
-
264
- return false;
265
- }
266
-
267
- isConfigOrUIContext(line) {
268
- // Same logic as ESLint rule
269
- const uiContexts = [
270
- 'inputtype', 'type:', 'type =', 'inputtype=',
271
- 'routes =', 'route:', 'path:', 'routes:',
272
- 'import {', 'export {', 'from ', 'import ',
273
- 'interface', 'type ', 'enum ',
274
- 'props:', 'defaultprops',
275
- 'schema', 'validator',
276
- 'hook', 'use', 'const use', 'import.*use',
277
- // React/UI specific
278
- 'textinput', 'input ', 'field ', 'form',
279
- 'component', 'page', 'screen', 'modal',
280
- // Route/navigation specific
281
- 'navigation', 'route', 'path', 'url:', 'route:',
282
- 'setuppassword', 'resetpassword', 'forgotpassword',
283
- 'changepassword', 'confirmpassword'
284
- ];
285
-
286
- return uiContexts.some(context => line.includes(context));
287
- }
288
-
289
- async analyzeWithRegex(filePath, content, config) {
290
- // Fallback to original regex approach if AST fails
291
- const originalAnalyzer = require('./analyzer.js');
292
- return originalAnalyzer.analyzeTypeScript(filePath, content, config);
293
- }
294
- }
295
-
296
- module.exports = new C041ASTAnalyzer();