@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
+ * S024 Main Analyzer - Protect against XPath Injection and XML External Entity (XXE)
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
5
+ * Command: node cli.js --rule=S024 --input=examples/rule-test-fixtures/rules/S024_xpath_xxe_protection --engine=heuristic
6
+ */
7
+
8
+ const S024SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
+ const S024RegexBasedAnalyzer = require("./regex-based-analyzer.js");
10
+
11
+ class S024Analyzer {
12
+ constructor(options = {}) {
13
+ if (process.env.SUNLINT_DEBUG) {
14
+ console.log(`🔧 [S024] Constructor called with options:`, !!options);
15
+ console.log(
16
+ `🔧 [S024] Options type:`,
17
+ typeof options,
18
+ Object.keys(options || {})
19
+ );
20
+ }
21
+
22
+ this.ruleId = "S024";
23
+ this.ruleName = "Protect against XPath Injection and XML External Entity (XXE)";
24
+ this.description =
25
+ "Protect against XPath Injection and XML External Entity (XXE) attacks. XPath injection occurs when user input is used to construct XPath queries without proper sanitization. XXE attacks exploit XML parsers that process external entities, potentially leading to data disclosure, server-side request forgery, or denial of service.";
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 S024SymbolBasedAnalyzer(this.semanticEngine);
39
+ if (process.env.SUNLINT_DEBUG) {
40
+ console.log(`🔧 [S024] Symbol analyzer created successfully`);
41
+ }
42
+ } catch (error) {
43
+ console.error(`🔧 [S024] Error creating symbol analyzer:`, error);
44
+ }
45
+
46
+ try {
47
+ this.regexAnalyzer = new S024RegexBasedAnalyzer(this.semanticEngine);
48
+ if (process.env.SUNLINT_DEBUG) {
49
+ console.log(`🔧 [S024] Regex analyzer created successfully`);
50
+ }
51
+ } catch (error) {
52
+ console.error(`🔧 [S024] 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(`🔧 [S024] 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(`🔧 [S024] 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(`📊 [S024] 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
+ `🔧 [S024] 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(`🔧 [S024] 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
+ `🔧 [S024] File ${filePath}: Found ${fileViolations.length} violations`
117
+ );
118
+ }
119
+ } catch (error) {
120
+ console.warn(
121
+ `⚠ [S024] Analysis failed for ${filePath}:`,
122
+ error.message
123
+ );
124
+ }
125
+ }
126
+
127
+ if (process.env.SUNLINT_DEBUG) {
128
+ console.log(`🔧 [S024] 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(`🔍 [S024] 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(`🔧 [S024] 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(`🔧 [S024] 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
+ `🔧 [S024] Symbol analysis completed: ${symbolViolations.length} violations`
173
+ );
174
+ }
175
+ } else {
176
+ if (process.env.SUNLINT_DEBUG) {
177
+ console.log(`🔧 [S024] Source file not found, falling back...`);
178
+ }
179
+ }
180
+ } catch (error) {
181
+ console.warn(`⚠ [S024] 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(`🔧 [S024] 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
+ `🔧 [S024] Regex analysis completed: ${regexViolations.length} violations`
204
+ );
205
+ }
206
+ } catch (error) {
207
+ console.warn(`⚠ [S024] 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
+ `🔧 [S024] 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 = S024Analyzer;
@@ -0,0 +1,152 @@
1
+ {
2
+ "rule": {
3
+ "id": "S024",
4
+ "name": "Protect against XPath Injection and XML External Entity (XXE)",
5
+ "description": "Protect against XPath Injection and XML External Entity (XXE) attacks. XPath injection occurs when user input is used to construct XPath queries without proper sanitization. XXE attacks exploit XML parsers that process external entities, potentially leading to data disclosure, server-side request forgery, or denial of service.",
6
+ "category": "security",
7
+ "severity": "error",
8
+ "languages": ["typescript", "javascript"],
9
+ "frameworks": ["express", "nestjs", "node"],
10
+ "version": "1.0.0",
11
+ "status": "stable",
12
+ "tags": ["security", "xpath", "xxe", "xml", "injection", "owasp"],
13
+ "references": [
14
+ "https://owasp.org/www-community/attacks/XPATH_Injection",
15
+ "https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing",
16
+ "https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html",
17
+ "https://portswigger.net/web-security/xpath-injection",
18
+ "https://portswigger.net/web-security/xxe"
19
+ ]
20
+ },
21
+ "configuration": {
22
+ "enableXPathInjectionDetection": true,
23
+ "enableXXEDetection": true,
24
+ "checkUserInputSources": [
25
+ "req",
26
+ "request",
27
+ "params",
28
+ "query",
29
+ "body",
30
+ "headers",
31
+ "cookies"
32
+ ],
33
+ "vulnerableXPathMethods": [
34
+ "evaluate",
35
+ "select",
36
+ "selectText",
37
+ "selectValue",
38
+ "selectNodes",
39
+ "selectSingleNode"
40
+ ],
41
+ "vulnerableXMLMethods": [
42
+ "parseString",
43
+ "parseXml",
44
+ "parseFromString",
45
+ "parse",
46
+ "parseXmlString",
47
+ "transform"
48
+ ],
49
+ "vulnerableXMLConstructors": [
50
+ "DOMParser",
51
+ "XSLTProcessor",
52
+ "SAXParser"
53
+ ],
54
+ "vulnerableXMLLibraries": [
55
+ "xml2js",
56
+ "libxmljs",
57
+ "xmldom",
58
+ "fast-xml-parser",
59
+ "node-xml2js",
60
+ "xml-parser",
61
+ "xmldoc",
62
+ "xpath"
63
+ ],
64
+ "xxeProtectionPatterns": [
65
+ "resolveExternalEntities\\s*:\\s*false",
66
+ "setFeature.*disallow-doctype-decl.*true",
67
+ "setFeature.*external-general-entities.*false",
68
+ "setFeature.*external-parameter-entities.*false",
69
+ "explicitChildren\\s*:\\s*false",
70
+ "ignoreAttrs\\s*:\\s*true",
71
+ "parseDoctype\\s*:\\s*false"
72
+ ],
73
+ "secureXPathPatterns": [
74
+ "parameterized",
75
+ "escaped",
76
+ "sanitized",
77
+ "validate"
78
+ ]
79
+ },
80
+ "examples": {
81
+ "violations": [
82
+ {
83
+ "description": "XPath injection via direct user input",
84
+ "code": "const result = xpath.evaluate(req.query.expression, doc);"
85
+ },
86
+ {
87
+ "description": "XPath injection via string concatenation",
88
+ "code": "const query = \"//user[@name='\" + req.body.username + \"']\";\nconst result = xpath.select(query, doc);"
89
+ },
90
+ {
91
+ "description": "XXE vulnerability in XML parsing",
92
+ "code": "const parser = new DOMParser();\nconst doc = parser.parseFromString(req.body.xml, 'text/xml');"
93
+ },
94
+ {
95
+ "description": "XXE vulnerability with xml2js",
96
+ "code": "xml2js.parseString(req.body.xml, (err, result) => { /* ... */ });"
97
+ }
98
+ ],
99
+ "fixes": [
100
+ {
101
+ "description": "Use parameterized XPath with validation",
102
+ "code": "const sanitizedInput = sanitize(req.query.expression);\nconst result = xpath.evaluate(sanitizedInput, doc);"
103
+ },
104
+ {
105
+ "description": "Disable external entities in XML parsing",
106
+ "code": "const parser = new DOMParser();\nparser.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true);\nparser.setFeature('http://xml.org/sax/features/external-general-entities', false);\nparser.setFeature('http://xml.org/sax/features/external-parameter-entities', false);"
107
+ },
108
+ {
109
+ "description": "Secure xml2js configuration",
110
+ "code": "xml2js.parseString(req.body.xml, {\n explicitChildren: false,\n ignoreAttrs: true\n}, (err, result) => { /* ... */ });"
111
+ }
112
+ ]
113
+ },
114
+ "testing": {
115
+ "testCases": [
116
+ {
117
+ "name": "xpath_injection_direct_input",
118
+ "type": "violation",
119
+ "description": "Direct user input in XPath query"
120
+ },
121
+ {
122
+ "name": "xpath_injection_concatenation",
123
+ "type": "violation",
124
+ "description": "String concatenation with user input"
125
+ },
126
+ {
127
+ "name": "xxe_domparser",
128
+ "type": "violation",
129
+ "description": "DOMParser without XXE protection"
130
+ },
131
+ {
132
+ "name": "xxe_xml2js",
133
+ "type": "violation",
134
+ "description": "xml2js without XXE protection"
135
+ },
136
+ {
137
+ "name": "secure_xpath_parameterized",
138
+ "type": "clean",
139
+ "description": "Parameterized XPath query"
140
+ },
141
+ {
142
+ "name": "secure_xml_xxe_protected",
143
+ "type": "clean",
144
+ "description": "XML parsing with XXE protection"
145
+ }
146
+ ]
147
+ },
148
+ "performance": {
149
+ "complexity": "O(n)",
150
+ "description": "Linear complexity based on number of function calls and expressions in the source code"
151
+ }
152
+ }