@sun-asterisk/sunlint 1.3.7 → 1.3.8

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 (38) hide show
  1. package/CHANGELOG.md +38 -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 +190 -35
  5. package/core/file-targeting-service.js +83 -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/S037_cache_headers/README.md +128 -0
  10. package/rules/security/S037_cache_headers/analyzer.js +263 -0
  11. package/rules/security/S037_cache_headers/config.json +50 -0
  12. package/rules/security/S037_cache_headers/regex-based-analyzer.js +463 -0
  13. package/rules/security/S037_cache_headers/symbol-based-analyzer.js +546 -0
  14. package/rules/security/S038_no_version_headers/README.md +234 -0
  15. package/rules/security/S038_no_version_headers/analyzer.js +262 -0
  16. package/rules/security/S038_no_version_headers/config.json +49 -0
  17. package/rules/security/S038_no_version_headers/regex-based-analyzer.js +339 -0
  18. package/rules/security/S038_no_version_headers/symbol-based-analyzer.js +375 -0
  19. package/rules/security/S039_no_session_tokens_in_url/README.md +198 -0
  20. package/rules/security/S039_no_session_tokens_in_url/analyzer.js +262 -0
  21. package/rules/security/S039_no_session_tokens_in_url/config.json +92 -0
  22. package/rules/security/S039_no_session_tokens_in_url/regex-based-analyzer.js +337 -0
  23. package/rules/security/S039_no_session_tokens_in_url/symbol-based-analyzer.js +436 -0
  24. package/rules/security/S049_short_validity_tokens/analyzer.js +175 -0
  25. package/rules/security/S049_short_validity_tokens/config.json +124 -0
  26. package/rules/security/S049_short_validity_tokens/regex-based-analyzer.js +295 -0
  27. package/rules/security/S049_short_validity_tokens/symbol-based-analyzer.js +389 -0
  28. package/rules/security/S051_password_length_policy/analyzer.js +410 -0
  29. package/rules/security/S051_password_length_policy/config.json +83 -0
  30. package/rules/security/S052_weak_otp_entropy/analyzer.js +403 -0
  31. package/rules/security/S052_weak_otp_entropy/config.json +57 -0
  32. package/rules/security/S054_no_default_accounts/README.md +129 -0
  33. package/rules/security/S054_no_default_accounts/analyzer.js +792 -0
  34. package/rules/security/S054_no_default_accounts/config.json +101 -0
  35. package/rules/security/S056_log_injection_protection/analyzer.js +242 -0
  36. package/rules/security/S056_log_injection_protection/config.json +148 -0
  37. package/rules/security/S056_log_injection_protection/regex-based-analyzer.js +120 -0
  38. package/rules/security/S056_log_injection_protection/symbol-based-analyzer.js +287 -0
