@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,101 @@
1
+ {
2
+ "ruleId": "S054",
3
+ "name": "Disallow Default/Built-in Accounts (admin/root/sa/...)",
4
+ "description": "Prevent use of default or shared accounts. Enforce per-user identities, initial password change, and disabling well-known built-ins.",
5
+ "category": "security",
6
+ "severity": "error",
7
+ "options": {
8
+ "blockedUsernames": [
9
+ "admin","root","sa","test","guest","operator","super","superuser","sys",
10
+ "postgres","mysql","mssql","oracle","elastic","kibana","grafana",
11
+ "administrator", "demo", "example", "default", "public", "anonymous",
12
+ "user", "password", "service", "support", "backup", "monitor"
13
+ ],
14
+ "codeCreationPatterns": [
15
+ "create(User|Account)\\s*\\(",
16
+ "new\\s+User\\s*\\(",
17
+ "user(Name|name|_name)\\s*:",
18
+ "username\\s*=\\s*",
19
+ "setUser(Name|name)\\s*\\(",
20
+ "addUser\\s*\\(",
21
+ "registerUser\\s*\\(",
22
+ "createAccount\\s*\\("
23
+ ],
24
+ "sqlInsertUserPatterns": [
25
+ "INSERT\\s+INTO\\s+\\w*user\\w*\\s*\\(",
26
+ "UPSERT\\s+INTO\\s+\\w*user\\w*\\s*\\(",
27
+ "CREATE\\s+USER\\s+",
28
+ "GRANT\\s+.+\\s+TO\\s+",
29
+ "REVOKE\\s+.+\\s+FROM\\s+"
30
+ ],
31
+ "infraPatterns": {
32
+ "terraform": [
33
+ "username\\s*=\\s*\"(admin|root|sa|test|guest)\"",
34
+ "user\\s*=\\s*\"(admin|root|sa|test|guest)\"",
35
+ "admin_username\\s*=\\s*\"(admin|root|sa|test|guest)\""
36
+ ],
37
+ "helmValues": [
38
+ "admin(User|Password)\\s*:",
39
+ "default(User|Pass)\\s*:",
40
+ "root(User|Password)\\s*:",
41
+ "service(User|Account)\\s*:"
42
+ ],
43
+ "docker": [
44
+ "ENV\\s+.*(USER|USERNAME|_ROOT_USERNAME)\\s*=\\s*(admin|root|sa)",
45
+ "POSTGRES_USER\\s*=\\s*(postgres|admin|root)",
46
+ "MONGO_INITDB_ROOT_USERNAME\\s*=\\s*(root|admin)",
47
+ "MYSQL_USER\\s*=\\s*(root|admin|mysql)",
48
+ "REDIS_USER\\s*=\\s*(redis|admin|root)"
49
+ ],
50
+ "kubernetes": [
51
+ "serviceAccount:\\s*default",
52
+ "user:\\s*(admin|root|sa|test|guest)",
53
+ "username:\\s*(admin|root|sa|test|guest)"
54
+ ]
55
+ },
56
+ "docPatterns": [
57
+ "login\\s*[:=]\\s*(admin|root|sa|test|guest)",
58
+ "user\\s*[:=]\\s*(admin|root|sa|test|guest)",
59
+ "username\\s*[:=]\\s*(admin|root|sa|test|guest)",
60
+ "password\\s*[:=]\\s*(admin|root|sa|test|guest|password|123456)"
61
+ ],
62
+ "passwordSmells": [
63
+ "password", "123456", "admin", "Admin@123", "Password1", "changeme",
64
+ "default", "qwerty", "letmein", "welcome", "secret", "pass123",
65
+ "root", "toor", "administrator", "guest"
66
+ ],
67
+ "configFilePatterns": [
68
+ "database\\.(username|user)\\s*=\\s*(admin|root|sa)",
69
+ "db\\.(username|user)\\s*=\\s*(admin|root|sa)",
70
+ "auth\\.(username|user)\\s*=\\s*(admin|root|sa)",
71
+ "admin\\.(username|user)\\s*=\\s*",
72
+ "spring\\.datasource\\.username\\s*=\\s*(admin|root|sa)"
73
+ ],
74
+ "policy": {
75
+ "requirePerUserAccount": true,
76
+ "requireInitialPasswordChange": true,
77
+ "forbidWellKnownServiceAccountsInAppDB": true,
78
+ "allowOnlyInEphemeralTests": true,
79
+ "mustDisableBuiltInsOnInfra": true
80
+ },
81
+ "allowlist": {
82
+ "paths": [
83
+ "test/", "tests/", "__tests__/", "e2e/", "playground/",
84
+ "local-dev/", "demo/", "example/", "mock/", "fixture/",
85
+ "spec/", ".spec.", ".test."
86
+ ],
87
+ "notes": "Vẫn cảnh báo nếu xuất hiện mật khẩu mặc định; cho phép username cấm chỉ khi data giả lập không public và không nối vào môi trường thật."
88
+ },
89
+ "thresholds": {
90
+ "maxFindings": 0,
91
+ "maxInAllowedPaths": 2,
92
+ "maxPasswordSmells": 0
93
+ },
94
+ "exemptions": {
95
+ "testDirectories": ["test", "tests", "__tests__", "e2e", "spec"],
96
+ "configFiles": ["jest.config", "test.config", "local.config"],
97
+ "allowTestData": true,
98
+ "allowDocumentationExamples": false
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,242 @@
1
+ /**
2
+ * S056 Main Analyzer - Protect against Log Injection attacks
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
5
+ * Command: node cli.js --rule=S056 --input=examples/rule-test-fixtures/rules/S056_log_injection_protection --engine=heuristic
6
+ */
7
+
8
+ const S056SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
+ const S056RegexBasedAnalyzer = require("./regex-based-analyzer.js");
10
+
11
+ class S056Analyzer {
12
+ constructor(options = {}) {
13
+ if (process.env.SUNLINT_DEBUG) {
14
+ console.log(`🔧 [S056] Constructor called with options:`, !!options);
15
+ console.log(
16
+ `🔧 [S056] Options type:`,
17
+ typeof options,
18
+ Object.keys(options || {})
19
+ );
20
+ }
21
+
22
+ this.ruleId = "S056";
23
+ this.ruleName = "Protect against Log Injection attacks";
24
+ this.description =
25
+ "Protect against Log Injection attacks. Log injection occurs when user-controlled data is written to log files without proper sanitization, potentially allowing attackers to manipulate log entries, inject malicious content, or exploit log processing systems.";
26
+ this.semanticEngine = options.semanticEngine || null;
27
+ this.verbose = options.verbose || false;
28
+
29
+ // Configuration
30
+ this.config = {
31
+ useSymbolBased: true, // Re-enabled with ts-morph API fixes
32
+ fallbackToRegex: true, // Secondary approach
33
+ regexBasedOnly: false, // Now we can use symbol analysis again
34
+ };
35
+
36
+ // Initialize analyzers
37
+ try {
38
+ this.symbolAnalyzer = new S056SymbolBasedAnalyzer(this.semanticEngine);
39
+ if (process.env.SUNLINT_DEBUG) {
40
+ console.log(`🔧 [S056] Symbol analyzer created successfully`);
41
+ }
42
+ } catch (error) {
43
+ console.error(`🔧 [S056] Error creating symbol analyzer:`, error);
44
+ }
45
+
46
+ try {
47
+ this.regexAnalyzer = new S056RegexBasedAnalyzer(this.semanticEngine);
48
+ if (process.env.SUNLINT_DEBUG) {
49
+ console.log(`🔧 [S056] Regex analyzer created successfully`);
50
+ }
51
+ } catch (error) {
52
+ console.error(`🔧 [S056] Error creating regex analyzer:`, error);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Initialize analyzer with semantic engine
58
+ */
59
+ async initialize(semanticEngine) {
60
+ this.semanticEngine = semanticEngine;
61
+
62
+ if (process.env.SUNLINT_DEBUG) {
63
+ console.log(`🔧 [S056] Main analyzer initializing...`);
64
+ }
65
+
66
+ // Initialize both analyzers
67
+ if (this.symbolAnalyzer) {
68
+ await this.symbolAnalyzer.initialize?.(semanticEngine);
69
+ }
70
+ if (this.regexAnalyzer) {
71
+ await this.regexAnalyzer.initialize?.(semanticEngine);
72
+ }
73
+
74
+ // Clean up if needed
75
+ if (this.regexAnalyzer) {
76
+ this.regexAnalyzer.cleanup?.();
77
+ }
78
+
79
+ if (process.env.SUNLINT_DEBUG) {
80
+ console.log(`🔧 [S056] Main analyzer initialized successfully`);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Single file analysis method for testing
86
+ */
87
+ analyzeSingle(filePath, options = {}) {
88
+ if (process.env.SUNLINT_DEBUG) {
89
+ console.log(`📊 [S056] analyzeSingle() called for: ${filePath}`);
90
+ }
91
+
92
+ // Return result using same format as analyze method
93
+ return this.analyze([filePath], "typescript", options);
94
+ }
95
+
96
+ async analyze(files, language, options = {}) {
97
+ if (process.env.SUNLINT_DEBUG) {
98
+ console.log(
99
+ `🔧 [S056] analyze() method called with ${files.length} files, language: ${language}`
100
+ );
101
+ }
102
+
103
+ const violations = [];
104
+
105
+ for (const filePath of files) {
106
+ try {
107
+ if (process.env.SUNLINT_DEBUG) {
108
+ console.log(`🔧 [S056] Processing file: ${filePath}`);
109
+ }
110
+
111
+ const fileViolations = await this.analyzeFile(filePath, options);
112
+ violations.push(...fileViolations);
113
+
114
+ if (process.env.SUNLINT_DEBUG) {
115
+ console.log(
116
+ `🔧 [S056] File ${filePath}: Found ${fileViolations.length} violations`
117
+ );
118
+ }
119
+ } catch (error) {
120
+ console.warn(
121
+ `⚠ [S056] Analysis failed for ${filePath}:`,
122
+ error.message
123
+ );
124
+ }
125
+ }
126
+
127
+ if (process.env.SUNLINT_DEBUG) {
128
+ console.log(`🔧 [S056] Total violations found: ${violations.length}`);
129
+ }
130
+
131
+ return violations;
132
+ }
133
+
134
+ async analyzeFile(filePath, options = {}) {
135
+ if (process.env.SUNLINT_DEBUG) {
136
+ console.log(`🔍 [S056] analyzeFile() called for: ${filePath}`);
137
+ }
138
+
139
+ // Create a Map to track unique violations and prevent duplicates
140
+ const violationMap = new Map();
141
+
142
+ // 1. Try Symbol-based analysis first (primary)
143
+ if (
144
+ this.config.useSymbolBased &&
145
+ this.semanticEngine?.project &&
146
+ this.semanticEngine?.initialized
147
+ ) {
148
+ try {
149
+ if (process.env.SUNLINT_DEBUG) {
150
+ console.log(`🔧 [S056] Trying symbol-based analysis...`);
151
+ }
152
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
153
+ if (sourceFile) {
154
+ if (process.env.SUNLINT_DEBUG) {
155
+ console.log(`🔧 [S056] Source file found, analyzing...`);
156
+ }
157
+ const symbolViolations = await this.symbolAnalyzer.analyze(
158
+ sourceFile,
159
+ filePath
160
+ );
161
+
162
+ // Add to violation map with deduplication
163
+ symbolViolations.forEach((violation) => {
164
+ const key = `${violation.line}:${violation.column}:${violation.message}`;
165
+ if (!violationMap.has(key)) {
166
+ violationMap.set(key, violation);
167
+ }
168
+ });
169
+
170
+ if (process.env.SUNLINT_DEBUG) {
171
+ console.log(
172
+ `🔧 [S056] Symbol analysis completed: ${symbolViolations.length} violations`
173
+ );
174
+ }
175
+ } else {
176
+ if (process.env.SUNLINT_DEBUG) {
177
+ console.log(`🔧 [S056] Source file not found, falling back...`);
178
+ }
179
+ }
180
+ } catch (error) {
181
+ console.warn(`⚠ [S056] Symbol analysis failed:`, error.message);
182
+ }
183
+ }
184
+
185
+ // 2. Try Regex-based analysis (fallback or additional)
186
+ if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
187
+ try {
188
+ if (process.env.SUNLINT_DEBUG) {
189
+ console.log(`🔧 [S056] Trying regex-based analysis...`);
190
+ }
191
+ const regexViolations = await this.regexAnalyzer.analyze(filePath);
192
+
193
+ // Add to violation map with deduplication
194
+ regexViolations.forEach((violation) => {
195
+ const key = `${violation.line}:${violation.column}:${violation.message}`;
196
+ if (!violationMap.has(key)) {
197
+ violationMap.set(key, violation);
198
+ }
199
+ });
200
+
201
+ if (process.env.SUNLINT_DEBUG) {
202
+ console.log(
203
+ `🔧 [S056] Regex analysis completed: ${regexViolations.length} violations`
204
+ );
205
+ }
206
+ } catch (error) {
207
+ console.warn(`⚠ [S056] Regex analysis failed:`, error.message);
208
+ }
209
+ }
210
+
211
+ // Convert Map values to array and add filePath to each violation
212
+ const finalViolations = Array.from(violationMap.values()).map(
213
+ (violation) => ({
214
+ ...violation,
215
+ filePath: filePath,
216
+ file: filePath, // Also add 'file' for compatibility
217
+ })
218
+ );
219
+
220
+ if (process.env.SUNLINT_DEBUG) {
221
+ console.log(
222
+ `🔧 [S056] File analysis completed: ${finalViolations.length} unique violations`
223
+ );
224
+ }
225
+
226
+ return finalViolations;
227
+ }
228
+
229
+ /**
230
+ * Clean up resources
231
+ */
232
+ cleanup() {
233
+ if (this.symbolAnalyzer?.cleanup) {
234
+ this.symbolAnalyzer.cleanup();
235
+ }
236
+ if (this.regexAnalyzer?.cleanup) {
237
+ this.regexAnalyzer.cleanup();
238
+ }
239
+ }
240
+ }
241
+
242
+ module.exports = S056Analyzer;
@@ -0,0 +1,148 @@
1
+ {
2
+ "rule": {
3
+ "id": "S056",
4
+ "name": "Protect against Log Injection attacks",
5
+ "description": "Protect against Log Injection attacks. Log injection occurs when user-controlled data is written to log files without proper sanitization, potentially allowing attackers to manipulate log entries, inject malicious content, or exploit log processing systems.",
6
+ "category": "security",
7
+ "severity": "error",
8
+ "languages": ["typescript", "javascript"],
9
+ "frameworks": ["express", "nestjs", "node"],
10
+ "version": "1.0.0",
11
+ "status": "stable",
12
+ "tags": ["security", "logging", "injection", "owasp", "crlf"],
13
+ "references": [
14
+ "https://owasp.org/www-community/attacks/Log_Injection",
15
+ "https://cwe.mitre.org/data/definitions/117.html",
16
+ "https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html",
17
+ "https://portswigger.net/kb/issues/00200200_log-injection"
18
+ ]
19
+ },
20
+ "configuration": {
21
+ "enableLogInjectionDetection": true,
22
+ "checkUserInputSources": [
23
+ "req",
24
+ "request",
25
+ "params",
26
+ "query",
27
+ "body",
28
+ "headers",
29
+ "cookies",
30
+ "session"
31
+ ],
32
+ "vulnerableLogMethods": [
33
+ "log",
34
+ "info",
35
+ "warn",
36
+ "error",
37
+ "debug",
38
+ "trace",
39
+ "write",
40
+ "writeSync"
41
+ ],
42
+ "vulnerableLogLibraries": [
43
+ "winston",
44
+ "bunyan",
45
+ "pino",
46
+ "log4js",
47
+ "console",
48
+ "morgan",
49
+ "debug"
50
+ ],
51
+ "dangerousCharacters": [
52
+ "\\r",
53
+ "\\n",
54
+ "\\r\\n",
55
+ "\\u000a",
56
+ "\\u000d",
57
+ "%0a",
58
+ "%0d",
59
+ "\\x0a",
60
+ "\\x0d"
61
+ ],
62
+ "secureLogPatterns": [
63
+ "sanitize",
64
+ "escape",
65
+ "clean",
66
+ "filter",
67
+ "validate",
68
+ "replace",
69
+ "strip"
70
+ ]
71
+ },
72
+ "examples": {
73
+ "violations": [
74
+ {
75
+ "description": "Direct user input in log message",
76
+ "code": "logger.info('User login: ' + req.body.username);"
77
+ },
78
+ {
79
+ "description": "Template literal with user input",
80
+ "code": "console.log(`User ${req.query.user} attempted login`);"
81
+ },
82
+ {
83
+ "description": "User input containing CRLF in log",
84
+ "code": "winston.error('Authentication failed for: ' + req.headers['user-agent']);"
85
+ },
86
+ {
87
+ "description": "Session data in log message",
88
+ "code": "logger.debug('Session: ' + JSON.stringify(req.session));"
89
+ }
90
+ ],
91
+ "fixes": [
92
+ {
93
+ "description": "Sanitize user input before logging",
94
+ "code": "const sanitizedUser = sanitize(req.body.username);\nlogger.info('User login: ' + sanitizedUser);"
95
+ },
96
+ {
97
+ "description": "Use structured logging with safe values",
98
+ "code": "logger.info('User login', { username: sanitize(req.body.username) });"
99
+ },
100
+ {
101
+ "description": "Filter dangerous characters",
102
+ "code": "const cleanInput = req.query.user.replace(/[\\r\\n]/g, '_');\nconsole.log(`User ${cleanInput} attempted login`);"
103
+ },
104
+ {
105
+ "description": "Validate input before logging",
106
+ "code": "if (validateInput(req.headers['user-agent'])) {\n winston.error('Authentication failed for: ' + req.headers['user-agent']);\n}"
107
+ }
108
+ ]
109
+ },
110
+ "testing": {
111
+ "testCases": [
112
+ {
113
+ "name": "log_injection_direct_input",
114
+ "type": "violation",
115
+ "description": "Direct user input in log message"
116
+ },
117
+ {
118
+ "name": "log_injection_template_literal",
119
+ "type": "violation",
120
+ "description": "Template literal with user input"
121
+ },
122
+ {
123
+ "name": "log_injection_crlf",
124
+ "type": "violation",
125
+ "description": "User input containing CRLF characters"
126
+ },
127
+ {
128
+ "name": "log_injection_json_stringify",
129
+ "type": "violation",
130
+ "description": "JSON.stringify with user input"
131
+ },
132
+ {
133
+ "name": "secure_log_sanitized",
134
+ "type": "clean",
135
+ "description": "Sanitized user input in log"
136
+ },
137
+ {
138
+ "name": "secure_log_structured",
139
+ "type": "clean",
140
+ "description": "Structured logging with safe values"
141
+ }
142
+ ]
143
+ },
144
+ "performance": {
145
+ "complexity": "O(n)",
146
+ "description": "Linear complexity based on number of function calls and expressions in the source code"
147
+ }
148
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * S056 Regex-Based Analyzer - Protect against Log Injection attacks
3
+ * Uses regular expressions for pattern-based analysis
4
+ */
5
+
6
+ const fs = require("fs");
7
+
8
+ class S056RegexBasedAnalyzer {
9
+ constructor() {
10
+ this.ruleId = "S056";
11
+ this.category = "security";
12
+
13
+ // Regex patterns for detecting log injection vulnerabilities
14
+ this.patterns = [
15
+ // Direct concatenation with user input
16
+ {
17
+ pattern: /(logger\.|console\.)(log|info|warn|error|debug)\s*\(\s*['"`].*?\+\s*(req\.|request\.)/g,
18
+ message: "Log injection vulnerability: Direct concatenation of user input in log statement"
19
+ },
20
+ // Template literals with user input
21
+ {
22
+ pattern: /(logger\.|console\.)(log|info|warn|error|debug)\s*\(\s*`.*?\$\{.*?(req\.|request\.)/g,
23
+ message: "Log injection vulnerability: Template literal with user input in log statement"
24
+ },
25
+ // Winston/Bunyan/Pino with user input
26
+ {
27
+ pattern: /(winston\.|bunyan\.|pino\.)(log|info|warn|error|debug)\s*\(\s*['"`].*?\+\s*(req\.|request\.)/g,
28
+ message: "Log injection vulnerability: User input concatenated in Winston/Bunyan/Pino log"
29
+ },
30
+ // JSON.stringify with user input in logs
31
+ {
32
+ pattern: /(logger\.|console\.)(log|info|warn|error|debug)\s*\(.*?JSON\.stringify\s*\(\s*(req\.|request\.|session)/g,
33
+ message: "Log injection vulnerability: JSON.stringify with user input in log statement"
34
+ },
35
+ // User input sources in log calls
36
+ {
37
+ pattern: /(logger\.|console\.|winston\.|bunyan\.|pino\.)(log|info|warn|error|debug).*?(req\.body|req\.query|req\.params|req\.headers|req\.cookies|req\.session)/g,
38
+ message: "Log injection vulnerability: User input source used in log statement without sanitization"
39
+ },
40
+ // CRLF injection patterns
41
+ {
42
+ pattern: /(logger\.|console\.)(log|info|warn|error|debug).*?(\\r|\\n|%0a|%0d)/g,
43
+ message: "Log injection vulnerability: CRLF characters detected in log statement"
44
+ }
45
+ ];
46
+
47
+ // Patterns that indicate secure usage (to reduce false positives)
48
+ this.securePatterns = [
49
+ /sanitize\s*\(/g,
50
+ /escape\s*\(/g,
51
+ /clean\s*\(/g,
52
+ /filter\s*\(/g,
53
+ /validate\s*\(/g,
54
+ /replace\s*\(/g,
55
+ /strip\s*\(/g
56
+ ];
57
+ }
58
+
59
+ async analyze(filePath) {
60
+ if (process.env.SUNLINT_DEBUG) {
61
+ console.log(`🔍 [${this.ruleId}] Regex: Starting analysis for ${filePath}`);
62
+ }
63
+
64
+ try {
65
+ const fileContent = fs.readFileSync(filePath, "utf8");
66
+ const violations = [];
67
+ const lines = fileContent.split('\n');
68
+
69
+ for (let i = 0; i < lines.length; i++) {
70
+ const line = lines[i];
71
+ const lineNumber = i + 1;
72
+
73
+ // Skip if line contains secure patterns
74
+ if (this.hasSecurePattern(line)) {
75
+ continue;
76
+ }
77
+
78
+ // Check each vulnerability pattern
79
+ for (const { pattern, message } of this.patterns) {
80
+ const matches = [...line.matchAll(pattern)];
81
+
82
+ for (const match of matches) {
83
+ const column = match.index + 1;
84
+
85
+ violations.push({
86
+ ruleId: this.ruleId,
87
+ message: message,
88
+ line: lineNumber,
89
+ column: column,
90
+ severity: "error",
91
+ category: this.category,
92
+ code: line.trim()
93
+ });
94
+ }
95
+ }
96
+ }
97
+
98
+ if (process.env.SUNLINT_DEBUG) {
99
+ console.log(
100
+ `🔍 [${this.ruleId}] Regex: Analysis completed, ${violations.length} violations found`
101
+ );
102
+ }
103
+
104
+ return violations;
105
+ } catch (error) {
106
+ console.warn(`⚠ [${this.ruleId}] Regex analysis failed:`, error.message);
107
+ return [];
108
+ }
109
+ }
110
+
111
+ hasSecurePattern(line) {
112
+ return this.securePatterns.some(pattern => pattern.test(line));
113
+ }
114
+
115
+ cleanup() {
116
+ // Cleanup resources if needed
117
+ }
118
+ }
119
+
120
+ module.exports = S056RegexBasedAnalyzer;