@sun-asterisk/sunlint 1.3.8 → 1.3.10

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 (27) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/config/rules/enhanced-rules-registry.json +79 -22
  3. package/core/cli-program.js +1 -1
  4. package/core/file-targeting-service.js +15 -0
  5. package/core/semantic-engine.js +4 -2
  6. package/package.json +1 -1
  7. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +116 -10
  8. package/rules/common/C060_no_override_superclass/analyzer.js +180 -0
  9. package/rules/common/C060_no_override_superclass/config.json +50 -0
  10. package/rules/common/C060_no_override_superclass/symbol-based-analyzer.js +220 -0
  11. package/rules/index.js +1 -0
  12. package/rules/security/S020_no_eval_dynamic_code/README.md +136 -0
  13. package/rules/security/S020_no_eval_dynamic_code/analyzer.js +263 -0
  14. package/rules/security/S020_no_eval_dynamic_code/config.json +54 -0
  15. package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +307 -0
  16. package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +280 -0
  17. package/rules/security/S024_xpath_xxe_protection/symbol-based-analyzer.js +3 -3
  18. package/rules/security/S025_server_side_validation/symbol-based-analyzer.js +3 -4
  19. package/rules/security/S030_directory_browsing_protection/README.md +128 -0
  20. package/rules/security/S030_directory_browsing_protection/analyzer.js +264 -0
  21. package/rules/security/S030_directory_browsing_protection/config.json +63 -0
  22. package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +483 -0
  23. package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +539 -0
  24. package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +8 -9
  25. package/rules/security/S039_no_session_tokens_in_url/symbol-based-analyzer.js +33 -26
  26. package/rules/security/S056_log_injection_protection/analyzer.js +2 -2
  27. package/rules/security/S056_log_injection_protection/symbol-based-analyzer.js +77 -118