@@ -0,0 +1,287 @@
1
+ /**
2
+ * S056 Symbol-Based Analyzer - Protect against Log Injection attacks
3
+ * Uses TypeScript compiler API for semantic analysis
4
+ */
5
+
6
+ const ts = require("typescript");
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(filePath) {
74
+ if (this.verbose) {
75
+ console.log(
76
+ `🔍 [${this.ruleId}] Symbol: Starting analysis for ${filePath}`
77
+ );
78
+ }
79
+
80
+ if (!this.semanticEngine) {
81
+ if (this.verbose) {
82
+ console.log(
83
+ `🔍 [${this.ruleId}] Symbol: No semantic engine available, skipping`
84
+ );
85
+ }
86
+ return [];
87
+ }
88
+
89
+ try {
90
+ const sourceFile = this.semanticEngine.getSourceFile(filePath);
91
+ if (!sourceFile) {
92
+ if (this.verbose) {
93
+ console.log(
94
+ `🔍 [${this.ruleId}] Symbol: No source file found, trying ts-morph fallback`
95
+ );
96
+ }
97
+ return await this.analyzeTsMorph(filePath);
98
+ }
99
+
100
+ if (this.verbose) {
101
+ console.log(`🔧 [${this.ruleId}] Source file found, analyzing...`);
102
+ }
103
+
104
+ const violations = [];
105
+ const typeChecker = this.semanticEngine.program?.getTypeChecker();
106
+
107
+ // Visit all nodes in the source file
108
+ const visit = (node) => {
109
+ // Check for log method calls
110
+ if (ts.isCallExpression(node)) {
111
+ this.checkLogMethodCall(node, violations, sourceFile, typeChecker);
112
+ }
113
+
114
+ ts.forEachChild(node, visit);
115
+ };
116
+
117
+ visit(sourceFile);
118
+
119
+ if (this.verbose) {
120
+ console.log(
121
+ `🔧 [${this.ruleId}] Symbol analysis completed: ${violations.length} violations found`
122
+ );
123
+ }
124
+
125
+ return violations;
126
+ } catch (error) {
127
+ console.warn(`⚠ [${this.ruleId}] Symbol analysis failed:`, error.message);
128
+ return [];
129
+ }
130
+ }
131
+
132
+ checkLogMethodCall(node, violations, sourceFile, typeChecker) {
133
+ // Check if this is a logging method call
134
+ const methodName = this.getMethodName(node);
135
+ if (!methodName || !this.logMethods.includes(methodName)) {
136
+ return;
137
+ }
138
+
139
+ // Check arguments for user input
140
+ if (node.arguments && node.arguments.length > 0) {
141
+ for (const arg of node.arguments) {
142
+ if (this.containsUserInput(arg, sourceFile)) {
143
+ const position = sourceFile.getLineAndCharacterOfPosition(node.getStart());
144
+ violations.push({
145
+ ruleId: this.ruleId,
146
+ message: `Log injection vulnerability: User input directly used in ${methodName}() call without sanitization`,
147
+ line: position.line + 1,
148
+ column: position.character + 1,
149
+ severity: "error",
150
+ category: this.category,
151
+ code: sourceFile.getFullText().slice(node.getStart(), node.getEnd())
152
+ });
153
+ break;
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ getMethodName(callExpression) {
160
+ const expression = callExpression.expression;
161
+
162
+ if (ts.isIdentifier(expression)) {
163
+ return expression.text;
164
+ }
165
+
166
+ if (ts.isPropertyAccessExpression(expression)) {
167
+ return expression.name.text;
168
+ }
169
+
170
+ return null;
171
+ }
172
+
173
+ containsUserInput(node, sourceFile) {
174
+ // Check for direct user input references
175
+ if (ts.isIdentifier(node)) {
176
+ return this.userInputSources.includes(node.text);
177
+ }
178
+
179
+ // Check for property access on user input (e.g., req.body, req.query)
180
+ if (ts.isPropertyAccessExpression(node)) {
181
+ const objectName = this.getObjectName(node);
182
+ return this.userInputSources.includes(objectName);
183
+ }
184
+
185
+ // Check for element access on user input (e.g., req["body"], headers['user-agent'])
186
+ if (ts.isElementAccessExpression(node)) {
187
+ const objectName = this.getObjectName(node);
188
+ return this.userInputSources.includes(objectName);
189
+ }
190
+
191
+ // Check for binary expressions (concatenation)
192
+ if (ts.isBinaryExpression(node)) {
193
+ return this.containsUserInput(node.left, sourceFile) ||
194
+ this.containsUserInput(node.right, sourceFile);
195
+ }
196
+
197
+ // Check for template literals
198
+ if (ts.isTemplateExpression(node)) {
199
+ return node.templateSpans.some(span =>
200
+ this.containsUserInput(span.expression, sourceFile)
201
+ );
202
+ }
203
+
204
+ // Check for function calls that might return user input
205
+ if (ts.isCallExpression(node)) {
206
+ // Check if it's JSON.stringify with user input
207
+ const methodName = this.getMethodName(node);
208
+ if (methodName === "stringify" && node.arguments.length > 0) {
209
+ return this.containsUserInput(node.arguments[0], sourceFile);
210
+ }
211
+ }
212
+
213
+ return false;
214
+ }
215
+
216
+ getObjectName(node) {
217
+ if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
218
+ if (ts.isIdentifier(node.expression)) {
219
+ return node.expression.text;
220
+ }
221
+ }
222
+ return null;
223
+ }
224
+
225
+ /**
226
+ * Fallback analysis using ts-morph when semantic engine is not available
227
+ */
228
+ async analyzeTsMorph(filePath) {
229
+ try {
230
+ const fs = require("fs");
231
+ const { Project } = require("ts-morph");
232
+
233
+ const project = new Project();
234
+ const sourceFile = project.addSourceFileAtPath(filePath);
235
+ const violations = [];
236
+
237
+ // Find all call expressions
238
+ sourceFile.forEachDescendant((node) => {
239
+ if (node.getKind() === ts.SyntaxKind.CallExpression) {
240
+ const callExpr = node;
241
+ const methodName = this.extractMethodName(callExpr.getText());
242
+
243
+ if (this.logMethods.includes(methodName)) {
244
+ const args = callExpr.getArguments();
245
+ for (const arg of args) {
246
+ if (this.containsUserInputText(arg.getText())) {
247
+ const line = sourceFile.getLineAndColumnAtPos(node.getStart()).line;
248
+ const column = sourceFile.getLineAndColumnAtPos(node.getStart()).column;
249
+
250
+ violations.push({
251
+ ruleId: this.ruleId,
252
+ message: `Log injection vulnerability: User input directly used in ${methodName}() call without sanitization`,
253
+ line: line,
254
+ column: column,
255
+ severity: "error",
256
+ category: this.category,
257
+ code: node.getText()
258
+ });
259
+ break;
260
+ }
261
+ }
262
+ }
263
+ }
264
+ });
265
+
266
+ return violations;
267
+ } catch (error) {
268
+ console.warn(`⚠ [${this.ruleId}] ts-morph analysis failed:`, error.message);
269
+ return [];
270
+ }
271
+ }
272
+
273
+ extractMethodName(callText) {
274
+ const match = callText.match(/(\w+)\s*\(/);
275
+ return match ? match[1] : null;
276
+ }
277
+
278
+ containsUserInputText(text) {
279
+ return this.userInputSources.some(source => text.includes(source));
280
+ }
281
+
282
+ cleanup() {
283
+ // Cleanup resources if needed
284
+ }
285
+ }
286
+
287
+ module.exports = S056SymbolBasedAnalyzer;