@sun-asterisk/sunlint 1.3.6 → 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 (50) hide show
  1. package/CHANGELOG.md +76 -1
  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 +230 -43
  5. package/core/analysis-orchestrator.js +9 -5
  6. package/core/file-targeting-service.js +83 -7
  7. package/core/performance-optimizer.js +8 -2
  8. package/package.json +1 -1
  9. package/rules/common/C065_one_behavior_per_test/analyzer.js +851 -0
  10. package/rules/common/C065_one_behavior_per_test/config.json +95 -0
  11. package/rules/common/C073_validate_required_config_on_startup/README.md +110 -0
  12. package/rules/common/C073_validate_required_config_on_startup/analyzer.js +770 -0
  13. package/rules/common/C073_validate_required_config_on_startup/config.json +46 -0
  14. package/rules/common/C073_validate_required_config_on_startup/symbol-based-analyzer.js +370 -0
  15. package/rules/security/S037_cache_headers/README.md +128 -0
  16. package/rules/security/S037_cache_headers/analyzer.js +263 -0
  17. package/rules/security/S037_cache_headers/config.json +50 -0
  18. package/rules/security/S037_cache_headers/regex-based-analyzer.js +463 -0
  19. package/rules/security/S037_cache_headers/symbol-based-analyzer.js +546 -0
  20. package/rules/security/S038_no_version_headers/README.md +234 -0
  21. package/rules/security/S038_no_version_headers/analyzer.js +262 -0
  22. package/rules/security/S038_no_version_headers/config.json +49 -0
  23. package/rules/security/S038_no_version_headers/regex-based-analyzer.js +339 -0
  24. package/rules/security/S038_no_version_headers/symbol-based-analyzer.js +375 -0
  25. package/rules/security/S039_no_session_tokens_in_url/README.md +198 -0
  26. package/rules/security/S039_no_session_tokens_in_url/analyzer.js +262 -0
  27. package/rules/security/S039_no_session_tokens_in_url/config.json +92 -0
  28. package/rules/security/S039_no_session_tokens_in_url/regex-based-analyzer.js +337 -0
  29. package/rules/security/S039_no_session_tokens_in_url/symbol-based-analyzer.js +436 -0
  30. package/rules/security/S049_short_validity_tokens/analyzer.js +175 -0
  31. package/rules/security/S049_short_validity_tokens/config.json +124 -0
  32. package/rules/security/S049_short_validity_tokens/regex-based-analyzer.js +295 -0
  33. package/rules/security/S049_short_validity_tokens/symbol-based-analyzer.js +389 -0
  34. package/rules/security/S051_password_length_policy/analyzer.js +410 -0
  35. package/rules/security/S051_password_length_policy/config.json +83 -0
  36. package/rules/security/S052_weak_otp_entropy/analyzer.js +403 -0
  37. package/rules/security/S052_weak_otp_entropy/config.json +57 -0
  38. package/rules/security/S054_no_default_accounts/README.md +129 -0
  39. package/rules/security/S054_no_default_accounts/analyzer.js +792 -0
  40. package/rules/security/S054_no_default_accounts/config.json +101 -0
  41. package/rules/security/S056_log_injection_protection/analyzer.js +242 -0
  42. package/rules/security/S056_log_injection_protection/config.json +148 -0
  43. package/rules/security/S056_log_injection_protection/regex-based-analyzer.js +120 -0
  44. package/rules/security/S056_log_injection_protection/symbol-based-analyzer.js +287 -0
  45. package/rules/security/S057_utc_logging/README.md +152 -0
  46. package/rules/security/S057_utc_logging/analyzer.js +457 -0
  47. package/rules/security/S057_utc_logging/config.json +105 -0
  48. package/rules/security/S058_no_ssrf/README.md +180 -0
  49. package/rules/security/S058_no_ssrf/analyzer.js +403 -0
  50. package/rules/security/S058_no_ssrf/config.json +125 -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;
