@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,510 @@
1
+ /**
2
+ * Heuristic analyzer for: S022 – Escape data properly based on output context
3
+ * Purpose: Prevent XSS attacks by ensuring proper escaping/sanitization based on output context
4
+ * Detects: unsafe innerHTML, eval, location assignments, dangerous attributes, etc.
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ class S022Analyzer {
11
+ constructor() {
12
+ this.ruleId = 'S022';
13
+ this.ruleName = 'Escape data properly based on output context';
14
+ this.description = 'Ensure data is properly escaped based on output context to prevent XSS';
15
+
16
+ // HTML context - dangerous methods
17
+ this.htmlDangerousMethods = [
18
+ 'innerHTML',
19
+ 'outerHTML',
20
+ 'insertAdjacentHTML',
21
+ 'document.write',
22
+ 'document.writeln'
23
+ ];
24
+
25
+ // JavaScript context - dangerous methods
26
+ this.jsDangerousMethods = [
27
+ 'eval',
28
+ 'Function',
29
+ 'setTimeout',
30
+ 'setInterval',
31
+ 'execScript'
32
+ ];
33
+
34
+ // URL context - dangerous assignments
35
+ this.urlDangerousMethods = [
36
+ 'location.href',
37
+ 'window.location',
38
+ 'location.assign',
39
+ 'location.replace',
40
+ 'window.open'
41
+ ];
42
+
43
+ // Framework-specific patterns
44
+ this.frameworkPatterns = {
45
+ react: /dangerouslySetInnerHTML\s*=\s*\{\{?\s*__html\s*:\s*([^}]+)\}\}?/g,
46
+ vue: /v-html\s*=\s*["']([^"']+)["']/g,
47
+ angular: /\[innerHTML\]\s*=\s*["']([^"']+)["']/g
48
+ };
49
+
50
+ // User input patterns
51
+ this.userInputPatterns = [
52
+ /req\.(body|query|params)/,
53
+ /request\.(body|query|params)/,
54
+ /localStorage\.getItem/,
55
+ /sessionStorage\.getItem/,
56
+ /window\.location/,
57
+ /location\.(search|hash|href)/,
58
+ /URLSearchParams/,
59
+ /document\.cookie/,
60
+ /window\.name/
61
+ ];
62
+
63
+ // Safe escaping/sanitization functions
64
+ this.safeEscapingFunctions = [
65
+ 'escape',
66
+ 'escapeHtml',
67
+ 'sanitize',
68
+ 'DOMPurify.sanitize',
69
+ 'textContent',
70
+ 'innerText',
71
+ 'setAttribute',
72
+ 'encodeURIComponent',
73
+ 'encodeURI',
74
+ 'validator.escape',
75
+ 'xss.filterXSS',
76
+ 'xss-filters'
77
+ ];
78
+ }
79
+
80
+ async analyze(files, language, options = {}) {
81
+ const violations = [];
82
+
83
+ for (const filePath of files) {
84
+ if (options.verbose) {
85
+ console.log(`🔍 Running S022 analysis on ${path.basename(filePath)}`);
86
+ }
87
+
88
+ try {
89
+ const content = fs.readFileSync(filePath, 'utf8');
90
+ const fileViolations = await this.analyzeFile(filePath, content, language, options);
91
+ violations.push(...fileViolations);
92
+ } catch (error) {
93
+ if (options.verbose) {
94
+ console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
95
+ }
96
+ }
97
+ }
98
+
99
+ return violations;
100
+ }
101
+
102
+ async analyzeFile(filePath, content, language, options = {}) {
103
+ switch (language) {
104
+ case 'typescript':
105
+ case 'javascript':
106
+ return this.analyzeJavaScript(filePath, content, options);
107
+ default:
108
+ return [];
109
+ }
110
+ }
111
+
112
+ async analyzeJavaScript(filePath, content, options = {}) {
113
+ const violations = [];
114
+ const lines = content.split('\n');
115
+
116
+ // 1. Check HTML context violations
117
+ violations.push(...this.checkHtmlContext(content, lines, filePath));
118
+
119
+ // 2. Check JavaScript context violations
120
+ violations.push(...this.checkJavaScriptContext(content, lines, filePath));
121
+
122
+ // 3. Check URL context violations
123
+ violations.push(...this.checkUrlContext(content, lines, filePath));
124
+
125
+ // 4. Check framework-specific violations
126
+ violations.push(...this.checkFrameworkPatterns(content, lines, filePath));
127
+
128
+ // 5. Check dangerous event handlers
129
+ violations.push(...this.checkDangerousEventHandlers(content, lines, filePath));
130
+
131
+ return violations;
132
+ }
133
+
134
+ checkHtmlContext(content, lines, filePath) {
135
+ const violations = [];
136
+
137
+ for (const method of this.htmlDangerousMethods) {
138
+ // Pattern: element.innerHTML = userInput
139
+ const pattern = new RegExp(`\\.${method}\\s*=\\s*([^;]+)`, 'gi');
140
+ let match;
141
+
142
+ while ((match = pattern.exec(content)) !== null) {
143
+ const lineNumber = content.substring(0, match.index).split('\n').length;
144
+ const lineText = lines[lineNumber - 1] || '';
145
+ const assignment = match[1].trim();
146
+
147
+ // Check if value comes from user input
148
+ if (this.isUserInput(assignment)) {
149
+ // Check if there's sanitization
150
+ if (!this.hasSanitization(assignment)) {
151
+ violations.push({
152
+ ruleId: this.ruleId,
153
+ file: filePath,
154
+ line: lineNumber,
155
+ column: match.index - content.lastIndexOf('\n', match.index),
156
+ message: `Unsafe use of '${method}' with unsanitized user input. Use textContent or sanitize with DOMPurify.`,
157
+ severity: 'error',
158
+ code: lineText.trim(),
159
+ type: 'html_context_unsafe',
160
+ context: 'html',
161
+ confidence: 0.9,
162
+ suggestion: `Use 'textContent' instead of '${method}' or sanitize with DOMPurify.sanitize()`
163
+ });
164
+ }
165
+ }
166
+ // Even if not direct user input, innerHTML is risky
167
+ else if (!this.hasSanitization(assignment) && !this.isLiteralString(assignment)) {
168
+ violations.push({
169
+ ruleId: this.ruleId,
170
+ file: filePath,
171
+ line: lineNumber,
172
+ column: match.index - content.lastIndexOf('\n', match.index),
173
+ message: `Potentially unsafe use of '${method}'. Consider using textContent or sanitizing the input.`,
174
+ severity: 'warning',
175
+ code: lineText.trim(),
176
+ type: 'html_context_potential',
177
+ context: 'html',
178
+ confidence: 0.6,
179
+ suggestion: `Verify the source of data and use appropriate escaping/sanitization`
180
+ });
181
+ }
182
+ }
183
+ }
184
+
185
+ return violations;
186
+ }
187
+
188
+ checkJavaScriptContext(content, lines, filePath) {
189
+ const violations = [];
190
+
191
+ // Check eval() - always dangerous
192
+ const evalPattern = /\beval\s*\(\s*([^)]+)\)/gi;
193
+ let match;
194
+
195
+ while ((match = evalPattern.exec(content)) !== null) {
196
+ const lineNumber = content.substring(0, match.index).split('\n').length;
197
+ const lineText = lines[lineNumber - 1] || '';
198
+ const argument = match[1].trim();
199
+
200
+ // Skip if it's clearly safe (literal string)
201
+ if (!this.isLiteralString(argument)) {
202
+ const isUserInput = this.isUserInput(argument);
203
+
204
+ violations.push({
205
+ ruleId: this.ruleId,
206
+ file: filePath,
207
+ line: lineNumber,
208
+ column: match.index - content.lastIndexOf('\n', match.index),
209
+ message: `Dangerous use of 'eval' with ${isUserInput ? 'user input' : 'dynamic code'}. Avoid using eval entirely.`,
210
+ severity: isUserInput ? 'error' : 'warning',
211
+ code: lineText.trim(),
212
+ type: 'javascript_context_unsafe',
213
+ context: 'javascript',
214
+ confidence: isUserInput ? 0.95 : 0.7,
215
+ suggestion: `Never use 'eval' with user input. Use safer alternatives like JSON.parse()`
216
+ });
217
+ }
218
+ }
219
+
220
+ // Check new Function() constructor - dangerous
221
+ const functionConstructorPattern = /new\s+Function\s*\(\s*([^)]+)\)/gi;
222
+
223
+ while ((match = functionConstructorPattern.exec(content)) !== null) {
224
+ const lineNumber = content.substring(0, match.index).split('\n').length;
225
+ const lineText = lines[lineNumber - 1] || '';
226
+ const argument = match[1].trim();
227
+
228
+ const isUserInput = this.isUserInput(argument);
229
+
230
+ violations.push({
231
+ ruleId: this.ruleId,
232
+ file: filePath,
233
+ line: lineNumber,
234
+ column: match.index - content.lastIndexOf('\n', match.index),
235
+ message: `Dangerous use of 'Function' constructor with ${isUserInput ? 'user input' : 'dynamic code'}. Avoid using Function constructor.`,
236
+ severity: isUserInput ? 'error' : 'warning',
237
+ code: lineText.trim(),
238
+ type: 'javascript_context_unsafe',
239
+ context: 'javascript',
240
+ confidence: isUserInput ? 0.95 : 0.7,
241
+ suggestion: `Never use 'new Function()' with user input. Use safer alternatives.`
242
+ });
243
+ }
244
+
245
+ // Check setTimeout/setInterval with STRING arguments (not function callbacks)
246
+ // Only flag if the first argument is a string, not a function
247
+ const timerPatterns = [
248
+ { method: 'setTimeout', pattern: /\bsetTimeout\s*\(\s*['"`]([^'"`]+)['"`]/gi },
249
+ { method: 'setInterval', pattern: /\bsetInterval\s*\(\s*['"`]([^'"`]+)['"`]/gi }
250
+ ];
251
+
252
+ for (const { method, pattern } of timerPatterns) {
253
+ while ((match = pattern.exec(content)) !== null) {
254
+ const lineNumber = content.substring(0, match.index).split('\n').length;
255
+ const lineText = lines[lineNumber - 1] || '';
256
+ const codeString = match[1].trim();
257
+
258
+ violations.push({
259
+ ruleId: this.ruleId,
260
+ file: filePath,
261
+ line: lineNumber,
262
+ column: match.index - content.lastIndexOf('\n', match.index),
263
+ message: `Dangerous use of '${method}' with string code. Use function callback instead.`,
264
+ severity: 'error',
265
+ code: lineText.trim(),
266
+ type: 'javascript_context_unsafe',
267
+ context: 'javascript',
268
+ confidence: 0.95,
269
+ suggestion: `Use ${method}(() => { /* code */ }, delay) instead of ${method}("code", delay)`
270
+ });
271
+ }
272
+ }
273
+
274
+ return violations;
275
+ }
276
+
277
+ checkUrlContext(content, lines, filePath) {
278
+ const violations = [];
279
+
280
+ // Patterns for URL assignments
281
+ const urlPatterns = [
282
+ /location\.href\s*=\s*([^;]+)/gi,
283
+ /window\.location\s*=\s*([^;]+)/gi,
284
+ /location\.assign\s*\(\s*([^)]+)\)/gi,
285
+ /location\.replace\s*\(\s*([^)]+)\)/gi,
286
+ /window\.open\s*\(\s*([^,)]+)/gi
287
+ ];
288
+
289
+ for (const pattern of urlPatterns) {
290
+ let match;
291
+
292
+ while ((match = pattern.exec(content)) !== null) {
293
+ const lineNumber = content.substring(0, match.index).split('\n').length;
294
+ const lineText = lines[lineNumber - 1] || '';
295
+ const urlValue = match[1].trim();
296
+
297
+ // Skip if it's a literal string or constant
298
+ if (this.isLiteralString(urlValue) || this.isConstantOrPropertyAccess(urlValue)) {
299
+ continue;
300
+ }
301
+
302
+ // Check if URL comes from user input
303
+ if (this.isUserInput(urlValue)) {
304
+ // Check if there's validation
305
+ if (!this.hasUrlValidation(content, match.index)) {
306
+ violations.push({
307
+ ruleId: this.ruleId,
308
+ file: filePath,
309
+ line: lineNumber,
310
+ column: match.index - content.lastIndexOf('\n', match.index),
311
+ message: `Unsafe URL assignment with user input. Validate and whitelist URLs to prevent open redirect vulnerabilities.`,
312
+ severity: 'error',
313
+ code: lineText.trim(),
314
+ type: 'url_context_unsafe',
315
+ context: 'url',
316
+ confidence: 0.85,
317
+ suggestion: 'Validate URLs against a whitelist of allowed hosts/domains'
318
+ });
319
+ }
320
+ }
321
+ }
322
+ }
323
+
324
+ return violations;
325
+ }
326
+
327
+ checkFrameworkPatterns(content, lines, filePath) {
328
+ const violations = [];
329
+
330
+ // React: dangerouslySetInnerHTML
331
+ const reactPattern = this.frameworkPatterns.react;
332
+ let match;
333
+
334
+ while ((match = reactPattern.exec(content)) !== null) {
335
+ const lineNumber = content.substring(0, match.index).split('\n').length;
336
+ const lineText = lines[lineNumber - 1] || '';
337
+ const htmlValue = match[1].trim();
338
+
339
+ // Check if sanitized
340
+ if (!this.hasSanitization(htmlValue)) {
341
+ violations.push({
342
+ ruleId: this.ruleId,
343
+ file: filePath,
344
+ line: lineNumber,
345
+ column: match.index - content.lastIndexOf('\n', match.index),
346
+ message: `React 'dangerouslySetInnerHTML' without sanitization. Use DOMPurify.sanitize() or avoid using this prop.`,
347
+ severity: 'error',
348
+ code: lineText.trim(),
349
+ type: 'react_unsafe_html',
350
+ context: 'html',
351
+ framework: 'react',
352
+ confidence: 0.9,
353
+ suggestion: 'Sanitize HTML with DOMPurify: dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(html)}}'
354
+ });
355
+ }
356
+ }
357
+
358
+ // Vue: v-html
359
+ const vuePattern = this.frameworkPatterns.vue;
360
+ while ((match = vuePattern.exec(content)) !== null) {
361
+ const lineNumber = content.substring(0, match.index).split('\n').length;
362
+ const lineText = lines[lineNumber - 1] || '';
363
+
364
+ violations.push({
365
+ ruleId: this.ruleId,
366
+ file: filePath,
367
+ line: lineNumber,
368
+ column: match.index - content.lastIndexOf('\n', match.index),
369
+ message: `Vue 'v-html' directive can lead to XSS. Use 'v-text' or sanitize with DOMPurify.`,
370
+ severity: 'warning',
371
+ code: lineText.trim(),
372
+ type: 'vue_unsafe_html',
373
+ context: 'html',
374
+ framework: 'vue',
375
+ confidence: 0.8,
376
+ suggestion: 'Use v-text for plain text or sanitize HTML before using v-html'
377
+ });
378
+ }
379
+
380
+ // Angular: [innerHTML]
381
+ const angularPattern = this.frameworkPatterns.angular;
382
+ while ((match = angularPattern.exec(content)) !== null) {
383
+ const lineNumber = content.substring(0, match.index).split('\n').length;
384
+ const lineText = lines[lineNumber - 1] || '';
385
+
386
+ violations.push({
387
+ ruleId: this.ruleId,
388
+ file: filePath,
389
+ line: lineNumber,
390
+ column: match.index - content.lastIndexOf('\n', match.index),
391
+ message: `Angular '[innerHTML]' binding can lead to XSS. Use Angular's DomSanitizer or avoid innerHTML binding.`,
392
+ severity: 'warning',
393
+ code: lineText.trim(),
394
+ type: 'angular_unsafe_html',
395
+ context: 'html',
396
+ framework: 'angular',
397
+ confidence: 0.8,
398
+ suggestion: 'Use DomSanitizer.sanitize() or bind to textContent instead'
399
+ });
400
+ }
401
+
402
+ return violations;
403
+ }
404
+
405
+ checkDangerousEventHandlers(content, lines, filePath) {
406
+ const violations = [];
407
+
408
+ // Pattern: element.setAttribute('onclick', ...)
409
+ const eventHandlerPattern = /\.setAttribute\s*\(\s*['"]on\w+['"]\s*,\s*([^)]+)\)/gi;
410
+ let match;
411
+
412
+ while ((match = eventHandlerPattern.exec(content)) !== null) {
413
+ const lineNumber = content.substring(0, match.index).split('\n').length;
414
+ const lineText = lines[lineNumber - 1] || '';
415
+ const handlerValue = match[1].trim();
416
+
417
+ // Check if handler contains user input
418
+ if (this.isUserInput(handlerValue) || !this.isLiteralString(handlerValue)) {
419
+ violations.push({
420
+ ruleId: this.ruleId,
421
+ file: filePath,
422
+ line: lineNumber,
423
+ column: match.index - content.lastIndexOf('\n', match.index),
424
+ message: `Dangerous dynamic event handler assignment. Avoid setting event handlers with user input.`,
425
+ severity: 'error',
426
+ code: lineText.trim(),
427
+ type: 'event_handler_unsafe',
428
+ context: 'attribute',
429
+ confidence: 0.85,
430
+ suggestion: 'Use addEventListener with proper event handling instead of setAttribute for events'
431
+ });
432
+ }
433
+ }
434
+
435
+ return violations;
436
+ }
437
+
438
+ isUserInput(code) {
439
+ return this.userInputPatterns.some(pattern => pattern.test(code));
440
+ }
441
+
442
+ hasSanitization(code) {
443
+ return this.safeEscapingFunctions.some(func =>
444
+ code.toLowerCase().includes(func.toLowerCase())
445
+ );
446
+ }
447
+
448
+ isLiteralString(code) {
449
+ // Check if it's a string literal (quoted)
450
+ const trimmed = code.trim();
451
+ return (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
452
+ (trimmed.startsWith("'") && trimmed.endsWith("'")) ||
453
+ (trimmed.startsWith('`') && trimmed.endsWith('`') && !trimmed.includes('${'));
454
+ }
455
+
456
+ isConstantOrPropertyAccess(code) {
457
+ // Check if it's accessing a constant or configuration object
458
+ const trimmed = code.trim();
459
+
460
+ // Common constant patterns: routes.X, CONSTANTS.X, config.X, CONFIG.X
461
+ const constantPatterns = [
462
+ /^routes\./i,
463
+ /^ROUTES\./,
464
+ /^constants\./i,
465
+ /^CONSTANTS\./,
466
+ /^config\./i,
467
+ /^CONFIG\./,
468
+ /^settings\./i,
469
+ /^SETTINGS\./,
470
+ /^[A-Z_]+\./, // ALL_CAPS.something
471
+ /^process\.env\./,
472
+ /^environment\./i,
473
+ ];
474
+
475
+ // Check if matches constant patterns
476
+ if (constantPatterns.some(pattern => pattern.test(trimmed))) {
477
+ return true;
478
+ }
479
+
480
+ // Check if it's a simple variable without property access from user input
481
+ // e.g., "redirectUrl" (variable) vs "req.query.redirect" (user input)
482
+ if (!trimmed.includes('.') && !this.isUserInput(trimmed)) {
483
+ // Simple variable, likely from constants or params
484
+ return true;
485
+ }
486
+
487
+ return false;
488
+ }
489
+
490
+ hasUrlValidation(content, matchIndex) {
491
+ // Check surrounding code for URL validation patterns
492
+ const contextStart = Math.max(0, matchIndex - 300);
493
+ const contextEnd = Math.min(content.length, matchIndex + 100);
494
+ const context = content.substring(contextStart, contextEnd);
495
+
496
+ const validationPatterns = [
497
+ /new\s+URL\s*\(/i,
498
+ /allowedHosts/i,
499
+ /whitelist/i,
500
+ /validateUrl/i,
501
+ /isValidUrl/i,
502
+ /\.hostname/i,
503
+ /\.protocol/i
504
+ ];
505
+
506
+ return validationPatterns.some(pattern => pattern.test(context));
507
+ }
508
+ }
509
+
510
+ module.exports = new S022Analyzer();