@sun-asterisk/sunlint 1.3.1 → 1.3.3

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 (120) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/CONTRIBUTING.md +210 -1691
  3. package/README.md +5 -3
  4. package/config/rule-analysis-strategies.js +17 -1
  5. package/config/rules/enhanced-rules-registry.json +506 -1161
  6. package/config/rules/rules-registry-generated.json +1 -1
  7. package/core/analysis-orchestrator.js +167 -42
  8. package/core/auto-performance-manager.js +243 -0
  9. package/core/cli-action-handler.js +9 -1
  10. package/core/cli-program.js +19 -5
  11. package/core/constants/defaults.js +56 -0
  12. package/core/enhanced-rules-registry.js +2 -1
  13. package/core/performance-optimizer.js +271 -0
  14. package/core/semantic-engine.js +15 -3
  15. package/core/semantic-rule-base.js +4 -2
  16. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
  17. package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
  18. package/docs/PERFORMANCE.md +311 -0
  19. package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
  20. package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
  21. package/docs/QUICK_FILE_LIMITS.md +64 -0
  22. package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
  23. package/engines/heuristic-engine.js +247 -9
  24. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  25. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  26. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  27. package/origin-rules/common-en.md +11 -7
  28. package/package.json +2 -1
  29. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  30. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  31. package/rules/common/C006_function_naming/analyzer.js +29 -3
  32. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  33. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  34. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  35. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  36. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  37. package/rules/common/C013_no_dead_code/config.json +61 -0
  38. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  39. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  40. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  41. package/rules/common/C014_dependency_injection/config.json +26 -0
  42. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  43. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  44. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  45. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  46. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  47. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  48. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  49. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  50. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  51. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  52. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  53. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  56. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  57. package/rules/common/C035_error_logging_context/analyzer.js +3 -1
  58. package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
  59. package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
  60. package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
  61. package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
  62. package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
  63. package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
  64. package/rules/index.js +7 -1
  65. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  66. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  67. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  68. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  69. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  70. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  71. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  72. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  73. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  74. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  75. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  76. package/rules/security/S017_use_parameterized_queries/README.md +128 -0
  77. package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
  78. package/rules/security/S017_use_parameterized_queries/config.json +109 -0
  79. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
  80. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
  81. package/rules/security/S031_secure_session_cookies/README.md +127 -0
  82. package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
  83. package/rules/security/S031_secure_session_cookies/config.json +86 -0
  84. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
  85. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
  86. package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
  87. package/rules/security/S032_httponly_session_cookies/README.md +184 -0
  88. package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
  89. package/rules/security/S032_httponly_session_cookies/config.json +96 -0
  90. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
  91. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
  92. package/rules/security/S033_samesite_session_cookies/README.md +227 -0
  93. package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
  94. package/rules/security/S033_samesite_session_cookies/config.json +87 -0
  95. package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
  96. package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
  97. package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
  98. package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
  99. package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
  100. package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
  101. package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
  102. package/rules/security/S035_path_session_cookies/README.md +257 -0
  103. package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
  104. package/rules/security/S035_path_session_cookies/config.json +99 -0
  105. package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
  106. package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
  107. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  108. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  109. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  110. package/rules/security/S055_content_type_validation/README.md +176 -0
  111. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  112. package/rules/security/S055_content_type_validation/config.json +48 -0
  113. package/rules/utils/rule-helpers.js +140 -1
  114. package/scripts/batch-processing-demo.js +334 -0
  115. package/scripts/consolidate-config.js +116 -0
  116. package/scripts/performance-test.js +541 -0
  117. package/scripts/quick-performance-test.js +108 -0
  118. package/config/rules/S027-categories.json +0 -122
  119. package/config/rules/rules-registry.json +0 -777
  120. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -0,0 +1,495 @@
