@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.
- package/CHANGELOG.md +63 -0
- package/config/defaults/default.json +2 -1
- package/config/rule-analysis-strategies.js +20 -0
- package/config/rules/enhanced-rules-registry.json +247 -53
- package/core/file-targeting-service.js +98 -7
- package/package.json +1 -1
- package/rules/common/C065_one_behavior_per_test/analyzer.js +851 -0
- package/rules/common/C065_one_behavior_per_test/config.json +95 -0
- 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/S037_cache_headers/README.md +128 -0
- package/rules/security/S037_cache_headers/analyzer.js +263 -0
- package/rules/security/S037_cache_headers/config.json +50 -0
- package/rules/security/S037_cache_headers/regex-based-analyzer.js +463 -0
- package/rules/security/S037_cache_headers/symbol-based-analyzer.js +546 -0
- package/rules/security/S038_no_version_headers/README.md +234 -0
- package/rules/security/S038_no_version_headers/analyzer.js +262 -0
- package/rules/security/S038_no_version_headers/config.json +49 -0
- package/rules/security/S038_no_version_headers/regex-based-analyzer.js +339 -0
- package/rules/security/S038_no_version_headers/symbol-based-analyzer.js +375 -0
- package/rules/security/S039_no_session_tokens_in_url/README.md +198 -0
- package/rules/security/S039_no_session_tokens_in_url/analyzer.js +262 -0
- package/rules/security/S039_no_session_tokens_in_url/config.json +92 -0
- package/rules/security/S039_no_session_tokens_in_url/regex-based-analyzer.js +337 -0
- package/rules/security/S039_no_session_tokens_in_url/symbol-based-analyzer.js +443 -0
- package/rules/security/S049_short_validity_tokens/analyzer.js +175 -0
- package/rules/security/S049_short_validity_tokens/config.json +124 -0
- package/rules/security/S049_short_validity_tokens/regex-based-analyzer.js +295 -0
- package/rules/security/S049_short_validity_tokens/symbol-based-analyzer.js +389 -0
- package/rules/security/S051_password_length_policy/analyzer.js +410 -0
- package/rules/security/S051_password_length_policy/config.json +83 -0
- package/rules/security/S052_weak_otp_entropy/analyzer.js +403 -0
- package/rules/security/S052_weak_otp_entropy/config.json +57 -0
- package/rules/security/S054_no_default_accounts/README.md +129 -0
- package/rules/security/S054_no_default_accounts/analyzer.js +792 -0
- package/rules/security/S054_no_default_accounts/config.json +101 -0
- package/rules/security/S056_log_injection_protection/analyzer.js +242 -0
- package/rules/security/S056_log_injection_protection/config.json +148 -0
- package/rules/security/S056_log_injection_protection/regex-based-analyzer.js +120 -0
- 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;
|