@sun-asterisk/sunlint 1.3.0 → 1.3.2

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 (124) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/CONTRIBUTING.md +249 -605
  3. package/README.md +3 -4
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/large-project.json +143 -0
  7. package/config/presets/all.json +0 -1
  8. package/config/release.json +70 -0
  9. package/config/rule-analysis-strategies.js +38 -3
  10. package/config/rules/enhanced-rules-registry.json +474 -1179
  11. package/config/rules/rules-registry-generated.json +3 -3
  12. package/core/cli-action-handler.js +24 -30
  13. package/core/cli-program.js +11 -3
  14. package/core/config-merger.js +29 -2
  15. package/core/enhanced-rules-registry.js +3 -2
  16. package/core/semantic-engine.js +129 -19
  17. package/core/semantic-rule-base.js +4 -2
  18. package/core/unified-rule-registry.js +1 -1
  19. package/docs/COMMAND-EXAMPLES.md +134 -0
  20. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  21. package/engines/heuristic-engine.js +135 -16
  22. package/integrations/eslint/plugin/index.js +0 -2
  23. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  24. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  25. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  26. package/origin-rules/common-en.md +19 -15
  27. package/package.json +1 -1
  28. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  29. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  30. package/rules/common/C006_function_naming/analyzer.js +29 -3
  31. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  32. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  33. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  34. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  35. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  36. package/rules/common/C013_no_dead_code/config.json +61 -0
  37. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  38. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  39. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  40. package/rules/common/C014_dependency_injection/config.json +26 -0
  41. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  42. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  43. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  44. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  45. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  46. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  47. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  48. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  49. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  50. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  51. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  52. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  53. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  56. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  57. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  58. package/rules/common/C033_separate_service_repository/README.md +78 -0
  59. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  60. package/rules/common/C033_separate_service_repository/config.json +50 -0
  61. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  62. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  63. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  64. package/rules/common/C035_error_logging_context/analyzer.js +232 -0
  65. package/rules/common/C035_error_logging_context/config.json +54 -0
  66. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  67. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  68. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  69. package/rules/common/C040_centralized_validation/config.json +46 -0
  70. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  71. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  72. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  73. package/rules/common/C076_explicit_function_types/README.md +30 -0
  74. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  75. package/rules/common/C076_explicit_function_types/config.json +15 -0
  76. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  77. package/rules/index.js +6 -1
  78. package/rules/parser/rule-parser.js +13 -2
  79. package/rules/security/S005_no_origin_auth/README.md +226 -0
  80. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  81. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  82. package/rules/security/S005_no_origin_auth/config.json +85 -0
  83. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  84. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  85. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  86. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  87. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  88. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  89. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  90. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  91. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  92. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  93. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  94. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  95. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  96. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  97. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  98. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  99. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  100. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  101. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  102. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  103. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  104. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  105. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  106. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  107. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  108. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  109. package/rules/security/S055_content_type_validation/README.md +176 -0
  110. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  111. package/rules/security/S055_content_type_validation/config.json +48 -0
  112. package/rules/utils/rule-helpers.js +140 -1
  113. package/scripts/consolidate-config.js +116 -0
  114. package/scripts/prepare-release.sh +1 -1
  115. package/config/rules/rules-registry.json +0 -765
  116. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  117. package/docs/FUTURE_PACKAGES.md +0 -83
  118. package/docs/HEURISTIC_VS_AI.md +0 -113
  119. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  120. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  121. package/docs/RELEASE_GUIDE.md +0 -230
  122. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  123. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  124. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -0,0 +1,276 @@
