@sun-asterisk/sunlint 1.3.7 → 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.
Files changed (51) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/config/defaults/default.json +2 -1
  3. package/config/rule-analysis-strategies.js +20 -0
  4. package/config/rules/enhanced-rules-registry.json +247 -53
  5. package/core/file-targeting-service.js +98 -7
  6. package/package.json +1 -1
  7. package/rules/common/C065_one_behavior_per_test/analyzer.js +851 -0
  8. package/rules/common/C065_one_behavior_per_test/config.json +95 -0
  9. package/rules/security/S020_no_eval_dynamic_code/README.md +136 -0
  10. package/rules/security/S020_no_eval_dynamic_code/analyzer.js +263 -0
  11. package/rules/security/S020_no_eval_dynamic_code/config.json +54 -0
  12. package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +307 -0
  13. package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +280 -0
  14. package/rules/security/S024_xpath_xxe_protection/symbol-based-analyzer.js +3 -3
  15. package/rules/security/S025_server_side_validation/symbol-based-analyzer.js +3 -4
  16. package/rules/security/S030_directory_browsing_protection/README.md +128 -0
  17. package/rules/security/S030_directory_browsing_protection/analyzer.js +264 -0
  18. package/rules/security/S030_directory_browsing_protection/config.json +63 -0
  19. package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +483 -0
  20. package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +539 -0
  21. package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +8 -9
  22. package/rules/security/S037_cache_headers/README.md +128 -0
  23. package/rules/security/S037_cache_headers/analyzer.js +263 -0
  24. package/rules/security/S037_cache_headers/config.json +50 -0
  25. package/rules/security/S037_cache_headers/regex-based-analyzer.js +463 -0
  26. package/rules/security/S037_cache_headers/symbol-based-analyzer.js +546 -0
  27. package/rules/security/S038_no_version_headers/README.md +234 -0
  28. package/rules/security/S038_no_version_headers/analyzer.js +262 -0
  29. package/rules/security/S038_no_version_headers/config.json +49 -0
  30. package/rules/security/S038_no_version_headers/regex-based-analyzer.js +339 -0
  31. package/rules/security/S038_no_version_headers/symbol-based-analyzer.js +375 -0
  32. package/rules/security/S039_no_session_tokens_in_url/README.md +198 -0
  33. package/rules/security/S039_no_session_tokens_in_url/analyzer.js +262 -0
  34. package/rules/security/S039_no_session_tokens_in_url/config.json +92 -0
  35. package/rules/security/S039_no_session_tokens_in_url/regex-based-analyzer.js +337 -0
  36. package/rules/security/S039_no_session_tokens_in_url/symbol-based-analyzer.js +443 -0
  37. package/rules/security/S049_short_validity_tokens/analyzer.js +175 -0
  38. package/rules/security/S049_short_validity_tokens/config.json +124 -0
  39. package/rules/security/S049_short_validity_tokens/regex-based-analyzer.js +295 -0
  40. package/rules/security/S049_short_validity_tokens/symbol-based-analyzer.js +389 -0
  41. package/rules/security/S051_password_length_policy/analyzer.js +410 -0
  42. package/rules/security/S051_password_length_policy/config.json +83 -0
  43. package/rules/security/S052_weak_otp_entropy/analyzer.js +403 -0
  44. package/rules/security/S052_weak_otp_entropy/config.json +57 -0
  45. package/rules/security/S054_no_default_accounts/README.md +129 -0
  46. package/rules/security/S054_no_default_accounts/analyzer.js +792 -0
  47. package/rules/security/S054_no_default_accounts/config.json +101 -0
  48. package/rules/security/S056_log_injection_protection/analyzer.js +242 -0
  49. package/rules/security/S056_log_injection_protection/config.json +148 -0
  50. package/rules/security/S056_log_injection_protection/regex-based-analyzer.js +120 -0
  51. package/rules/security/S056_log_injection_protection/symbol-based-analyzer.js +246 -0