@@ -0,0 +1,152 @@
1
+ # S057 - Log with UTC Timestamps
2
+
3
+ ## Overview
4
+ Enforce UTC usage in logging and time formatting to ensure consistency across systems and avoid timezone-related issues in log analysis.
5
+
6
+ ## Rule Details
7
+
8
+ This rule enforces:
9
+ - **UTC Timestamps Only**: All logged timestamps must use UTC format
10
+ - **ISO 8601/RFC3339 Standard**: Prefer standardized time formats
11
+ - **No Local Time**: Prevent usage of local time in logs
12
+ - **Framework Configuration**: Ensure logging frameworks are configured for UTC
13
+
14
+ ## ❌ Incorrect Examples
15
+
16
+ ```javascript
17
+ // Using local time
18
+ console.log('Event at:', new Date().toString());
19
+ console.log('Timestamp:', new Date().toLocaleString());
20
+
21
+ // Non-UTC moment.js
22
+ const moment = require('moment');
23
+ logger.info(`Event time: ${moment().format()}`);
24
+
25
+ // Local DateTime patterns
26
+ logger.error(`Error at ${DateTime.now()}`);
27
+ logger.warn(`Time: ${LocalDateTime.now()}`);
28
+
29
+ // Framework without UTC config
30
+ const winston = require('winston');
31
+ const logger = winston.createLogger({
32
+ level: 'info',
33
+ format: winston.format.json(),
34
+ // Missing UTC timezone configuration
35
+ });
36
+ ```
37
+
38
+ ## ✅ Correct Examples
39
+
40
+ ```javascript
41
+ // Using UTC ISO format
42
+ console.log('Event at:', new Date().toISOString());
43
+ logger.info(`Event time: ${new Date().toISOString()}`);
44
+
45
+ // UTC moment.js
46
+ const moment = require('moment');
47
+ logger.info(`Event time: ${moment.utc().format()}`);
48
+
49
+ // UTC DateTime patterns
50
+ logger.error(`Error at ${Instant.now()}`);
51
+ logger.warn(`Time: ${OffsetDateTime.now(ZoneOffset.UTC)}`);
52
+
53
+ // Proper framework configuration
54
+ const winston = require('winston');
55
+ const logger = winston.createLogger({
56
+ level: 'info',
57
+ format: winston.format.combine(
58
+ winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
59
+ winston.format.timezone('UTC'),
60
+ winston.format.json()
61
+ )
62
+ });
63
+ ```
64
+
65
+ ## Configuration Options
66
+
67
+ ### disallowedDatePatterns
68
+ Patterns that create timezone-dependent timestamps:
69
+ - `new Date().toString()`
70
+ - `new Date().toLocaleString()`
71
+ - `DateTime.now()`
72
+ - `LocalDateTime.now()`
73
+ - `moment().format()`
74
+
75
+ ### allowedUtcPatterns
76
+ UTC-safe timestamp patterns:
77
+ - `toISOString()`
78
+ - `Instant.now()`
79
+ - `moment.utc()`
80
+ - `dayjs.utc()`
81
+ - `OffsetDateTime.now(ZoneOffset.UTC)`
82
+
83
+ ### logFrameworks
84
+ Supported logging frameworks for configuration checking:
85
+ - Winston
86
+ - Pino
87
+ - Bunyan
88
+ - Log4js
89
+ - Console methods
90
+
91
+ ## Benefits
92
+
93
+ 1. **Consistent Logs**: All timestamps in the same timezone
94
+ 2. **Global Compatibility**: Works across multiple regions/servers
95
+ 3. **Easy Analysis**: No timezone conversion needed for log correlation
96
+ 4. **Audit Compliance**: Standardized timestamps for security auditing
97
+ 5. **Debugging**: Simplified troubleshooting across distributed systems
98
+
99
+ ## Security Implications
100
+
101
+ - **Audit Trail**: Consistent timestamps critical for security incident investigation
102
+ - **Compliance**: Many security standards require UTC logging
103
+ - **Forensics**: Timezone consistency essential for timeline reconstruction
104
+ - **Correlation**: Multi-system log correlation requires synchronized time
105
+
106
+ ## Related Rules
107
+
108
+ - **C019**: Log Level Usage - Proper log severity levels
109
+ - **S056**: Sensitive Data Logging - Avoid logging sensitive information
110
+ - **S058**: SSRF Protection - Related to secure logging practices
111
+
112
+ ## Implementation Notes
113
+
114
+ ### NTP Synchronization
115
+ While this rule focuses on timestamp format, ensure your systems use NTP for time synchronization:
116
+
117
+ ```bash
118
+ # Check NTP status
119
+ timedatectl status
120
+
121
+ # Enable NTP sync
122
+ sudo timedatectl set-ntp true
123
+ ```
124
+
125
+ ### Docker Configuration
126
+ For containerized applications:
127
+
128
+ ```dockerfile
129
+ # Set timezone to UTC
130
+ ENV TZ=UTC
131
+ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
132
+ ```
133
+
134
+ ### Database Considerations
135
+ Ensure database timestamps also use UTC:
136
+
137
+ ```sql
138
+ -- PostgreSQL
139
+ SET timezone = 'UTC';
140
+
141
+ -- MySQL
142
+ SET time_zone = '+00:00';
143
+ ```
144
+
145
+ ## False Positives
146
+
147
+ This rule may flag legitimate use cases in:
148
+ - Test files (can be exempted via configuration)
149
+ - Display/UI code (where local time is appropriate)
150
+ - Time calculation utilities (where local time is intentional)
151
+
152
+ Configure exemptions in the rule configuration as needed.