@sun-asterisk/sunlint 1.3.26 → 1.3.28

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 (69) hide show
  1. package/config/rules/enhanced-rules-registry.json +101 -17
  2. package/config/rules/rules-registry-generated.json +22 -22
  3. package/origin-rules/security-en.md +351 -338
  4. package/package.json +1 -1
  5. package/rules/common/C003_no_vague_abbreviations/analyzer.js +73 -21
  6. package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +206 -2
  7. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +553 -58
  8. package/rules/common/C029_catch_block_logging/analyzer.js +47 -12
  9. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +35 -15
  10. package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +9 -5
  11. package/rules/security/S003_open_redirect_protection/README.md +371 -0
  12. package/rules/security/S003_open_redirect_protection/analyzer.js +135 -0
  13. package/rules/security/S003_open_redirect_protection/config.json +58 -0
  14. package/rules/security/S003_open_redirect_protection/symbol-based-analyzer.js +884 -0
  15. package/rules/security/S004_sensitive_data_logging/analyzer.js +135 -0
  16. package/rules/security/S004_sensitive_data_logging/config.json +62 -0
  17. package/rules/security/S004_sensitive_data_logging/symbol-based-analyzer.js +592 -0
  18. package/rules/security/S005_no_origin_auth/analyzer.js +97 -148
  19. package/rules/security/S005_no_origin_auth/config.json +28 -67
  20. package/rules/security/S005_no_origin_auth/symbol-based-analyzer.js +708 -0
  21. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +170 -31
  22. package/rules/security/S010_no_insecure_encryption/analyzer.js +8 -2
  23. package/rules/security/S012_hardcoded_secrets/analyzer.js +149 -0
  24. package/rules/security/S012_hardcoded_secrets/config.json +75 -0
  25. package/rules/security/S012_hardcoded_secrets/symbol-based-analyzer.js +1204 -0
  26. package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +87 -0
  27. package/rules/security/S017_use_parameterized_queries/analyzer.js +11 -78
  28. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +1146 -1
  29. package/rules/security/S019_smtp_injection_protection/analyzer.js +120 -0
  30. package/rules/security/S019_smtp_injection_protection/config.json +35 -0
  31. package/rules/security/S019_smtp_injection_protection/symbol-based-analyzer.js +687 -0
  32. package/rules/security/S020_no_eval_dynamic_code/analyzer.js +55 -130
  33. package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +4 -19
  34. package/rules/security/S022_escape_output_context/README.md +254 -0
  35. package/rules/security/S022_escape_output_context/analyzer.js +510 -0
  36. package/rules/security/S022_escape_output_context/config.json +229 -0
  37. package/rules/security/S023_no_json_injection/analyzer.js +15 -0
  38. package/rules/security/S023_no_json_injection/ast-analyzer.js +18 -3
  39. package/rules/security/S023_no_json_injection/config.json +133 -0
  40. package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +41 -0
  41. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +67 -8
  42. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +29 -6
  43. package/rules/security/S029_csrf_protection/config.json +127 -0
  44. package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +160 -28
  45. package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +81 -19
  46. package/rules/security/S031_secure_session_cookies/analyzer.js +20 -2
  47. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +100 -0
  48. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +8 -1
  49. package/rules/security/S032_httponly_session_cookies/analyzer.js +2 -2
  50. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +115 -0
  51. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +39 -10
  52. package/rules/security/S036_lfi_rfi_protection/analyzer.js +224 -0
  53. package/rules/security/S036_lfi_rfi_protection/config.json +20 -0
  54. package/rules/security/S040_session_fixation_protection/analyzer.js +153 -0
  55. package/rules/security/S040_session_fixation_protection/config.json +20 -0
  56. package/rules/security/S042_require_re_authentication_for_long_lived/README.md +83 -0
  57. package/rules/security/S042_require_re_authentication_for_long_lived/analyzer.js +153 -0
  58. package/rules/security/S042_require_re_authentication_for_long_lived/config.json +41 -0
  59. package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js +1139 -0
  60. package/rules/security/S043_password_changes_invalidate_all_sessions/README.md +107 -0
  61. package/rules/security/S043_password_changes_invalidate_all_sessions/analyzer.js +153 -0
  62. package/rules/security/S043_password_changes_invalidate_all_sessions/config.json +41 -0
  63. package/rules/security/S043_password_changes_invalidate_all_sessions/symbol-based-analyzer.js +541 -0
  64. package/docs/COMMAND-EXAMPLES.md +0 -390
  65. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +0 -151
  66. package/docs/FOLDER_STRUCTURE.md +0 -59
  67. package/docs/SIMPLIFIED_USAGE_GUIDE.md +0 -208
  68. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +0 -541
  69. package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +0 -307