@@ -0,0 +1,246 @@
1
+ /**
2
+ * S056 Symbol-Based Analyzer - Protect against Log Injection attacks
3
+ * Uses ts-morph for semantic analysis (consistent with SunLint architecture)
4
+ */
5
+
6
+ const { SyntaxKind } = require("ts-morph");
7
+
8
+ class S056SymbolBasedAnalyzer {
9
+ constructor(semanticEngine = null) {
10
+ this.semanticEngine = semanticEngine;
11
+ this.ruleId = "S056";
12
+ this.category = "security";
13
+
14
+ // Log method names that can be vulnerable
15
+ this.logMethods = [
16
+ "log",
17
+ "info",
18
+ "warn",
19
+ "error",
20
+ "debug",
21
+ "trace",
22
+ "write",
23
+ "writeSync"
24
+ ];
25
+
26
+ // User input sources that could lead to injection
27
+ this.userInputSources = [
28
+ "req",
29
+ "request",
30
+ "params",
31
+ "query",
32
+ "body",
33
+ "headers",
34
+ "cookies",
35
+ "session"
36
+ ];
37
+
38
+ // Dangerous characters for log injection
39
+ this.dangerousCharacters = [
40
+ "\\r",
41
+ "\\n",
42
+ "\\r\\n",
43
+ "\\u000a",
44
+ "\\u000d",
45
+ "%0a",
46
+ "%0d",
47
+ "\\x0a",
48
+ "\\x0d"
49
+ ];
50
+
51
+ // Secure log patterns
52
+ this.securePatterns = [
53
+ "sanitize",
54
+ "escape",
55
+ "clean",
56
+ "filter",
57
+ "validate",
58
+ "replace",
59
+ "strip"
60
+ ];
61
+ }
62
+
63
+ /**
64
+ * Initialize analyzer with semantic engine
65
+ */
66
+ async initialize(semanticEngine) {
67
+ this.semanticEngine = semanticEngine;
68
+ if (this.verbose) {
69
+ console.log(`🔍 [${this.ruleId}] Symbol: Semantic engine initialized`);
70
+ }
71
+ }
72
+
73
+ async analyze(sourceFileOrPath) {
74
+ // Handle both sourceFile object and file path
75
+ let sourceFile;
76
+ let filePath;
77
+
78
+ if (typeof sourceFileOrPath === 'string') {
79
+ filePath = sourceFileOrPath;
80
+ // Try to get from semantic engine first
81
+ if (this.semanticEngine?.project) {
82
+ sourceFile = this.semanticEngine.project.getSourceFile(filePath);
83
+ }
84
+
85
+ // If not found, create new ts-morph project
86
+ if (!sourceFile) {
87
+ const { Project } = require("ts-morph");
88
+ const project = new Project();
89
+ sourceFile = project.addSourceFileAtPath(filePath);
90
+ }
91
+ } else {
92
+ // Assume it's already a ts-morph SourceFile
93
+ sourceFile = sourceFileOrPath;
94
+ filePath = sourceFile.getFilePath();
95
+ }
96
+
97
+ if (this.verbose) {
98
+ console.log(
99
+ `🔍 [${this.ruleId}] Symbol: Starting analysis for ${filePath}`
100
+ );
101
+ }
102
+
103
+ if (!sourceFile) {
104
+ if (this.verbose) {
105
+ console.log(
106
+ `� [${this.ruleId}] Symbol: Could not create source file`
107
+ );
108
+ }
109
+ return [];
110
+ }
111
+
112
+ try {
113
+ const violations = [];
114
+
115
+ // Find all call expressions using ts-morph API
116
+ sourceFile.forEachDescendant((node) => {
117
+ if (node.getKind() === SyntaxKind.CallExpression) {
118
+ this.checkLogMethodCall(node, violations, sourceFile);
119
+ }
120
+ });
121
+
122
+ if (this.verbose) {
123
+ console.log(
124
+ `🔧 [${this.ruleId}] Symbol analysis completed: ${violations.length} violations found`
125
+ );
126
+ }
127
+
128
+ return violations;
129
+ } catch (error) {
130
+ console.warn(`⚠ [${this.ruleId}] Symbol analysis failed:`, error.message);
131
+ return [];
132
+ }
133
+ }
134
+
135
+ checkLogMethodCall(callExprNode, violations, sourceFile) {
136
+ // Check if this is a logging method call using ts-morph API
137
+ const methodName = this.getMethodName(callExprNode);
138
+ if (!methodName || !this.logMethods.includes(methodName)) {
139
+ return;
140
+ }
141
+
142
+ // Check arguments for user input
143
+ const args = callExprNode.getArguments();
144
+ if (args && args.length > 0) {
145
+ for (const arg of args) {
146
+ if (this.containsUserInput(arg)) {
147
+ const lineAndCol = sourceFile.getLineAndColumnAtPos(callExprNode.getStart());
148
+
149
+ violations.push({
150
+ ruleId: this.ruleId,
151
+ message: `Log injection vulnerability: User input directly used in ${methodName}() call without sanitization`,
152
+ line: lineAndCol.line,
153
+ column: lineAndCol.column,
154
+ severity: "error",
155
+ category: this.category,
156
+ code: callExprNode.getText()
157
+ });
158
+ break;
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ getMethodName(callExpression) {
165
+ // Use ts-morph API instead of TypeScript compiler API
166
+ const expression = callExpression.getExpression();
167
+
168
+ if (expression.getKind() === SyntaxKind.Identifier) {
169
+ return expression.getText();
170
+ }
171
+
172
+ if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
173
+ return expression.getName();
174
+ }
175
+
176
+ return null;
177
+ }
178
+
179
+ containsUserInput(node) {
180
+ // Check for direct user input references using ts-morph API
181
+ if (node.getKind() === SyntaxKind.Identifier) {
182
+ return this.userInputSources.includes(node.getText());
183
+ }
184
+
185
+ // Check for property access on user input (e.g., req.body, req.query)
186
+ if (node.getKind() === SyntaxKind.PropertyAccessExpression) {
187
+ const objectName = this.getObjectName(node);
188
+ return this.userInputSources.includes(objectName);
189
+ }
190
+
191
+ // Check for element access on user input (e.g., req["body"], headers['user-agent'])
192
+ if (node.getKind() === SyntaxKind.ElementAccessExpression) {
193
+ const objectName = this.getObjectName(node);
194
+ return this.userInputSources.includes(objectName);
195
+ }
196
+
197
+ // Check for binary expressions (concatenation)
198
+ if (node.getKind() === SyntaxKind.BinaryExpression) {
199
+ const left = node.getLeft();
200
+ const right = node.getRight();
201
+ return this.containsUserInput(left) || this.containsUserInput(right);
202
+ }
203
+
204
+ // Check for template literals
205
+ if (node.getKind() === SyntaxKind.TemplateExpression) {
206
+ const templateSpans = node.getTemplateSpans();
207
+ return templateSpans.some(span =>
208
+ this.containsUserInput(span.getExpression())
209
+ );
210
+ }
211
+
212
+ // Check for function calls that might return user input
213
+ if (node.getKind() === SyntaxKind.CallExpression) {
214
+ // Check if it's JSON.stringify with user input
215
+ const methodName = this.getMethodName(node);
216
+ if (methodName === "stringify") {
217
+ const args = node.getArguments();
218
+ if (args.length > 0) {
219
+ return this.containsUserInput(args[0]);
220
+ }
221
+ }
222
+ }
223
+
224
+ return false;
225
+ }
226
+
227
+ getObjectName(node) {
228
+ if (node.getKind() === SyntaxKind.PropertyAccessExpression ||
229
+ node.getKind() === SyntaxKind.ElementAccessExpression) {
230
+ const expression = node.getExpression();
231
+ if (expression.getKind() === SyntaxKind.Identifier) {
232
+ return expression.getText();
233
+ }
234
+ }
235
+ return null;
236
+ }
237
+
238
+ /**
239
+ * Clean up resources
240
+ */
241
+ cleanup() {
242
+ // Cleanup resources if needed
243
+ }
244
+ }
245
+
246
+ module.exports = S056SymbolBasedAnalyzer;