@sun-asterisk/sunlint 1.3.8 โ 1.3.9
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.
- package/CHANGELOG.md +25 -0
- package/config/rules/enhanced-rules-registry.json +61 -22
- package/core/file-targeting-service.js +15 -0
- package/package.json +1 -1
- package/rules/security/S020_no_eval_dynamic_code/README.md +136 -0
- package/rules/security/S020_no_eval_dynamic_code/analyzer.js +263 -0
- package/rules/security/S020_no_eval_dynamic_code/config.json +54 -0
- package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +307 -0
- package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +280 -0
- package/rules/security/S024_xpath_xxe_protection/symbol-based-analyzer.js +3 -3
- package/rules/security/S025_server_side_validation/symbol-based-analyzer.js +3 -4
- package/rules/security/S030_directory_browsing_protection/README.md +128 -0
- package/rules/security/S030_directory_browsing_protection/analyzer.js +264 -0
- package/rules/security/S030_directory_browsing_protection/config.json +63 -0
- package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +483 -0
- package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +539 -0
- package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +8 -9
- package/rules/security/S039_no_session_tokens_in_url/symbol-based-analyzer.js +33 -26
- package/rules/security/S056_log_injection_protection/analyzer.js +2 -2
- package/rules/security/S056_log_injection_protection/symbol-based-analyzer.js +77 -118
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
## ๐ง **v1.3.9 - File Targeting Regression Fix (October 2, 2025)**
|
|
6
|
+
|
|
7
|
+
**Release Date**: October 2, 2025
|
|
8
|
+
**Type**: Bug Fix
|
|
9
|
+
**Branch**: `feature.sunlint.heuristic_rule_c065`
|
|
10
|
+
|
|
11
|
+
### ๐ **Critical Bug Fixes**
|
|
12
|
+
- **FIXED**: File targeting regression where user-specified source directories were incorrectly optimized
|
|
13
|
+
- **Issue**: When using `--input=examples/project-samples/replace-fe/src`, file count dropped from 2.2K to 254 files
|
|
14
|
+
- **Root Cause**: `optimizeProjectPaths` function incorrectly treated user-specified source directories as project roots
|
|
15
|
+
- **Solution**: Added source directory detection logic to bypass optimization for `src`, `lib`, `app`, `packages`, `test` directories
|
|
16
|
+
- **Impact**: Full file coverage restored - all 1507 .tsx files now properly included
|
|
17
|
+
|
|
18
|
+
### โก **Performance Improvements**
|
|
19
|
+
- **ENHANCED**: File targeting logic with smart source directory detection
|
|
20
|
+
- **OPTIMIZED**: Direct targeting for user-specified source paths
|
|
21
|
+
|
|
22
|
+
### ๐ **Technical Details**
|
|
23
|
+
- Modified `file-targeting-service.js` `optimizeProjectPaths` function
|
|
24
|
+
- Added `sourceDirectoryNames` array for known source directory patterns
|
|
25
|
+
- Implemented basename checking to detect when users specify source directories directly
|
|
26
|
+
- Maintained backward compatibility with existing project structure detection
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
5
30
|
## ๐งช **v1.3.8 - C065 Rule Enhancement & Advanced Context Analysis (October 1, 2025)**
|
|
6
31
|
|
|
7
32
|
**Release Date**: October 1, 2025
|
|
@@ -660,16 +660,51 @@
|
|
|
660
660
|
"tags": ["security", "email", "injection"]
|
|
661
661
|
},
|
|
662
662
|
"S020": {
|
|
663
|
-
"name": "
|
|
664
|
-
"description": "
|
|
663
|
+
"name": "Avoid using eval() or executing dynamic code",
|
|
664
|
+
"description": "Avoid using eval() or executing dynamic code as it can lead to code injection vulnerabilities and compromise application security.",
|
|
665
665
|
"category": "security",
|
|
666
666
|
"severity": "error",
|
|
667
667
|
"languages": ["typescript", "javascript"],
|
|
668
|
-
"analyzer": "
|
|
669
|
-
"
|
|
668
|
+
"analyzer": "./rules/security/S020_no_eval_dynamic_code/analyzer.js",
|
|
669
|
+
"config": "./rules/security/S020_no_eval_dynamic_code/config.json",
|
|
670
670
|
"version": "1.0.0",
|
|
671
|
-
"status": "
|
|
672
|
-
"tags": ["security", "eval", "dynamic-execution"]
|
|
671
|
+
"status": "experimental",
|
|
672
|
+
"tags": ["security", "eval", "dynamic-execution", "code-injection"],
|
|
673
|
+
"strategy": {
|
|
674
|
+
"preferred": "ast",
|
|
675
|
+
"fallbacks": ["ast", "regex"],
|
|
676
|
+
"accuracy": { "ast": 95, "regex": 85 }
|
|
677
|
+
},
|
|
678
|
+
"engineMappings": {
|
|
679
|
+
"heuristic": ["rules/security/S020_no_eval_dynamic_code/analyzer.js"]
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
"S030": {
|
|
683
|
+
"name": "Disable directory browsing and protect sensitive metadata files",
|
|
684
|
+
"description": "Disable directory browsing and protect sensitive metadata files (.git/, .env, config files, etc.) to prevent information disclosure and potential security vulnerabilities.",
|
|
685
|
+
"category": "security",
|
|
686
|
+
"severity": "error",
|
|
687
|
+
"languages": ["typescript", "javascript"],
|
|
688
|
+
"analyzer": "./rules/security/S030_directory_browsing_protection/analyzer.js",
|
|
689
|
+
"config": "./rules/security/S030_directory_browsing_protection/config.json",
|
|
690
|
+
"version": "1.0.0",
|
|
691
|
+
"status": "experimental",
|
|
692
|
+
"tags": [
|
|
693
|
+
"security",
|
|
694
|
+
"directory-browsing",
|
|
695
|
+
"information-disclosure",
|
|
696
|
+
"metadata-protection"
|
|
697
|
+
],
|
|
698
|
+
"strategy": {
|
|
699
|
+
"preferred": "ast",
|
|
700
|
+
"fallbacks": ["ast", "regex"],
|
|
701
|
+
"accuracy": { "ast": 90, "regex": 75 }
|
|
702
|
+
},
|
|
703
|
+
"engineMappings": {
|
|
704
|
+
"heuristic": [
|
|
705
|
+
"rules/security/S030_directory_browsing_protection/analyzer.js"
|
|
706
|
+
]
|
|
707
|
+
}
|
|
673
708
|
},
|
|
674
709
|
"S022": {
|
|
675
710
|
"name": "Output Encoding Required",
|
|
@@ -791,18 +826,6 @@
|
|
|
791
826
|
"status": "stable",
|
|
792
827
|
"tags": ["security", "csrf", "protection"]
|
|
793
828
|
},
|
|
794
|
-
"S030": {
|
|
795
|
-
"name": "No Directory Browsing",
|
|
796
|
-
"description": "Prevent directory browsing vulnerabilities",
|
|
797
|
-
"category": "security",
|
|
798
|
-
"severity": "error",
|
|
799
|
-
"languages": ["typescript", "javascript"],
|
|
800
|
-
"analyzer": "eslint",
|
|
801
|
-
"eslintRule": "custom/typescript_s030",
|
|
802
|
-
"version": "1.0.0",
|
|
803
|
-
"status": "stable",
|
|
804
|
-
"tags": ["security", "directory-browsing", "information-disclosure"]
|
|
805
|
-
},
|
|
806
829
|
"S031": {
|
|
807
830
|
"name": "Set Secure flag for Session Cookies",
|
|
808
831
|
"description": "Set Secure flag for Session Cookies to protect via HTTPS. This ensures cookies are only transmitted over secure connections, preventing interception.",
|
|
@@ -1141,7 +1164,7 @@
|
|
|
1141
1164
|
"name": "Password length policy enforcement (12-64 chars recommended, reject >128)",
|
|
1142
1165
|
"description": "Enforce strong password length policies with multi-signal detection. Prevent weak validators, missing limits, and FE/BE mismatches.",
|
|
1143
1166
|
"category": "security",
|
|
1144
|
-
"severity": "error",
|
|
1167
|
+
"severity": "error",
|
|
1145
1168
|
"languages": ["typescript", "javascript"],
|
|
1146
1169
|
"analyzer": "./rules/security/S051_password_length_policy/analyzer.js",
|
|
1147
1170
|
"config": "./rules/security/S051_password_length_policy/config.json",
|
|
@@ -1151,7 +1174,9 @@
|
|
|
1151
1174
|
"tags": ["security", "password", "validation", "length", "policy"],
|
|
1152
1175
|
"engineMappings": {
|
|
1153
1176
|
"eslint": ["custom/typescript_s051"],
|
|
1154
|
-
"heuristic": [
|
|
1177
|
+
"heuristic": [
|
|
1178
|
+
"./rules/security/S051_password_length_policy/analyzer.js"
|
|
1179
|
+
]
|
|
1155
1180
|
}
|
|
1156
1181
|
},
|
|
1157
1182
|
"C065": {
|
|
@@ -1191,13 +1216,27 @@
|
|
|
1191
1216
|
"description": "Prevent use of default or shared accounts. Enforce per-user identities, initial password change, and disabling well-known built-ins.",
|
|
1192
1217
|
"category": "security",
|
|
1193
1218
|
"severity": "error",
|
|
1194
|
-
"languages": [
|
|
1219
|
+
"languages": [
|
|
1220
|
+
"typescript",
|
|
1221
|
+
"javascript",
|
|
1222
|
+
"sql",
|
|
1223
|
+
"terraform",
|
|
1224
|
+
"yaml",
|
|
1225
|
+
"dockerfile",
|
|
1226
|
+
"all"
|
|
1227
|
+
],
|
|
1195
1228
|
"analyzer": "./rules/security/S054_no_default_accounts/analyzer.js",
|
|
1196
1229
|
"config": "./rules/security/S054_no_default_accounts/config.json",
|
|
1197
1230
|
"eslintRule": "custom/typescript_s054",
|
|
1198
1231
|
"version": "1.0.0",
|
|
1199
1232
|
"status": "stable",
|
|
1200
|
-
"tags": [
|
|
1233
|
+
"tags": [
|
|
1234
|
+
"security",
|
|
1235
|
+
"accounts",
|
|
1236
|
+
"default",
|
|
1237
|
+
"authentication",
|
|
1238
|
+
"authorization"
|
|
1239
|
+
],
|
|
1201
1240
|
"engines": {
|
|
1202
1241
|
"eslint": ["custom/typescript_s054"],
|
|
1203
1242
|
"heuristic": ["./rules/security/S054_no_default_accounts/analyzer.js"]
|
|
@@ -163,6 +163,21 @@ class FileTargetingService {
|
|
|
163
163
|
for (const inputPath of inputPaths) {
|
|
164
164
|
// If targeting entire project directory, try to find source/test subdirectories
|
|
165
165
|
if (fs.existsSync(inputPath) && fs.statSync(inputPath).isDirectory()) {
|
|
166
|
+
const absoluteInputPath = path.resolve(inputPath);
|
|
167
|
+
const inputDirName = path.basename(absoluteInputPath);
|
|
168
|
+
|
|
169
|
+
// If user already specified a source directory (src, lib, app, packages, test, etc.),
|
|
170
|
+
// don't try to optimize further - use it as is
|
|
171
|
+
const sourceDirectoryNames = ['src', 'lib', 'app', 'packages', 'test', 'tests', '__tests__', 'spec', 'specs'];
|
|
172
|
+
if (sourceDirectoryNames.includes(inputDirName)) {
|
|
173
|
+
if (cliOptions.verbose) {
|
|
174
|
+
console.log(chalk.blue(`๐ฏ Direct targeting: Using specified source directory ${inputDirName}`));
|
|
175
|
+
}
|
|
176
|
+
optimizedPaths.push(inputPath);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Only optimize if this appears to be a project root directory
|
|
166
181
|
const projectOptimization = this.findProjectSourceDirs(inputPath, cliOptions);
|
|
167
182
|
if (projectOptimization.length > 0) {
|
|
168
183
|
if (cliOptions.verbose) {
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|