@sun-asterisk/sunlint 1.3.6 → 1.3.7
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 +38 -1
- package/config/rules/enhanced-rules-registry.json +42 -10
- package/core/analysis-orchestrator.js +9 -5
- package/core/performance-optimizer.js +8 -2
- package/package.json +1 -1
- package/rules/common/C073_validate_required_config_on_startup/README.md +110 -0
- package/rules/common/C073_validate_required_config_on_startup/analyzer.js +770 -0
- package/rules/common/C073_validate_required_config_on_startup/config.json +46 -0
- package/rules/common/C073_validate_required_config_on_startup/symbol-based-analyzer.js +370 -0
- package/rules/security/S057_utc_logging/README.md +152 -0
- package/rules/security/S057_utc_logging/analyzer.js +457 -0
- package/rules/security/S057_utc_logging/config.json +105 -0
- package/rules/security/S058_no_ssrf/README.md +180 -0
- package/rules/security/S058_no_ssrf/analyzer.js +403 -0
- package/rules/security/S058_no_ssrf/config.json +125 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "C073",
|
|
3
|
+
"name": "C073_validate_required_config_on_startup",
|
|
4
|
+
"category": "configuration",
|
|
5
|
+
"description": "C073 - Validate mandatory configuration at startup and fail fast on invalid/missing values.",
|
|
6
|
+
"severity": "error",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"semantic": { "enabled": true, "priority": "high", "fallback": "heuristic" },
|
|
9
|
+
"patterns": {
|
|
10
|
+
"include": ["**/*.ts","**/*.tsx","**/*.js","**/*.jsx","**/*.java","**/*.go"],
|
|
11
|
+
"exclude": ["**/*.test.*","**/*.spec.*","**/__tests__/**","**/fixtures/**","**/examples/**"]
|
|
12
|
+
},
|
|
13
|
+
"options": {
|
|
14
|
+
"configModules": {
|
|
15
|
+
"typescript": ["src/config/**","**/config/**","**/bootstrap/**","**/main.ts"],
|
|
16
|
+
"java": ["**/config/**","**/Configuration/**","**/Application.java","**/*Application.java"],
|
|
17
|
+
"go": ["cmd/**","**/config/**","**/main.go"]
|
|
18
|
+
},
|
|
19
|
+
"envAccessors": {
|
|
20
|
+
"typescript": ["process.env.*"],
|
|
21
|
+
"java": ["System.getenv(*)","System.getProperty(*)"],
|
|
22
|
+
"go": ["os.Getenv(*)"]
|
|
23
|
+
},
|
|
24
|
+
"schemaDetectors": {
|
|
25
|
+
"typescript": ["zod","joi","yup","envalid","dotenv-safe","class-validator"],
|
|
26
|
+
"java": ["@ConfigurationProperties","@Validated","jakarta.validation","hibernate.validator"],
|
|
27
|
+
"go": ["github.com/kelseyhightower/envconfig","github.com/spf13/viper"]
|
|
28
|
+
},
|
|
29
|
+
"failFastSignals": {
|
|
30
|
+
"typescript": ["throw new Error(*)","process.exit(1)"],
|
|
31
|
+
"java": ["throw new RuntimeException(*)","SpringApplication.exit(*)","System.exit(1)"],
|
|
32
|
+
"go": ["log.Fatal(*)","panic(*)","os.Exit(1)"]
|
|
33
|
+
},
|
|
34
|
+
"dangerousDefaults": ["|| ''","|| 0","|| 'http://localhost'","?: ''","?: 0"],
|
|
35
|
+
"thresholds": {
|
|
36
|
+
"maxEnvReadsOutsideConfig": 3
|
|
37
|
+
},
|
|
38
|
+
"policy": {
|
|
39
|
+
"requireSchemaOrExplicitChecks": true,
|
|
40
|
+
"requireFailFast": true,
|
|
41
|
+
"forbidEnvReadsOutsideConfig": true,
|
|
42
|
+
"flagDangerousDefaults": true,
|
|
43
|
+
"requireStartupConnectivityChecks": true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
const { traverse } = require('@babel/traverse');
|
|
2
|
+
const t = require('@babel/types');
|
|
3
|
+
|
|
4
|
+
class C073SymbolBasedAnalyzer {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
this.configModules = options.configModules || {};
|
|
8
|
+
this.envAccessors = options.envAccessors || {};
|
|
9
|
+
this.schemaDetectors = options.schemaDetectors || {};
|
|
10
|
+
this.failFastSignals = options.failFastSignals || {};
|
|
11
|
+
this.dangerousDefaults = options.dangerousDefaults || [];
|
|
12
|
+
this.policy = options.policy || {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
analyze(ast, filePath, content) {
|
|
16
|
+
const analysis = {
|
|
17
|
+
envAccess: [],
|
|
18
|
+
schemaValidation: [],
|
|
19
|
+
failFastMechanisms: [],
|
|
20
|
+
dangerousDefaults: [],
|
|
21
|
+
connectivityChecks: [],
|
|
22
|
+
imports: [],
|
|
23
|
+
configValidation: false,
|
|
24
|
+
hasFailFast: false
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const language = this.detectLanguageFromPath(filePath);
|
|
28
|
+
|
|
29
|
+
// Analyze both config files and service files for different patterns
|
|
30
|
+
const isConfigFile = this.isConfigOrStartupFile(filePath, language);
|
|
31
|
+
|
|
32
|
+
// Traverse AST to collect information
|
|
33
|
+
traverse(ast, {
|
|
34
|
+
// Track imports for schema validation libraries
|
|
35
|
+
ImportDeclaration: (path) => {
|
|
36
|
+
const source = path.node.source.value;
|
|
37
|
+
analysis.imports.push(source);
|
|
38
|
+
|
|
39
|
+
// Check for schema validation libraries
|
|
40
|
+
if (this.isSchemaValidationLibrary(source, language)) {
|
|
41
|
+
analysis.schemaValidation.push({
|
|
42
|
+
library: source,
|
|
43
|
+
line: path.node.loc?.start.line || 1
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Track environment variable access
|
|
49
|
+
MemberExpression: (path) => {
|
|
50
|
+
if (this.isEnvAccess(path.node, language)) {
|
|
51
|
+
const envProperty = this.getEnvProperty(path.node);
|
|
52
|
+
analysis.envAccess.push({
|
|
53
|
+
variable: envProperty,
|
|
54
|
+
match: `process.env.${envProperty}`,
|
|
55
|
+
line: path.node.loc?.start.line || 1,
|
|
56
|
+
hasDefault: this.hasDefaultValue(path.parent)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// Track call expressions for multiple purposes
|
|
62
|
+
CallExpression: (path) => {
|
|
63
|
+
const node = path.node;
|
|
64
|
+
|
|
65
|
+
// Check for require statements
|
|
66
|
+
if (t.isIdentifier(node.callee, { name: 'require' })) {
|
|
67
|
+
const source = node.arguments[0]?.value;
|
|
68
|
+
if (source && this.isSchemaValidationLibrary(source, language)) {
|
|
69
|
+
analysis.schemaValidation.push({
|
|
70
|
+
library: source,
|
|
71
|
+
line: node.loc?.start.line || 1
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check for fail-fast calls (process.exit, throw, etc.)
|
|
77
|
+
if (this.isFailFastCall(node, language)) {
|
|
78
|
+
analysis.failFastMechanisms.push({
|
|
79
|
+
type: this.getFailFastType(node),
|
|
80
|
+
line: node.loc?.start.line || 1
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check for connectivity patterns (ping, connect, health check)
|
|
85
|
+
if (this.isConnectivityCheck(node)) {
|
|
86
|
+
analysis.connectivityChecks.push({
|
|
87
|
+
method: this.getConnectivityMethod(node),
|
|
88
|
+
line: node.loc?.start.line || 1
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// Track dangerous default patterns
|
|
94
|
+
LogicalExpression: (path) => {
|
|
95
|
+
if (this.isDangerousDefault(path.node)) {
|
|
96
|
+
analysis.dangerousDefaults.push({
|
|
97
|
+
operator: path.node.operator,
|
|
98
|
+
pattern: this.getDangerousDefaultPattern(path.node),
|
|
99
|
+
line: path.node.loc?.start.line || 1
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// Track conditional expressions with dangerous defaults
|
|
105
|
+
ConditionalExpression: (path) => {
|
|
106
|
+
if (this.isDangerousConditionalDefault(path.node)) {
|
|
107
|
+
analysis.dangerousDefaults.push({
|
|
108
|
+
type: 'conditional',
|
|
109
|
+
pattern: this.getDangerousDefaultPattern(path.node),
|
|
110
|
+
line: path.node.loc?.start.line || 1
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Check for validation patterns
|
|
116
|
+
IfStatement: (path) => {
|
|
117
|
+
if (this.isConfigValidation(path.node, analysis.envAccess)) {
|
|
118
|
+
analysis.configValidation = true;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// Check for throw statements with configuration errors
|
|
123
|
+
ThrowStatement: (path) => {
|
|
124
|
+
if (this.isConfigRelatedError(path.node)) {
|
|
125
|
+
analysis.failFastMechanisms.push({
|
|
126
|
+
type: 'throw',
|
|
127
|
+
line: path.node.loc?.start.line || 1
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return analysis;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
detectLanguageFromPath(filePath) {
|
|
137
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
138
|
+
if (['ts', 'tsx', 'js', 'jsx'].includes(ext)) return 'typescript';
|
|
139
|
+
if (['java'].includes(ext)) return 'java';
|
|
140
|
+
if (['go'].includes(ext)) return 'go';
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
isConfigOrStartupFile(filePath, language) {
|
|
145
|
+
const configPatterns = this.configModules[language] || [];
|
|
146
|
+
return configPatterns.some(pattern => {
|
|
147
|
+
const globPattern = pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*');
|
|
148
|
+
const regex = new RegExp(globPattern.replace(/\//g, '\\/'));
|
|
149
|
+
return regex.test(filePath);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
isSchemaValidationLibrary(source, language) {
|
|
154
|
+
const libraries = this.schemaDetectors[language] || [];
|
|
155
|
+
return libraries.some(lib => source.includes(lib));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
isEnvAccess(node, language) {
|
|
159
|
+
if (language === 'typescript') {
|
|
160
|
+
// process.env.VARIABLE
|
|
161
|
+
return (
|
|
162
|
+
t.isMemberExpression(node) &&
|
|
163
|
+
t.isMemberExpression(node.object) &&
|
|
164
|
+
t.isIdentifier(node.object.object, { name: 'process' }) &&
|
|
165
|
+
t.isIdentifier(node.object.property, { name: 'env' })
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getEnvProperty(node) {
|
|
172
|
+
if (t.isIdentifier(node.property)) {
|
|
173
|
+
return node.property.name;
|
|
174
|
+
}
|
|
175
|
+
if (t.isStringLiteral(node.property)) {
|
|
176
|
+
return node.property.value;
|
|
177
|
+
}
|
|
178
|
+
return 'unknown';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
hasDefaultValue(parent) {
|
|
182
|
+
return (
|
|
183
|
+
t.isLogicalExpression(parent) ||
|
|
184
|
+
t.isConditionalExpression(parent) ||
|
|
185
|
+
(t.isAssignmentExpression(parent) && parent.right)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
isFailFastCall(node, language) {
|
|
190
|
+
if (language === 'typescript') {
|
|
191
|
+
// process.exit(1)
|
|
192
|
+
if (
|
|
193
|
+
t.isMemberExpression(node.callee) &&
|
|
194
|
+
t.isIdentifier(node.callee.object, { name: 'process' }) &&
|
|
195
|
+
t.isIdentifier(node.callee.property, { name: 'exit' })
|
|
196
|
+
) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
getFailFastType(node) {
|
|
204
|
+
if (t.isMemberExpression(node.callee)) {
|
|
205
|
+
if (t.isIdentifier(node.callee.property, { name: 'exit' })) {
|
|
206
|
+
return 'process.exit';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (t.isThrowStatement(node)) {
|
|
210
|
+
return 'throw';
|
|
211
|
+
}
|
|
212
|
+
return 'unknown';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
isConnectivityCheck(node) {
|
|
216
|
+
if (!t.isCallExpression(node)) return false;
|
|
217
|
+
|
|
218
|
+
// Check for common connectivity patterns
|
|
219
|
+
const patterns = [
|
|
220
|
+
'ping', 'connect', 'healthCheck', 'testConnection',
|
|
221
|
+
'validateConnection', 'checkHealth', 'authenticate'
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
if (t.isMemberExpression(node.callee)) {
|
|
225
|
+
const methodName = node.callee.property?.name;
|
|
226
|
+
return patterns.includes(methodName);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (t.isIdentifier(node.callee)) {
|
|
230
|
+
return patterns.includes(node.callee.name);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
getConnectivityMethod(node) {
|
|
237
|
+
if (t.isMemberExpression(node.callee)) {
|
|
238
|
+
return node.callee.property?.name || 'unknown';
|
|
239
|
+
}
|
|
240
|
+
if (t.isIdentifier(node.callee)) {
|
|
241
|
+
return node.callee.name;
|
|
242
|
+
}
|
|
243
|
+
return 'unknown';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
isDangerousDefault(node) {
|
|
247
|
+
if (!t.isLogicalExpression(node, { operator: '||' })) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const rightValue = this.getDefaultValue(node.right);
|
|
252
|
+
const dangerousPatterns = [
|
|
253
|
+
"''", '""', '0', 'null', 'undefined',
|
|
254
|
+
"'localhost'", "'http://localhost'", "'dev'", "'development'"
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
return dangerousPatterns.includes(rightValue);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
isDangerousConditionalDefault(node) {
|
|
261
|
+
if (!t.isConditionalExpression(node)) return false;
|
|
262
|
+
|
|
263
|
+
const consequent = this.getDefaultValue(node.consequent);
|
|
264
|
+
const alternate = this.getDefaultValue(node.alternate);
|
|
265
|
+
|
|
266
|
+
const dangerousPatterns = [
|
|
267
|
+
"''", '""', '0', 'null', 'undefined',
|
|
268
|
+
"'localhost'", "'http://localhost'", "'dev'", "'development'"
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
return dangerousPatterns.includes(consequent) || dangerousPatterns.includes(alternate);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
getDangerousDefaultPattern(node) {
|
|
275
|
+
if (t.isLogicalExpression(node)) {
|
|
276
|
+
return this.getDefaultValue(node.right);
|
|
277
|
+
}
|
|
278
|
+
if (t.isConditionalExpression(node)) {
|
|
279
|
+
const consequent = this.getDefaultValue(node.consequent);
|
|
280
|
+
const alternate = this.getDefaultValue(node.alternate);
|
|
281
|
+
return `${consequent} ? ${alternate}`;
|
|
282
|
+
}
|
|
283
|
+
return 'unknown';
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
getDefaultValue(node) {
|
|
287
|
+
if (t.isStringLiteral(node)) {
|
|
288
|
+
return `'${node.value}'`;
|
|
289
|
+
}
|
|
290
|
+
if (t.isNumericLiteral(node)) {
|
|
291
|
+
return node.value.toString();
|
|
292
|
+
}
|
|
293
|
+
if (t.isNullLiteral(node)) {
|
|
294
|
+
return 'null';
|
|
295
|
+
}
|
|
296
|
+
if (t.isIdentifier(node, { name: 'undefined' })) {
|
|
297
|
+
return 'undefined';
|
|
298
|
+
}
|
|
299
|
+
return 'unknown';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
isConfigValidation(node, envAccess) {
|
|
303
|
+
// Simple heuristic: if an if statement contains a check for environment variables
|
|
304
|
+
// and has a throw or process.exit in the consequent, it's likely validation
|
|
305
|
+
if (t.isIfStatement(node)) {
|
|
306
|
+
const test = node.test;
|
|
307
|
+
const consequent = node.consequent;
|
|
308
|
+
|
|
309
|
+
// Check if test involves environment variables
|
|
310
|
+
const involvesEnv = this.containsEnvAccess(test);
|
|
311
|
+
|
|
312
|
+
// Check if consequent has fail-fast behavior
|
|
313
|
+
const hasFailFast = this.containsFailFast(consequent);
|
|
314
|
+
|
|
315
|
+
return involvesEnv && hasFailFast;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
containsEnvAccess(node) {
|
|
322
|
+
// Simple traversal to check if node contains process.env access
|
|
323
|
+
let hasEnvAccess = false;
|
|
324
|
+
|
|
325
|
+
traverse(node, {
|
|
326
|
+
MemberExpression: (path) => {
|
|
327
|
+
if (this.isEnvAccess(path.node, 'typescript')) {
|
|
328
|
+
hasEnvAccess = true;
|
|
329
|
+
path.stop();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}, null, {});
|
|
333
|
+
|
|
334
|
+
return hasEnvAccess;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
containsFailFast(node) {
|
|
338
|
+
// Check if node contains fail-fast patterns
|
|
339
|
+
let hasFailFast = false;
|
|
340
|
+
|
|
341
|
+
traverse(node, {
|
|
342
|
+
CallExpression: (path) => {
|
|
343
|
+
if (this.isFailFastCall(path.node, 'typescript')) {
|
|
344
|
+
hasFailFast = true;
|
|
345
|
+
path.stop();
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
ThrowStatement: () => {
|
|
349
|
+
hasFailFast = true;
|
|
350
|
+
}
|
|
351
|
+
}, null, {});
|
|
352
|
+
|
|
353
|
+
return hasFailFast;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
isConfigRelatedError(node) {
|
|
357
|
+
// Check if the error message is configuration-related
|
|
358
|
+
if (t.isThrowStatement(node) && t.isNewExpression(node.argument)) {
|
|
359
|
+
const args = node.argument.arguments;
|
|
360
|
+
if (args.length > 0 && t.isStringLiteral(args[0])) {
|
|
361
|
+
const message = args[0].value.toLowerCase();
|
|
362
|
+
const configKeywords = ['config', 'environment', 'env', 'missing', 'required', 'invalid'];
|
|
363
|
+
return configKeywords.some(keyword => message.includes(keyword));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
module.exports = C073SymbolBasedAnalyzer;
|
|
@@ -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.
|