1
+ /**
2
+ * S016 Main Analyzer - Sensitive Data in URL Query Parameters Detection
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
5
+ * Command: node cli.js --rule=S016 --input=examples/rule-test-fixtures/rules/S016_no_sensitive_querystring --engine=heuristic
6
+ */
7
+
8
+ const S016SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
+ const S016RegexBasedAnalyzer = require("./regex-based-analyzer.js");
10
+
11
+ class S016Analyzer {
12
+ constructor(options = {}) {
13
+ if (process.env.SUNLINT_DEBUG) {
14
+ console.log(`🔧 [S016] Constructor called with options:`, !!options);
15
+ console.log(
16
+ `🔧 [S016] Options type:`,
17
+ typeof options,
18
+ Object.keys(options || {})
19
+ );
20
+ }
21
+
22
+ this.ruleId = "S016";
23
+ this.ruleName = "Sensitive Data in URL Query Parameters";
24
+ this.description =
25
+ "Do not pass sensitive data (e.g. password, token, secret, apiKey, etc.) via query string in URLs. This can lead to exposure in logs, browser history, and network traces";
26
+ this.semanticEngine = options.semanticEngine || null;
27
+ this.verbose = options.verbose || false;
28
+
29
+ // Configuration
30
+ this.config = {
31
+ useSymbolBased: true, // Primary approach
32
+ fallbackToRegex: true, // Only when symbol fails completely
33
+ symbolBasedOnly: false, // Can be set to true for pure mode
34
+ };
35
+
36
+ // Initialize both analyzers
37
+ try {
38
+ this.symbolAnalyzer = new S016SymbolBasedAnalyzer(this.semanticEngine);
39
+ if (process.env.SUNLINT_DEBUG) {
40
+ console.log(`🔧 [S016] Symbol analyzer created successfully`);
41
+ }
42
+ } catch (error) {
43
+ console.error(`🔧 [S016] Error creating symbol analyzer:`, error);
44
+ }
45
+
46
+ try {
47
+ this.regexAnalyzer = new S016RegexBasedAnalyzer(this.semanticEngine);
48
+ if (process.env.SUNLINT_DEBUG) {
49
+ console.log(`🔧 [S016] Regex analyzer created successfully`);
50
+ }
51
+ } catch (error) {
52
+ console.error(`🔧 [S016] Error creating regex analyzer:`, error);
53
+ }
54
+
55
+ if (process.env.SUNLINT_DEBUG) {
56
+ console.log(`🔧 [S016] Constructor completed`);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Initialize with semantic engine
62
+ */
63
+ async initialize(semanticEngine = null) {
64
+ if (semanticEngine) {
65
+ this.semanticEngine = semanticEngine;
66
+ }
67
+ this.verbose = semanticEngine?.verbose || false;
68
+
69
+ // Initialize both analyzers
70
+ await this.symbolAnalyzer.initialize(semanticEngine);
71
+ await this.regexAnalyzer.initialize(semanticEngine);
72
+
73
+ // Ensure verbose flag is propagated
74
+ this.regexAnalyzer.verbose = this.verbose;
75
+ this.symbolAnalyzer.verbose = this.verbose;
76
+
77
+ if (this.verbose) {
78
+ console.log(
79
+ `🔧 [S016 Hybrid] Analyzer initialized - verbose: ${this.verbose}`
80
+ );
81
+ }
82
+ }
83
+
84
+ async analyze(files, language, options = {}) {
85
+ if (process.env.SUNLINT_DEBUG) {
86
+ console.log(
87
+ `🔧 [S016] analyze() method called with ${files.length} files, language: ${language}`
88
+ );
89
+ }
90
+
91
+ const violations = [];
92
+
93
+ for (const filePath of files) {
94
+ try {
95
+ if (process.env.SUNLINT_DEBUG) {
96
+ console.log(`🔧 [S016] Processing file: ${filePath}`);
97
+ }
98
+
99
+ const fileViolations = await this.analyzeFile(filePath, options);
100
+ violations.push(...fileViolations);
101
+
102
+ if (process.env.SUNLINT_DEBUG) {
103
+ console.log(
104
+ `🔧 [S016] File ${filePath}: Found ${fileViolations.length} violations`
105
+ );
106
+ }
107
+ } catch (error) {
108
+ console.warn(
109
+ `⚠ [S016] Analysis failed for ${filePath}:`,
110
+ error.message
111
+ );
112
+ }
113
+ }
114
+
115
+ if (process.env.SUNLINT_DEBUG) {
116
+ console.log(`🔧 [S016] Total violations found: ${violations.length}`);
117
+ }
118
+
119
+ return violations;
120
+ }
121
+
122
+ async analyzeFile(filePath, options = {}) {
123
+ if (process.env.SUNLINT_DEBUG) {
124
+ console.log(`🔧 [S016] analyzeFile() called for: ${filePath}`);
125
+ }
126
+
127
+ // 1. Try Symbol-based analysis first (primary)
128
+ if (
129
+ this.config.useSymbolBased &&
130
+ this.semanticEngine?.project &&
131
+ this.semanticEngine?.initialized
132
+ ) {
133
+ try {
134
+ if (process.env.SUNLINT_DEBUG) {
135
+ console.log(`🔧 [S016] Trying symbol-based analysis...`);
136
+ }
137
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
138
+ if (sourceFile) {
139
+ if (process.env.SUNLINT_DEBUG) {
140
+ console.log(
141
+ `🔧 [S016] Source file found, analyzing with symbol-based...`
142
+ );
143
+ }
144
+ const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(
145
+ filePath,
146
+ { ...options, verbose: options.verbose }
147
+ );
148
+
149
+ // Mark violations with analysis strategy
150
+ violations.forEach((v) => (v.analysisStrategy = "symbol-based"));
151
+
152
+ if (process.env.SUNLINT_DEBUG) {
153
+ console.log(
154
+ `✅ [S016] Symbol-based analysis: ${violations.length} violations`
155
+ );
156
+ }
157
+ return violations; // Return even if 0 violations - symbol analysis completed successfully
158
+ } else {
159
+ if (process.env.SUNLINT_DEBUG) {
160
+ console.log(`⚠️ [S016] Source file not found in project`);
161
+ }
162
+ }
163
+ } catch (error) {
164
+ console.warn(`⚠️ [S016] Symbol analysis failed: ${error.message}`);
165
+ // Continue to fallback
166
+ }
167
+ } else {
168
+ if (process.env.SUNLINT_DEBUG) {
169
+ console.log(`🔄 [S016] Symbol analysis conditions check:`);
170
+ console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
171
+ console.log(` - semanticEngine: ${!!this.semanticEngine}`);
172
+ console.log(
173
+ ` - semanticEngine.project: ${!!this.semanticEngine?.project}`
174
+ );
175
+ console.log(
176
+ ` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`
177
+ );
178
+ console.log(
179
+ `🔄 [S016] Symbol analysis unavailable, using regex fallback`
180
+ );
181
+ }
182
+ }
183
+
184
+ // 2. Fallback to regex-based analysis
185
+ if (this.config.fallbackToRegex) {
186
+ try {
187
+ if (process.env.SUNLINT_DEBUG) {
188
+ console.log(`🔧 [S016] Trying regex-based analysis...`);
189
+ }
190
+ const violations = await this.regexAnalyzer.analyzeFileBasic(
191
+ filePath,
192
+ options
193
+ );
194
+
195
+ // Mark violations with analysis strategy
196
+ violations.forEach((v) => (v.analysisStrategy = "regex-fallback"));
197
+
198
+ if (process.env.SUNLINT_DEBUG) {
199
+ console.log(
200
+ `🔄 [S016] Regex-based analysis: ${violations.length} violations`
201
+ );
202
+ }
203
+ return violations;
204
+ } catch (error) {
205
+ console.error(`⚠ [S016] Regex analysis failed: ${error.message}`);
206
+ }
207
+ }
208
+
209
+ console.log(`🔧 [S016] No analysis methods succeeded, returning empty`);
210
+ return [];
211
+ }
212
+
213
+ async analyzeFileBasic(filePath, options = {}) {
214
+ console.log(`🔧 [S016] analyzeFileBasic() called for: ${filePath}`);
215
+ console.log(`🔧 [S016] semanticEngine exists: ${!!this.semanticEngine}`);
216
+ console.log(`🔧 [S016] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
217
+ console.log(`🔧 [S016] regexAnalyzer exists: ${!!this.regexAnalyzer}`);
218
+
219
+ try {
220
+ // Try symbol-based analysis first
221
+ if (
222
+ this.semanticEngine?.isSymbolEngineReady?.() &&
223
+ this.semanticEngine.project
224
+ ) {
225
+ if (this.verbose) {
226
+ console.log(`🔍 [S016] Using symbol-based analysis for ${filePath}`);
227
+ }
228
+
229
+ const violations = await this.symbolAnalyzer.analyzeFileBasic(
230
+ filePath,
231
+ options
232
+ );
233
+ return violations;
234
+ }
235
+ } catch (error) {
236
+ if (this.verbose) {
237
+ console.warn(`⚠️ [S016] Symbol analysis failed: ${error.message}`);
238
+ }
239
+ }
240
+
241
+ // Fallback to regex-based analysis
242
+ if (this.verbose) {
243
+ console.log(
244
+ `🔄 [S016] Using regex-based analysis (fallback) for ${filePath}`
245
+ );
246
+ }
247
+
248
+ console.log(`🔧 [S016] About to call regexAnalyzer.analyzeFileBasic()`);
249
+ try {
250
+ const result = await this.regexAnalyzer.analyzeFileBasic(
251
+ filePath,
252
+ options
253
+ );
254
+ console.log(
255
+ `🔧 [S016] Regex analyzer returned: ${result.length} violations`
256
+ );
257
+ return result;
258
+ } catch (error) {
259
+ console.error(`🔧 [S016] Error in regex analyzer:`, error);
260
+ return [];
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Methods for compatibility with different engine invocation patterns
266
+ */
267
+ async analyzeFileWithSymbols(filePath, options = {}) {
268
+ return this.analyzeFile(filePath, options);
269
+ }
270
+
271
+ async analyzeWithSemantics(filePath, options = {}) {
272
+ return this.analyzeFile(filePath, options);
273
+ }
274
+ }
275
+
276
+ module.exports = S016Analyzer;
@@ -0,0 +1,127 @@
1
+ {
2
+ "id": "S016",
3
+ "name": "Do not pass sensitive data via query string",
4
+ "category": "security",
5
+ "description": "S016 - Do not pass sensitive data (e.g. password, token, secret, apiKey, etc.) via query string in URLs. This can lead to exposure in logs, browser history, and network traces",
6
+ "severity": "error",
7
+ "enabled": true,
8
+ "semantic": {
9
+ "enabled": true,
10
+ "priority": "high",
11
+ "fallback": "heuristic"
12
+ },
13
+ "patterns": {
14
+ "include": ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx"],
15
+ "exclude": [
16
+ "**/*.test.js",
17
+ "**/*.test.ts",
18
+ "**/*.spec.js",
19
+ "**/*.spec.ts",
20
+ "**/node_modules/**",
21
+ "**/dist/**",
22
+ "**/build/**"
23
+ ]
24
+ },
25
+ "analysis": {
26
+ "approach": "symbol-based-primary",
27
+ "fallback": "regex-based",
28
+ "depth": 2,
29
+ "timeout": 5000
30
+ },
31
+ "validation": {
32
+ "urlPatterns": [
33
+ "new URL",
34
+ "URLSearchParams",
35
+ "fetch",
36
+ "axios",
37
+ "request",
38
+ "location.href",
39
+ "location.search",
40
+ "querystring.stringify",
41
+ "qs.stringify"
42
+ ],
43
+ "sensitivePatterns": [
44
+ "password",
45
+ "passwd",
46
+ "pwd",
47
+ "pass",
48
+ "token",
49
+ "jwt",
50
+ "accesstoken",
51
+ "refreshtoken",
52
+ "bearertoken",
53
+ "secret",
54
+ "secretkey",
55
+ "clientsecret",
56
+ "serversecret",
57
+ "apikey",
58
+ "api_key",
59
+ "key",
60
+ "privatekey",
61
+ "publickey",
62
+ "auth",
63
+ "authorization",
64
+ "authenticate",
65
+ "sessionid",
66
+ "session_id",
67
+ "jsessionid",
68
+ "csrf",
69
+ "csrftoken",
70
+ "xsrf",
71
+ "ssn",
72
+ "social",
73
+ "socialsecurity",
74
+ "creditcard",
75
+ "cardnumber",
76
+ "cardnum",
77
+ "ccnumber",
78
+ "cvv",
79
+ "cvc",
80
+ "cvd",
81
+ "cid",
82
+ "pin",
83
+ "pincode",
84
+ "bankaccount",
85
+ "routing",
86
+ "iban",
87
+ "email",
88
+ "emailaddress",
89
+ "mail",
90
+ "phone",
91
+ "phonenumber",
92
+ "mobile",
93
+ "tel",
94
+ "address",
95
+ "homeaddress",
96
+ "zipcode",
97
+ "postal",
98
+ "birthdate",
99
+ "birthday",
100
+ "dob",
101
+ "license",
102
+ "passport",
103
+ "identity",
104
+ "salary",
105
+ "income",
106
+ "wage",
107
+ "medical",
108
+ "health",
109
+ "diagnosis"
110
+ ],
111
+ "httpClientPatterns": [
112
+ "fetch",
113
+ "axios.get",
114
+ "axios.post",
115
+ "axios.put",
116
+ "axios.delete",
117
+ "axios.patch",
118
+ "axios.request",
119
+ "request.get",
120
+ "request.post",
121
+ "http.get",
122
+ "http.request",
123
+ "https.get",
124
+ "https.request"
125
+ ]
126
+ }
127
+ }
@@ -0,0 +1,258 @@
1
+ /**
2
+ * S016 Regex-based Analyzer - Sensitive Data in URL Query Parameters Detection
3
+ * Purpose: Fallback pattern matching when symbol analysis fails
4
+ */
5
+
6
+ class S016RegexBasedAnalyzer {
7
+ constructor(semanticEngine = null) {
8
+ this.ruleId = 'S016';
9
+ this.ruleName = 'Sensitive Data in URL Query Parameters (Regex-Based)';
10
+ this.semanticEngine = semanticEngine;
11
+ this.verbose = false;
12
+
13
+ // URL construction patterns (regex)
14
+ this.urlConstructionPatterns = [
15
+ /new\s+URL\s*\([^)]*\)/gi,
16
+ /new\s+URLSearchParams\s*\([^)]*\)/gi,
17
+ /window\.location\.href\s*=\s*[^;]+/gi,
18
+ /location\.href\s*=\s*[^;]+/gi,
19
+ /location\.search\s*[+]?=\s*[^;]+/gi
20
+ ];
21
+
22
+ // HTTP client patterns
23
+ this.httpClientPatterns = [
24
+ /fetch\s*\(\s*[`"'][^`"']*[?][^`"']*[`"']/gi,
25
+ /axios\.(get|post|put|delete|patch|request)\s*\([^)]*\)/gi,
26
+ /request\.(get|post)\s*\([^)]*\)/gi,
27
+ /https?\.(?:get|request)\s*\([^)]*\)/gi
28
+ ];
29
+
30
+ // Query string manipulation patterns
31
+ this.queryStringPatterns = [
32
+ /querystring\.stringify\s*\([^)]*\)/gi,
33
+ /qs\.stringify\s*\([^)]*\)/gi,
34
+ /URLSearchParams\s*\([^)]*\)/gi,
35
+ /\.search\s*[+]?=\s*[^;]+/gi,
36
+ /[?&]\w+=[^&\s]+/g
37
+ ];
38
+
39
+ // Sensitive data patterns (same as symbol-based)
40
+ this.sensitivePatterns = [
41
+ // Authentication & Authorization
42
+ /\b(?:password|passwd|pwd|pass)\b/gi,
43
+ /\b(?:token|jwt|accesstoken|refreshtoken|bearertoken)\b/gi,
44
+ /\b(?:secret|secretkey|clientsecret|serversecret)\b/gi,
45
+ /\b(?:apikey|api_key|key|privatekey|publickey)\b/gi,
46
+ /\b(?:auth|authorization|authenticate)\b/gi,
47
+ /\b(?:sessionid|session_id|jsessionid)\b/gi,
48
+ /\b(?:csrf|csrftoken|xsrf)\b/gi,
49
+
50
+ // Financial & Personal
51
+ /\b(?:ssn|social|socialsecurity)\b/gi,
52
+ /\b(?:creditcard|cardnumber|cardnum|ccnumber)\b/gi,
53
+ /\b(?:cvv|cvc|cvd|cid)\b/gi,
54
+ /\b(?:pin|pincode)\b/gi,
55
+ /\b(?:bankaccount|routing|iban)\b/gi,
56
+
57
+ // Personal Identifiable Information
58
+ /\b(?:email|emailaddress|mail)\b/gi,
59
+ /\b(?:phone|phonenumber|mobile|tel)\b/gi,
60
+ /\b(?:address|homeaddress|zipcode|postal)\b/gi,
61
+ /\b(?:birthdate|birthday|dob)\b/gi,
62
+ /\b(?:license|passport|identity)\b/gi
63
+ ];
64
+
65
+ // Combined patterns for efficiency
66
+ this.allUrlPatterns = [
67
+ ...this.urlConstructionPatterns,
68
+ ...this.httpClientPatterns,
69
+ ...this.queryStringPatterns
70
+ ];
71
+ }
72
+
73
+ async initialize(semanticEngine = null) {
74
+ if (semanticEngine) {
75
+ this.semanticEngine = semanticEngine;
76
+ }
77
+ this.verbose = semanticEngine?.verbose || false;
78
+
79
+ if (this.verbose) {
80
+ console.log(`🔧 [S016 Regex-Based] Analyzer initialized`);
81
+ }
82
+ }
83
+
84
+ async analyzeFileBasic(filePath, options = {}) {
85
+ const fs = require('fs');
86
+ const violations = [];
87
+
88
+ if (this.verbose) {
89
+ console.log(`🔧 [S016 Regex] Starting analysis for: ${filePath}`);
90
+ }
91
+
92
+ try {
93
+ const content = fs.readFileSync(filePath, 'utf8');
94
+
95
+ if (this.verbose) {
96
+ console.log(`🔧 [S016 Regex] File content length: ${content.length}`);
97
+ }
98
+
99
+ const lines = content.split('\n');
100
+
101
+ // Find all URL/query related patterns
102
+ const urlMatches = this.findUrlPatterns(content);
103
+
104
+ if (this.verbose) {
105
+ console.log(`🔧 [S016 Regex] Found ${urlMatches.length} URL patterns`);
106
+ }
107
+
108
+ for (const match of urlMatches) {
109
+ const matchViolations = this.analyzeUrlMatch(match, lines, filePath);
110
+ if (this.verbose && matchViolations.length > 0) {
111
+ console.log(`🔧 [S016 Regex] Match violations: ${matchViolations.length}`);
112
+ }
113
+ violations.push(...matchViolations);
114
+ }
115
+
116
+ if (this.verbose) {
117
+ console.log(`🔧 [S016 Regex] Total violations found: ${violations.length}`);
118
+ }
119
+
120
+ return violations;
121
+ } catch (error) {
122
+ if (this.verbose) {
123
+ console.error(`🔧 [S016 Regex] Error analyzing ${filePath}:`, error);
124
+ }
125
+ return [];
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Find URL patterns in content using regex
131
+ */
132
+ findUrlPatterns(content) {
133
+ const matches = [];
134
+
135
+ for (const pattern of this.allUrlPatterns) {
136
+ pattern.lastIndex = 0; // Reset regex
137
+ let match;
138
+
139
+ while ((match = pattern.exec(content)) !== null) {
140
+ const fullMatch = match[0];
141
+
142
+ // Calculate line number
143
+ const beforeMatch = content.substring(0, match.index);
144
+ const lineNumber = beforeMatch.split('\n').length;
145
+ const lineStart = beforeMatch.lastIndexOf('\n') + 1;
146
+ const columnNumber = match.index - lineStart + 1;
147
+
148
+ matches.push({
149
+ fullMatch,
150
+ lineNumber,
151
+ columnNumber,
152
+ startIndex: match.index,
153
+ pattern: pattern.source
154
+ });
155
+ }
156
+ }
157
+
158
+ return matches;
159
+ }
160
+
161
+ /**
162
+ * Analyze URL match for sensitive data violations
163
+ */
164
+ analyzeUrlMatch(match, lines, filePath) {
165
+ const violations = [];
166
+ const { fullMatch, lineNumber, columnNumber, pattern } = match;
167
+
168
+ // Check for sensitive data in the matched content
169
+ const sensitiveData = this.findSensitiveDataInMatch(fullMatch);
170
+
171
+ if (sensitiveData.length > 0) {
172
+ const patternType = this.identifyPatternType(pattern);
173
+
174
+ violations.push({
175
+ ruleId: this.ruleId,
176
+ severity: 'error',
177
+ message: 'Sensitive data detected in URL query parameters',
178
+ source: this.ruleId,
179
+ file: filePath,
180
+ line: lineNumber,
181
+ column: columnNumber,
182
+ description: `[REGEX-FALLBACK] Sensitive patterns detected: ${sensitiveData.join(', ')}. URLs with sensitive data can be exposed in logs, browser history, and network traces.`,
183
+ suggestion: 'Move sensitive data to request body (POST/PUT) or use secure headers. For authentication, use proper Authorization header.',
184
+ category: 'security',
185
+ patternType: patternType,
186
+ matchedText: fullMatch.length > 100 ? fullMatch.substring(0, 100) + '...' : fullMatch
187
+ });
188
+ }
189
+
190
+ return violations;
191
+ }
192
+
193
+ /**
194
+ * Find sensitive data patterns in matched text
195
+ */
196
+ findSensitiveDataInMatch(matchText) {
197
+ const sensitiveData = [];
198
+
199
+ for (const pattern of this.sensitivePatterns) {
200
+ pattern.lastIndex = 0; // Reset regex
201
+ const matches = matchText.match(pattern);
202
+ if (matches) {
203
+ sensitiveData.push(...matches.map(m => m.toLowerCase()));
204
+ }
205
+ }
206
+
207
+ return [...new Set(sensitiveData)]; // Remove duplicates
208
+ }
209
+
210
+ /**
211
+ * Identify what type of pattern was matched
212
+ */
213
+ identifyPatternType(patternSource) {
214
+ if (patternSource.includes('URL') || patternSource.includes('location')) {
215
+ return 'url_construction';
216
+ } else if (patternSource.includes('fetch') || patternSource.includes('axios') || patternSource.includes('request')) {
217
+ return 'http_client';
218
+ } else if (patternSource.includes('stringify') || patternSource.includes('search')) {
219
+ return 'query_string';
220
+ }
221
+ return 'unknown';
222
+ }
223
+
224
+ async analyze(files, language, options = {}) {
225
+ if (this.verbose) {
226
+ console.log(`🔧 [S016 Regex] analyze() called with ${files.length} files, language: ${language}`);
227
+ }
228
+
229
+ const violations = [];
230
+
231
+ for (const filePath of files) {
232
+ try {
233
+ if (this.verbose) {
234
+ console.log(`🔧 [S016 Regex] Processing file: ${filePath}`);
235
+ }
236
+
237
+ const fileViolations = await this.analyzeFileBasic(filePath, options);
238
+ violations.push(...fileViolations);
239
+
240
+ if (this.verbose) {
241
+ console.log(`🔧 [S016 Regex] File ${filePath}: Found ${fileViolations.length} violations`);
242
+ }
243
+ } catch (error) {
244
+ if (this.verbose) {
245
+ console.warn(`⚠ [S016 Regex] Analysis failed for ${filePath}:`, error.message);
246
+ }
247
+ }
248
+ }
249
+
250
+ if (this.verbose) {
251
+ console.log(`🔧 [S016 Regex] Total violations found: ${violations.length}`);
252
+ }
253
+
254
+ return violations;
255
+ }
256
+ }
257
+
258
+ module.exports = S016RegexBasedAnalyzer;