@@ -0,0 +1,592 @@
1
+ /**
2
+ * S004 - Sensitive Data Logging Protection (Symbol-based Analyzer)
3
+ *
4
+ * Detects logging of sensitive information like passwords, tokens, credit cards
5
+ * without proper redaction or masking.
6
+ *
7
+ * Based on:
8
+ * - OWASP A09:2021 - Security Logging and Monitoring Failures
9
+ * - CWE-532: Insertion of Sensitive Information into Log File
10
+ */
11
+
12
+ const { SyntaxKind } = require("ts-morph");
13
+
14
+ class S004SymbolBasedAnalyzer {
15
+ constructor(semanticEngine = null) {
16
+ this.ruleId = "S004";
17
+ this.semanticEngine = semanticEngine;
18
+
19
+ // Logging function patterns (common across frameworks)
20
+ this.loggingFunctions = [
21
+ "console.log",
22
+ "console.info",
23
+ "console.warn",
24
+ "console.error",
25
+ "console.debug",
26
+ "logger.log",
27
+ "logger.info",
28
+ "logger.warn",
29
+ "logger.error",
30
+ "logger.debug",
31
+ "log.info",
32
+ "log.warn",
33
+ "log.error",
34
+ "log.debug",
35
+ "winston.log",
36
+ "winston.info",
37
+ "winston.error",
38
+ "pino.info",
39
+ "pino.error",
40
+ "bunyan.info",
41
+ "bunyan.error",
42
+ "print", // Python
43
+ "logging.info", // Python
44
+ "logging.error",
45
+ "log.printf", // Go
46
+ "log.println",
47
+ "logger.info", // Go/Java
48
+ "logger.error",
49
+ "system.out.println", // Java
50
+ "nslog", // Objective-C
51
+ "os_log", // Swift
52
+ ];
53
+
54
+ // Sensitive field patterns (field names that indicate sensitive data)
55
+ this.sensitiveFieldPatterns = [
56
+ // Authentication & Authorization
57
+ "password",
58
+ "passwd",
59
+ "pwd",
60
+ "secret",
61
+ "secretkey",
62
+ "secret_key",
63
+ "apikey",
64
+ "api_key",
65
+ "access_token",
66
+ "accesstoken",
67
+ "refresh_token",
68
+ "refreshtoken",
69
+ "auth_token",
70
+ "authtoken",
71
+ "bearer",
72
+ "authorization",
73
+ "session",
74
+ "sessionid",
75
+ "session_id",
76
+ "jwt",
77
+ "token",
78
+ "otp",
79
+ "pin",
80
+ "privatekey",
81
+ "private_key",
82
+ "credentials",
83
+ "credential",
84
+
85
+ // Payment & Financial
86
+ "credit_card",
87
+ "creditcard",
88
+ "cardnumber",
89
+ "card_number",
90
+ "cvv",
91
+ "cvc",
92
+ "ccv",
93
+ "card_cvv",
94
+ "expiry",
95
+ "expiration",
96
+ "pan", // Primary Account Number
97
+ "iban",
98
+ "account_number",
99
+ "accountnumber",
100
+ "routing_number",
101
+ "bank_account",
102
+ "ssn", // Social Security Number
103
+ "tax_id",
104
+
105
+ // Personal Information
106
+ "email",
107
+ "phone",
108
+ "phonenumber",
109
+ "phone_number",
110
+ "address",
111
+ "birthday",
112
+ "birth_date",
113
+ "dob",
114
+ "passport",
115
+ "license",
116
+ "idcard",
117
+ "id_card",
118
+ "national_id",
119
+ ];
120
+
121
+ // Patterns that indicate the data is already masked/redacted (safe to log)
122
+ this.safeLoggingPatterns = [
123
+ "masked",
124
+ "redacted",
125
+ "sanitized",
126
+ "filtered",
127
+ "hashed",
128
+ "encrypted",
129
+ "****",
130
+ "***",
131
+ "...",
132
+ "[redacted]",
133
+ "[masked]",
134
+ ".mask(",
135
+ ".redact(",
136
+ ".sanitize(",
137
+ ".hash(",
138
+ ".encrypt(",
139
+ ".replace(/", // password.replace(/./g, '*')
140
+ ".replace(", // password.replace('', '')
141
+ "replacewith", // password.replaceWith('***')
142
+ "substr(0,", // Partial masking: token.substr(0, 4) + '****'
143
+ "substring(0,",
144
+ "slice(0,",
145
+ ];
146
+
147
+ // Patterns that indicate selective field logging (safe approach)
148
+ this.selectiveLoggingPatterns = [
149
+ "omit(",
150
+ "pick(",
151
+ "exclude(",
152
+ "without(",
153
+ "except(",
154
+ ".filter(",
155
+ "filterkeys",
156
+ "filter_keys",
157
+ ];
158
+
159
+ // Objects/structures that commonly contain sensitive data
160
+ this.sensitiveBulkPatterns = [
161
+ "req.body",
162
+ "request.body",
163
+ "req.headers",
164
+ "request.headers",
165
+ "formdata",
166
+ "form-data",
167
+ "req.query",
168
+ "req.params",
169
+ "credentials",
170
+ "auth",
171
+ "payment",
172
+ "paymentinfo",
173
+ "payment_info",
174
+ "userdata",
175
+ "user_data",
176
+ "profile",
177
+ ];
178
+ }
179
+
180
+ /**
181
+ * Main analysis method
182
+ */
183
+ analyze(sourceFile) {
184
+ const violations = [];
185
+
186
+ // Check for logging function calls
187
+ const callExpressions = sourceFile.getDescendantsOfKind(
188
+ SyntaxKind.CallExpression
189
+ );
190
+
191
+ for (const callExpr of callExpressions) {
192
+ const callText = callExpr.getText().toLowerCase();
193
+
194
+ // Check if this is a logging function call
195
+ if (!this.isLoggingFunction(callText)) {
196
+ continue;
197
+ }
198
+
199
+ // Get the arguments being logged
200
+ const args = callExpr.getArguments();
201
+ if (args.length === 0) {
202
+ continue;
203
+ }
204
+
205
+ // Check each argument for sensitive data
206
+ for (const arg of args) {
207
+ const argText = arg.getText();
208
+ const argTextLower = argText.toLowerCase();
209
+
210
+ // Skip if already masked/redacted
211
+ if (this.isSafelyMasked(argTextLower)) {
212
+ continue;
213
+ }
214
+
215
+ // Skip if using selective logging (omit/pick specific fields)
216
+ if (this.isSelectiveLogging(argTextLower)) {
217
+ continue;
218
+ }
219
+
220
+ // Check for sensitive field names
221
+ const sensitiveField = this.findSensitiveField(argTextLower);
222
+ if (sensitiveField) {
223
+ // Context-aware filtering for common false positives
224
+ let shouldSkip = false;
225
+
226
+ // Skip if keyword appears in uppercase in message (e.g., "NO EMAIL ERROR")
227
+ if (this.isUppercaseInMessage(sensitiveField, argText)) {
228
+ shouldSkip = true;
229
+ }
230
+
231
+ // Skip if pattern is in a message string (e.g., "no session", "session is null")
232
+ if (this.isInMessageString(sensitiveField, argText)) {
233
+ shouldSkip = true;
234
+ }
235
+
236
+ // Skip if it's selective property access of non-sensitive fields
237
+ // e.g., credentials.type, payment.id, paymentIntentId
238
+ if (sensitiveField === 'credentials' && argTextLower.includes('credentials.type')) {
239
+ shouldSkip = true;
240
+ }
241
+ if (sensitiveField === 'payment') {
242
+ // Skip if it's paymentIntentId, paymentId, or payment.id (non-sensitive IDs)
243
+ if (argTextLower.includes('paymentintentid') ||
244
+ argTextLower.includes('paymentid') ||
245
+ argTextLower.includes('payment.id') ||
246
+ argTextLower.includes('payment.status') ||
247
+ // Check if "payment" appears only as part of a safe compound word
248
+ (argTextLower.includes('payment') &&
249
+ !argTextLower.includes('paymentdata') &&
250
+ !argTextLower.includes('paymentinfo') &&
251
+ (argTextLower.match(/payment[a-z]+id/i) || // paymentIntentId, paymentMethodId
252
+ argTextLower.match(/payment[a-z]+status/i) || // paymentStatus
253
+ argTextLower.match(/payment[a-z]+type/i)))) { // paymentType
254
+ shouldSkip = true;
255
+ }
256
+ }
257
+
258
+ if (!shouldSkip) {
259
+ violations.push({
260
+ line: callExpr.getStartLineNumber(),
261
+ column: callExpr.getStart() - callExpr.getStartLinePos(),
262
+ message: `Sensitive data logging: Logging '${argText.substring(0, 80)}${argText.length > 80 ? '...' : ''}' may expose sensitive field '${sensitiveField}' - use masking/redaction before logging`,
263
+ severity: "warning",
264
+ ruleId: this.ruleId,
265
+ });
266
+ break; // One violation per log statement is enough
267
+ }
268
+ }
269
+
270
+ // Check for bulk sensitive objects (req.body, req.headers, etc.)
271
+ const sensitiveBulk = this.findSensitiveBulkPattern(argTextLower, argText);
272
+ if (sensitiveBulk) {
273
+ violations.push({
274
+ line: callExpr.getStartLineNumber(),
275
+ column: callExpr.getStart() - callExpr.getStartLinePos(),
276
+ message: `Sensitive data logging: Logging '${sensitiveBulk}' may expose sensitive fields like passwords, tokens, or payment data - use selective field logging or redaction`,
277
+ severity: "warning",
278
+ ruleId: this.ruleId,
279
+ });
280
+ break;
281
+ }
282
+
283
+ // Check for template literals containing sensitive fields
284
+ if (this.containsSensitiveFieldInTemplate(argText)) {
285
+ const field = this.findSensitiveFieldInTemplate(argText);
286
+ violations.push({
287
+ line: callExpr.getStartLineNumber(),
288
+ column: callExpr.getStart() - callExpr.getStartLinePos(),
289
+ message: `Sensitive data logging: Template literal contains sensitive field '${field}' - mask before logging`,
290
+ severity: "warning",
291
+ ruleId: this.ruleId,
292
+ });
293
+ break;
294
+ }
295
+ }
296
+ }
297
+
298
+ return violations;
299
+ }
300
+
301
+ /**
302
+ * Check if the function call is a logging function
303
+ */
304
+ isLoggingFunction(text) {
305
+ return this.loggingFunctions.some((func) => text.includes(func));
306
+ }
307
+
308
+ /**
309
+ * Check if the data is safely masked/redacted
310
+ */
311
+ isSafelyMasked(text) {
312
+ // Check for masking patterns
313
+ if (this.safeLoggingPatterns.some((pattern) => text.includes(pattern))) {
314
+ return true;
315
+ }
316
+
317
+ // Check if it's just a string literal message (not actual data)
318
+ // e.g., 'Authenticating with token:' is just a label, not the token itself
319
+ if (text.startsWith("'") || text.startsWith('"') || text.startsWith('`')) {
320
+ // It's a string literal - check if it's just a message/label
321
+ // Look for patterns like 'message: ${var}' or 'message:', var
322
+ // If the string literal itself doesn't contain variables and is just text, it's safe
323
+ if (!text.includes('${') && text.length < 100) {
324
+ return true; // It's just a log message label
325
+ }
326
+ }
327
+
328
+ return false;
329
+ }
330
+
331
+ /**
332
+ * Check if using selective logging (omit/pick specific fields)
333
+ */
334
+ isSelectiveLogging(text) {
335
+ return this.selectiveLoggingPatterns.some((pattern) => text.includes(pattern));
336
+ }
337
+
338
+ /**
339
+ * Find sensitive field name in the text
340
+ */
341
+ findSensitiveField(text) {
342
+ // Remove comments from text before checking
343
+ const textWithoutComments = text.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
344
+
345
+ for (const pattern of this.sensitiveFieldPatterns) {
346
+ // Match whole words or property access patterns
347
+ const regex = new RegExp(
348
+ `\\b${pattern}\\b|["'\`]${pattern}["'\`]|\\.${pattern}\\b|\\[["'\`]${pattern}["'\`]\\]`,
349
+ "i"
350
+ );
351
+ if (regex.test(textWithoutComments)) {
352
+ return pattern;
353
+ }
354
+ }
355
+ return null;
356
+ }
357
+
358
+ /**
359
+ * Find sensitive bulk object pattern (req.body, req.headers, etc.)
360
+ * Now with context-awareness to reduce false positives
361
+ */
362
+ findSensitiveBulkPattern(textLower, originalText) {
363
+ for (const pattern of this.sensitiveBulkPatterns) {
364
+ if (!textLower.includes(pattern)) {
365
+ continue;
366
+ }
367
+
368
+ // Check if it's selective field access like req.headers['user-agent']
369
+ if (pattern === 'req.headers' || pattern === 'request.headers') {
370
+ if (textLower.includes("['user-agent']") ||
371
+ textLower.includes("['content-type']") ||
372
+ textLower.includes("['accept']") ||
373
+ textLower.includes("['referer']") ||
374
+ textLower.includes("[\"user-agent\"]") ||
375
+ textLower.includes("[\"content-type\"]") ||
376
+ textLower.includes("[\"accept\"]") ||
377
+ textLower.includes("[\"referer\"]") ||
378
+ textLower.includes(".get('user-agent')") ||
379
+ textLower.includes(".get('content-type')") ||
380
+ textLower.includes(".get('accept')") ||
381
+ textLower.includes(".get('referer')")) {
382
+ continue; // Skip, it's safe
383
+ }
384
+ }
385
+
386
+ // Skip if pattern appears ONLY in a pure string literal (no variables)
387
+ // e.g., 'Send email success' or "Payment completed" are just messages
388
+ if (this.isOnlyInStringLiteral(pattern, originalText)) {
389
+ continue;
390
+ }
391
+
392
+ // For "payment" and "auth" patterns, be more strict
393
+ if (pattern === 'payment' || pattern === 'auth' || pattern === 'credentials') {
394
+ // Skip if it's inside a log message string literal
395
+ // Pattern: logger.log('Message with payment/auth word')
396
+ if (this.isInMessageString(pattern, originalText)) {
397
+ continue;
398
+ }
399
+ }
400
+
401
+ return pattern;
402
+ }
403
+ return null;
404
+ }
405
+
406
+ /**
407
+ * Check if pattern appears ONLY inside string literals (not as variables)
408
+ * e.g., 'Send email to user' vs logger.log(email)
409
+ */
410
+ isOnlyInStringLiteral(pattern, text) {
411
+ // Check if text is a pure string literal without interpolation
412
+ if ((text.startsWith("'") && text.endsWith("'") && !text.includes('${')) ||
413
+ (text.startsWith('"') && text.endsWith('"') && !text.includes('${'))) {
414
+ return true;
415
+ }
416
+
417
+ // Check if pattern appears in template literal but not in variables
418
+ // Template: `Message with ${var}` - pattern in "Message" part is safe
419
+ if (text.includes('`')) {
420
+ // Extract template string parts (not inside ${})
421
+ const withoutVars = text.replace(/\$\{[^}]+\}/g, '');
422
+ if (withoutVars.toLowerCase().includes(pattern) && !text.match(new RegExp(`\\$\\{[^}]*${pattern}`, 'i'))) {
423
+ return true;
424
+ }
425
+ }
426
+
427
+ return false;
428
+ }
429
+
430
+ /**
431
+ * Check if pattern appears in uppercase in a message string
432
+ * e.g., "NO EMAIL ERROR" vs logger.log(email)
433
+ */
434
+ isUppercaseInMessage(pattern, text) {
435
+ const upperPattern = pattern.toUpperCase();
436
+
437
+ // Check if pattern appears in uppercase and surrounded by other uppercase words
438
+ // This suggests it's part of a message constant, not actual data
439
+ if (text.includes(upperPattern)) {
440
+ // Extract words around the uppercase pattern
441
+ const regex = new RegExp(`([A-Z_]+\\s+)?${upperPattern}(\\s+[A-Z_]+)?`);
442
+ const match = text.match(regex);
443
+ if (match) {
444
+ // If surrounded by other uppercase words, it's likely a message
445
+ return true;
446
+ }
447
+ }
448
+
449
+ return false;
450
+ }
451
+
452
+ /**
453
+ * Check if pattern is inside a message string (not actual data)
454
+ * e.g., "Error in payment processing" vs logger.log(paymentData)
455
+ */
456
+ isInMessageString(pattern, text) {
457
+ const lowerText = text.toLowerCase();
458
+
459
+ // Common message patterns that are safe
460
+ const safeMessagePatterns = [
461
+ `${pattern} processing`,
462
+ `${pattern} completed`,
463
+ `${pattern} failed`,
464
+ `${pattern} success`,
465
+ `${pattern} error`,
466
+ `${pattern} schedule`,
467
+ `confirm ${pattern}`,
468
+ `send ${pattern}`,
469
+ `update ${pattern}`,
470
+ `${pattern} service`,
471
+ `${pattern} notice`,
472
+ `no ${pattern}`, // "no email error"
473
+ `${pattern} is null`, // "session is null"
474
+ `${pattern} not found`, // "session not found"
475
+ `${pattern} is missing`, // "token is missing"
476
+ `verify ${pattern}`, // "verify email"
477
+ `could not verify ${pattern}`, // "could not verify email"
478
+ `could update ${pattern}`, // "could update secondary email"
479
+ `could not update ${pattern}`, // "could not update email"
480
+ `${pattern} of the user`, // "email of the user"
481
+ `${pattern} points`, // "expiration points"
482
+ `${pattern} scan`, // "expiration scan"
483
+ `updated ${pattern}`, // "updated expiration points"
484
+ `end user`, // "end user X expiration"
485
+ ];
486
+
487
+ if (safeMessagePatterns.some(msg => lowerText.includes(msg))) {
488
+ return true;
489
+ }
490
+
491
+ // Check for patterns like "send {pattern} to user" (e.g., "send email to user")
492
+ if (lowerText.includes(`send ${pattern} to`) ||
493
+ lowerText.includes(`sent ${pattern} to`)) {
494
+ return true;
495
+ }
496
+
497
+ // Check for patterns with words in between (e.g., "could update secondary email")
498
+ if (lowerText.includes('could') && lowerText.includes('update') &&
499
+ lowerText.includes(pattern) && lowerText.includes('cause')) {
500
+ return true;
501
+ }
502
+
503
+ // Check for patterns like "could not" with action verbs
504
+ if (lowerText.includes(`could not`) &&
505
+ (lowerText.includes('verify') || lowerText.includes('update') ||
506
+ lowerText.includes('change') || lowerText.includes('set')) &&
507
+ lowerText.includes(pattern)) {
508
+ return true;
509
+ }
510
+
511
+ // Check for URL paths (e.g., '/api/get-impersonate-token')
512
+ if (lowerText.includes('/api/') || lowerText.includes('/auth/')) {
513
+ return true;
514
+ }
515
+
516
+ // Check for localStorage/sessionStorage keys (e.g., 'auth_timestamp')
517
+ if ((lowerText.includes("'") || lowerText.includes('"')) &&
518
+ (lowerText.includes('_timestamp') || lowerText.includes('_key') || lowerText.includes('_storage'))) {
519
+ return true;
520
+ }
521
+
522
+ // Check for error message constants (e.g., 'BE_E_ActionDeniedDueToUnauthorized')
523
+ if (lowerText.includes('error') && lowerText.includes('_e_')) {
524
+ return true;
525
+ }
526
+
527
+ // Check for common words containing the pattern as substring
528
+ // e.g., "Authentication required" contains "auth" but isn't logging auth data
529
+ const safeSubstrings = [
530
+ 'authentication',
531
+ 'authorization',
532
+ 'authenticate',
533
+ 'authorized',
534
+ 'unauthorized',
535
+ 'authenticating',
536
+ ];
537
+ if (safeSubstrings.some(word => lowerText.includes(word))) {
538
+ return true;
539
+ }
540
+
541
+ return false;
542
+ }
543
+
544
+ /**
545
+ * Check if template literal contains sensitive fields
546
+ */
547
+ containsSensitiveFieldInTemplate(text) {
548
+ // Check if it's a template literal with interpolation
549
+ if (!text.includes('${') || !text.includes('}')) {
550
+ return false;
551
+ }
552
+
553
+ // Extract variable names from template
554
+ const varMatches = text.match(/\$\{([^}]+)\}/g);
555
+ if (!varMatches) {
556
+ return false;
557
+ }
558
+
559
+ for (const varMatch of varMatches) {
560
+ const varContent = varMatch.slice(2, -1).toLowerCase();
561
+
562
+ // Check if variable name contains sensitive field
563
+ if (this.findSensitiveField(varContent)) {
564
+ return true;
565
+ }
566
+ }
567
+
568
+ return false;
569
+ }
570
+
571
+ /**
572
+ * Find which sensitive field is in the template
573
+ */
574
+ findSensitiveFieldInTemplate(text) {
575
+ const varMatches = text.match(/\$\{([^}]+)\}/g);
576
+ if (!varMatches) {
577
+ return null;
578
+ }
579
+
580
+ for (const varMatch of varMatches) {
581
+ const varContent = varMatch.slice(2, -1).toLowerCase();
582
+ const field = this.findSensitiveField(varContent);
583
+ if (field) {
584
+ return varContent;
585
+ }
586
+ }
587
+
588
+ return null;
589
+ }
590
+ }
591
+
592
+ module.exports = S004SymbolBasedAnalyzer;