1
+ /**
2
+ * S016 Symbol-based Analyzer - Sensitive Data in URL Query Parameters Detection
3
+ * Purpose: Use AST + Symbol Resolution to detect sensitive data passed via query strings
4
+ */
5
+
6
+ const { SyntaxKind } = require("ts-morph");
7
+
8
+ class S016SymbolBasedAnalyzer {
9
+ constructor(semanticEngine = null) {
10
+ this.ruleId = "S016";
11
+ this.ruleName = "Sensitive Data in URL Query Parameters (Symbol-Based)";
12
+ this.semanticEngine = semanticEngine;
13
+ this.verbose = false;
14
+
15
+ // URL construction patterns
16
+ this.urlPatterns = {
17
+ // Direct URL construction
18
+ urlConstructor: ["URL", "new URL"],
19
+ urlSearchParams: ["URLSearchParams", "new URLSearchParams"],
20
+
21
+ // HTTP client libraries
22
+ fetch: ["fetch"],
23
+ axios: [
24
+ "axios.get",
25
+ "axios.post",
26
+ "axios.put",
27
+ "axios.delete",
28
+ "axios.patch",
29
+ "axios.request",
30
+ ],
31
+ request: ["request", "request.get", "request.post"],
32
+
33
+ // Node.js modules
34
+ http: ["http.get", "http.request", "https.get", "https.request"],
35
+ querystring: ["querystring.stringify", "qs.stringify"],
36
+
37
+ // Framework specific
38
+ express: ["res.redirect", "req.query"],
39
+ nextjs: ["router.push", "router.replace", "Link"],
40
+ react: ["window.location.href", "location.href"],
41
+ };
42
+
43
+ // Sensitive data patterns (more comprehensive)
44
+ this.sensitivePatterns = [
45
+ // Authentication & Authorization
46
+ "password",
47
+ "passwd",
48
+ "pwd",
49
+ "pass",
50
+ "token",
51
+ "jwt",
52
+ "accesstoken",
53
+ "refreshtoken",
54
+ "bearertoken",
55
+ "secret",
56
+ "secretkey",
57
+ "clientsecret",
58
+ "serversecret",
59
+ "apikey",
60
+ "api_key",
61
+ "key",
62
+ "privatekey",
63
+ "publickey",
64
+ "auth",
65
+ "authorization",
66
+ "authenticate",
67
+ "sessionid",
68
+ "session_id",
69
+ "jsessionid",
70
+ "csrf",
71
+ "csrftoken",
72
+ "xsrf",
73
+
74
+ // Financial & Personal
75
+ "ssn",
76
+ "social",
77
+ "socialsecurity",
78
+ "creditcard",
79
+ "cardnumber",
80
+ "cardnum",
81
+ "ccnumber",
82
+ "cvv",
83
+ "cvc",
84
+ "cvd",
85
+ "cid",
86
+ "pin",
87
+ "pincode",
88
+ "bankaccount",
89
+ "routing",
90
+ "iban",
91
+
92
+ // Personal Identifiable Information
93
+ "email",
94
+ "emailaddress",
95
+ "mail",
96
+ "phone",
97
+ "phonenumber",
98
+ "mobile",
99
+ "tel",
100
+ "address",
101
+ "homeaddress",
102
+ "zipcode",
103
+ "postal",
104
+ "birthdate",
105
+ "birthday",
106
+ "dob",
107
+ "license",
108
+ "passport",
109
+ "identity",
110
+
111
+ // Business sensitive
112
+ "salary",
113
+ "income",
114
+ "wage",
115
+ "medical",
116
+ "health",
117
+ "diagnosis",
118
+ ];
119
+
120
+ // Query parameter indicators
121
+ this.queryIndicators = [
122
+ "query",
123
+ "params",
124
+ "search",
125
+ "searchparams",
126
+ "urlparams",
127
+ "querystring",
128
+ "qs",
129
+ ];
130
+ }
131
+
132
+ async initialize(semanticEngine = null) {
133
+ if (semanticEngine) {
134
+ this.semanticEngine = semanticEngine;
135
+ }
136
+ this.verbose = semanticEngine?.verbose || false;
137
+
138
+ if (process.env.SUNLINT_DEBUG) {
139
+ console.log(
140
+ `🔧 [S016 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`
141
+ );
142
+ }
143
+ }
144
+
145
+ async analyzeFileBasic(filePath, options = {}) {
146
+ return await this.analyzeFileWithSymbols(filePath, options);
147
+ }
148
+
149
+ async analyzeFileWithSymbols(filePath, options = {}) {
150
+ const violations = [];
151
+
152
+ const verbose = options.verbose || this.verbose;
153
+
154
+ if (!this.semanticEngine?.project) {
155
+ if (verbose) {
156
+ console.warn(
157
+ "[S016 Symbol-Based] No semantic engine available, skipping analysis"
158
+ );
159
+ }
160
+ return violations;
161
+ }
162
+
163
+ if (verbose) {
164
+ console.log(`🔍 [S016 Symbol-Based] Starting analysis for ${filePath}`);
165
+ }
166
+
167
+ try {
168
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
169
+ if (!sourceFile) {
170
+ return violations;
171
+ }
172
+
173
+ // Find various URL/query construction patterns
174
+ const urlConstructions = this.findUrlConstructions(sourceFile, verbose);
175
+ const queryStringUsages = this.findQueryStringUsages(sourceFile, verbose);
176
+ const httpClientCalls = this.findHttpClientCalls(sourceFile, verbose);
177
+
178
+ if (verbose) {
179
+ console.log(
180
+ `🔍 [S016 Symbol-Based] Found ${urlConstructions.length} URL constructions, ${queryStringUsages.length} query usages, ${httpClientCalls.length} HTTP calls`
181
+ );
182
+ }
183
+
184
+ // Analyze each pattern
185
+ const allPatterns = [
186
+ ...urlConstructions,
187
+ ...queryStringUsages,
188
+ ...httpClientCalls,
189
+ ];
190
+
191
+ for (const pattern of allPatterns) {
192
+ const patternViolations = this.analyzeUrlPattern(
193
+ pattern,
194
+ sourceFile,
195
+ filePath,
196
+ verbose
197
+ );
198
+ violations.push(...patternViolations);
199
+ }
200
+
201
+ if (verbose) {
202
+ console.log(
203
+ `🔍 [S016 Symbol-Based] Total violations found: ${violations.length}`
204
+ );
205
+ }
206
+
207
+ return violations;
208
+ } catch (error) {
209
+ if (verbose) {
210
+ console.warn(
211
+ `[S016 Symbol-Based] Analysis failed for ${filePath}:`,
212
+ error.message
213
+ );
214
+ }
215
+ return violations;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Find URL construction patterns (new URL, URLSearchParams, etc.)
221
+ */
222
+ findUrlConstructions(sourceFile, verbose = false) {
223
+ const patterns = [];
224
+
225
+ // Find 'new URL()' constructions
226
+ const newExpressions = sourceFile.getDescendantsOfKind(
227
+ SyntaxKind.NewExpression
228
+ );
229
+ for (const newExpr of newExpressions) {
230
+ const identifier = newExpr.getExpression();
231
+ if (
232
+ identifier.getText() === "URL" ||
233
+ identifier.getText() === "URLSearchParams"
234
+ ) {
235
+ patterns.push({
236
+ type: "constructor",
237
+ node: newExpr,
238
+ method: identifier.getText(),
239
+ });
240
+ }
241
+ }
242
+
243
+ if (verbose) {
244
+ console.log(
245
+ `🔍 [S016 Symbol-Based] Found ${patterns.length} URL constructor patterns`
246
+ );
247
+ }
248
+
249
+ return patterns;
250
+ }
251
+
252
+ /**
253
+ * Find query string manipulation patterns
254
+ */
255
+ findQueryStringUsages(sourceFile, verbose = false) {
256
+ const patterns = [];
257
+
258
+ // Find property access expressions that might involve query strings
259
+ const propertyAccess = sourceFile.getDescendantsOfKind(
260
+ SyntaxKind.PropertyAccessExpression
261
+ );
262
+
263
+ for (const propAccess of propertyAccess) {
264
+ const fullText = propAccess.getText().toLowerCase();
265
+
266
+ // Check for query-related property access
267
+ if (
268
+ this.queryIndicators.some((indicator) => fullText.includes(indicator))
269
+ ) {
270
+ patterns.push({
271
+ type: "property_access",
272
+ node: propAccess,
273
+ method: propAccess.getText(),
274
+ });
275
+ }
276
+
277
+ // Check for location.search, window.location.search, etc.
278
+ if (fullText.includes("search") || fullText.includes("query")) {
279
+ patterns.push({
280
+ type: "location_search",
281
+ node: propAccess,
282
+ method: propAccess.getText(),
283
+ });
284
+ }
285
+ }
286
+
287
+ if (verbose) {
288
+ console.log(
289
+ `🔍 [S016 Symbol-Based] Found ${patterns.length} query string usage patterns`
290
+ );
291
+ }
292
+
293
+ return patterns;
294
+ }
295
+
296
+ /**
297
+ * Find HTTP client calls that might include query parameters
298
+ */
299
+ findHttpClientCalls(sourceFile, verbose = false) {
300
+ const patterns = [];
301
+
302
+ const callExpressions = sourceFile.getDescendantsOfKind(
303
+ SyntaxKind.CallExpression
304
+ );
305
+
306
+ for (const callExpr of callExpressions) {
307
+ const expression = callExpr.getExpression();
308
+ const callText = expression.getText().toLowerCase();
309
+
310
+ // Check against known HTTP client patterns
311
+ for (const [client, methods] of Object.entries(this.urlPatterns)) {
312
+ for (const method of methods) {
313
+ if (callText.includes(method.toLowerCase())) {
314
+ patterns.push({
315
+ type: "http_client",
316
+ node: callExpr,
317
+ method: method,
318
+ client: client,
319
+ });
320
+ break;
321
+ }
322
+ }
323
+ }
324
+ }
325
+
326
+ if (verbose) {
327
+ console.log(
328
+ `🔍 [S016 Symbol-Based] Found ${patterns.length} HTTP client call patterns`
329
+ );
330
+ }
331
+
332
+ return patterns;
333
+ }
334
+
335
+ /**
336
+ * Analyze URL pattern for sensitive data in query parameters
337
+ */
338
+ analyzeUrlPattern(pattern, sourceFile, filePath, verbose = false) {
339
+ const violations = [];
340
+ const lineNumber = pattern.node.getStartLineNumber();
341
+ const columnNumber =
342
+ pattern.node.getStart() - pattern.node.getStartLinePos();
343
+
344
+ if (verbose) {
345
+ console.log(
346
+ `🔍 [S016 Symbol-Based] Analyzing ${pattern.type} pattern: ${pattern.method}`
347
+ );
348
+ }
349
+
350
+ // Only check for sensitive keys in actual query string
351
+ let queryString = "";
352
+ let sensitiveParams = [];
353
+
354
+ if (pattern.type === "constructor" || pattern.type === "http_client") {
355
+ const args = pattern.node.getArguments?.() || [];
356
+ // Only check first argument (URL)
357
+ if (args.length > 0) {
358
+ const urlText = args[0].getText();
359
+ const match = urlText.match(/\?(.*)/);
360
+ if (match && match[1]) {
361
+ queryString = match[1];
362
+ // Split query string into keys
363
+ const keys = queryString
364
+ .split("&")
365
+ .map((pair) => pair.split("=")[0].toLowerCase());
366
+ sensitiveParams = keys.filter((key) =>
367
+ this.sensitivePatterns.includes(key)
368
+ );
369
+ }
370
+ }
371
+ } else if (
372
+ pattern.type === "property_access" ||
373
+ pattern.type === "location_search"
374
+ ) {
375
+ // Only check if .searchParams.set or .query is used with sensitive key
376
+ const methodText = pattern.method.toLowerCase();
377
+ for (const sensitiveKey of this.sensitivePatterns) {
378
+ // Only match if set as key in searchParams or query
379
+ const regex = new RegExp(`\.set\(['"]${sensitiveKey}['"]`, "i");
380
+ if (regex.test(methodText)) {
381
+ sensitiveParams.push(sensitiveKey);
382
+ }
383
+ }
384
+ }
385
+
386
+ if (sensitiveParams.length > 0) {
387
+ violations.push({
388
+ ruleId: this.ruleId,
389
+ severity: "error",
390
+ message: "Sensitive data detected in URL query parameters",
391
+ source: this.ruleId,
392
+ file: filePath,
393
+ line: lineNumber,
394
+ column: columnNumber,
395
+ description: `[SYMBOL-BASED] Sensitive parameters detected: ${sensitiveParams.join(
396
+ ", "
397
+ )}. This can expose data in logs, browser history, and network traces.`,
398
+ suggestion:
399
+ "Move sensitive data to request body (POST/PUT) or use secure headers. For authentication, use proper header-based tokens.",
400
+ category: "security",
401
+ patternType: pattern.type,
402
+ method: pattern.method,
403
+ });
404
+ }
405
+
406
+ // Additional checks for specific patterns
407
+ if (
408
+ pattern.type === "constructor" &&
409
+ pattern.method === "URLSearchParams"
410
+ ) {
411
+ // Special handling for URLSearchParams constructor
412
+ const constructorViolations = this.analyzeURLSearchParamsConstructor(
413
+ pattern.node,
414
+ filePath,
415
+ verbose
416
+ );
417
+ violations.push(...constructorViolations);
418
+ }
419
+
420
+ return violations;
421
+ }
422
+
423
+ /**
424
+ * Analyze URLSearchParams constructor specifically
425
+ */
426
+ analyzeURLSearchParamsConstructor(node, filePath, verbose = false) {
427
+ const violations = [];
428
+ const args = node.getArguments();
429
+
430
+ if (args.length === 0) return violations;
431
+
432
+ const firstArg = args[0];
433
+
434
+ // If first argument is an object literal, check its properties
435
+ if (firstArg.getKind() === SyntaxKind.ObjectLiteralExpression) {
436
+ const properties = firstArg.getProperties();
437
+
438
+ for (const prop of properties) {
439
+ if (prop.getKind() === SyntaxKind.PropertyAssignment) {
440
+ const propName = prop.getName()?.toLowerCase() || "";
441
+
442
+ const matchingSensitivePattern = this.sensitivePatterns.find(
443
+ (pattern) => {
444
+ const regex = new RegExp(`\\b${pattern}\\b`, "i");
445
+ return regex.test(propName);
446
+ }
447
+ );
448
+
449
+ if (matchingSensitivePattern) {
450
+ violations.push({
451
+ ruleId: this.ruleId,
452
+ severity: "error",
453
+ message: `Sensitive parameter '${propName}' in URLSearchParams constructor`,
454
+ source: this.ruleId,
455
+ file: filePath,
456
+ line: prop.getStartLineNumber(),
457
+ column: prop.getStart() - prop.getStartLinePos(),
458
+ description: `[SYMBOL-BASED] Parameter '${propName}' contains sensitive data pattern '${matchingSensitivePattern}'. URLSearchParams will be visible in URLs.`,
459
+ suggestion:
460
+ "Move sensitive parameters to request body or secure headers",
461
+ category: "security",
462
+ });
463
+ }
464
+ }
465
+ }
466
+ }
467
+
468
+ return violations;
469
+ }
470
+
471
+ /**
472
+ * Find sensitive parameters in content
473
+ */
474
+ findSensitiveParameters(content, verbose = false) {
475
+ const sensitiveParams = [];
476
+ const lowerContent = content.toLowerCase();
477
+
478
+ for (const pattern of this.sensitivePatterns) {
479
+ // Use word boundaries to avoid false positives
480
+ const regex = new RegExp(`\\b${pattern}\\b`, "i");
481
+ if (regex.test(lowerContent)) {
482
+ sensitiveParams.push(pattern);
483
+ if (verbose) {
484
+ console.log(
485
+ `🔍 [S016 Symbol-Based] Sensitive pattern detected: '${pattern}'`
486
+ );
487
+ }
488
+ }
489
+ }
490
+
491
+ return [...new Set(sensitiveParams)]; // Remove duplicates
492
+ }
493
+ }
494
+
495
+ module.exports = S016SymbolBasedAnalyzer;
@@ -0,0 +1,128 @@
1
+ # S017 - Always use parameterized queries
2
+
3
+ ## Rule Description
4
+
5
+ Always use parameterized queries instead of string concatenation to build SQL queries. This prevents SQL injection attacks by separating SQL logic from data.
6
+
7
+ ## Risk Level
8
+
9
+ **HIGH** - SQL injection is one of the most critical security vulnerabilities
10
+
11
+ ## Rule Details
12
+
13
+ This rule detects potentially vulnerable SQL query construction patterns:
14
+
15
+ ### ❌ Violations (Dangerous Patterns)
16
+
17
+ ```javascript
18
+ // String concatenation with user input
19
+ const query = "SELECT * FROM users WHERE id = " + userId;
20
+ db.query(query);
21
+
22
+ // Template literals with interpolation
23
+ const sql = `SELECT * FROM products WHERE name = '${productName}'`;
24
+ connection.execute(sql);
25
+
26
+ // Direct variable insertion
27
+ const deleteQuery = "DELETE FROM orders WHERE status = '" + status + "'";
28
+ mysql.query(deleteQuery);
29
+
30
+ // Complex concatenation
31
+ const updateSql =
32
+ "UPDATE users SET name = '" +
33
+ name +
34
+ "', email = '" +
35
+ email +
36
+ "' WHERE id = " +
37
+ id;
38
+ ```
39
+
40
+ ### ✅ Correct Usage (Safe Patterns)
41
+
42
+ ```javascript
43
+ // Using parameterized queries with placeholders
44
+ const query = "SELECT * FROM users WHERE id = ?";
45
+ db.query(query, [userId]);
46
+
47
+ // Using named parameters
48
+ const sql = "SELECT * FROM products WHERE name = $1";
49
+ client.query(sql, [productName]);
50
+
51
+ // Using prepared statements
52
+ const stmt = db.prepare("DELETE FROM orders WHERE status = ?");
53
+ stmt.run(status);
54
+
55
+ // ORM usage (usually safe)
56
+ const user = await User.findOne({ where: { id: userId } });
57
+
58
+ // Using parameter objects
59
+ const updateSql = "UPDATE users SET name = ?, email = ? WHERE id = ?";
60
+ db.query(updateSql, [name, email, id]);
61
+ ```
62
+
63
+ ## Detected Libraries
64
+
65
+ - **Database drivers**: mysql, mysql2, pg, sqlite3, mssql, oracle
66
+ - **ORMs**: sequelize, typeorm, prisma, mongoose
67
+ - **Query builders**: knex, objection
68
+
69
+ ## Security Impact
70
+
71
+ - **SQL Injection attacks**: Malicious SQL code execution
72
+ - **Data breach**: Unauthorized access to sensitive data
73
+ - **Data manipulation**: Unauthorized data modification/deletion
74
+ - **Authentication bypass**: Circumventing login mechanisms
75
+ - **Privilege escalation**: Gaining admin access
76
+
77
+ ## Best Practices
78
+
79
+ 1. **Always use parameterized queries** with placeholders (?, $1, etc.)
80
+ 2. **Use prepared statements** when available
81
+ 3. **Validate and sanitize** all user inputs
82
+ 4. **Use ORMs** that handle parameterization automatically
83
+ 5. **Apply principle of least privilege** for database accounts
84
+ 6. **Regular security audits** of SQL query patterns
85
+
86
+ ## Examples by Library
87
+
88
+ ### MySQL/MySQL2
89
+
90
+ ```javascript
91
+ // ❌ Vulnerable
92
+ const query = `SELECT * FROM users WHERE email = '${email}'`;
93
+ connection.query(query);
94
+
95
+ // ✅ Safe
96
+ const query = "SELECT * FROM users WHERE email = ?";
97
+ connection.query(query, [email]);
98
+ ```
99
+
100
+ ### PostgreSQL (pg)
101
+
102
+ ```javascript
103
+ // ❌ Vulnerable
104
+ const text = `SELECT * FROM users WHERE id = ${id}`;
105
+ client.query(text);
106
+
107
+ // ✅ Safe
108
+ const text = "SELECT * FROM users WHERE id = $1";
109
+ client.query(text, [id]);
110
+ ```
111
+
112
+ ### SQLite
113
+
114
+ ```javascript
115
+ // ❌ Vulnerable
116
+ const sql = "INSERT INTO logs (message) VALUES ('" + message + "')";
117
+ db.run(sql);
118
+
119
+ // ✅ Safe
120
+ const sql = "INSERT INTO logs (message) VALUES (?)";
121
+ db.run(sql, [message]);
122
+ ```
123
+
124
+ ## References
125
+
126
+ - [OWASP SQL Injection Prevention](https://owasp.org/www-community/attacks/SQL_Injection)
127
+ - [CWE-89: SQL Injection](https://cwe.mitre.org/data/definitions/89.html)
128
+ - [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)