@@ -0,0 +1,220 @@
1
+ /**
2
+ * C060 Symbol-based Analyzer - Advanced Do not scatter hardcoded constants throughout the logic
3
+ * Purpose: The rule prevents scattering hardcoded constants throughout the logic. Instead, constants should be defined in a single place to improve maintainability and readability.
4
+ */
5
+
6
+ const { SyntaxKind } = require('ts-morph');
7
+
8
+ class C060SymbolBasedAnalyzer {
9
+ constructor(semanticEngine = null) {
10
+ this.ruleId = 'C060';
11
+ this.ruleName = 'Do not override superclass methods (Symbol-Based)';
12
+ this.semanticEngine = semanticEngine;
13
+ this.verbose = false;
14
+
15
+ // === Ignore Configuration ===
16
+ this.ignoredClasses = [
17
+ "React.Component", // React components
18
+ "React.PureComponent",
19
+ "BaseMock", // Custom test mocks
20
+ ];
21
+
22
+ this.ignoredMethods = [
23
+ "render", // React render
24
+ "componentDidMount", // Some lifecycle hooks
25
+ "componentWillUnmount",
26
+ ];
27
+
28
+ this.ignoredFilePatterns = [
29
+ /node_modules/,
30
+ /\.d\.ts$/,
31
+ /\.test\.ts$/,
32
+ ];
33
+ }
34
+
35
+ async initialize(semanticEngine = null) {
36
+ if (semanticEngine) {
37
+ this.semanticEngine = semanticEngine;
38
+ }
39
+ this.verbose = semanticEngine?.verbose || false;
40
+
41
+ if (process.env.SUNLINT_DEBUG) {
42
+ console.log(`🔧 [C060 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`);
43
+ }
44
+ }
45
+
46
+ async analyzeFileBasic(filePath, options = {}) {
47
+ // This is the main entry point called by the hybrid analyzer
48
+ return await this.analyzeFileWithSymbols(filePath, options);
49
+ }
50
+
51
+ async analyzeFileWithSymbols(filePath, options = {}) {
52
+ const violations = [];
53
+
54
+ // Enable verbose mode if requested
55
+ const verbose = options.verbose || this.verbose;
56
+
57
+ if (!this.semanticEngine?.project) {
58
+ if (verbose) {
59
+ console.warn('[C060 Symbol-Based] No semantic engine available, skipping analysis');
60
+ }
61
+ return violations;
62
+ }
63
+
64
+ if (this.shouldIgnoreFile(filePath)) {
65
+ if (verbose) console.log(`[${this.ruleId}] Ignoring ${filePath}`);
66
+ return violations;
67
+ }
68
+
69
+ if (verbose) {
70
+ console.log(`🔍 [C060 Symbol-Based] Starting analysis for ${filePath}`);
71
+ }
72
+
73
+ try {
74
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
75
+ if (!sourceFile) {
76
+ return violations;
77
+ }
78
+
79
+ const classDeclarations = sourceFile.getClasses();
80
+ for (const classDeclaration of classDeclarations) {
81
+ const classViolations = this.analyzeClass(classDeclaration, filePath, verbose);
82
+ violations.push(...classViolations);
83
+ }
84
+
85
+ if (verbose) {
86
+ console.log(`🔍 [C060 Symbol-Based] Total violations found: ${violations.length}`);
87
+ }
88
+
89
+ return violations;
90
+ } catch (error) {
91
+ if (verbose) {
92
+ console.warn(`[C060 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
93
+ }
94
+
95
+ return violations;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Analyze one class for override violations
101
+ */
102
+ analyzeClass(classDeclaration, filePath, verbose) {
103
+ const violations = [];
104
+ const baseClass = classDeclaration.getBaseClass();
105
+ if (!baseClass) return violations;
106
+
107
+ // Check if this class should be ignored
108
+ if (this.shouldIgnoreClass(baseClass)) {
109
+ if (verbose) {
110
+ console.log(`[${this.ruleId}] Skipping ignored base class: ${baseClass.getName()}`);
111
+ }
112
+ return violations;
113
+ }
114
+
115
+ const baseMethods = this.collectBaseMethods(baseClass);
116
+
117
+ for (const method of classDeclaration.getMethods()) {
118
+ // Skip ignored methods
119
+ if (this.shouldIgnoreMethod(method)) {
120
+ if (verbose) {
121
+ console.log(`[${this.ruleId}] Skipping ignored method: ${method.getName()}`);
122
+ }
123
+ continue;
124
+ }
125
+
126
+ if (baseMethods.has(method.getName())) {
127
+ const violation = this.checkMethodOverride(method, classDeclaration, filePath, verbose);
128
+ if (violation) {
129
+ violations.push(violation);
130
+ }
131
+ }
132
+ }
133
+
134
+ return violations;
135
+ }
136
+
137
+ /**
138
+ * Collect all method names from base class
139
+ */
140
+ collectBaseMethods(baseClass) {
141
+ return new Set(baseClass.getMethods().map((method) => method.getName()));
142
+ }
143
+
144
+ /**
145
+ * Check if overridden method properly calls super.method()
146
+ */
147
+ checkMethodOverride(method, classDeclaration, filePath, verbose) {
148
+ const body = method.getBodyText();
149
+ if (!body) return null;
150
+
151
+ const methodName = method.getName();
152
+ const baseClass = classDeclaration.getBaseClass();
153
+ if (!baseClass) return null;
154
+ const baseClassName = baseClass.getName();
155
+
156
+ // 1 Get all CallExpressions in the method body
157
+ const calls = method.getDescendantsOfKind(SyntaxKind.CallExpression);
158
+
159
+ // 2 Check for super.method() or BaseClass.prototype.method.call(this)
160
+ const hasSuperCall = calls.some((call) => {
161
+ const expression = call.getExpression().getText();
162
+ return expression === `super.${methodName}`;
163
+ });
164
+
165
+ const hasBaseCall = calls.some((call) => {
166
+ const expression = call.getExpression().getText();
167
+ return expression === `${baseClassName}.prototype.${methodName}.call`;
168
+ });
169
+
170
+ if (!hasSuperCall && !hasBaseCall) {
171
+ const violation = {
172
+ ruleId: this.ruleId,
173
+ severity: 'warning',
174
+ message: `Overridden method '${method.getName()}' in '${classDeclaration.getName()}' does not call 'super.${method.getName()}()', potentially skipping lifecycle/resource logic.`,
175
+ source: this.ruleId,
176
+ file: filePath,
177
+ line: method.getStartLineNumber(),
178
+ column: method.getStart() - method.getStartLinePos(),
179
+ description: `[SYMBOL-BASED] Overriding methods should call the superclass implementation to ensure proper behavior.`,
180
+ suggestion: `Add a call to 'super.${method.getName()}()' within the method body.`,
181
+ category: 'best-practices',
182
+ };
183
+
184
+ if (verbose) {
185
+ console.log(
186
+ `⚠️ [${this.ruleId}] Violation in ${filePath}: Method '${method.getName()}' overrides superclass method but does not call super.`
187
+ );
188
+ }
189
+
190
+ return violation;
191
+ }
192
+
193
+ return null;
194
+ }
195
+
196
+ /**
197
+ * Ignore logic for classes
198
+ */
199
+ shouldIgnoreClass(baseClass) {
200
+ const baseName = baseClass.getName();
201
+ return this.ignoredClasses.includes(baseName);
202
+ }
203
+
204
+ /**
205
+ * Ignore logic for methods
206
+ */
207
+ shouldIgnoreMethod(method) {
208
+ const methodName = method.getName();
209
+ return this.ignoredMethods.includes(methodName);
210
+ }
211
+
212
+ /**
213
+ * Ignore files based on patterns
214
+ */
215
+ shouldIgnoreFile(filePath) {
216
+ return this.ignoredFilePatterns.some((pattern) => pattern.test(filePath));
217
+ }
218
+ }
219
+
220
+ module.exports = C060SymbolBasedAnalyzer;
package/rules/index.js CHANGED
@@ -64,6 +64,7 @@ const commonRules = {
64
64
  C048: loadRule('common', 'C048_no_bypass_architectural_layers'),
65
65
  C052: loadRule('common', 'C052_parsing_or_data_transformation'),
66
66
  C047: loadRule('common', 'C047_no_duplicate_retry_logic'),
67
+ C060: loadRule('common', 'C060_no_override_superclass'),
67
68
  };
68
69
 
69
70
  // 🔒 Security Rules (S-series) - Ready for migration
@@ -0,0 +1,136 @@
1
+ # S020 - Avoid using eval() or executing dynamic code
2
+
3
+ ## Overview
4
+
5
+ This rule detects and prevents the use of `eval()` and other dynamic code execution mechanisms that can lead to security vulnerabilities, particularly code injection attacks.
6
+
7
+ ## Why This Rule Matters
8
+
9
+ Dynamic code execution through `eval()` and similar functions poses significant security risks:
10
+
11
+ 1. **Code Injection Vulnerabilities**: Untrusted input can be executed as code
12
+ 2. **Performance Issues**: Dynamic code execution is slower than static code
13
+ 3. **Debugging Difficulties**: Dynamic code is harder to debug and analyze
14
+ 4. **Security Auditing**: Static analysis tools cannot analyze dynamically generated code
15
+
16
+ ## What This Rule Detects
17
+
18
+ ### ❌ Dangerous Functions
19
+
20
+ - `eval()` - Direct code execution
21
+ - `new Function()` - Function constructor with string
22
+ - `setTimeout(string)` - Timer with string code
23
+ - `setInterval(string)` - Interval with string code
24
+ - `execScript()` - Legacy IE function
25
+ - `setImmediate(string)` - Immediate execution with string
26
+
27
+ ### ❌ Global Access Patterns
28
+
29
+ - `window.eval()`
30
+ - `global.eval()`
31
+ - `globalThis.eval()`
32
+ - `self.eval()`
33
+
34
+ ### ❌ Suspicious Variable Patterns
35
+
36
+ Variables containing dynamic code indicators:
37
+
38
+ - Variables named with `code`, `script`, `expression`, `formula`, `template`, `eval`
39
+
40
+ ## Examples
41
+
42
+ ### ❌ Violations
43
+
44
+ ```javascript
45
+ // Direct eval usage
46
+ eval("console.log('Hello')"); // ERROR
47
+
48
+ // Function constructor
49
+ const fn = new Function("return 1 + 1"); // ERROR
50
+
51
+ // setTimeout with string
52
+ setTimeout("console.log('test')", 1000); // WARNING
53
+
54
+ // Global eval access
55
+ window.eval("alert('test')"); // ERROR
56
+
57
+ // Dynamic code variables
58
+ const userCode = req.body.code;
59
+ eval(userCode); // ERROR - code injection risk
60
+ ```
61
+
62
+ ### ✅ Safe Alternatives
63
+
64
+ ```javascript
65
+ // Instead of eval(), use proper parsing and validation
66
+ const result = JSON.parse(jsonString);
67
+
68
+ // Instead of Function constructor, use proper function definitions
69
+ function add(a, b) {
70
+ return a + b;
71
+ }
72
+
73
+ // Instead of setTimeout with string, use function reference
74
+ setTimeout(() => console.log("test"), 1000);
75
+
76
+ // Instead of dynamic code execution, use configuration objects
77
+ const actions = {
78
+ add: (a, b) => a + b,
79
+ multiply: (a, b) => a * b,
80
+ };
81
+ const result = actions[operation](x, y);
82
+
83
+ // For template engines, use safe templating libraries
84
+ const template = handlebars.compile(templateString);
85
+ const result = template(data);
86
+ ```
87
+
88
+ ## Configuration
89
+
90
+ The rule can be configured in `config.json`:
91
+
92
+ ```json
93
+ {
94
+ "validation": {
95
+ "dangerousFunctions": ["eval", "Function", "setTimeout", "setInterval"],
96
+ "dangerousPatterns": ["new Function", "window.eval", "global.eval"],
97
+ "dynamicCodeIndicators": ["code", "script", "expression", "formula"]
98
+ }
99
+ }
100
+ ```
101
+
102
+ ## Security Best Practices
103
+
104
+ 1. **Input Validation**: Always validate and sanitize user input
105
+ 2. **Use Safe Alternatives**: Prefer configuration objects over dynamic code
106
+ 3. **Template Engines**: Use established, secure template libraries
107
+ 4. **Content Security Policy**: Implement CSP headers to prevent code injection
108
+ 5. **Code Review**: Carefully review any dynamic code patterns
109
+
110
+ ## Related Rules
111
+
112
+ - **S023**: JSON Injection Protection
113
+ - **S025**: Server-side Validation
114
+ - **S056**: Log Injection Protection
115
+
116
+ ## Severity Levels
117
+
118
+ - **ERROR**: Direct `eval()`, `Function()` constructor, global eval access
119
+ - **WARNING**: `setTimeout`/`setInterval` with strings, suspicious variable patterns
120
+
121
+ ## Framework-Specific Notes
122
+
123
+ ### Node.js
124
+
125
+ - Be especially careful with `vm` module usage
126
+ - Avoid `child_process.exec()` with user input
127
+
128
+ ### Browser
129
+
130
+ - Consider Content Security Policy to prevent eval
131
+ - Be cautious with `innerHTML` and similar DOM manipulation
132
+
133
+ ### React/Vue
134
+
135
+ - Avoid `dangerouslySetInnerHTML` with user content
136
+ - Use proper event handlers instead of inline scripts
@@ -0,0 +1,263 @@
1
+ /**
2
+ * S020 Main Analyzer - Avoid using eval() or executing dynamic code
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
5
+ * Command: node cli.js --rule=S020 --input=examples/rule-test-fixtures/rules/S020_no_eval_dynamic_code --engine=heuristic
6
+ */
7
+
8
+ const S020SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
+ const S020RegexBasedAnalyzer = require("./regex-based-analyzer.js");
10
+
11
+ class S020Analyzer {
12
+ constructor(options = {}) {
13
+ if (process.env.SUNLINT_DEBUG) {
14
+ console.log(`🔧 [S020] Constructor called with options:`, !!options);
15
+ console.log(
16
+ `🔧 [S020] Options type:`,
17
+ typeof options,
18
+ Object.keys(options || {})
19
+ );
20
+ }
21
+
22
+ this.ruleId = "S020";
23
+ this.ruleName = "Avoid using eval() or executing dynamic code";
24
+ this.description =
25
+ "Avoid using eval() or executing dynamic code as it can lead to code injection vulnerabilities and compromise application security.";
26
+ this.semanticEngine = options.semanticEngine || null;
27
+ this.verbose = options.verbose || false;
28
+
29
+ this.config = {
30
+ useSymbolBased: true,
31
+ fallbackToRegex: true,
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 S020SymbolBasedAnalyzer(this.semanticEngine);
39
+ if (process.env.SUNLINT_DEBUG)
40
+ console.log(`🔧 [S020] Symbol analyzer created successfully`);
41
+ } catch (error) {
42
+ console.error(`🔧 [S020] Error creating symbol analyzer:`, error);
43
+ }
44
+
45
+ try {
46
+ this.regexAnalyzer = new S020RegexBasedAnalyzer(this.semanticEngine);
47
+ if (process.env.SUNLINT_DEBUG)
48
+ console.log(`🔧 [S020] Regex analyzer created successfully`);
49
+ } catch (error) {
50
+ console.error(`🔧 [S020] Error creating regex analyzer:`, error);
51
+ }
52
+ }
53
+
54
+ async initialize(semanticEngine) {
55
+ this.semanticEngine = semanticEngine;
56
+ if (process.env.SUNLINT_DEBUG)
57
+ console.log(`🔧 [S020] Main analyzer initializing...`);
58
+
59
+ if (this.symbolAnalyzer)
60
+ await this.symbolAnalyzer.initialize?.(semanticEngine);
61
+ if (this.regexAnalyzer)
62
+ await this.regexAnalyzer.initialize?.(semanticEngine);
63
+ if (this.regexAnalyzer) this.regexAnalyzer.cleanup?.();
64
+
65
+ if (process.env.SUNLINT_DEBUG)
66
+ console.log(`🔧 [S020] Main analyzer initialized successfully`);
67
+ }
68
+
69
+ analyzeSingle(filePath, options = {}) {
70
+ if (process.env.SUNLINT_DEBUG)
71
+ console.log(`🔍 [S020] analyzeSingle() called for: ${filePath}`);
72
+ return this.analyze([filePath], "typescript", options);
73
+ }
74
+
75
+ async analyze(files, language, options = {}) {
76
+ if (process.env.SUNLINT_DEBUG) {
77
+ console.log(
78
+ `🔧 [S020] analyze() method called with ${files.length} files, language: ${language}`
79
+ );
80
+ }
81
+
82
+ const violations = [];
83
+ for (const filePath of files) {
84
+ try {
85
+ if (process.env.SUNLINT_DEBUG)
86
+ console.log(`🔧 [S020] Processing file: ${filePath}`);
87
+ const fileViolations = await this.analyzeFile(filePath, options);
88
+ violations.push(...fileViolations);
89
+ if (process.env.SUNLINT_DEBUG)
90
+ console.log(
91
+ `🔧 [S020] File ${filePath}: Found ${fileViolations.length} violations`
92
+ );
93
+ } catch (error) {
94
+ console.warn(
95
+ `⚠ [S020] Analysis failed for ${filePath}:`,
96
+ error.message
97
+ );
98
+ }
99
+ }
100
+
101
+ if (process.env.SUNLINT_DEBUG)
102
+ console.log(`🔧 [S020] Total violations found: ${violations.length}`);
103
+ return violations;
104
+ }
105
+
106
+ async analyzeFile(filePath, options = {}) {
107
+ if (process.env.SUNLINT_DEBUG)
108
+ console.log(`🔍 [S020] analyzeFile() called for: ${filePath}`);
109
+ const violationMap = new Map();
110
+
111
+ // Try symbol-based analysis first when semantic engine is available OR when explicitly enabled
112
+ if (process.env.SUNLINT_DEBUG) {
113
+ console.log(
114
+ `🔧 [S020] Symbol check: useSymbolBased=${
115
+ this.config.useSymbolBased
116
+ }, semanticEngine=${!!this.semanticEngine}, project=${!!this
117
+ .semanticEngine?.project}, initialized=${!!this.semanticEngine
118
+ ?.initialized}`
119
+ );
120
+ }
121
+
122
+ const canUseSymbol =
123
+ this.config.useSymbolBased &&
124
+ ((this.semanticEngine?.project && this.semanticEngine?.initialized) ||
125
+ (!this.semanticEngine && this.config.fallbackToSymbol !== false));
126
+
127
+ if (canUseSymbol) {
128
+ try {
129
+ if (process.env.SUNLINT_DEBUG)
130
+ console.log(`🔧 [S020] Trying symbol-based analysis...`);
131
+
132
+ let sourceFile = null;
133
+ if (this.semanticEngine?.project) {
134
+ sourceFile = this.semanticEngine.project.getSourceFile(filePath);
135
+ if (process.env.SUNLINT_DEBUG) {
136
+ console.log(
137
+ `🔧 [S020] Checked existing semantic engine project: sourceFile=${!!sourceFile}`
138
+ );
139
+ }
140
+ }
141
+
142
+ if (!sourceFile) {
143
+ // Create a minimal ts-morph project for this analysis
144
+ if (process.env.SUNLINT_DEBUG)
145
+ console.log(
146
+ `🔧 [S020] Creating temporary ts-morph project for: ${filePath}`
147
+ );
148
+ try {
149
+ const fs = require("fs");
150
+ const path = require("path");
151
+ const { Project } = require("ts-morph");
152
+
153
+ // Check if file exists and read content
154
+ if (!fs.existsSync(filePath)) {
155
+ throw new Error(`File not found: ${filePath}`);
156
+ }
157
+
158
+ const fileContent = fs.readFileSync(filePath, "utf8");
159
+ const fileName = path.basename(filePath);
160
+
161
+ const tempProject = new Project({
162
+ useInMemoryFileSystem: true,
163
+ compilerOptions: {
164
+ allowJs: true,
165
+ allowSyntheticDefaultImports: true,
166
+ },
167
+ });
168
+
169
+ // Add file content to in-memory project
170
+ sourceFile = tempProject.createSourceFile(fileName, fileContent);
171
+ if (process.env.SUNLINT_DEBUG)
172
+ console.log(
173
+ `🔧 [S020] Temporary project created successfully with file: ${fileName}`
174
+ );
175
+ } catch (projectError) {
176
+ if (process.env.SUNLINT_DEBUG)
177
+ console.log(
178
+ `🔧 [S020] Failed to create temporary project:`,
179
+ projectError.message
180
+ );
181
+ throw projectError;
182
+ }
183
+ }
184
+
185
+ if (sourceFile) {
186
+ const symbolViolations = await this.symbolAnalyzer.analyze(
187
+ sourceFile,
188
+ filePath
189
+ );
190
+ symbolViolations.forEach((v) => {
191
+ const key = `${v.line}:${v.column}:${v.message}`;
192
+ if (!violationMap.has(key)) violationMap.set(key, v);
193
+ });
194
+ if (process.env.SUNLINT_DEBUG)
195
+ console.log(
196
+ `🔧 [S020] Symbol analysis completed: ${symbolViolations.length} violations`
197
+ );
198
+
199
+ // If symbol-based found violations AND prioritizeSymbolic is true, skip regex
200
+ // But still run regex if symbol-based didn't find any violations
201
+ if (this.config.prioritizeSymbolic && symbolViolations.length > 0) {
202
+ const finalViolations = Array.from(violationMap.values()).map(
203
+ (v) => ({
204
+ ...v,
205
+ filePath,
206
+ file: filePath,
207
+ })
208
+ );
209
+ if (process.env.SUNLINT_DEBUG)
210
+ console.log(
211
+ `🔧 [S020] Symbol-based analysis prioritized: ${finalViolations.length} violations`
212
+ );
213
+ return finalViolations;
214
+ }
215
+ } else {
216
+ if (process.env.SUNLINT_DEBUG)
217
+ console.log(
218
+ `🔧 [S020] No source file found, skipping symbol analysis`
219
+ );
220
+ }
221
+ } catch (error) {
222
+ console.warn(`⚠ [S020] Symbol analysis failed:`, error.message);
223
+ }
224
+ }
225
+
226
+ // Fallback to regex-based analysis
227
+ if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
228
+ try {
229
+ if (process.env.SUNLINT_DEBUG)
230
+ console.log(`🔧 [S020] Trying regex-based analysis...`);
231
+ const regexViolations = await this.regexAnalyzer.analyze(filePath);
232
+ regexViolations.forEach((v) => {
233
+ const key = `${v.line}:${v.column}:${v.message}`;
234
+ if (!violationMap.has(key)) violationMap.set(key, v);
235
+ });
236
+ if (process.env.SUNLINT_DEBUG)
237
+ console.log(
238
+ `🔧 [S020] Regex analysis completed: ${regexViolations.length} violations`
239
+ );
240
+ } catch (error) {
241
+ console.warn(`⚠ [S020] Regex analysis failed:`, error.message);
242
+ }
243
+ }
244
+
245
+ const finalViolations = Array.from(violationMap.values()).map((v) => ({
246
+ ...v,
247
+ filePath,
248
+ file: filePath,
249
+ }));
250
+ if (process.env.SUNLINT_DEBUG)
251
+ console.log(
252
+ `🔧 [S020] File analysis completed: ${finalViolations.length} unique violations`
253
+ );
254
+ return finalViolations;
255
+ }
256
+
257
+ cleanup() {
258
+ if (this.symbolAnalyzer?.cleanup) this.symbolAnalyzer.cleanup();
259
+ if (this.regexAnalyzer?.cleanup) this.regexAnalyzer.cleanup();
260
+ }
261
+ }
262
+
263
+ module.exports = S020Analyzer;
@@ -0,0 +1,54 @@
1
+ {
2
+ "id": "S020",
3
+ "name": "Avoid using eval() or executing dynamic code",
4
+ "category": "security",
5
+ "description": "S020 - Avoid using eval() or executing dynamic code as it can lead to code injection vulnerabilities and compromise application security.",
6
+ "severity": "error",
7
+ "enabled": true,
8
+ "semantic": {
9
+ "enabled": true,
10
+ "priority": "high",
11
+ "fallback": "heuristic"
12
+ },
13
+ "patterns": {
14
+ "include": ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx"],
15
+ "exclude": [
16
+ "**/*.test.js",
17
+ "**/*.test.ts",
18
+ "**/*.spec.js",
19
+ "**/*.spec.ts",
20
+ "**/node_modules/**",
21
+ "**/dist/**",
22
+ "**/build/**"
23
+ ]
24
+ },
25
+ "analysis": {
26
+ "approach": "symbol-based-primary",
27
+ "fallback": "regex-based",
28
+ "depth": 2,
29
+ "timeout": 5000
30
+ },
31
+ "validation": {
32
+ "dangerousFunctions": [
33
+ "eval",
34
+ "Function",
35
+ "setTimeout",
36
+ "setInterval",
37
+ "execScript",
38
+ "setImmediate"
39
+ ],
40
+ "dangerousPatterns": [
41
+ "new Function",
42
+ "window.eval",
43
+ "global.eval",
44
+ "globalThis.eval"
45
+ ],
46
+ "dynamicCodeIndicators": [
47
+ "code",
48
+ "script",
49
+ "expression",
50
+ "formula",
51
+ "template"
52
+ ]
53
+ }
54
+ }