@sun-asterisk/sunlint 1.3.31 → 1.3.32

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.
@@ -1,691 +1,24 @@
1
- // rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js
2
- const { SyntaxKind, Project, Node } = require("ts-morph");
1
+ /**
2
+ * C067 Symbol-Based Analyzer - Simplified Version
3
+ *
4
+ * Detects hardcoded configuration that should be externalized:
5
+ * - API URLs/endpoints (http/https to external services)
6
+ * - Credentials (passwords, API keys, secrets)
7
+ * - Timeouts, retry intervals, batch sizes (only in specific contexts)
8
+ *
9
+ * Does NOT flag:
10
+ * - Values in centralized config files (config.ts, constants.ts, etc.)
11
+ * - Values from environment variables
12
+ * - Common safe values (HTTP status codes, indices, etc.)
13
+ * - Database field definitions, UI strings, validation messages
14
+ */
15
+
16
+ const { Project, SyntaxKind } = require("ts-morph");
3
17
 
4
18
  class C067SymbolBasedAnalyzer {
5
19
  constructor(semanticEngine = null) {
6
20
  this.semanticEngine = semanticEngine;
7
21
  this.verbose = false;
8
-
9
- // Common UI/framework strings that should be excluded
10
- this.UI_STRINGS = [
11
- "checkbox",
12
- "button",
13
- "search",
14
- "remove",
15
- "submit",
16
- "cancel",
17
- "ok",
18
- "close",
19
- "Authorization",
20
- "User-Agent",
21
- "Content-Type",
22
- "Accept",
23
- "Bearer",
24
- "ArrowDown",
25
- "ArrowUp",
26
- "ArrowLeft",
27
- "ArrowRight",
28
- "bottom",
29
- "top",
30
- "left",
31
- "right",
32
- "next-auth/react",
33
- "@nestjs/swagger",
34
- "@nestjs/common",
35
- "nestjs-pino",
36
- ];
37
-
38
- // Test-related strings to exclude
39
- this.TEST_PATTERNS = [
40
- /^(test|mock|example|dummy|placeholder|fixture|stub)/i,
41
- /^(User \d+|Test User|Admin User)/i,
42
- /^(group\d+|item\d+|element\d+)/i,
43
- /^(abcdef\d+|123456|test-\w+)/i,
44
- ];
45
-
46
- // Configuration patterns to detect - based on Rule C067 requirements
47
- this.configPatterns = {
48
- // API URLs and endpoints - external URLs that differ by environment
49
- urls: {
50
- regex:
51
- /^https?:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)([a-zA-Z0-9-]+\.[a-zA-Z]{2,}|[^\/\s]+\.[^\/\s]+)(\/[^\s]*)?$/,
52
- exclude: [
53
- /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?/, // Local development
54
- /^https?:\/\/(example\.com|test\.com|dummy\.com)/, // Test domains
55
- /^(http|https):\/\/\$\{.+\}/, // Template URLs with variables
56
- ],
57
- },
58
-
59
- // Timeouts, retry intervals, batch sizes - environment-dependent numeric values
60
- environmentNumbers: {
61
- isEnvironmentDependent: (value, context) => {
62
- const lowerContext = context.toLowerCase();
63
-
64
- // Business logic numbers are NOT environment config
65
- const businessLogicPatterns = [
66
- /limit|max|min|size|count|length|threshold/i,
67
- /page|record|item|batch|chunk|export/i,
68
- /width|height|margin|padding/i,
69
- /attempt|retry|step/i,
70
- ];
71
-
72
- if (businessLogicPatterns.some((pattern) => pattern.test(context))) {
73
- return false;
74
- }
75
-
76
- // Skip common business constants
77
- const businessConstants = [
78
- 20000,
79
- 10000,
80
- 5000,
81
- 1000,
82
- 500,
83
- 100,
84
- 50,
85
- 20,
86
- 10,
87
- 5, // Common limits
88
- 404,
89
- 500,
90
- 200,
91
- 201,
92
- 400,
93
- 401,
94
- 403, // HTTP status codes
95
- 24,
96
- 60,
97
- 3600,
98
- 86400, // Time constants (hours, minutes, seconds)
99
- 1,
100
- 2,
101
- 3,
102
- 4,
103
- 5,
104
- 6,
105
- 7,
106
- 8,
107
- 9,
108
- 10, // Simple counters
109
- ];
110
-
111
- if (businessConstants.includes(value)) {
112
- return false;
113
- }
114
-
115
- // Timeouts and intervals (values > 1000ms that might differ by environment)
116
- if (typeof value === "number" && value > 1000) {
117
- return /timeout|interval|delay|duration|retry|batch/i.test(context);
118
- }
119
-
120
- // Port numbers (except common development ports)
121
- if (typeof value === "number" && value > 1000 && value < 65536) {
122
- const commonDevPorts = [3000, 8000, 8080, 9000, 5000, 4200, 4000];
123
- if (!commonDevPorts.includes(value)) {
124
- return /port|listen|bind|server/i.test(context);
125
- }
126
- }
127
-
128
- return false;
129
- },
130
- },
131
-
132
- // Database and connection strings
133
- connections: {
134
- regex: /^(mongodb|mysql|postgres|redis|elasticsearch):\/\/|^jdbc:|^Server=|^Data Source=/i,
135
- },
136
-
137
- // Credentials - API keys, passwords, tokens
138
- credentials: {
139
- keywords: [
140
- "apikey",
141
- "api_key",
142
- "secret_key",
143
- "access_token",
144
- "client_secret",
145
- "password",
146
- "token",
147
- "key",
148
- ],
149
- exclude: [
150
- /must contain|should contain|invalid|error|message/i, // Validation messages
151
- /description|comment|note/i, // Descriptions
152
- /^[a-z\s]{10,}$/i, // Long descriptive text
153
- ],
154
- },
155
-
156
- // Feature flags and toggles
157
- featureFlags: {
158
- keywords: ["feature", "flag", "toggle", "enable", "disable", "enabled", "disabled"],
159
- patterns: [
160
- /^(enable|disable)[A-Z]/, // enableFeature, disableLogging
161
- /[A-Z][a-z]+(Flag|Toggle|Enabled|Disabled)$/, // newUIFlag, debugEnabled
162
- /^FEATURE_[A-Z_]+$/, // FEATURE_NEW_UI
163
- /^(is|has)[A-Z][a-z]+Enabled$/, // isDebugEnabled
164
- ],
165
- },
166
-
167
- // Thresholds and limits that might vary by environment
168
- thresholds: {
169
- keywords: ["threshold", "limit", "max", "min"],
170
- contextPatterns: [
171
- /memory|cpu|disk|storage/i, // Resource thresholds
172
- /rate|request|connection/i, // Rate limiting
173
- /pool|queue|buffer/i, // Resource pools
174
- ],
175
- },
176
-
177
- // Security & Authentication - Phase 1 extension
178
- security: {
179
- corsOrigins: {
180
- keywords: ["cors", "origin", "allowed"],
181
- patterns: [
182
- /^https?:\/\/[^\/]+$/, // URLs without paths
183
- /\.(com|org|net|dev|staging|prod)$/i,
184
- ],
185
- },
186
- sessionConfig: {
187
- keywords: ["session", "jwt", "token", "auth", "expiry", "expire"],
188
- timePatterns: [
189
- /^\d+[smhd]$/, // 24h, 30m, 60s, 7d
190
- /^\d{3,}$/, // Large numbers (seconds)
191
- ],
192
- },
193
- },
194
-
195
- // Infrastructure Config - Phase 1 extension
196
- infrastructure: {
197
- caching: {
198
- keywords: ["cache", "ttl", "expire", "redis", "prefix"],
199
- patterns: [
200
- /^[a-zA-Z]+:[a-zA-Z]+:/, // Prefixes like "myapp:prod:"
201
- /^\d{3,}$/, // TTL values in seconds
202
- ],
203
- },
204
- logging: {
205
- keywords: ["log", "level"],
206
- levels: ["trace", "debug", "info", "warn", "error", "fatal"],
207
- },
208
- performance: {
209
- keywords: ["worker", "thread", "concurrency", "queue", "upload", "download"],
210
- contextPatterns: [
211
- /worker|thread|process/i,
212
- /concurrency|parallel|queue/i,
213
- /upload|download|file.*size/i,
214
- ],
215
- },
216
- },
217
-
218
- // Environment-specific patterns
219
- environments: {
220
- names: ["production", "prod", "staging", "stage", "development", "dev", "test"],
221
- patterns: [
222
- /^(production|prod|staging|stage|development|dev|test)$/i,
223
- /\.(prod|staging|dev)\./, // domain patterns
224
- /_(prod|staging|dev)_/i, // variable patterns
225
- ],
226
- },
227
-
228
- // Service dependencies
229
- services: {
230
- keywords: ["service", "endpoint", "host", "port"],
231
- patterns: [
232
- /^https?:\/\/[a-zA-Z-]+-service/, // microservice URLs
233
- /:[0-9]{4,5}$/, // Port numbers
234
- /service.*url|url.*service/i,
235
- ],
236
- },
237
-
238
- // ============ Phase 2: Critical Configuration Patterns ============
239
-
240
- // Database & Storage Configuration
241
- database: {
242
- poolConfig: {
243
- keywords: ["pool", "connection", "max", "min", "idle"],
244
- patterns: [
245
- /pool.*size|max.*connections?|min.*connections?/i,
246
- /connection.*pool/i,
247
- /idle.*timeout|acquire.*timeout/i,
248
- ],
249
- },
250
- queryConfig: {
251
- keywords: ["query", "timeout", "retry", "transaction"],
252
- patterns: [
253
- /query.*timeout|statement.*timeout/i,
254
- /transaction.*isolation|isolation.*level/i,
255
- /read.*timeout|write.*timeout/i,
256
- ],
257
- },
258
- schemaNames: {
259
- keywords: ["table", "collection", "database", "schema", "shard", "partition"],
260
- patterns: [
261
- /^[a-z_]+_20\d{2}$/, // Table names with year: users_2024
262
- /^shard_\d+$|^partition_\d+$/i, // Shard identifiers
263
- /table.*name|collection.*name/i,
264
- ],
265
- },
266
- },
267
-
268
- // Security & Authentication (Extended)
269
- securityExtended: {
270
- tokenConfig: {
271
- keywords: ["token", "jwt", "expiry", "expire", "ttl"],
272
- patterns: [
273
- /^\d{3,}$/, // Expiry in seconds: 7200, 86400
274
- /^\d+[smhd]$/, // Human readable: 2h, 30m, 7d
275
- /expir(y|e|ation)|ttl/i,
276
- ],
277
- },
278
- passwordPolicy: {
279
- keywords: ["password", "length", "complexity", "require", "min", "max"],
280
- patterns: [
281
- /min.*(password|length)|password.*min/i,
282
- /password.*(complexity|requirement|policy)/i,
283
- /must.*contain|should.*contain|require.*\d+/i,
284
- ],
285
- },
286
- rateLimiting: {
287
- keywords: ["rate", "limit", "attempt", "throttle", "max"],
288
- patterns: [
289
- /max.*(attempt|tries|request)|attempt.*limit/i,
290
- /rate.*limit|throttle/i,
291
- /request.*per.*(minute|hour|second)/i,
292
- ],
293
- },
294
- encryptionConfig: {
295
- keywords: ["encrypt", "cipher", "algorithm", "mode", "aes", "rsa"],
296
- patterns: [
297
- /^(AES|RSA|DES|3DES|Blowfish)-\d+(-[A-Z]+)?$/i, // AES-256-GCM
298
- /encryption.*algorithm|cipher.*mode/i,
299
- ],
300
- },
301
- oauthConfig: {
302
- keywords: ["oauth", "scope", "grant", "client"],
303
- patterns: [
304
- /^(read|write|admin):[a-z_]+$/i, // OAuth scopes: read:user
305
- /scope|grant.*type|client.*id/i,
306
- ],
307
- },
308
- },
309
-
310
- // File System & Paths
311
- fileSystem: {
312
- directories: {
313
- keywords: ["dir", "directory", "path", "folder", "upload", "download", "temp"],
314
- patterns: [
315
- /^\/[a-z]+\/[a-z]+\//i, // Unix absolute paths: /var/www/uploads
316
- /^[A-Z]:\\/i, // Windows paths: C:\Users\...
317
- /upload.*dir|download.*dir|temp.*dir/i,
318
- /^\.\/[a-z_-]+\//i, // Relative paths: ./uploads/
319
- ],
320
- },
321
- fileLimits: {
322
- keywords: ["file", "size", "limit", "max", "upload"],
323
- patterns: [
324
- /file.*size|max.*size|size.*limit/i,
325
- /upload.*limit|download.*limit/i,
326
- /^\d{6,}$/, // Large byte values: 10485760 (10MB)
327
- ],
328
- },
329
- fileTypes: {
330
- keywords: ["extension", "type", "allow", "mime", "accept"],
331
- patterns: [
332
- /^\.[a-z0-9]{2,4}$/i, // File extensions: .jpg, .pdf
333
- /allowed.*(type|extension)|accept.*type/i,
334
- /^(image|video|audio|application)\/[a-z0-9+-]+$/i, // MIME types
335
- ],
336
- },
337
- logPaths: {
338
- keywords: ["log", "path", "file"],
339
- patterns: [/^\/var\/log\//i, /\.log$/i, /log.*file|log.*path/i],
340
- },
341
- },
342
-
343
- // ============ Phase 3: Important Configuration Patterns ============
344
-
345
- // Network & Protocol Configuration
346
- network: {
347
- httpConfig: {
348
- keywords: ["method", "header", "content", "type", "accept"],
349
- httpMethods: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
350
- patterns: [
351
- /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/,
352
- /allowed.*method|http.*method/i,
353
- /content-type|accept|user-agent/i,
354
- ],
355
- },
356
- timeouts: {
357
- keywords: ["connect", "read", "write", "timeout", "socket"],
358
- patterns: [
359
- /connect.*timeout|connection.*timeout/i,
360
- /read.*timeout|write.*timeout/i,
361
- /socket.*timeout|idle.*timeout/i,
362
- ],
363
- },
364
- bufferConfig: {
365
- keywords: ["buffer", "size", "socket", "receive", "send"],
366
- patterns: [/buffer.*size|socket.*buffer/i, /receive.*buffer|send.*buffer/i],
367
- },
368
- keepAlive: {
369
- keywords: ["keepalive", "keep", "alive", "ping", "interval"],
370
- patterns: [/keep.*alive|keepalive/i, /ping.*interval|heartbeat/i, /websocket.*ping/i],
371
- },
372
- },
373
-
374
- // Business Rules & Limits
375
- business: {
376
- pricing: {
377
- keywords: ["price", "cost", "fee", "charge", "plan"],
378
- patterns: [
379
- /^\d+\.\d{2}$/, // Price values: 49.99, 19.95
380
- /price|cost|fee|charge/i,
381
- /plan.*(price|cost)/i,
382
- ],
383
- },
384
- quotas: {
385
- keywords: ["quota", "limit", "plan", "tier", "free", "premium"],
386
- patterns: [
387
- /quota|limit.*per.*plan|plan.*limit/i,
388
- /free.*plan|premium.*plan|enterprise.*plan/i,
389
- /api.*calls.*per|request.*per.*day/i,
390
- ],
391
- },
392
- discounts: {
393
- keywords: ["discount", "promo", "coupon", "rate", "percent"],
394
- patterns: [
395
- /^0\.[0-9]{1,2}$/, // Decimal rates: 0.15 (15%)
396
- /discount.*rate|promo.*code/i,
397
- ],
398
- },
399
- trials: {
400
- keywords: ["trial", "demo", "expiry", "days", "period"],
401
- patterns: [/trial.*days|trial.*period/i, /demo.*period|expiry.*days/i],
402
- },
403
- },
404
-
405
- // Monitoring & Observability
406
- monitoring: {
407
- metricsConfig: {
408
- keywords: ["metric", "interval", "collect", "sample", "export"],
409
- patterns: [/metric.*interval|collection.*interval/i, /export.*interval|push.*interval/i],
410
- },
411
- alertThresholds: {
412
- keywords: ["alert", "threshold", "warn", "error", "critical"],
413
- patterns: [
414
- /alert.*threshold|threshold.*alert/i,
415
- /error.*rate|error.*threshold/i,
416
- /^0\.[0-9]{1,3}$/, // Percentage thresholds: 0.05 (5%)
417
- ],
418
- },
419
- samplingRates: {
420
- keywords: ["sample", "sampling", "rate", "trace"],
421
- patterns: [
422
- /sampling.*rate|sample.*rate/i,
423
- /trace.*sampling/i,
424
- /^0\.[0-9]{1,2}$/, // Sampling rates: 0.1 (10%)
425
- ],
426
- },
427
- healthChecks: {
428
- keywords: ["health", "check", "interval", "ping", "probe"],
429
- patterns: [/health.*check|liveness|readiness/i, /check.*interval|probe.*interval/i],
430
- },
431
- },
432
-
433
- // ============ Phase 4: Enhancement Configuration Patterns ============
434
-
435
- // Message Queue & Event Configuration
436
- messageQueue: {
437
- queueConfig: {
438
- keywords: ["queue", "size", "capacity", "max", "buffer"],
439
- patterns: [/queue.*size|max.*queue|queue.*capacity/i, /buffer.*size.*queue/i],
440
- },
441
- messageTTL: {
442
- keywords: ["message", "ttl", "expiry", "expire", "retention"],
443
- patterns: [/message.*ttl|message.*expiry/i, /retention.*period|expire.*after/i],
444
- },
445
- queueNames: {
446
- keywords: ["queue", "topic", "exchange", "dlq", "dead"],
447
- patterns: [
448
- /^[a-z-]+-queue$/i, // Queue names: user-events-queue
449
- /dead.*letter|dlq|failed.*messages?/i,
450
- /topic.*name|exchange.*name/i,
451
- ],
452
- },
453
- consumerConfig: {
454
- keywords: ["consumer", "group", "partition", "offset"],
455
- patterns: [/consumer.*group|group.*id/i, /partition.*count|offset.*reset/i],
456
- },
457
- },
458
-
459
- // Deployment & Infrastructure
460
- deployment: {
461
- resourceLimits: {
462
- keywords: ["cpu", "memory", "limit", "request", "resource"],
463
- patterns: [
464
- /^\d+m$/i, // CPU millicores: 2000m
465
- /^\d+[MG]i$/i, // Memory: 4Gi, 512Mi
466
- /cpu.*limit|memory.*limit/i,
467
- /resource.*limit|resource.*request/i,
468
- ],
469
- },
470
- scalingConfig: {
471
- keywords: ["scale", "replica", "min", "max", "threshold"],
472
- patterns: [
473
- /min.*replicas?|max.*replicas?/i,
474
- /scale.*threshold|auto.*scale/i,
475
- /horizontal.*pod.*autoscaler|hpa/i,
476
- ],
477
- },
478
- regionConfig: {
479
- keywords: ["region", "zone", "location", "deploy", "availability"],
480
- patterns: [
481
- /^[a-z]{2}-[a-z]+-\d+$/i, // AWS regions: us-east-1
482
- /deploy.*region|region.*name/i,
483
- /availability.*zone/i,
484
- ],
485
- },
486
- instanceTypes: {
487
- keywords: ["instance", "type", "machine", "node", "vm"],
488
- patterns: [
489
- /^[a-z]\d\.[a-z]+$/i, // AWS instance types: t3.micro
490
- /instance.*type|machine.*type/i,
491
- /node.*selector|node.*type/i,
492
- ],
493
- },
494
- },
495
-
496
- // Third-party Integration
497
- integration: {
498
- webhookURLs: {
499
- keywords: ["webhook", "callback", "notify", "hook"],
500
- patterns: [/webhook|callback.*url/i, /^https?:\/\/[^\/]+\/webhooks?\//i],
501
- },
502
- externalServices: {
503
- keywords: ["provider", "service", "integration", "api"],
504
- patterns: [
505
- /^(stripe|paypal|twilio|sendgrid|slack|github)$/i, // Service names
506
- /provider.*name|integration.*name/i,
507
- /external.*service/i,
508
- ],
509
- },
510
- apiVersions: {
511
- keywords: ["version", "api", "v"],
512
- patterns: [
513
- /^v?\d{4}-\d{2}-\d{2}$/i, // Date versions: 2023-10-16
514
- /^v\d+(\.\d+)?$/i, // Semantic versions: v1, v2.1
515
- /api.*version/i,
516
- ],
517
- },
518
- channelIds: {
519
- keywords: ["channel", "room", "chat", "notify"],
520
- patterns: [
521
- /^#[a-z-]+$/i, // Slack channels: #production-alerts
522
- /channel.*id|room.*id/i,
523
- ],
524
- },
525
- },
526
-
527
- // Localization & Formatting
528
- localization: {
529
- timezones: {
530
- keywords: ["timezone", "zone", "tz"],
531
- patterns: [
532
- /^[A-Z][a-z]+\/[A-Z][a-z]+$/i, // IANA timezones: Asia/Tokyo
533
- /timezone|time.*zone/i,
534
- ],
535
- },
536
- dateFormats: {
537
- keywords: ["date", "format", "pattern", "time"],
538
- patterns: [
539
- /^[YMDHms\-\/:\s]+$/, // Date format patterns: YYYY-MM-DD
540
- /date.*format|time.*format/i,
541
- /format.*string.*date/i,
542
- ],
543
- },
544
- currencies: {
545
- keywords: ["currency", "code", "symbol"],
546
- patterns: [
547
- /^[A-Z]{3}$/i, // Currency codes: USD, EUR, JPY
548
- /currency.*code/i,
549
- ],
550
- },
551
- locales: {
552
- keywords: ["locale", "language", "lang", "i18n"],
553
- patterns: [
554
- /^[a-z]{2}-[A-Z]{2}$/i, // Locale codes: en-US, ja-JP
555
- /locale|language.*code/i,
556
- ],
557
- },
558
- numberFormats: {
559
- keywords: ["number", "format", "decimal", "thousand", "separator"],
560
- patterns: [/number.*format|decimal.*separator/i, /thousand.*separator/i],
561
- },
562
- },
563
-
564
- // ============ Additional Critical Patterns ============
565
-
566
- // Environment Variable Names (hardcoded)
567
- environmentVars: {
568
- keywords: ["process.env", "env"],
569
- patterns: [
570
- /^(PROD|DEV|STAGING|TEST)_[A-Z_]+$/i, // Environment-prefixed vars
571
- /^[A-Z_]+_(PROD|DEV|STAGING|TEST)$/i,
572
- /^(PRODUCTION|DEVELOPMENT)_/i,
573
- ],
574
- },
575
-
576
- // Third-party Service IDs
577
- thirdPartyServices: {
578
- stripe: {
579
- patterns: [
580
- /^pk_(test|live)_[a-zA-Z0-9]{24,}$/, // Publishable keys
581
- /^sk_(test|live)_[a-zA-Z0-9]{24,}$/, // Secret keys
582
- ],
583
- },
584
- googleAnalytics: {
585
- patterns: [
586
- /^UA-\d+-\d+$/, // Universal Analytics
587
- /^G-[A-Z0-9]{10}$/, // GA4
588
- /^GTM-[A-Z0-9]+$/, // Google Tag Manager
589
- ],
590
- },
591
- sentry: {
592
- patterns: [/^https?:\/\/[a-f0-9]+@[^\/]+\.ingest\.sentry\.io\/\d+$/i],
593
- },
594
- googleMaps: {
595
- patterns: [
596
- /^AIzaSy[a-zA-Z0-9_-]{33}$/, // Google Maps API Key
597
- ],
598
- },
599
- firebase: {
600
- patterns: [/^[a-z0-9-]+\.firebaseapp\.com$/i, /^[a-z0-9-]+\.firebase(io|database)\.com$/i],
601
- },
602
- aws: {
603
- patterns: [
604
- /^AKIA[A-Z0-9]{16}$/, // AWS Access Key ID
605
- /^[a-z0-9-]+\.s3\.[a-z0-9-]+\.amazonaws\.com$/i,
606
- ],
607
- },
608
- social: {
609
- patterns: [
610
- /^\d{15,16}$/, // Facebook App ID
611
- /^[a-zA-Z0-9]{25}$/, // Twitter Bearer Token format
612
- ],
613
- },
614
- },
615
-
616
- // IP Addresses & Hostnames
617
- ipAddresses: {
618
- patterns: [
619
- /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, // IPv4
620
- /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/, // IPv6
621
- /^(?:[0-9a-fA-F]{1,4}:){1,7}:$/, // IPv6 shortened
622
- ],
623
- keywords: ["host", "ip", "address", "server"],
624
- privateRanges: [/^10\./, /^172\.(1[6-9]|2[0-9]|3[0-1])\./, /^192\.168\./, /^127\./, /^0\.0\.0\.0$/],
625
- },
626
-
627
- // Internal Hostnames
628
- hostnames: {
629
- patterns: [
630
- /^[a-z0-9-]+\.(internal|local|corp|lan)$/i,
631
- /^[a-z0-9-]+-(?:master|slave|replica|primary|secondary)$/i,
632
- /^(?:db|redis|kafka|mongo|postgres|mysql)-[a-z0-9-]+$/i,
633
- ],
634
- keywords: ["host", "hostname", "server", "broker", "endpoint"],
635
- },
636
-
637
- // Cron Job Schedules
638
- cronSchedules: {
639
- patterns: [
640
- /^[\d\*\/,\-]+\s+[\d\*\/,\-]+\s+[\d\*\/,\-]+\s+[\d\*\/,\-]+\s+[\d\*\/,\-]+$/, // Standard cron
641
- /^@(?:yearly|annually|monthly|weekly|daily|hourly|reboot)$/i, // Predefined schedules
642
- ],
643
- keywords: ["cron", "schedule", "interval"],
644
- },
645
-
646
- // Magic Numbers (Business Logic)
647
- magicNumbers: {
648
- // Numbers that appear in business logic contexts
649
- keywords: ["age", "limit", "max", "min", "threshold", "rate", "tax", "fee", "discount", "commission"],
650
- businessContexts: [
651
- /legal.*age|minimum.*age|age.*requirement/i,
652
- /tax.*rate|vat.*rate|commission.*rate/i,
653
- /interest.*rate|exchange.*rate/i,
654
- /shipping.*fee|processing.*fee|service.*fee/i,
655
- /credit.*limit|withdrawal.*limit|transfer.*limit/i,
656
- ],
657
- },
658
-
659
- // Email & SMS Templates
660
- messageTemplates: {
661
- email: {
662
- keywords: ["subject", "body", "template", "email"],
663
- patterns: [
664
- /^[A-Z][a-zA-Z\s]{10,}$/, // English subject lines
665
- /\{{\s*[a-z_]+\s*\}}/, // Template variables
666
- ],
667
- },
668
- sms: {
669
- keywords: ["sms", "text", "message"],
670
- patterns: [/^Your\s+[A-Z]/i, /OTP|verification code|confirm/i],
671
- },
672
- },
673
-
674
- // Version Numbers
675
- versions: {
676
- patterns: [
677
- /^v?\d+\.\d+\.\d+(?:-[a-z0-9.]+)?$/i, // Semantic version: 1.2.3, v1.2.3-beta
678
- /^v\d+$/i, // API version: v1, v2
679
- ],
680
- keywords: ["version", "api.*version", "min.*version", "max.*version"],
681
- },
682
-
683
- // Default Pagination
684
- pagination: {
685
- keywords: ["page", "limit", "size", "offset", "per.*page"],
686
- defaults: [10, 20, 25, 50, 100], // Common pagination values
687
- },
688
- };
689
22
  }
690
23
 
691
24
  async initialize(semanticEngine = null) {
@@ -701,29 +34,15 @@ class C067SymbolBasedAnalyzer {
701
34
  try {
702
35
  const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
703
36
  if (!sourceFile) {
704
- if (this.verbose) {
705
- console.log(
706
- `[DEBUG] 🔍 C067: File not in semantic project, trying standalone: ${filePath.split("/").pop()}`
707
- );
708
- }
709
- // Fallback to standalone analysis if file not in semantic project
710
37
  return await this.analyzeFileStandalone(filePath, options);
711
38
  }
712
39
 
713
- if (this.verbose) {
714
- console.log(`[DEBUG] 🔍 C067: Analyzing hardcoded config in ${filePath.split("/").pop()}`);
715
- }
716
-
717
- // Skip test files and config files themselves
718
- if (this.isConfigOrTestFile(filePath)) {
719
- if (this.verbose) {
720
- console.log(`[DEBUG] 🔍 C067: Skipping config/test file: ${filePath.split("/").pop()}`);
721
- }
40
+ // Skip config/constant files - they ARE the centralized config location
41
+ if (this.isConfigFile(filePath)) {
722
42
  return violations;
723
43
  }
724
44
 
725
- // Find hardcoded configuration values
726
- const hardcodedConfigs = this.findHardcodedConfigs(sourceFile);
45
+ const hardcodedConfigs = this.findHardcodedConfigs(sourceFile, filePath);
727
46
 
728
47
  for (const config of hardcodedConfigs) {
729
48
  violations.push({
@@ -740,15 +59,8 @@ class C067SymbolBasedAnalyzer {
740
59
  });
741
60
  }
742
61
 
743
- if (this.verbose) {
744
- console.log(`[DEBUG] 🔍 C067: Found ${violations.length} hardcoded config violations`);
745
- }
746
-
747
62
  return violations;
748
63
  } catch (error) {
749
- if (this.verbose) {
750
- console.error(`[DEBUG] ❌ C067: Symbol analysis error: ${error.message}`);
751
- }
752
64
  throw error;
753
65
  }
754
66
  }
@@ -757,54 +69,34 @@ class C067SymbolBasedAnalyzer {
757
69
  const violations = [];
758
70
 
759
71
  try {
760
- // Create a standalone ts-morph project for this analysis
761
72
  const project = new Project({
762
73
  compilerOptions: {
763
74
  target: "ES2020",
764
75
  module: "CommonJS",
765
76
  allowJs: true,
766
- allowSyntheticDefaultImports: true,
767
- esModuleInterop: true,
768
77
  skipLibCheck: true,
769
78
  strict: false,
770
79
  },
771
80
  useInMemoryFileSystem: true,
772
81
  });
773
82
 
774
- // Add the source file to the project
775
83
  const fs = require("fs");
776
84
  const path = require("path");
777
85
 
778
- // Check if file exists first
779
86
  if (!fs.existsSync(filePath)) {
780
- throw new Error(`File not found on filesystem: ${filePath}`);
87
+ throw new Error(`File not found: ${filePath}`);
781
88
  }
782
89
 
783
- // Read file content and create source file
784
90
  const fileContent = fs.readFileSync(filePath, "utf8");
785
91
  const fileName = path.basename(filePath);
786
92
  const sourceFile = project.createSourceFile(fileName, fileContent);
787
93
 
788
- if (!sourceFile) {
789
- throw new Error(`Source file not found: ${filePath}`);
790
- }
791
-
792
- if (this.verbose) {
793
- console.log(
794
- `[DEBUG] 🔍 C067: Analyzing hardcoded config in ${filePath.split("/").pop()} (standalone)`
795
- );
796
- }
797
-
798
- // Skip test files and config files themselves
799
- if (this.isConfigOrTestFile(filePath)) {
800
- if (this.verbose) {
801
- console.log(`[DEBUG] 🔍 C067: Skipping config/test file: ${filePath.split("/").pop()}`);
802
- }
94
+ // Skip config/constant files
95
+ if (this.isConfigFile(filePath)) {
803
96
  return violations;
804
97
  }
805
98
 
806
- // Find hardcoded configuration values
807
- const hardcodedConfigs = this.findHardcodedConfigs(sourceFile);
99
+ const hardcodedConfigs = this.findHardcodedConfigs(sourceFile, filePath);
808
100
 
809
101
  for (const config of hardcodedConfigs) {
810
102
  violations.push({
@@ -821,3032 +113,475 @@ class C067SymbolBasedAnalyzer {
821
113
  });
822
114
  }
823
115
 
824
- if (this.verbose) {
825
- console.log(`[DEBUG] 🔍 C067: Found ${violations.length} hardcoded config violations (standalone)`);
826
- }
827
-
828
- // Clean up the project
829
- project.removeSourceFile(sourceFile);
830
-
831
116
  return violations;
832
117
  } catch (error) {
833
- if (this.verbose) {
834
- console.error(`[DEBUG] ❌ C067: Standalone analysis error: ${error.message}`);
835
- }
836
118
  throw error;
837
119
  }
838
120
  }
839
121
 
840
- isConfigOrTestFile(filePath) {
841
- // Skip config files themselves and test files, including dummy/test data files
122
+ /**
123
+ * Check if file is a centralized config file (should be skipped)
124
+ */
125
+ isConfigFile(filePath) {
842
126
  const fileName = filePath.toLowerCase();
127
+
843
128
  const configPatterns = [
129
+ // Config files
844
130
  /config\.(ts|js|json)$/,
845
131
  /\.config\.(ts|js)$/,
846
- /\.env$/,
847
- /\.env\./,
848
- /constants?\.(ts|js)$/, // constants.ts, constant.ts
849
- /\.constants?\.(ts|js)$/, // app.constants.ts, common.constant.ts
132
+ /configs?\//,
133
+ /configuration\//,
134
+
135
+ // Constants files - centralized config location
136
+ /constants?\.(ts|js)$/,
137
+ /\.constants?\.(ts|js)$/,
138
+
139
+ // Settings/defaults
850
140
  /settings\.(ts|js)$/,
851
- /defaults\.(ts|js)$/,
852
- ];
141
+ /defaults?\.(ts|js)$/,
853
142
 
854
- const testPatterns = [
143
+ // Environment files
144
+ /\.env/,
145
+ /environment\.(ts|js)$/,
146
+
147
+ // Test files
855
148
  /\.(test|spec)\.(ts|tsx|js|jsx)$/,
856
149
  /\/__tests__\//,
857
150
  /\/test\//,
858
151
  /\/tests\//,
859
- /\.stories\.(ts|tsx|js|jsx)$/,
860
- /\.mock\.(ts|tsx|js|jsx)$/,
861
- /\/dummy\//, // Skip dummy data files
862
- /dummy\.(ts|js)$/, // Skip dummy files
863
- /test-fixtures\//, // Skip test fixture files
864
- /\.fixture\.(ts|js)$/, // Skip fixture files
865
- /entity\.(ts|js)$/, // Skip entity/ORM files (contain DB constraints)
152
+ /\.mock\./,
153
+ /\.fixture\./,
154
+ /test-fixtures\//,
155
+
156
+ // Entity/model files (contain DB constraints)
157
+ /\.entity\.(ts|js)$/,
158
+ /entities\//,
159
+
160
+ // DTO/interface files
161
+ /\.dto\.(ts|js)$/,
162
+ /\.interface\.(ts|js)$/,
163
+
164
+ // Enum files
165
+ /\.enum\.(ts|js)$/,
166
+ /enums\//,
866
167
  ];
867
168
 
868
- return (
869
- configPatterns.some((pattern) => pattern.test(fileName)) ||
870
- testPatterns.some((pattern) => pattern.test(fileName))
871
- );
169
+ return configPatterns.some(pattern => pattern.test(fileName));
872
170
  }
873
171
 
874
- findHardcodedConfigs(sourceFile) {
172
+ /**
173
+ * Find hardcoded configurations - focused on real violations
174
+ */
175
+ findHardcodedConfigs(sourceFile, filePath) {
875
176
  const configs = [];
876
177
 
877
- // Traverse all nodes in the source file
878
178
  sourceFile.forEachDescendant((node) => {
879
- // Check string literals for URLs, credentials, feature flags
179
+ // Check string literals for URLs and credentials
880
180
  if (node.getKind() === SyntaxKind.StringLiteral) {
881
181
  const config = this.analyzeStringLiteral(node, sourceFile);
882
- if (config) {
883
- configs.push(config);
884
- }
182
+ if (config) configs.push(config);
885
183
  }
886
184
 
887
- // Check numeric literals for timeouts, ports, batch sizes
185
+ // Check numeric literals for timeouts/intervals in specific contexts
888
186
  if (node.getKind() === SyntaxKind.NumericLiteral) {
889
187
  const config = this.analyzeNumericLiteral(node, sourceFile);
890
- if (config) {
891
- configs.push(config);
892
- }
893
- }
894
-
895
- // Check boolean literals for feature flags
896
- if (node.getKind() === SyntaxKind.TrueKeyword || node.getKind() === SyntaxKind.FalseKeyword) {
897
- const config = this.analyzeBooleanLiteral(node, sourceFile);
898
- if (config) {
899
- configs.push(config);
900
- }
901
- }
902
-
903
- // Check template literals for URLs with embedded values
904
- if (node.getKind() === SyntaxKind.TemplateExpression) {
905
- const config = this.analyzeTemplateLiteral(node, sourceFile);
906
- if (config) {
907
- configs.push(config);
908
- }
909
- }
910
-
911
- // Check property assignments for configuration objects
912
- if (node.getKind() === SyntaxKind.PropertyAssignment) {
913
- const config = this.analyzePropertyAssignment(node, sourceFile);
914
- if (config) {
915
- configs.push(config);
916
- }
917
- }
918
-
919
- // Check variable declarations for configuration constants
920
- if (node.getKind() === SyntaxKind.VariableDeclaration) {
921
- const config = this.analyzeVariableDeclaration(node, sourceFile);
922
- if (config) {
923
- configs.push(config);
924
- }
188
+ if (config) configs.push(config);
925
189
  }
926
190
  });
927
191
 
928
192
  return configs;
929
193
  }
930
194
 
195
+ /**
196
+ * Analyze string literals - focus on URLs and credentials
197
+ */
931
198
  analyzeStringLiteral(node, sourceFile) {
932
199
  const value = node.getLiteralValue();
933
200
  const position = sourceFile.getLineAndColumnAtPos(node.getStart());
934
201
 
935
- // Skip short strings and common UI values
936
- if (value.length < 3) return null;
202
+ // Skip short strings
203
+ if (value.length < 5) return null;
937
204
 
938
- const parentContext = this.getParentContext(node);
205
+ // Skip common safe patterns
206
+ if (this.isSafeString(value, node)) return null;
939
207
 
940
- // Skip import paths and module names
941
- if (this.isImportPath(value, node)) return null;
942
-
943
- // Skip UI strings and labels
944
- if (this.isUIString(value)) return null;
945
-
946
- // Skip Japanese UI text and form labels (NEW - reduces false positives)
947
- if (this.isJapaneseUIText(value, parentContext)) return null;
948
-
949
- // Skip form field labels (NEW - reduces false positives)
950
- if (this.isFormFieldLabel(value, parentContext)) return null;
951
-
952
- // Skip test data and mocks
953
- if (this.isTestData(value, parentContext)) return null;
954
-
955
- // Skip validation messages and error messages
956
- if (this.isValidationMessage(value, parentContext)) return null;
957
-
958
- // Skip file names and descriptions
959
- if (this.isFileNameOrDescription(value, parentContext)) return null;
960
-
961
- // Skip config keys (like 'api.baseUrl', 'features.newUI', etc.)
962
- if (this.looksLikeConfigKey(value)) {
963
- return null;
964
- }
965
-
966
- // Skip if this is used in a config service call
967
- if (
968
- parentContext.includes("config.get") ||
969
- parentContext.includes("config.getString") ||
970
- parentContext.includes("config.getBoolean") ||
971
- parentContext.includes("config.getNumber")
972
- ) {
973
- return null;
974
- }
975
-
976
- // Skip if this is a property key in an object literal
977
- if (this.isPropertyKey(node)) {
978
- return null;
979
- }
980
-
981
- // Check for API URLs and endpoints - Rule C067 requirement
982
- if (this.configPatterns.urls.regex.test(value)) {
983
- if (!this.isExcludedUrl(value, node) && this.isEnvironmentDependentUrl(value)) {
984
- return {
985
- type: "api_url",
986
- value: value,
987
- line: position.line,
988
- column: position.column,
989
- node: node,
990
- suggestion: "Move API URLs to environment variables or config files",
991
- };
992
- }
993
- }
994
-
995
- // Check for credentials - Rule C067 requirement
996
- if (this.isRealCredential(value, parentContext)) {
208
+ // 1. Detect hardcoded API URLs (http/https URLs)
209
+ if (this.isHardcodedUrl(value, node)) {
997
210
  return {
998
- type: "credential",
999
- value: this.maskSensitiveValue(value),
211
+ type: "api_url",
212
+ value: value.length > 60 ? value.substring(0, 60) + "..." : value,
1000
213
  line: position.line,
1001
214
  column: position.column,
1002
- node: node,
1003
- context: parentContext,
1004
- suggestion: "Move credentials to secure environment variables or vault",
1005
215
  };
1006
216
  }
1007
217
 
1008
- // Check for connection strings - Rule C067 requirement
1009
- if (this.configPatterns.connections.regex.test(value)) {
218
+ // 2. Detect hardcoded credentials
219
+ const credentialType = this.detectCredential(value, node);
220
+ if (credentialType) {
1010
221
  return {
1011
- type: "connection_string",
1012
- value: this.maskSensitiveValue(value),
222
+ type: credentialType,
223
+ value: this.maskValue(value),
1013
224
  line: position.line,
1014
225
  column: position.column,
1015
- node: node,
1016
- suggestion: "Move connection strings to environment variables",
1017
226
  };
1018
227
  }
1019
228
 
1020
- // Phase 1 extensions - Security & Infrastructure
229
+ return null;
230
+ }
1021
231
 
1022
- // Check for CORS origins
1023
- if (this.isCorsOrigin(value, parentContext)) {
1024
- return {
1025
- type: "cors_origin",
1026
- value: value,
1027
- line: position.line,
1028
- column: position.column,
1029
- node: node,
1030
- suggestion: "Move CORS origins to environment configuration",
1031
- };
1032
- }
232
+ /**
233
+ * Check if string is a hardcoded URL that should be externalized
234
+ */
235
+ isHardcodedUrl(value, node) {
236
+ // Must be HTTP/HTTPS URL
237
+ if (!value.match(/^https?:\/\//i)) return false;
1033
238
 
1034
- // Check for session/JWT configuration
1035
- if (this.isSessionConfig(value, parentContext)) {
1036
- return {
1037
- type: "session_config",
1038
- value: value,
1039
- line: position.line,
1040
- column: position.column,
1041
- node: node,
1042
- suggestion: "Move session configuration to environment variables",
1043
- };
1044
- }
239
+ // Skip localhost/127.0.0.1 - these are often dev defaults
240
+ if (value.match(/localhost|127\.0\.0\.1|0\.0\.0\.0/i)) return false;
1045
241
 
1046
- // Check for cache configuration
1047
- if (this.isCacheConfig(value, parentContext)) {
1048
- return {
1049
- type: "cache_config",
1050
- value: value,
1051
- line: position.line,
1052
- column: position.column,
1053
- node: node,
1054
- suggestion: "Move cache configuration to environment settings",
1055
- };
1056
- }
242
+ // Skip placeholder/example URLs
243
+ if (value.match(/example\.com|placeholder|test\.com|sample\.com/i)) return false;
1057
244
 
1058
- // Check for log levels
1059
- if (this.isLogLevel(value, parentContext)) {
1060
- return {
1061
- type: "log_level",
1062
- value: value,
1063
- line: position.line,
1064
- column: position.column,
1065
- node: node,
1066
- suggestion: "Move log level to environment configuration",
1067
- };
1068
- }
245
+ // Skip documentation URLs
246
+ if (value.match(/docs\.|documentation|readme|github\.com|stackoverflow/i)) return false;
1069
247
 
1070
- // Check for environment names
1071
- if (this.isEnvironmentName(value, parentContext)) {
1072
- return {
1073
- type: "environment_name",
1074
- value: value,
1075
- line: position.line,
1076
- column: position.column,
1077
- node: node,
1078
- suggestion: "Avoid hardcoding environment names, use environment detection",
1079
- };
1080
- }
248
+ // Skip XML/SVG namespace URLs
249
+ if (value.match(/w3\.org|xmlns|schema\.org/i)) return false;
1081
250
 
1082
- // Check for service dependencies
1083
- if (this.isServiceDependency(value, parentContext)) {
1084
- return {
1085
- type: "service_dependency",
1086
- value: value,
1087
- line: position.line,
1088
- column: position.column,
1089
- node: node,
1090
- suggestion: "Move service URLs to service discovery or configuration",
1091
- };
1092
- }
251
+ // Skip CDN/public resource URLs (fonts, icons, etc.)
252
+ if (value.match(/fonts\.googleapis|fonts\.gstatic|cdnjs|unpkg|jsdelivr|fontawesome|cdn\./i)) return false;
1093
253
 
1094
- // ============ Phase 2: Critical String Configurations ============
254
+ // Check parent context - skip if in safe locations
255
+ const parent = node.getParent();
256
+ if (parent) {
257
+ const parentText = parent.getText().toLowerCase();
1095
258
 
1096
- // Check for database schema/table names
1097
- if (this.isSchemaName(value, parentContext)) {
1098
- return {
1099
- type: "schema_name",
1100
- value: value,
1101
- line: position.line,
1102
- column: position.column,
1103
- node: node,
1104
- suggestion: "Move schema names to database configuration",
1105
- };
259
+ // Skip error messages, logging
260
+ if (parentText.match(/\berror\b|\blog\b|\bwarn\b|\binfo\b|\bdebug\b|\bmessage\b|\bdescription\b/)) return false;
1106
261
  }
1107
262
 
1108
- // Check for encryption algorithms
1109
- if (this.isEncryptionConfig(value, parentContext)) {
1110
- return {
1111
- type: "encryption_config",
1112
- value: value,
1113
- line: position.line,
1114
- column: position.column,
1115
- node: node,
1116
- suggestion: "Move encryption configuration to security settings",
1117
- };
1118
- }
263
+ // This looks like a real hardcoded API URL
264
+ return true;
265
+ }
1119
266
 
1120
- // Check for OAuth scopes
1121
- if (this.isOAuthConfig(value, parentContext)) {
1122
- return {
1123
- type: "oauth_config",
1124
- value: value,
1125
- line: position.line,
1126
- column: position.column,
1127
- node: node,
1128
- suggestion: "Move OAuth configuration to authentication settings",
1129
- };
1130
- }
267
+ /**
268
+ * Detect hardcoded credentials
269
+ */
270
+ detectCredential(value, node) {
271
+ // Skip import paths and module paths
272
+ if (value.includes("/") || value.startsWith("~") || value.startsWith("@")) return null;
1131
273
 
1132
- // Check for directories and paths
1133
- if (this.isDirectory(value, parentContext)) {
1134
- return {
1135
- type: "directory",
1136
- value: value,
1137
- line: position.line,
1138
- column: position.column,
1139
- node: node,
1140
- suggestion: "Move directory paths to environment configuration",
1141
- };
274
+ // Check AWS key pattern FIRST (before class name skip)
275
+ if (value.match(/^AKIA[A-Z0-9]{16}$/)) {
276
+ return "aws_key";
1142
277
  }
1143
278
 
1144
- // Check for file extensions and MIME types
1145
- if (this.isFileType(value, parentContext)) {
1146
- return {
1147
- type: "file_type",
1148
- value: value,
1149
- line: position.line,
1150
- column: position.column,
1151
- node: node,
1152
- suggestion: "Move file type restrictions to configuration",
1153
- };
1154
- }
279
+ // Skip if value looks like a class name, module name, or identifier
280
+ // But not if it's all uppercase (could be a key/secret)
281
+ if (value.match(/^[A-Z][a-zA-Z0-9]*$/) && !value.match(/^[A-Z0-9]+$/)) return null;
282
+ if (value.match(/^[a-z][a-zA-Z0-9]*Service$/)) return null;
283
+ if (value.match(/^[a-z][a-zA-Z0-9]*Controller$/)) return null;
284
+ if (value.match(/^[a-z][a-zA-Z0-9]*Module$/)) return null;
1155
285
 
1156
- // Check for log file paths
1157
- if (this.isLogPath(value, parentContext)) {
1158
- return {
1159
- type: "log_path",
1160
- value: value,
1161
- line: position.line,
1162
- column: position.column,
1163
- node: node,
1164
- suggestion: "Move log paths to logging configuration",
1165
- };
286
+ const parent = node.getParent();
287
+ if (!parent) return null;
288
+
289
+ // Skip import declarations
290
+ const grandParent = parent.getParent();
291
+ if (grandParent) {
292
+ const gpKind = grandParent.getKindName();
293
+ if (gpKind === "ImportDeclaration" || gpKind === "ImportSpecifier") return null;
1166
294
  }
1167
295
 
1168
- // ============ Phase 3: Important String Configurations ============
296
+ const parentText = parent.getText().toLowerCase();
297
+ const parentKind = parent.getKindName();
1169
298
 
1170
- // Check for HTTP methods and headers
1171
- if (this.isHttpConfig(value, parentContext)) {
1172
- return {
1173
- type: "http_config",
1174
- value: value,
1175
- line: position.line,
1176
- column: position.column,
1177
- node: node,
1178
- suggestion: "Move HTTP configuration to network settings",
1179
- };
299
+ // Only check property assignments and variable declarations
300
+ if (parentKind !== "PropertyAssignment" && parentKind !== "VariableDeclaration" && parentKind !== "BinaryExpression") {
301
+ return null;
1180
302
  }
1181
303
 
1182
- // Check for queue/topic names
1183
- if (this.isQueueConfig(value, parentContext)) {
1184
- return {
1185
- type: "queue_config",
1186
- value: value,
1187
- line: position.line,
1188
- column: position.column,
1189
- node: node,
1190
- suggestion: "Move queue names to messaging configuration",
1191
- };
1192
- }
304
+ // Check if parent property name suggests credential
305
+ const credentialPatterns = [
306
+ { pattern: /password\s*[=:]|passwd\s*[=:]|pwd\s*[=:]/i, type: "password" },
307
+ { pattern: /api[_-]?key\s*[=:]|apikey\s*[=:]/i, type: "api_key" },
308
+ { pattern: /secret[_-]?key\s*[=:]|secretkey\s*[=:]|client[_-]?secret\s*[=:]/i, type: "secret" },
309
+ { pattern: /access[_-]?token\s*[=:]|auth[_-]?token\s*[=:]/i, type: "token" },
310
+ { pattern: /private[_-]?key\s*[=:]/i, type: "private_key" },
311
+ ];
1193
312
 
1194
- // Check for consumer group IDs
1195
- if (this.isConsumerConfig(value, parentContext)) {
1196
- return {
1197
- type: "consumer_config",
1198
- value: value,
1199
- line: position.line,
1200
- column: position.column,
1201
- node: node,
1202
- suggestion: "Move consumer configuration to messaging settings",
1203
- };
1204
- }
313
+ for (const { pattern, type } of credentialPatterns) {
314
+ if (pattern.test(parentText)) {
315
+ // Skip if value is from env or config
316
+ if (value.match(/process\.env|config\.|getenv|env\[/i)) return null;
1205
317
 
1206
- // ============ Phase 4: Enhancement String Configurations ============
318
+ // Skip placeholder values (but be specific, not just starting patterns)
319
+ if (value.match(/^(your[-_]?(password|key|secret|token)|my[-_]?(password|key|secret|token)|test[-_]?(password|key|secret|token)|placeholder|xxx+|changeme|TODO|FIXME)/i)) return null;
320
+ if (value.match(/^(example|sample|demo)[-_]/i)) return null;
1207
321
 
1208
- // Check for resource limits (Kubernetes style)
1209
- if (this.isResourceLimit(value, parentContext)) {
1210
- return {
1211
- type: "resource_limit",
1212
- value: value,
1213
- line: position.line,
1214
- column: position.column,
1215
- node: node,
1216
- suggestion: "Move resource limits to deployment configuration",
1217
- };
1218
- }
322
+ // Skip empty or very short values
323
+ if (value.length < 6) return null;
1219
324
 
1220
- // Check for deployment regions
1221
- if (this.isRegionConfig(value, parentContext)) {
1222
- return {
1223
- type: "region_config",
1224
- value: value,
1225
- line: position.line,
1226
- column: position.column,
1227
- node: node,
1228
- suggestion: "Move region configuration to deployment settings",
1229
- };
325
+ return type;
326
+ }
1230
327
  }
1231
328
 
1232
- // Check for instance types
1233
- if (this.isInstanceType(value, parentContext)) {
1234
- return {
1235
- type: "instance_type",
1236
- value: value,
1237
- line: position.line,
1238
- column: position.column,
1239
- node: node,
1240
- suggestion: "Move instance types to infrastructure configuration",
1241
- };
329
+ // Check value patterns that look like credentials
330
+ // Connection strings with credentials
331
+ if (value.match(/mongodb(\+srv)?:\/\/[^:]+:[^@]+@/i) ||
332
+ value.match(/postgres:\/\/[^:]+:[^@]+@/i) ||
333
+ value.match(/mysql:\/\/[^:]+:[^@]+@/i) ||
334
+ value.match(/redis:\/\/:[^@]+@/i)) {
335
+ return "connection_string";
1242
336
  }
1243
337
 
1244
- // Check for webhook URLs
1245
- if (this.isWebhookURL(value, parentContext)) {
1246
- return {
1247
- type: "webhook_url",
1248
- value: value,
1249
- line: position.line,
1250
- column: position.column,
1251
- node: node,
1252
- suggestion: "Move webhook URLs to integration configuration",
1253
- };
338
+ // JWT tokens (base64 with dots)
339
+ if (value.match(/^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/)) {
340
+ return "jwt_token";
1254
341
  }
1255
342
 
1256
- // Check for external service names
1257
- if (this.isExternalService(value, parentContext)) {
1258
- return {
1259
- type: "external_service",
1260
- value: value,
1261
- line: position.line,
1262
- column: position.column,
1263
- node: node,
1264
- suggestion: "Move service provider names to integration configuration",
1265
- };
343
+ // AWS-style keys
344
+ if (value.match(/^AKIA[A-Z0-9]{16}$/)) {
345
+ return "aws_key";
1266
346
  }
1267
347
 
1268
- // Check for API versions
1269
- if (this.isApiVersion(value, parentContext)) {
1270
- return {
1271
- type: "api_version",
1272
- value: value,
1273
- line: position.line,
1274
- column: position.column,
1275
- node: node,
1276
- suggestion: "Move API versions to integration configuration",
1277
- };
1278
- }
348
+ return null;
349
+ }
350
+
351
+ /**
352
+ * Analyze numeric literals - only in specific timeout/config contexts
353
+ */
354
+ analyzeNumericLiteral(node, sourceFile) {
355
+ const value = node.getLiteralValue();
356
+ const position = sourceFile.getLineAndColumnAtPos(node.getStart());
357
+
358
+ const parent = node.getParent();
359
+ if (!parent) return null;
360
+
361
+ const parentText = parent.getText().toLowerCase();
362
+ const grandParent = parent.getParent();
363
+ const grandParentText = grandParent ? grandParent.getText().toLowerCase() : "";
1279
364
 
1280
- // Check for channel IDs (Slack, etc.)
1281
- if (this.isChannelId(value, parentContext)) {
365
+ // Check specific config patterns FIRST (before safe number check)
366
+ // This allows us to detect batch_size: 1000 even though 1000 is normally "safe"
367
+
368
+ // Only flag numbers in specific configuration contexts
369
+ // 1. Timeout configurations (must be in setTimeout context or named as timeout)
370
+ if (this.isTimeoutConfig(value, parentText, grandParentText)) {
1282
371
  return {
1283
- type: "channel_id",
372
+ type: "timeout",
1284
373
  value: value,
1285
374
  line: position.line,
1286
375
  column: position.column,
1287
- node: node,
1288
- suggestion: "Move channel IDs to notification configuration",
1289
376
  };
1290
377
  }
1291
378
 
1292
- // Check for timezones
1293
- if (this.isTimezone(value, parentContext)) {
379
+ // 2. Retry configurations
380
+ if (this.isRetryConfig(value, parentText, grandParentText)) {
1294
381
  return {
1295
- type: "timezone",
382
+ type: "retry_config",
1296
383
  value: value,
1297
384
  line: position.line,
1298
385
  column: position.column,
1299
- node: node,
1300
- suggestion: "Move timezone to localization configuration",
1301
386
  };
1302
387
  }
1303
388
 
1304
- // Check for date formats
1305
- if (this.isDateFormat(value, parentContext)) {
389
+ // 3. Batch size configurations
390
+ if (this.isBatchConfig(value, parentText, grandParentText)) {
1306
391
  return {
1307
- type: "date_format",
392
+ type: "batch_size",
1308
393
  value: value,
1309
394
  line: position.line,
1310
395
  column: position.column,
1311
- node: node,
1312
- suggestion: "Move date formats to localization configuration",
1313
396
  };
1314
397
  }
1315
398
 
1316
- // Check for currency codes
1317
- if (this.isCurrency(value, parentContext)) {
1318
- return {
1319
- type: "currency",
1320
- value: value,
1321
- line: position.line,
1322
- column: position.column,
1323
- node: node,
1324
- suggestion: "Move currency codes to localization configuration",
1325
- };
1326
- }
1327
-
1328
- // Check for locale identifiers
1329
- if (this.isLocale(value, parentContext)) {
1330
- return {
1331
- type: "locale",
1332
- value: value,
1333
- line: position.line,
1334
- column: position.column,
1335
- node: node,
1336
- suggestion: "Move locale to localization configuration",
1337
- };
1338
- }
1339
-
1340
- // ============ Additional Critical Configurations ============
1341
-
1342
- // Check for hardcoded environment variable names
1343
- if (this.isHardcodedEnvVar(value, parentContext)) {
1344
- return {
1345
- type: "environment_var",
1346
- value: value,
1347
- line: position.line,
1348
- column: position.column,
1349
- node: node,
1350
- suggestion: "Use environment-agnostic variable names or config service",
1351
- };
1352
- }
1353
-
1354
- // Check for third-party service IDs/keys
1355
- if (this.isThirdPartyServiceId(value, parentContext)) {
1356
- return {
1357
- type: "third_party_service",
1358
- value: this.maskSensitiveValue(value),
1359
- line: position.line,
1360
- column: position.column,
1361
- node: node,
1362
- suggestion: "Move API keys/service IDs to secret management (e.g., .env, AWS Secrets Manager)",
1363
- };
1364
- }
1365
-
1366
- // Check for IP addresses
1367
- if (this.isIPAddress(value, parentContext)) {
1368
- return {
1369
- type: "ip_address",
1370
- value: value,
1371
- line: position.line,
1372
- column: position.column,
1373
- node: node,
1374
- suggestion: "Move IP addresses to configuration or service discovery",
1375
- };
1376
- }
1377
-
1378
- // Check for internal hostnames
1379
- if (this.isInternalHostname(value, parentContext)) {
1380
- return {
1381
- type: "hostname",
1382
- value: value,
1383
- line: position.line,
1384
- column: position.column,
1385
- node: node,
1386
- suggestion: "Move hostnames to configuration or use service discovery",
1387
- };
1388
- }
1389
-
1390
- // Check for cron schedules
1391
- if (this.isCronSchedule(value, parentContext)) {
1392
- return {
1393
- type: "cron_schedule",
1394
- value: value,
1395
- line: position.line,
1396
- column: position.column,
1397
- node: node,
1398
- suggestion: "Move cron schedules to scheduler configuration",
1399
- };
1400
- }
1401
-
1402
- // Check for message templates
1403
- if (this.isMessageTemplate(value, parentContext)) {
1404
- return {
1405
- type: "message_template",
1406
- value: value.substring(0, 50) + (value.length > 50 ? "..." : ""),
1407
- line: position.line,
1408
- column: position.column,
1409
- node: node,
1410
- suggestion: "Move email/SMS templates to template management system",
1411
- };
1412
- }
1413
-
1414
- // Check for version numbers
1415
- if (this.isVersionNumber(value, parentContext)) {
1416
- return {
1417
- type: "version",
1418
- value: value,
1419
- line: position.line,
1420
- column: position.column,
1421
- node: node,
1422
- suggestion: "Move version numbers to build configuration or package.json",
1423
- };
1424
- }
1425
-
1426
- return null;
1427
- }
1428
-
1429
- analyzeBooleanLiteral(node, sourceFile) {
1430
- const value = node.getKind() === SyntaxKind.TrueKeyword;
1431
- const position = sourceFile.getLineAndColumnAtPos(node.getStart());
1432
- const parentContext = this.getParentContext(node);
1433
-
1434
- // Skip React state initial values (NEW - reduces false positives)
1435
- // Common patterns: useState(false), const [isOpen, setIsOpen] = useState(false)
1436
- if (/useState|useReducer|createContext/.test(parentContext)) {
1437
- return null;
1438
- }
1439
-
1440
- // Skip component props default values
1441
- if (/defaultProps|default\s*:/i.test(parentContext)) {
1442
- return null;
1443
- }
1444
-
1445
- // Skip UI state management (open, close, show, hide, loading, disabled, etc.)
1446
- const uiStatePatterns = [
1447
- /isOpen|isClose|isShow|isHide/i,
1448
- /isVisible|isHidden/i,
1449
- /isLoading|isDisabled|isEnabled|isActive/i,
1450
- /isChecked|isSelected/i,
1451
- /hasError|hasWarning/i,
1452
- /showModal|closeModal|openDialog/i,
1453
- ];
1454
-
1455
- if (uiStatePatterns.some((pattern) => pattern.test(parentContext))) {
1456
- return null;
1457
- }
1458
-
1459
- // Check if this boolean is a feature flag or toggle - Rule C067 requirement
1460
- if (this.isFeatureFlag(parentContext)) {
1461
- return {
1462
- type: "feature_flag",
1463
- value: value,
1464
- line: position.line,
1465
- column: position.column,
1466
- node: node,
1467
- context: parentContext,
1468
- suggestion: "Move feature flags to configuration management system",
1469
- };
1470
- }
1471
-
1472
- return null;
1473
- }
1474
-
1475
- analyzeNumericLiteral(node, sourceFile) {
1476
- const value = node.getLiteralValue();
1477
- const position = sourceFile.getLineAndColumnAtPos(node.getStart());
1478
- const parentContext = this.getParentContext(node);
1479
-
1480
- // Check for timeouts and retry intervals - Rule C067 requirement
1481
- if (this.isTimeout(value, parentContext)) {
1482
- return {
1483
- type: "timeout",
1484
- value: value,
1485
- line: position.line,
1486
- column: position.column,
1487
- node: node,
1488
- context: parentContext,
1489
- suggestion: "Move timeout values to configuration files",
1490
- };
1491
- }
1492
-
1493
- // Check for retry intervals - Rule C067 requirement
1494
- if (this.isRetryInterval(value, parentContext)) {
1495
- return {
1496
- type: "retry_interval",
1497
- value: value,
1498
- line: position.line,
1499
- column: position.column,
1500
- node: node,
1501
- context: parentContext,
1502
- suggestion: "Move retry intervals to environment configuration",
1503
- };
1504
- }
1505
-
1506
- // Check for batch sizes - Rule C067 requirement
1507
- if (this.isBatchSize(value, parentContext)) {
1508
- return {
1509
- type: "batch_size",
1510
- value: value,
1511
- line: position.line,
1512
- column: position.column,
1513
- node: node,
1514
- context: parentContext,
1515
- suggestion: "Move batch sizes to configuration management",
1516
- };
1517
- }
1518
-
1519
- // Check for thresholds that might vary by environment
1520
- if (this.isThreshold(value, parentContext)) {
1521
- return {
1522
- type: "threshold",
1523
- value: value,
1524
- line: position.line,
1525
- column: position.column,
1526
- node: node,
1527
- context: parentContext,
1528
- suggestion: "Move thresholds to environment-specific configuration",
1529
- };
1530
- }
1531
-
1532
- // Phase 1 extensions - Infrastructure numeric configs
1533
-
1534
- // Check for session/JWT expiry times
1535
- if (this.isSessionConfig(value, parentContext)) {
1536
- return {
1537
- type: "session_config",
1538
- value: value,
1539
- line: position.line,
1540
- column: position.column,
1541
- node: node,
1542
- context: parentContext,
1543
- suggestion: "Move session timeouts to environment configuration",
1544
- };
1545
- }
1546
-
1547
- // Check for cache TTL values
1548
- if (this.isCacheConfig(value, parentContext)) {
1549
- return {
1550
- type: "cache_config",
1551
- value: value,
1552
- line: position.line,
1553
- column: position.column,
1554
- node: node,
1555
- context: parentContext,
1556
- suggestion: "Move cache TTL to configuration management",
1557
- };
1558
- }
1559
-
1560
- // Check for performance configuration
1561
- if (this.isPerformanceConfig(value, parentContext)) {
1562
- return {
1563
- type: "performance_config",
1564
- value: value,
1565
- line: position.line,
1566
- column: position.column,
1567
- node: node,
1568
- context: parentContext,
1569
- suggestion: "Move performance settings to environment configuration",
1570
- };
1571
- }
1572
-
1573
- // ============ Phase 2: Critical Numeric Configurations ============
1574
-
1575
- // Check for database pool sizes
1576
- if (this.isDatabasePoolConfig(value, parentContext)) {
1577
- return {
1578
- type: "database_pool",
1579
- value: value,
1580
- line: position.line,
1581
- column: position.column,
1582
- node: node,
1583
- context: parentContext,
1584
- suggestion: "Move database pool configuration to environment settings",
1585
- };
1586
- }
1587
-
1588
- // Check for query timeouts
1589
- if (this.isQueryConfig(value, parentContext)) {
1590
- return {
1591
- type: "query_config",
1592
- value: value,
1593
- line: position.line,
1594
- column: position.column,
1595
- node: node,
1596
- context: parentContext,
1597
- suggestion: "Move query timeout to database configuration",
1598
- };
1599
- }
1600
-
1601
- // Check for token expiry times
1602
- if (this.isTokenConfig(value, parentContext)) {
1603
- return {
1604
- type: "token_config",
1605
- value: value,
1606
- line: position.line,
1607
- column: position.column,
1608
- node: node,
1609
- context: parentContext,
1610
- suggestion: "Move token expiry to security configuration",
1611
- };
1612
- }
1613
-
1614
- // Check for password policy values
1615
- if (this.isPasswordPolicy(value, parentContext)) {
1616
- return {
1617
- type: "password_policy",
1618
- value: value,
1619
- line: position.line,
1620
- column: position.column,
1621
- node: node,
1622
- context: parentContext,
1623
- suggestion: "Move password policy to security configuration",
1624
- };
1625
- }
1626
-
1627
- // Check for rate limits
1628
- if (this.isRateLimiting(value, parentContext)) {
1629
- return {
1630
- type: "rate_limiting",
1631
- value: value,
1632
- line: position.line,
1633
- column: position.column,
1634
- node: node,
1635
- context: parentContext,
1636
- suggestion: "Move rate limits to security configuration",
1637
- };
1638
- }
1639
-
1640
- // Check for file size limits
1641
- if (this.isFileLimit(value, parentContext)) {
1642
- return {
1643
- type: "file_limit",
1644
- value: value,
1645
- line: position.line,
1646
- column: position.column,
1647
- node: node,
1648
- context: parentContext,
1649
- suggestion: "Move file size limits to upload configuration",
1650
- };
1651
- }
1652
-
1653
- // ============ Phase 3: Important Numeric Configurations ============
1654
-
1655
- // Check for network timeouts (distinct from general timeouts)
1656
- if (this.isNetworkTimeout(value, parentContext)) {
1657
- return {
1658
- type: "network_timeout",
1659
- value: value,
1660
- line: position.line,
1661
- column: position.column,
1662
- node: node,
1663
- context: parentContext,
1664
- suggestion: "Move network timeouts to network configuration",
1665
- };
1666
- }
1667
-
1668
- // Check for buffer sizes
1669
- if (this.isBufferConfig(value, parentContext)) {
1670
- return {
1671
- type: "buffer_config",
1672
- value: value,
1673
- line: position.line,
1674
- column: position.column,
1675
- node: node,
1676
- context: parentContext,
1677
- suggestion: "Move buffer sizes to network configuration",
1678
- };
1679
- }
1680
-
1681
- // Check for keep-alive intervals
1682
- if (this.isKeepAliveConfig(value, parentContext)) {
1683
- return {
1684
- type: "keepalive_config",
1685
- value: value,
1686
- line: position.line,
1687
- column: position.column,
1688
- node: node,
1689
- context: parentContext,
1690
- suggestion: "Move keep-alive settings to network configuration",
1691
- };
1692
- }
1693
-
1694
- // Check for pricing values
1695
- if (this.isPricing(value, parentContext)) {
1696
- return {
1697
- type: "pricing",
1698
- value: value,
1699
- line: position.line,
1700
- column: position.column,
1701
- node: node,
1702
- context: parentContext,
1703
- suggestion: "Move pricing to business configuration",
1704
- };
1705
- }
1706
-
1707
- // Check for quotas
1708
- if (this.isQuota(value, parentContext)) {
1709
- return {
1710
- type: "quota",
1711
- value: value,
1712
- line: position.line,
1713
- column: position.column,
1714
- node: node,
1715
- context: parentContext,
1716
- suggestion: "Move quotas to business configuration",
1717
- };
1718
- }
1719
-
1720
- // Check for discount rates
1721
- if (this.isDiscount(value, parentContext)) {
1722
- return {
1723
- type: "discount",
1724
- value: value,
1725
- line: position.line,
1726
- column: position.column,
1727
- node: node,
1728
- context: parentContext,
1729
- suggestion: "Move discount rates to pricing configuration",
1730
- };
1731
- }
1732
-
1733
- // Check for trial periods
1734
- if (this.isTrial(value, parentContext)) {
1735
- return {
1736
- type: "trial",
1737
- value: value,
1738
- line: position.line,
1739
- column: position.column,
1740
- node: node,
1741
- context: parentContext,
1742
- suggestion: "Move trial periods to subscription configuration",
1743
- };
1744
- }
1745
-
1746
- // Check for metrics intervals
1747
- if (this.isMetricsConfig(value, parentContext)) {
1748
- return {
1749
- type: "metrics_config",
1750
- value: value,
1751
- line: position.line,
1752
- column: position.column,
1753
- node: node,
1754
- context: parentContext,
1755
- suggestion: "Move metrics intervals to monitoring configuration",
1756
- };
1757
- }
1758
-
1759
- // Check for alert thresholds
1760
- if (this.isAlertThreshold(value, parentContext)) {
1761
- return {
1762
- type: "alert_threshold",
1763
- value: value,
1764
- line: position.line,
1765
- column: position.column,
1766
- node: node,
1767
- context: parentContext,
1768
- suggestion: "Move alert thresholds to monitoring configuration",
1769
- };
1770
- }
1771
-
1772
- // Check for sampling rates
1773
- if (this.isSamplingRate(value, parentContext)) {
1774
- return {
1775
- type: "sampling_rate",
1776
- value: value,
1777
- line: position.line,
1778
- column: position.column,
1779
- node: node,
1780
- context: parentContext,
1781
- suggestion: "Move sampling rates to observability configuration",
1782
- };
1783
- }
1784
-
1785
- // Check for health check intervals
1786
- if (this.isHealthCheck(value, parentContext)) {
1787
- return {
1788
- type: "health_check",
1789
- value: value,
1790
- line: position.line,
1791
- column: position.column,
1792
- node: node,
1793
- context: parentContext,
1794
- suggestion: "Move health check intervals to monitoring configuration",
1795
- };
1796
- }
1797
-
1798
- // ============ Phase 4: Enhancement Numeric Configurations ============
1799
-
1800
- // Check for message TTL
1801
- if (this.isMessageTTL(value, parentContext)) {
1802
- return {
1803
- type: "message_ttl",
1804
- value: value,
1805
- line: position.line,
1806
- column: position.column,
1807
- node: node,
1808
- context: parentContext,
1809
- suggestion: "Move message TTL to messaging configuration",
1810
- };
1811
- }
1812
-
1813
- // Check for scaling configuration
1814
- if (this.isScalingConfig(value, parentContext)) {
1815
- return {
1816
- type: "scaling_config",
1817
- value: value,
1818
- line: position.line,
1819
- column: position.column,
1820
- node: node,
1821
- context: parentContext,
1822
- suggestion: "Move scaling configuration to deployment settings",
1823
- };
1824
- }
1825
-
1826
- // ============ Additional Critical Numeric Configurations ============
1827
-
1828
- // Check for magic numbers in business logic
1829
- if (this.isMagicNumber(value, parentContext)) {
1830
- return {
1831
- type: "magic_number",
1832
- value: value,
1833
- line: position.line,
1834
- column: position.column,
1835
- node: node,
1836
- context: parentContext,
1837
- suggestion: "Move business constants to configuration or constants file",
1838
- };
1839
- }
1840
-
1841
- // Check for pagination defaults
1842
- if (this.isPaginationDefault(value, parentContext)) {
1843
- return {
1844
- type: "pagination_default",
1845
- value: value,
1846
- line: position.line,
1847
- column: position.column,
1848
- node: node,
1849
- context: parentContext,
1850
- suggestion: "Move pagination defaults to API configuration",
1851
- };
1852
- }
1853
-
1854
- return null;
1855
- }
1856
-
1857
- analyzeTemplateLiteral(node, sourceFile) {
1858
- // For now, focus on simple template literals that might contain URLs
1859
- const templateText = node.getFullText();
1860
- if (templateText.includes("http://") || templateText.includes("https://")) {
1861
- const position = sourceFile.getLineAndColumnAtPos(node.getStart());
1862
-
1863
- // Check if it's using environment variables or config
1864
- if (!templateText.includes("process.env") && !templateText.includes("config.")) {
1865
- return {
1866
- type: "template_url",
1867
- value: templateText.trim(),
1868
- line: position.line,
1869
- column: position.column,
1870
- node: node,
1871
- };
1872
- }
1873
- }
1874
-
1875
- return null;
1876
- }
1877
-
1878
- analyzePropertyAssignment(node, sourceFile) {
1879
- const nameNode = node.getNameNode();
1880
- const valueNode = node.getInitializer();
1881
-
1882
- if (!nameNode || !valueNode) return null;
1883
-
1884
- const propertyName = nameNode.getText();
1885
- const position = sourceFile.getLineAndColumnAtPos(node.getStart());
1886
-
1887
- // Skip ALL field mapping objects and ORM/database entity configurations
1888
- const ancestorObj = node.getParent();
1889
- if (ancestorObj && Node.isObjectLiteralExpression(ancestorObj)) {
1890
- const objParent = ancestorObj.getParent();
1891
- if (objParent && Node.isVariableDeclaration(objParent)) {
1892
- const varName = objParent.getName();
1893
- // Skip field mappings, database schemas, etc.
1894
- if (/mapping|map|field|column|decode|schema|entity|constraint|table/i.test(varName)) {
1895
- return null;
1896
- }
1897
- }
1898
-
1899
- // Check if this looks like a table column definition or field mapping
1900
- const objText = ancestorObj.getText();
1901
- if (/primaryKeyConstraintName|foreignKeyConstraintName|key.*may contain/i.test(objText)) {
1902
- return null; // Skip database constraint definitions
1903
- }
1904
- }
1905
-
1906
- // Skip properties that are clearly field mappings or business data
1907
- const businessLogicProperties = [
1908
- // Field mappings
1909
- "key",
1910
- "field",
1911
- "dataKey",
1912
- "valueKey",
1913
- "labelKey",
1914
- "sortKey",
1915
- // Business logic
1916
- "endpoint",
1917
- "path",
1918
- "route",
1919
- "method",
1920
- "limit",
1921
- "pageSize",
1922
- "batchSize",
1923
- "maxResults",
1924
- "retry",
1925
- "retries",
1926
- "maxRetries",
1927
- "attempts",
1928
- "count",
1929
- "max",
1930
- "min",
1931
- "size",
1932
- "length",
1933
- // UI properties
1934
- "className",
1935
- "style",
1936
- "disabled",
1937
- "readonly",
1938
- // Database/ORM
1939
- "primaryKeyConstraintName",
1940
- "foreignKeyConstraintName",
1941
- "constraintName",
1942
- "tableName",
1943
- "columnName",
1944
- "schemaName",
1945
- ];
1946
-
1947
- const lowerPropertyName = propertyName.toLowerCase();
1948
- if (businessLogicProperties.some((prop) => lowerPropertyName.includes(prop))) {
1949
- return null; // Skip these completely
1950
- }
1951
-
1952
- // Only check for CLEARLY environment-dependent properties
1953
- const trulyEnvironmentDependentProps = [
1954
- "baseurl",
1955
- "baseURL",
1956
- "host",
1957
- "hostname",
1958
- "server",
1959
- "endpoint",
1960
- "apikey",
1961
- "api_key",
1962
- "secret_key",
1963
- "client_secret",
1964
- "database",
1965
- "connectionstring",
1966
- "dbhost",
1967
- "dbport",
1968
- "port",
1969
- "timeout", // Only when they have suspicious values
1970
- "bucket",
1971
- "region", // Cloud-specific
1972
- "clientid",
1973
- "tenantid", // OAuth-specific
1974
- ];
1975
-
1976
- if (!trulyEnvironmentDependentProps.some((prop) => lowerPropertyName.includes(prop))) {
1977
- return null; // Not clearly environment-dependent
1978
- }
1979
-
1980
- let value = null;
1981
- let configType = null;
1982
-
1983
- if (valueNode.getKind() === SyntaxKind.StringLiteral) {
1984
- value = valueNode.getLiteralValue();
1985
-
1986
- // Only flag URLs or clearly sensitive values
1987
- if (this.configPatterns.urls.regex.test(value) && this.isEnvironmentDependentUrl(value)) {
1988
- configType = "url";
1989
- } else if (this.isRealCredential(value, propertyName)) {
1990
- configType = "credential";
1991
- } else {
1992
- return null; // Skip other string values
1993
- }
1994
- } else if (valueNode.getKind() === SyntaxKind.NumericLiteral) {
1995
- value = valueNode.getLiteralValue();
1996
- const parentContext = this.getParentContext(node);
1997
-
1998
- // Only flag numbers that are clearly environment-dependent
1999
- if (this.configPatterns.environmentNumbers.isEnvironmentDependent(value, parentContext)) {
2000
- configType = "environment_config";
2001
- } else {
2002
- return null;
2003
- }
2004
- } else {
2005
- return null; // Skip other value types
2006
- }
2007
-
2008
- if (configType) {
2009
- return {
2010
- type: configType,
2011
- value: value,
2012
- line: position.line,
2013
- column: position.column,
2014
- node: node,
2015
- propertyName: propertyName,
2016
- };
2017
- }
2018
-
2019
- return null;
2020
- }
2021
-
2022
- analyzeVariableDeclaration(node, sourceFile) {
2023
- const nameNode = node.getNameNode();
2024
- const initializer = node.getInitializer();
2025
-
2026
- if (!nameNode || !initializer) return null;
2027
-
2028
- const variableName = nameNode.getText();
2029
- const position = sourceFile.getLineAndColumnAtPos(node.getStart());
2030
-
2031
- // Check if variable name suggests environment-dependent configuration
2032
- if (this.isEnvironmentDependentProperty(variableName)) {
2033
- let value = null;
2034
-
2035
- if (initializer.getKind() === SyntaxKind.StringLiteral) {
2036
- value = initializer.getLiteralValue();
2037
- } else if (initializer.getKind() === SyntaxKind.NumericLiteral) {
2038
- value = initializer.getLiteralValue();
2039
- }
2040
-
2041
- if (value !== null && this.looksLikeEnvironmentConfig(variableName, value)) {
2042
- return {
2043
- type: "variable_config",
2044
- value: value,
2045
- line: position.line,
2046
- column: position.column,
2047
- node: node,
2048
- variableName: variableName,
2049
- };
2050
- }
2051
- }
2052
-
2053
- return null;
2054
- }
2055
-
2056
- getParentContext(node) {
2057
- // Get surrounding context to understand the purpose of the literal
2058
- let parent = node.getParent();
2059
- let context = "";
2060
-
2061
- // Check if this is a method call argument or property access
2062
- while (parent && context.length < 100) {
2063
- const parentText = parent.getText();
2064
-
2065
- // If parent is CallExpression and this node is an argument, it might be a config key
2066
- if (parent.getKind() === SyntaxKind.CallExpression) {
2067
- const callExpr = parent;
2068
- const methodName = this.getMethodName(callExpr);
2069
- if (["get", "getBoolean", "getNumber", "getArray", "getString"].includes(methodName)) {
2070
- return `config.${methodName}()`; // This indicates it's a config key
2071
- }
2072
- }
2073
-
2074
- if (parentText.length < 200) {
2075
- context = parentText;
2076
- break;
2077
- }
2078
- parent = parent.getParent();
2079
- }
2080
-
2081
- return context;
2082
- }
2083
-
2084
- getMethodName(callExpression) {
2085
- const expression = callExpression.getExpression();
2086
- if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
2087
- return expression.getName();
2088
- }
2089
- if (expression.getKind() === SyntaxKind.Identifier) {
2090
- return expression.getText();
2091
- }
2092
- return "";
2093
- }
2094
-
2095
- isExcludedUrl(value, node) {
2096
- return this.configPatterns.urls.exclude.some((pattern) => pattern.test(value));
2097
- }
2098
-
2099
- isExcludedCredential(value, node) {
2100
- return this.configPatterns.credentials.exclude.some((pattern) => pattern.test(value));
2101
- }
2102
-
2103
- containsCredentialKeyword(context) {
2104
- const lowerContext = context.toLowerCase();
2105
-
2106
- // Skip if this looks like a header name or property key definition
2107
- if (context.includes("':") || context.includes('": ') || context.includes(" = ")) {
2108
- // This might be a key-value pair where the string is the key
2109
- return false;
2110
- }
2111
-
2112
- return this.configPatterns.credentials.keywords.some((keyword) => lowerContext.includes(keyword));
2113
- }
2114
-
2115
- looksLikeUIValue(value, context) {
2116
- // Check if it's likely a UI-related value (like input type, label, etc.)
2117
- const uiKeywords = ["input", "type", "field", "label", "placeholder", "text", "button"];
2118
- const lowerContext = context.toLowerCase();
2119
- return uiKeywords.some((keyword) => lowerContext.includes(keyword));
2120
- }
2121
-
2122
- looksLikeConfigKey(value) {
2123
- // Check if it looks like a config key path (e.g., 'api.baseUrl', 'features.newUI')
2124
- if (/^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*$/.test(value)) {
2125
- return true;
2126
- }
2127
-
2128
- // Check for other config key patterns
2129
- const configKeyPatterns = [
2130
- /^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z]/, // dotted notation like 'api.url'
2131
- /^[A-Z_][A-Z0-9_]*$/, // CONSTANT_CASE like 'API_URL'
2132
- /^get[A-Z]/, // getter methods like 'getApiUrl'
2133
- /^config\./, // config namespace
2134
- /^settings\./, // settings namespace
2135
- /^env\./, // env namespace
2136
- ];
2137
-
2138
- return configKeyPatterns.some((pattern) => pattern.test(value));
2139
- }
2140
-
2141
- isPropertyKey(node) {
2142
- // Check if this string literal is used as a property key in an object literal
2143
- const parent = node.getParent();
2144
-
2145
- // If parent is PropertyAssignment and this node is the name, it's a property key
2146
- if (parent && parent.getKind() === SyntaxKind.PropertyAssignment) {
2147
- const nameNode = parent.getNameNode();
2148
- return nameNode === node;
2149
- }
2150
-
2151
- return false;
2152
- }
2153
-
2154
- isImportPath(value, node) {
2155
- // Check if this is likely an import path or module name
2156
- const parent = node.getParent();
2157
-
2158
- // Check if it's in an import statement
2159
- let currentNode = parent;
2160
- while (currentNode) {
2161
- const kind = currentNode.getKind();
2162
- if (
2163
- kind === SyntaxKind.ImportDeclaration ||
2164
- kind === SyntaxKind.ExportDeclaration ||
2165
- kind === SyntaxKind.CallExpression
2166
- ) {
2167
- const text = currentNode.getText();
2168
- if (text.includes("require(") || text.includes("import ") || text.includes("from ")) {
2169
- return true;
2170
- }
2171
- }
2172
- currentNode = currentNode.getParent();
2173
- }
2174
-
2175
- // Check for common import path patterns
2176
- return (
2177
- /^[@a-z][a-z0-9\-_]*\/|^[a-z][a-z0-9\-_]*$|^\.{1,2}\//.test(value) ||
2178
- value.endsWith(".js") ||
2179
- value.endsWith(".ts") ||
2180
- value.endsWith(".json") ||
2181
- value.endsWith(".css") ||
2182
- value.endsWith(".scss") ||
2183
- value.endsWith(".html")
2184
- );
2185
- }
2186
-
2187
- isUIString(value) {
2188
- // Check against predefined UI string patterns, but don't skip credentials
2189
- if (
2190
- typeof value === "string" &&
2191
- value.length > 20 &&
2192
- (/token|key|secret|bearer|auth/i.test(value) || /^[a-f0-9-]{30,}$/i.test(value))
2193
- ) {
2194
- // Don't skip potential credentials/tokens even if they contain UI keywords
2195
- return false;
2196
- }
2197
-
2198
- return this.UI_STRINGS.some((pattern) => {
2199
- if (typeof pattern === "string") {
2200
- return value === pattern; // Exact match only, not includes
2201
- } else {
2202
- return pattern.test(value);
2203
- }
2204
- });
2205
- }
2206
-
2207
- isJapaneseUIText(value, context) {
2208
- if (typeof value !== "string") return false;
2209
-
2210
- // Check for Japanese/CJK characters
2211
- const hasJapanese = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf]/.test(value);
2212
- if (!hasJapanese) return false;
2213
-
2214
- // Japanese particles that indicate UI text
2215
- const japaneseParticles = /[はがをにでとへのもやけだ]/;
2216
- if (japaneseParticles.test(value)) return true;
2217
-
2218
- // Common Japanese UI terms
2219
- const japaneseUITerms = [
2220
- /ID$/, // xxxID
2221
- /名$/, // xxx名 (name)
2222
- /用/, // xxx用 (for xxx)
2223
- /番号/, // 番号 (number)
2224
- /情報/, // 情報 (information)
2225
- /設定/, // 設定 (settings)
2226
- /画面/, // 画面 (screen)
2227
- /ボタン/, // ボタン (button)
2228
- /入力/, // 入力 (input)
2229
- /選択/, // 選択 (selection)
2230
- /一覧/, // 一覧 (list)
2231
- /詳細/, // 詳細 (details)
2232
- /確認/, // 確認 (confirmation)
2233
- /登録/, // 登録 (registration)
2234
- /更新/, // 更新 (update)
2235
- /削除/, // 削除 (delete)
2236
- /検索/, // 検索 (search)
2237
- /表示/, // 表示 (display)
2238
- /メール/, // メール (email)
2239
- /電話/, // 電話 (phone)
2240
- /住所/, // 住所 (address)
2241
- /連絡/, // 連絡 (contact)
2242
- /会社/, // 会社 (company)
2243
- /工場/, // 工場 (factory)
2244
- /整備/, // 整備 (maintenance)
2245
- ];
2246
-
2247
- if (japaneseUITerms.some((pattern) => pattern.test(value))) return true;
2248
-
2249
- // Check if context suggests it's a label or placeholder
2250
- const lowerContext = context.toLowerCase();
2251
- const labelPatterns = [
2252
- /label|placeholder|title|heading|text/i,
2253
- /register.*label|field.*label/i,
2254
- /header.*text|tooltip/i,
2255
- ];
2256
-
2257
- if (hasJapanese && labelPatterns.some((pattern) => pattern.test(context))) {
2258
- return true;
2259
- }
2260
-
2261
- return false;
2262
- }
2263
-
2264
- isFormFieldLabel(value, context) {
2265
- if (typeof value !== "string") return false;
2266
-
2267
- const lowerContext = context.toLowerCase();
2268
-
2269
- // Check if it's in a form field context
2270
- const formFieldPatterns = [
2271
- /register.*label|label.*register/i,
2272
- /field.*label|label.*field/i,
2273
- /form.*label|label.*form/i,
2274
- /input.*label|label.*input/i,
2275
- /placeholder|tooltip|helper.*text/i,
2276
- /error.*message|validation.*message/i,
2277
- /\.label\b|label:/i,
2278
- ];
2279
-
2280
- if (formFieldPatterns.some((pattern) => pattern.test(context))) {
2281
- return true;
2282
- }
2283
-
2284
- // Check for React Hook Form's register with label
2285
- if (context.includes("register(") && context.includes("label")) {
2286
- return true;
2287
- }
2288
-
2289
- return false;
2290
- }
2291
-
2292
- isTestData(value, context) {
2293
- // Check if variable name suggests it's test/mock data
2294
- const lowerContext = context.toLowerCase();
2295
- const testVarPatterns = [
2296
- /mock[a-z]/i, // mockApiKey, mockUser
2297
- /test[a-z]/i, // testUser, testKey
2298
- /dummy[a-z]/i, // dummyData
2299
- /sample[a-z]/i, // sampleData
2300
- /fake[a-z]/i, // fakeKey
2301
- /example[a-z]/i, // exampleToken
2302
- ];
2303
-
2304
- if (testVarPatterns.some((pattern) => pattern.test(context))) {
2305
- return true;
2306
- }
2307
-
2308
- // Don't skip credentials/tokens even in dummy files if they look real
2309
- if (
2310
- typeof value === "string" &&
2311
- value.length > 20 &&
2312
- (/^sk-live|^pk-live|^prod-|^production-/i.test(value) || // Clearly production keys
2313
- /^[a-f0-9]{32,}$/i.test(value) || // Long hex strings
2314
- /^[A-Za-z0-9+/]{40,}={0,2}$/i.test(value)) // Base64 tokens
2315
- ) {
2316
- return false; // Don't skip potential real credentials
2317
- }
2318
-
2319
- // Check for test patterns in value - but be more restrictive
2320
- if (this.TEST_PATTERNS.some((pattern) => pattern.test(value))) {
2321
- // Only skip if it's clearly test data, not production dummy data
2322
- const isInTestFile =
2323
- /\.(test|spec)\.(ts|tsx|js|jsx)$/i.test(context) ||
2324
- /\/__tests__\//i.test(context) ||
2325
- /\/test\//i.test(context);
2326
- return isInTestFile;
2327
- }
2328
-
2329
- // Check for test context
2330
- const testKeywords = ["test", "spec", "mock", "fixture", "stub", "describe", "it("];
2331
- return testKeywords.some((keyword) => lowerContext.includes(keyword));
2332
- }
2333
-
2334
- isValidationMessage(value, context) {
2335
- // Skip validation/error messages
2336
- const validationPatterns = [
2337
- /must contain|should contain|invalid|error|required|missing/i,
2338
- /password|username|email/i, // Common validation contexts
2339
- /^[A-Z][a-z\s]{10,}$/, // Sentence-like messages
2340
- /\s(at least|one|letter|uppercase|lowercase|numeric)/i,
2341
- ];
2342
-
2343
- return (
2344
- validationPatterns.some((pattern) => pattern.test(value)) ||
2345
- /message|error|validation|description/i.test(context)
2346
- );
2347
- }
2348
-
2349
- isFileNameOrDescription(value, context) {
2350
- // Skip file names and descriptions
2351
- const filePatterns = [
2352
- /\.(csv|json|xml|txt|md)$/i,
2353
- /^[a-z_\-]+\.(csv|json|xml|txt)$/i,
2354
- /description|comment|note|foreign key|identity/i,
2355
- ];
2356
-
2357
- return (
2358
- filePatterns.some((pattern) => pattern.test(value)) ||
2359
- /description|comment|note|identity|foreign|table/i.test(context)
2360
- );
2361
- }
2362
-
2363
- isEnvironmentDependentUrl(value) {
2364
- // Only flag URLs that are likely to differ between environments
2365
- const envDependentPatterns = [
2366
- /\.amazonaws\.com/, // AWS services
2367
- /\.azure\.com/, // Azure services
2368
- /\.googleapis\.com/, // Google services
2369
- /\.herokuapp\.com/, // Heroku apps
2370
- /\.vercel\.app/, // Vercel deployments
2371
- /\.netlify\.app/, // Netlify deployments
2372
- /api\./, // API endpoints
2373
- /auth\./, // Auth services
2374
- /\.dev|\.staging|\.prod/i, // Environment-specific domains
2375
- /\/api\/v\d+/, // Versioned API endpoints
2376
- /https?:\/\/[^\/]+\.com\/api/, // External API endpoints
2377
- ];
2378
-
2379
- // Skip localhost and development URLs
2380
- if (/localhost|127\.0\.0\.1|0\.0\.0\.0/.test(value)) {
2381
- return false;
2382
- }
2383
-
2384
- return envDependentPatterns.some((pattern) => pattern.test(value));
2385
- }
2386
-
2387
- isRealCredential(value, context) {
2388
- // Check for real credentials, not validation messages
2389
- const credentialKeywords = this.configPatterns.credentials.keywords;
2390
- const lowerContext = context.toLowerCase();
2391
-
2392
- // Must have credential keyword in context
2393
- if (!credentialKeywords.some((keyword) => lowerContext.includes(keyword))) {
2394
- return false;
2395
- }
2396
-
2397
- // Skip property/field names (camelCase identifiers)
2398
- // These are field names, not actual credential values
2399
- if (/^[a-z][a-zA-Z0-9]*$/.test(value)) {
2400
- // Common field name patterns that are not credentials
2401
- const fieldNamePatterns = [
2402
- /^(is|has|can|should)[A-Z]/, // isJoinFPL, hasAccess
2403
- /Flg$/i, // loginFlg, deleteFlg
2404
- /Id$/i, // userId, accountId
2405
- /Key$/i, // sortKey, primaryKey
2406
- /Name$/i, // userName, keyName
2407
- /Code$/i, // errorCode, statusCode
2408
- /Type$/i, // tokenType, keyType
2409
- /Status$/i, // loginStatus
2410
- /^[a-z]+[A-Z][a-z]+$/, // General camelCase
2411
- ];
2412
-
2413
- if (fieldNamePatterns.some((pattern) => pattern.test(value))) {
2414
- return false;
2415
- }
2416
- }
2417
-
2418
- // Skip URL paths (routes)
2419
- if (/^\/[a-z-/]*$/.test(value)) {
2420
- return false; // /login, /verify-login, etc.
2421
- }
2422
-
2423
- // Skip Japanese UI text and labels
2424
- if (this.isJapaneseUIText(value, context)) {
2425
- return false;
2426
- }
2427
-
2428
- // Skip form field labels
2429
- if (this.isFormFieldLabel(value, context)) {
2430
- return false;
2431
- }
2432
-
2433
- // Skip if it's excluded (validation messages, etc.)
2434
- if (this.configPatterns.credentials.exclude.some((pattern) => pattern.test(value))) {
2435
- return false;
2436
- }
2437
-
2438
- // Skip validation messages and descriptions
2439
- if (this.isValidationMessage(value, context)) {
2440
- return false;
2441
- }
2442
-
2443
- // Real credentials are usually:
2444
- // - Random strings with mix of chars: abc123XYZ789
2445
- // - Long alphanumeric: sk-1234567890abcdef
2446
- // - Base64 encoded: YWJjZGVmZ2hpams=
2447
- // - UUID format: 550e8400-e29b-41d4-a716-446655440000
2448
- const looksLikeRealCredential =
2449
- /^[a-zA-Z0-9+/]{32,}={0,2}$/.test(value) || // Base64
2450
- /^[a-f0-9]{32,}$/.test(value) || // Hex
2451
- /^[a-z]{2,4}-[a-zA-Z0-9]{20,}$/.test(value) || // Prefixed keys: sk-..., pk-...
2452
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); // UUID
2453
-
2454
- // Must be reasonably long and look like an actual credential value
2455
- return value.length >= 16 && looksLikeRealCredential && !this.looksLikeUIValue(value, context);
2456
- }
2457
-
2458
- isEnvironmentDependentProperty(propertyName) {
2459
- // Skip UI/framework related property names
2460
- const uiPropertyPatterns = [
2461
- /^key[A-Z]/, // keyXxx (UI field keys)
2462
- /^field[A-Z]/, // fieldXxx
2463
- /^prop[A-Z]/, // propXxx
2464
- /^data[A-Z]/, // dataXxx
2465
- /CheckDisplay/, // UI display control keys
2466
- /InputPossible/, // UI input control keys
2467
- /Flag$/, // UI flags
2468
- /Class$/, // CSS classes
2469
- /^(disabled|readonly|active)Class$/i, // UI state classes
2470
- ];
2471
-
2472
- if (uiPropertyPatterns.some((pattern) => pattern.test(propertyName))) {
2473
- return false;
2474
- }
2475
-
2476
- // Properties that are likely to differ between environments
2477
- const envDependentProps = [
2478
- "baseurl",
2479
- "baseURL",
2480
- "host",
2481
- "hostname",
2482
- "server",
2483
- "apikey",
2484
- "api_key",
2485
- "secret",
2486
- "token",
2487
- "password",
2488
- "credential",
2489
- "database",
2490
- "db",
2491
- "connection",
2492
- "connectionstring",
2493
- "timeout", // Only long timeouts
2494
- "port", // Only non-standard ports
2495
- "authorization",
2496
- "auth",
2497
- "authentication", // Auth headers and codes
2498
- "apptoken",
2499
- "devicetoken",
2500
- "accesstoken",
2501
- "refreshtoken", // App tokens
2502
- "code",
2503
- "hash",
2504
- "signature",
2505
- "key", // Various security values
2506
- "clientsecret",
2507
- "clientid",
2508
- "sessionkey", // OAuth and session
2509
- "requestid",
2510
- "sessionid",
2511
- "transactionid",
2512
- "otp", // Request/session tracking
2513
- ];
2514
-
2515
- const lowerName = propertyName.toLowerCase();
2516
- return envDependentProps.some((prop) => lowerName.includes(prop));
2517
- }
2518
-
2519
- looksLikeEnvironmentConfig(propertyName, value) {
2520
- // Check if this property/value combination looks like environment config
2521
- const lowerPropertyName = propertyName.toLowerCase();
2522
-
2523
- if (typeof value === "string") {
2524
- // Skip test data (common test passwords, etc.)
2525
- const testDataPatterns = [
2526
- /^(password123|test123|admin123|user123|wrongpassword|testpassword)$/i,
2527
- /^(test|mock|dummy|sample|example)/i,
2528
- /^\/(api|mock|test)/, // Test API paths
2529
- /^[a-z]+\d+$/i, // Simple test values like 'user1', 'test2'
2530
- ];
2531
-
2532
- // Check if variable name suggests it's test/mock data
2533
- const isMockVariable = /^(mock|test|dummy|sample|fake|example)[A-Z]/i.test(propertyName);
2534
- if (isMockVariable) {
2535
- return false; // Skip mock variables even if they contain credentials
2536
- }
2537
-
2538
- // Don't skip common test patterns if they appear in credential contexts
2539
- const isCredentialContext = /token|key|secret|auth|otp|code|password|credential/i.test(propertyName);
2540
-
2541
- if (!isCredentialContext && testDataPatterns.some((pattern) => pattern.test(value))) {
2542
- return false;
2543
- }
2544
-
2545
- // Skip object property paths and field names
2546
- const propertyPathPatterns = [
2547
- /^[a-zA-Z][a-zA-Z0-9]*(\[[0-9]+\])?\.[a-zA-Z][a-zA-Z0-9]*$/, // obj[0].prop, obj.prop
2548
- /^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*$/, // obj.prop.subprop
2549
- /^[a-zA-Z][a-zA-Z0-9]*(\[[0-9]+\])+$/, // obj[0], obj[0][1]
2550
- /^(key|field|prop|data)[A-Z]/, // keyXxx, fieldXxx, propXxx, dataXxx
2551
- /CheckDisplay|InputPossible|Flag$/i, // Common UI field patterns
2552
- /^exflg|^flg|Support$/i, // Business logic flags
2553
- ];
2554
-
2555
- if (propertyPathPatterns.some((pattern) => pattern.test(value))) {
2556
- return false;
2557
- }
2558
-
2559
- // Skip CSS classes and UI constants
2560
- const uiPatterns = [
2561
- /^bg-|text-|cursor-|border-|flex-|grid-/, // CSS classes
2562
- /^(disabled|readonly|active|inactive)$/i, // UI states
2563
- /class$/i, // className values
2564
- ];
2565
-
2566
- if (uiPatterns.some((pattern) => pattern.test(value))) {
2567
- return false;
2568
- }
2569
-
2570
- // Skip internal system identifiers (queue names, service names, route names)
2571
- const systemIdentifierPatterns = [
2572
- /-queue$/i, // Queue names
2573
- /-task$/i, // Task names
2574
- /-activity$/i, // Activity names
2575
- /-service$/i, // Service names
2576
- /-worker$/i, // Worker names
2577
- /^[A-Z_]+_QUEUE$/, // CONSTANT_QUEUE names
2578
- /^[A-Z_]+_TASK$/, // CONSTANT_TASK names
2579
- /^(register|login|logout|reset-password|verify|update)$/i, // Route names
2580
- /password|token/i && /invalid|expired|attempts|exceeded/i, // Error messages
2581
- ];
2582
-
2583
- if (systemIdentifierPatterns.some((pattern) => pattern.test(value))) {
2584
- return false;
2585
- }
2586
-
2587
- // Skip error messages and validation messages
2588
- const messagePatterns = [
2589
- /invalid|expired|exceeded|failed|error|success/i,
2590
- /attempts|required|missing|not found/i,
2591
- /^[A-Z][a-z\s]{10,}$/, // Sentence-like messages
2592
- /は|が|を|に|で|と/, // Japanese particles (UI text)
2593
- /情報|画面|ボタン|入力/, // Japanese UI terms
2594
- ];
2595
-
2596
- if (messagePatterns.some((pattern) => pattern.test(value))) {
2597
- return false;
2598
- }
2599
-
2600
- // URLs are environment-dependent
2601
- if (this.configPatterns.urls.regex.test(value)) {
2602
- return this.isEnvironmentDependentUrl(value);
2603
- }
2604
-
2605
- // Credentials - but exclude test data
2606
- if (
2607
- lowerPropertyName.includes("key") ||
2608
- lowerPropertyName.includes("secret") ||
2609
- lowerPropertyName.includes("token") ||
2610
- lowerPropertyName.includes("password")
2611
- ) {
2612
- return value.length > 10; // Real secrets are usually longer
2613
- }
2614
-
2615
- // Skip short endpoint names or simple strings
2616
- if (value.length < 10 && !value.includes(".") && !value.includes("/")) {
2617
- return false;
2618
- }
2619
- }
2620
-
2621
- if (typeof value === "number") {
2622
- // Only flag environment-dependent numbers
2623
- return this.configPatterns.environmentNumbers.isEnvironmentDependent(value, propertyName);
2624
- }
2625
-
2626
- return true;
2627
- }
2628
-
2629
- isCommonConstant(value) {
2630
- // Common constants that are usually OK to hardcode
2631
- const commonConstants = [100, 200, 300, 400, 500, 1000, 2000, 3000, 5000, 8080, 3000];
2632
- return commonConstants.includes(value);
2633
- }
2634
-
2635
- isConfigProperty(propertyName) {
2636
- const configProps = [
2637
- "url",
2638
- "endpoint",
2639
- "baseurl",
2640
- "apiurl",
2641
- "host",
2642
- "port",
2643
- "timeout",
2644
- "interval",
2645
- "delay",
2646
- "retry",
2647
- "retries",
2648
- "username",
2649
- "password",
2650
- "apikey",
2651
- "secret",
2652
- "token",
2653
- "database",
2654
- "connection",
2655
- "connectionstring",
2656
- "maxsize",
2657
- "batchsize",
2658
- "pagesize",
2659
- "limit",
2660
- ];
2661
-
2662
- const lowerName = propertyName.toLowerCase();
2663
- return configProps.some((prop) => lowerName.includes(prop));
2664
- }
2665
-
2666
- isConfigVariable(variableName) {
2667
- const configVars = [
2668
- "api",
2669
- "url",
2670
- "endpoint",
2671
- "host",
2672
- "port",
2673
- "timeout",
2674
- "interval",
2675
- "delay",
2676
- "retry",
2677
- "config",
2678
- "setting",
2679
- "constant",
2680
- ];
2681
-
2682
- const lowerName = variableName.toLowerCase();
2683
- return configVars.some((var_) => lowerName.includes(var_));
2684
- }
2685
-
2686
- looksLikeHardcodedConfig(name, value) {
2687
- // Skip obvious constants and UI values
2688
- if (typeof value === "string") {
2689
- if (value.length < 3) return false;
2690
- if (/^(ok|yes|no|true|false|success|error|info|warn)$/i.test(value)) return false;
2691
- }
2692
-
2693
- if (typeof value === "number") {
2694
- if (this.isCommonConstant(value)) return false;
2695
- }
2696
-
2697
- return true;
2698
- }
2699
-
2700
- // Helper methods for specific configuration types
2701
- isTimeout(value, context) {
2702
- const lowerContext = context.toLowerCase();
2703
- return typeof value === "number" && value > 1000 && /timeout|delay|wait|duration/i.test(context);
2704
- }
2705
-
2706
- isRetryInterval(value, context) {
2707
- const lowerContext = context.toLowerCase();
2708
- return typeof value === "number" && value > 100 && /retry|interval|backoff|attempt/i.test(context);
2709
- }
2710
-
2711
- // Phase 1 extensions - Security & Infrastructure
2712
- isCorsOrigin(value, context) {
2713
- if (typeof value !== "string") return false;
2714
- const lowerContext = context.toLowerCase();
2715
-
2716
- // Check if it's a URL pattern that matches CORS origin
2717
- const matchesPattern = this.configPatterns.security.corsOrigins.patterns.some((pattern) =>
2718
- pattern.test(value)
2719
- );
2720
-
2721
- if (!matchesPattern) return false;
2722
-
2723
- // If context contains CORS-related keywords, it's definitely a CORS origin
2724
- if (this.configPatterns.security.corsOrigins.keywords.some((keyword) => lowerContext.includes(keyword))) {
2725
- return true;
2726
- }
2727
-
2728
- // Also check variable name patterns for CORS/allowed origins
2729
- if (/cors|allowed.*origin|origin.*allowed/i.test(context)) {
2730
- return true;
2731
- }
2732
-
2733
- return false;
2734
- }
2735
-
2736
- isSessionConfig(value, context) {
2737
- const lowerContext = context.toLowerCase();
2738
-
2739
- // Check keywords
2740
- if (
2741
- !this.configPatterns.security.sessionConfig.keywords.some((keyword) => lowerContext.includes(keyword))
2742
- ) {
2743
- return false;
2744
- }
2745
-
2746
- // Check for time patterns
2747
- if (typeof value === "string") {
2748
- return this.configPatterns.security.sessionConfig.timePatterns.some((pattern) => pattern.test(value));
2749
- }
2750
-
2751
- // Check for numeric values (seconds)
2752
- if (typeof value === "number" && value > 300) {
2753
- // > 5 minutes
2754
- return /session|jwt|token|expiry|expire/i.test(context);
2755
- }
2756
-
2757
- return false;
2758
- }
2759
-
2760
- isCacheConfig(value, context) {
2761
- const lowerContext = context.toLowerCase();
2762
-
2763
- if (
2764
- !this.configPatterns.infrastructure.caching.keywords.some((keyword) => lowerContext.includes(keyword))
2765
- ) {
2766
- return false;
2767
- }
2768
-
2769
- if (typeof value === "string") {
2770
- // Check for Redis prefixes
2771
- return this.configPatterns.infrastructure.caching.patterns.some((pattern) => pattern.test(value));
2772
- }
2773
-
2774
- if (typeof value === "number" && value > 60) {
2775
- // TTL > 1 minute
2776
- return /cache|ttl|expire/i.test(context);
2777
- }
2778
-
2779
- return false;
2780
- }
2781
-
2782
- isLogLevel(value, context) {
2783
- if (typeof value !== "string") return false;
2784
- const lowerContext = context.toLowerCase();
2785
-
2786
- // Check if value is a valid log level
2787
- const isValidLevel = this.configPatterns.infrastructure.logging.levels.includes(value.toLowerCase());
2788
- if (!isValidLevel) return false;
2789
-
2790
- // Check if context suggests it's a log level configuration
2791
- return /log.*level|level.*log|^log_level$|^loglevel$/i.test(context);
2792
- }
2793
-
2794
- isPerformanceConfig(value, context) {
2795
- const lowerContext = context.toLowerCase();
2796
-
2797
- if (typeof value !== "number" || value <= 1) {
2798
- return false;
2799
- }
2800
-
2801
- // Check for performance-related keywords in context
2802
- const hasKeyword = this.configPatterns.infrastructure.performance.keywords.some((keyword) =>
2803
- lowerContext.includes(keyword)
2804
- );
2805
-
2806
- if (!hasKeyword) return false;
2807
-
2808
- // Check for specific performance patterns
2809
- return this.configPatterns.infrastructure.performance.contextPatterns.some((pattern) =>
2810
- pattern.test(context)
2811
- );
2812
- }
2813
-
2814
- isEnvironmentName(value, context) {
2815
- if (typeof value !== "string") return false;
2816
- const lowerContext = context.toLowerCase();
2817
-
2818
- // Check if value is a known environment name
2819
- const isEnvName =
2820
- this.configPatterns.environments.names.includes(value.toLowerCase()) ||
2821
- this.configPatterns.environments.patterns.some((pattern) => pattern.test(value));
2822
-
2823
- if (!isEnvName) return false;
2824
-
2825
- // Check if context suggests it's environment configuration
2826
- return /environment|env|node_env|current.*env/i.test(context);
2827
- }
2828
-
2829
- isServiceDependency(value, context) {
2830
- if (typeof value !== "string") return false;
2831
- const lowerContext = context.toLowerCase();
2832
-
2833
- return (
2834
- this.configPatterns.services.keywords.some((keyword) => lowerContext.includes(keyword)) &&
2835
- this.configPatterns.services.patterns.some((pattern) => pattern.test(value))
2836
- );
2837
- }
2838
-
2839
- isBatchSize(value, context) {
2840
- const lowerContext = context.toLowerCase();
2841
-
2842
- // Skip UI/display related sizes
2843
- if (/ui|display|view|page.*size|page.*limit|default.*page/i.test(context)) {
2844
- return false;
2845
- }
2846
-
2847
- // Skip small values that are likely UI constants (pagination, etc.)
2848
- if (typeof value === "number" && value <= 50 && /page|size|limit/i.test(context)) {
2849
- return false;
2850
- }
2851
-
2852
- // Only flag larger batch sizes for processing
2853
- return (
2854
- typeof value === "number" &&
2855
- value > 100 &&
2856
- /batch|chunk|buffer|pool|queue|processing/i.test(context) &&
2857
- !/ui|display|view|page/i.test(context)
2858
- );
2859
- }
2860
-
2861
- isThreshold(value, context) {
2862
- const lowerContext = context.toLowerCase();
2863
- return (
2864
- typeof value === "number" &&
2865
- value > 0 &&
2866
- this.configPatterns.thresholds.contextPatterns.some((pattern) => pattern.test(context))
2867
- );
2868
- }
2869
-
2870
- isFeatureFlag(context) {
2871
- const lowerContext = context.toLowerCase();
2872
- return (
2873
- this.configPatterns.featureFlags.keywords.some((keyword) => lowerContext.includes(keyword)) ||
2874
- this.configPatterns.featureFlags.patterns.some((pattern) => pattern.test(context))
2875
- );
2876
- }
2877
-
2878
- // ============ Phase 2: Critical Configuration Helpers ============
2879
-
2880
- // Database & Storage Configuration helpers
2881
- isDatabasePoolConfig(value, context) {
2882
- const lowerContext = context.toLowerCase();
2883
- if (typeof value !== "number" || value <= 0) return false;
2884
-
2885
- return this.configPatterns.database.poolConfig.patterns.some((pattern) => pattern.test(context));
2886
- }
2887
-
2888
- isQueryConfig(value, context) {
2889
- const lowerContext = context.toLowerCase();
2890
-
2891
- if (typeof value === "number" && value > 1000) {
2892
- return this.configPatterns.database.queryConfig.patterns.some((pattern) => pattern.test(context));
2893
- }
2894
-
2895
- if (typeof value === "string") {
2896
- return this.configPatterns.database.queryConfig.patterns.some((pattern) => pattern.test(context));
2897
- }
2898
-
2899
- return false;
2900
- }
2901
-
2902
- isSchemaName(value, context) {
2903
- if (typeof value !== "string") return false;
2904
- const lowerContext = context.toLowerCase();
2905
-
2906
- // Check if it matches schema name patterns
2907
- const matchesPattern = this.configPatterns.database.schemaNames.patterns.some((pattern) =>
2908
- pattern.test(value)
2909
- );
2910
- if (!matchesPattern) return false;
2911
-
2912
- // Check if context suggests it's a schema/table name
2913
- return this.configPatterns.database.schemaNames.keywords.some((keyword) =>
2914
- lowerContext.includes(keyword)
2915
- );
2916
- }
2917
-
2918
- // Security & Authentication (Extended) helpers
2919
- isTokenConfig(value, context) {
2920
- const lowerContext = context.toLowerCase();
2921
-
2922
- if (
2923
- !this.configPatterns.securityExtended.tokenConfig.keywords.some((keyword) =>
2924
- lowerContext.includes(keyword)
2925
- )
2926
- ) {
2927
- return false;
2928
- }
2929
-
2930
- if (typeof value === "string") {
2931
- return this.configPatterns.securityExtended.tokenConfig.patterns.some((pattern) => pattern.test(value));
2932
- }
2933
-
2934
- if (typeof value === "number" && value > 300) {
2935
- return /expir|ttl|token|jwt/i.test(context);
2936
- }
2937
-
2938
- return false;
2939
- }
2940
-
2941
- isPasswordPolicy(value, context) {
2942
- const lowerContext = context.toLowerCase();
2943
-
2944
- if (typeof value === "number" && value > 0 && value < 128) {
2945
- return this.configPatterns.securityExtended.passwordPolicy.patterns.some((pattern) =>
2946
- pattern.test(context)
2947
- );
2948
- }
2949
-
2950
- if (typeof value === "string") {
2951
- return this.configPatterns.securityExtended.passwordPolicy.patterns.some((pattern) =>
2952
- pattern.test(value)
2953
- );
2954
- }
2955
-
2956
- return false;
2957
- }
2958
-
2959
- isRateLimiting(value, context) {
2960
- const lowerContext = context.toLowerCase();
2961
-
2962
- if (typeof value !== "number" || value <= 0) return false;
2963
-
2964
- return this.configPatterns.securityExtended.rateLimiting.patterns.some((pattern) =>
2965
- pattern.test(context)
2966
- );
2967
- }
2968
-
2969
- isEncryptionConfig(value, context) {
2970
- if (typeof value !== "string") return false;
2971
- const lowerContext = context.toLowerCase();
2972
-
2973
- const matchesPattern = this.configPatterns.securityExtended.encryptionConfig.patterns.some((pattern) =>
2974
- pattern.test(value)
2975
- );
2976
- if (!matchesPattern) return false;
2977
-
2978
- return this.configPatterns.securityExtended.encryptionConfig.keywords.some((keyword) =>
2979
- lowerContext.includes(keyword)
2980
- );
2981
- }
2982
-
2983
- isOAuthConfig(value, context) {
2984
- if (typeof value !== "string") return false;
2985
- const lowerContext = context.toLowerCase();
2986
-
2987
- const matchesPattern = this.configPatterns.securityExtended.oauthConfig.patterns.some((pattern) =>
2988
- pattern.test(value)
2989
- );
2990
- if (!matchesPattern) return false;
2991
-
2992
- return this.configPatterns.securityExtended.oauthConfig.keywords.some((keyword) =>
2993
- lowerContext.includes(keyword)
2994
- );
2995
- }
2996
-
2997
- // File System & Paths helpers
2998
- isDirectory(value, context) {
2999
- if (typeof value !== "string") return false;
3000
- const lowerContext = context.toLowerCase();
3001
-
3002
- const matchesPattern = this.configPatterns.fileSystem.directories.patterns.some((pattern) =>
3003
- pattern.test(value)
3004
- );
3005
- if (!matchesPattern) return false;
3006
-
3007
- return this.configPatterns.fileSystem.directories.keywords.some((keyword) =>
3008
- lowerContext.includes(keyword)
3009
- );
3010
- }
3011
-
3012
- isFileLimit(value, context) {
3013
- const lowerContext = context.toLowerCase();
3014
-
3015
- if (typeof value !== "number" || value <= 1000) return false;
3016
-
3017
- return this.configPatterns.fileSystem.fileLimits.patterns.some((pattern) => pattern.test(context));
3018
- }
3019
-
3020
- isFileType(value, context) {
3021
- if (typeof value !== "string") return false;
3022
- const lowerContext = context.toLowerCase();
3023
-
3024
- const matchesPattern = this.configPatterns.fileSystem.fileTypes.patterns.some((pattern) =>
3025
- pattern.test(value)
3026
- );
3027
- if (!matchesPattern) return false;
3028
-
3029
- return this.configPatterns.fileSystem.fileTypes.keywords.some((keyword) =>
3030
- lowerContext.includes(keyword)
3031
- );
3032
- }
3033
-
3034
- isLogPath(value, context) {
3035
- if (typeof value !== "string") return false;
3036
- const lowerContext = context.toLowerCase();
3037
-
3038
- return this.configPatterns.fileSystem.logPaths.patterns.some((pattern) => pattern.test(value));
3039
- }
3040
-
3041
- // ============ Phase 3: Important Configuration Helpers ============
3042
-
3043
- // Network & Protocol Configuration helpers
3044
- isHttpConfig(value, context) {
3045
- if (typeof value !== "string") return false;
3046
- const lowerContext = context.toLowerCase();
3047
-
3048
- // Check if it's an HTTP method
3049
- if (this.configPatterns.network.httpConfig.httpMethods.includes(value)) {
3050
- return /method|allowed|http/i.test(context);
3051
- }
3052
-
3053
- // Check other HTTP patterns
3054
- return this.configPatterns.network.httpConfig.patterns.some((pattern) => pattern.test(value));
3055
- }
3056
-
3057
- isNetworkTimeout(value, context) {
3058
- const lowerContext = context.toLowerCase();
3059
-
3060
- if (typeof value !== "number" || value <= 100) return false;
3061
-
3062
- return this.configPatterns.network.timeouts.patterns.some((pattern) => pattern.test(context));
399
+ return null;
3063
400
  }
3064
401
 
3065
- isBufferConfig(value, context) {
3066
- const lowerContext = context.toLowerCase();
3067
-
3068
- if (typeof value !== "number" || value <= 1024) return false;
402
+ /**
403
+ * Check if number is a timeout configuration
404
+ */
405
+ isTimeoutConfig(value, parentText, grandParentText) {
406
+ // Must be a reasonable timeout value (1000ms - 5min)
407
+ if (value < 1000 || value > 300000) return false;
408
+
409
+ // Common timeout patterns
410
+ const timeoutPatterns = [
411
+ /timeout\s*[=:]/,
412
+ /\.timeout\s*=/,
413
+ /connectiontimeout/,
414
+ /requesttimeout/,
415
+ /sockettimeout/,
416
+ ];
3069
417
 
3070
- return this.configPatterns.network.bufferConfig.patterns.some((pattern) => pattern.test(context));
418
+ return timeoutPatterns.some(p => p.test(parentText) || p.test(grandParentText));
3071
419
  }
3072
420
 
3073
- isKeepAliveConfig(value, context) {
3074
- const lowerContext = context.toLowerCase();
3075
-
3076
- if (typeof value === "boolean") {
3077
- return /keepalive|keep.*alive/i.test(context);
3078
- }
421
+ /**
422
+ * Check if number is a retry configuration
423
+ */
424
+ isRetryConfig(value, parentText, grandParentText) {
425
+ const context = parentText + " " + grandParentText;
3079
426
 
3080
- if (typeof value === "number" && value > 0) {
3081
- return this.configPatterns.network.keepAlive.patterns.some((pattern) => pattern.test(context));
3082
- }
427
+ const retryPatterns = [
428
+ /retry[_-]?count\s*[=:]/,
429
+ /max[_-]?retries?\s*[=:]/,
430
+ /retry[_-]?interval\s*[=:]/,
431
+ /retry[_-]?delay\s*[=:]/,
432
+ /backoff/,
433
+ ];
3083
434
 
3084
- return false;
435
+ return retryPatterns.some(p => p.test(context));
3085
436
  }
3086
437
 
3087
- // Business Rules & Limits helpers
3088
- isPricing(value, context) {
3089
- const lowerContext = context.toLowerCase();
438
+ /**
439
+ * Check if number is a batch size configuration
440
+ */
441
+ isBatchConfig(value, parentText, grandParentText) {
442
+ // Batch sizes (typically 50-10000)
443
+ if (value < 50 || value > 100000) return false;
3090
444
 
3091
- if (typeof value === "number" && value > 0) {
3092
- // Check for price pattern (e.g., 49.99)
3093
- if (this.configPatterns.business.pricing.patterns.some((pattern) => pattern.test(value.toString()))) {
3094
- return this.configPatterns.business.pricing.keywords.some((keyword) =>
3095
- lowerContext.includes(keyword)
3096
- );
445
+ // Skip unit conversion constants (1024 for KB, MB, GB conversions)
446
+ // These appear in expressions like: * 1024 * 1024
447
+ if (value === 1024 || value === 2048 || value === 4096) {
448
+ // Check if this is used in multiplication (unit conversion)
449
+ if (parentText.includes('*') && parentText.includes('1024')) {
450
+ return false;
3097
451
  }
3098
452
  }
3099
453
 
3100
- return false;
3101
- }
3102
-
3103
- isQuota(value, context) {
3104
- const lowerContext = context.toLowerCase();
3105
-
3106
- if (typeof value !== "number" || value <= 0) return false;
3107
-
3108
- return this.configPatterns.business.quotas.patterns.some((pattern) => pattern.test(context));
3109
- }
3110
-
3111
- isDiscount(value, context) {
3112
- const lowerContext = context.toLowerCase();
3113
-
3114
- if (typeof value !== "number") return false;
3115
-
3116
- // Check for decimal rate patterns
3117
- if (this.configPatterns.business.discounts.patterns.some((pattern) => pattern.test(value.toString()))) {
3118
- return this.configPatterns.business.discounts.keywords.some((keyword) =>
3119
- lowerContext.includes(keyword)
3120
- );
3121
- }
3122
-
3123
- return false;
3124
- }
3125
-
3126
- isTrial(value, context) {
3127
- const lowerContext = context.toLowerCase();
3128
-
3129
- if (typeof value !== "number" || value <= 0) return false;
3130
-
3131
- return this.configPatterns.business.trials.patterns.some((pattern) => pattern.test(context));
3132
- }
3133
-
3134
- // Monitoring & Observability helpers
3135
- isMetricsConfig(value, context) {
3136
- const lowerContext = context.toLowerCase();
3137
-
3138
- if (typeof value !== "number" || value <= 0) return false;
3139
-
3140
- return this.configPatterns.monitoring.metricsConfig.patterns.some((pattern) => pattern.test(context));
3141
- }
3142
-
3143
- isAlertThreshold(value, context) {
3144
- const lowerContext = context.toLowerCase();
3145
-
3146
- if (typeof value !== "number") return false;
3147
-
3148
- return this.configPatterns.monitoring.alertThresholds.patterns.some((pattern) => pattern.test(context));
3149
- }
3150
-
3151
- isSamplingRate(value, context) {
3152
- const lowerContext = context.toLowerCase();
3153
-
3154
- if (typeof value !== "number" || value < 0 || value > 1) return false;
3155
-
3156
- return this.configPatterns.monitoring.samplingRates.patterns.some((pattern) => pattern.test(context));
3157
- }
3158
-
3159
- isHealthCheck(value, context) {
3160
- const lowerContext = context.toLowerCase();
3161
-
3162
- if (typeof value !== "number" || value <= 0) return false;
3163
-
3164
- return this.configPatterns.monitoring.healthChecks.patterns.some((pattern) => pattern.test(context));
3165
- }
3166
-
3167
- // ============ Phase 4: Enhancement Configuration Helpers ============
3168
-
3169
- // Message Queue & Event Configuration helpers
3170
- isQueueConfig(value, context) {
3171
- const lowerContext = context.toLowerCase();
3172
-
3173
- if (typeof value === "number" && value > 0) {
3174
- return this.configPatterns.messageQueue.queueConfig.patterns.some((pattern) => pattern.test(context));
3175
- }
3176
-
3177
- if (typeof value === "string") {
3178
- return (
3179
- this.configPatterns.messageQueue.queueNames.patterns.some((pattern) => pattern.test(value)) &&
3180
- this.configPatterns.messageQueue.queueNames.keywords.some((keyword) => lowerContext.includes(keyword))
3181
- );
3182
- }
3183
-
3184
- return false;
3185
- }
3186
-
3187
- isMessageTTL(value, context) {
3188
- const lowerContext = context.toLowerCase();
3189
-
3190
- if (typeof value !== "number" || value <= 0) return false;
454
+ const context = parentText + " " + grandParentText;
3191
455
 
3192
- return this.configPatterns.messageQueue.messageTTL.patterns.some((pattern) => pattern.test(context));
3193
- }
3194
-
3195
- isConsumerConfig(value, context) {
3196
- if (typeof value !== "string") return false;
3197
- const lowerContext = context.toLowerCase();
3198
-
3199
- // Skip CSS classes (Tailwind, Bootstrap, etc.)
3200
- // CSS classes typically have: spaces, dashes, colons, brackets
3201
- const cssIndicators = [
3202
- /\s+(flex|grid|block|inline|hidden|visible)/, // Layout utilities
3203
- /\s+(text|bg|border|rounded|shadow|p-|m-|w-|h-)/, // Common CSS utilities
3204
- /:\w+/, // Pseudo-classes like :hover, data-[...]
3205
- /\[[\w-]+\]/, // Attribute selectors
3206
- /(sm|md|lg|xl|2xl):/, // Responsive breakpoints
456
+ const batchPatterns = [
457
+ /batch[_-]?size\s*[=:]/,
458
+ /page[_-]?size\s*[=:]/,
459
+ /chunk[_-]?size\s*[=:]/,
3207
460
  ];
3208
461
 
3209
- if (cssIndicators.some((pattern) => pattern.test(value))) {
3210
- return false;
3211
- }
3212
-
3213
- // Skip className/class attributes
3214
- if (/className|class\s*=/.test(context)) {
3215
- return false;
3216
- }
3217
-
3218
- // Only detect if context has consumer/group/queue keywords
3219
- const hasConsumerContext = /consumer|queue|group|topic|subscription/i.test(context);
3220
- if (!hasConsumerContext) return false;
3221
-
3222
- return this.configPatterns.messageQueue.consumerConfig.patterns.some((pattern) => pattern.test(context));
3223
- }
3224
-
3225
- // Deployment & Infrastructure helpers
3226
- isResourceLimit(value, context) {
3227
- if (typeof value !== "string") return false;
3228
- const lowerContext = context.toLowerCase();
3229
-
3230
- const matchesPattern = this.configPatterns.deployment.resourceLimits.patterns.some((pattern) =>
3231
- pattern.test(value)
3232
- );
3233
- if (!matchesPattern) return false;
3234
-
3235
- return this.configPatterns.deployment.resourceLimits.keywords.some((keyword) =>
3236
- lowerContext.includes(keyword)
3237
- );
462
+ return batchPatterns.some(p => p.test(context));
3238
463
  }
3239
464
 
3240
- isScalingConfig(value, context) {
3241
- const lowerContext = context.toLowerCase();
3242
-
3243
- if (typeof value !== "number" || value <= 0) return false;
465
+ /**
466
+ * Check if string value is safe (not a violation)
467
+ */
468
+ isSafeString(value, node) {
469
+ // Skip relative paths
470
+ if (value.startsWith("./") || value.startsWith("../") || value.startsWith("/")) return true;
3244
471
 
3245
- return this.configPatterns.deployment.scalingConfig.patterns.some((pattern) => pattern.test(context));
3246
- }
472
+ // Skip common safe strings
473
+ const safePatterns = [
474
+ // File extensions, MIME types
475
+ /^\.(ts|js|json|html|css|xml|yaml|yml|md|txt)$/i,
476
+ /^(application|text|image|video|audio)\//,
3247
477
 
3248
- isRegionConfig(value, context) {
3249
- if (typeof value !== "string") return false;
3250
- const lowerContext = context.toLowerCase();
478
+ // HTTP methods, headers
479
+ /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/,
480
+ /^(OK|Created|Not Found|Unauthorized|Forbidden)$/i,
481
+ /^(Content-Type|Authorization|Accept|User-Agent)$/i,
3251
482
 
3252
- const matchesPattern = this.configPatterns.deployment.regionConfig.patterns.some((pattern) =>
3253
- pattern.test(value)
3254
- );
3255
- if (!matchesPattern) return false;
483
+ // Common field names, identifiers (lowercase only, not uppercase keys)
484
+ /^[a-z_][a-z0-9_]*$/,
3256
485
 
3257
- return this.configPatterns.deployment.regionConfig.keywords.some((keyword) =>
3258
- lowerContext.includes(keyword)
3259
- );
3260
- }
486
+ // Date/time formats
487
+ /^(YYYY|MM|DD|HH|mm|ss|ISO|UTC)/,
3261
488
 
3262
- isInstanceType(value, context) {
3263
- if (typeof value !== "string") return false;
3264
- const lowerContext = context.toLowerCase();
489
+ // Color codes
490
+ /^#[0-9a-f]{3,8}$/i,
3265
491
 
3266
- const matchesPattern = this.configPatterns.deployment.instanceTypes.patterns.some((pattern) =>
3267
- pattern.test(value)
3268
- );
3269
- if (!matchesPattern) return false;
492
+ // Empty or whitespace
493
+ /^\s*$/,
3270
494
 
3271
- return this.configPatterns.deployment.instanceTypes.keywords.some((keyword) =>
3272
- lowerContext.includes(keyword)
3273
- );
3274
- }
495
+ // SQL/ORM keywords
496
+ /^(ASC|DESC|NULL|NOT NULL|PRIMARY|UNIQUE|FOREIGN|INDEX)$/i,
3275
497
 
3276
- // Third-party Integration helpers
3277
- isWebhookURL(value, context) {
3278
- if (typeof value !== "string") return false;
3279
- const lowerContext = context.toLowerCase();
3280
-
3281
- // Skip UI text/error messages - they're not webhook URLs!
3282
- const uiTextIndicators = [
3283
- /failed|error|success|warning/i,
3284
- /please|try again|invalid/i,
3285
- /\s{2,}/, // Multiple spaces (typical in messages)
3286
- value.length > 100, // Long text is likely a message
498
+ // Common string values
499
+ /^(true|false|null|undefined|none|default)$/i,
3287
500
  ];
3288
501
 
3289
- if (uiTextIndicators.some((pattern) => (typeof pattern === "boolean" ? pattern : pattern.test(value)))) {
3290
- return false;
3291
- }
3292
-
3293
- // Skip single words that aren't paths
3294
- if (!/[\/\.]/.test(value) && value.length < 20) {
3295
- // Single word like "Callback" is not a webhook URL
3296
- return false;
3297
- }
3298
-
3299
- return (
3300
- this.configPatterns.integration.webhookURLs.patterns.some((pattern) => pattern.test(value)) ||
3301
- this.configPatterns.integration.webhookURLs.keywords.some((keyword) => lowerContext.includes(keyword))
3302
- );
3303
- }
3304
-
3305
- isExternalService(value, context) {
3306
- if (typeof value !== "string") return false;
3307
- const lowerContext = context.toLowerCase();
3308
-
3309
- const matchesPattern = this.configPatterns.integration.externalServices.patterns.some((pattern) =>
3310
- pattern.test(value)
3311
- );
3312
- if (!matchesPattern) return false;
3313
-
3314
- return this.configPatterns.integration.externalServices.keywords.some((keyword) =>
3315
- lowerContext.includes(keyword)
3316
- );
3317
- }
3318
-
3319
- isApiVersion(value, context) {
3320
- if (typeof value !== "string") return false;
3321
- const lowerContext = context.toLowerCase();
3322
-
3323
- const matchesPattern = this.configPatterns.integration.apiVersions.patterns.some((pattern) =>
3324
- pattern.test(value)
3325
- );
3326
- if (!matchesPattern) return false;
3327
-
3328
- return this.configPatterns.integration.apiVersions.keywords.some((keyword) =>
3329
- lowerContext.includes(keyword)
3330
- );
3331
- }
3332
-
3333
- isChannelId(value, context) {
3334
- if (typeof value !== "string") return false;
3335
- const lowerContext = context.toLowerCase();
3336
-
3337
- const matchesPattern = this.configPatterns.integration.channelIds.patterns.some((pattern) =>
3338
- pattern.test(value)
3339
- );
3340
- if (!matchesPattern) return false;
3341
-
3342
- return this.configPatterns.integration.channelIds.keywords.some((keyword) =>
3343
- lowerContext.includes(keyword)
3344
- );
3345
- }
3346
-
3347
- // Localization & Formatting helpers
3348
- isTimezone(value, context) {
3349
- if (typeof value !== "string") return false;
3350
- const lowerContext = context.toLowerCase();
3351
-
3352
- const matchesPattern = this.configPatterns.localization.timezones.patterns.some((pattern) =>
3353
- pattern.test(value)
3354
- );
3355
- if (!matchesPattern) return false;
3356
-
3357
- return this.configPatterns.localization.timezones.keywords.some((keyword) =>
3358
- lowerContext.includes(keyword)
3359
- );
3360
- }
3361
-
3362
- isDateFormat(value, context) {
3363
- if (typeof value !== "string") return false;
3364
- const lowerContext = context.toLowerCase();
3365
-
3366
- const matchesPattern = this.configPatterns.localization.dateFormats.patterns.some((pattern) =>
3367
- pattern.test(value)
3368
- );
3369
- if (!matchesPattern) return false;
3370
-
3371
- return this.configPatterns.localization.dateFormats.keywords.some((keyword) =>
3372
- lowerContext.includes(keyword)
3373
- );
3374
- }
3375
-
3376
- isCurrency(value, context) {
3377
- if (typeof value !== "string") return false;
3378
- const lowerContext = context.toLowerCase();
3379
-
3380
- const matchesPattern = this.configPatterns.localization.currencies.patterns.some((pattern) =>
3381
- pattern.test(value)
3382
- );
3383
- if (!matchesPattern) return false;
3384
-
3385
- return this.configPatterns.localization.currencies.keywords.some((keyword) =>
3386
- lowerContext.includes(keyword)
3387
- );
3388
- }
3389
-
3390
- isLocale(value, context) {
3391
- if (typeof value !== "string") return false;
3392
- const lowerContext = context.toLowerCase();
3393
-
3394
- const matchesPattern = this.configPatterns.localization.locales.patterns.some((pattern) =>
3395
- pattern.test(value)
3396
- );
3397
- if (!matchesPattern) return false;
3398
-
3399
- return this.configPatterns.localization.locales.keywords.some((keyword) =>
3400
- lowerContext.includes(keyword)
3401
- );
3402
- }
3403
-
3404
- isNumberFormat(value, context) {
3405
- if (typeof value !== "string") return false;
3406
- const lowerContext = context.toLowerCase();
3407
-
3408
- return this.configPatterns.localization.numberFormats.patterns.some((pattern) => pattern.test(context));
3409
- }
3410
-
3411
- // ============ Additional Critical Configuration Helpers ============
502
+ if (safePatterns.some(p => p.test(value))) return true;
3412
503
 
3413
- // Environment Variable Names (hardcoded)
3414
- isHardcodedEnvVar(value, context) {
3415
- if (typeof value !== "string") return false;
3416
-
3417
- // Check if it's accessing process.env with hardcoded environment-specific name
3418
- // Look for patterns like: process.env.PROD_API_URL, process.env['DEV_DATABASE']
3419
- if (!context.includes("process.env")) return false;
3420
-
3421
- // The value should be the environment variable name itself
3422
- return this.configPatterns.environmentVars.patterns.some((pattern) => pattern.test(value));
3423
- }
3424
-
3425
- // Third-party Service IDs
3426
- isThirdPartyServiceId(value, context) {
3427
- if (typeof value !== "string") return false;
3428
-
3429
- const { thirdPartyServices } = this.configPatterns;
3430
-
3431
- // Check Stripe keys
3432
- if (thirdPartyServices.stripe.patterns.some((p) => p.test(value))) {
3433
- return true;
3434
- }
3435
-
3436
- // Check Google Analytics IDs
3437
- if (thirdPartyServices.googleAnalytics.patterns.some((p) => p.test(value))) {
3438
- return true;
3439
- }
3440
-
3441
- // Check Sentry DSN
3442
- if (thirdPartyServices.sentry.patterns.some((p) => p.test(value))) {
3443
- return true;
3444
- }
3445
-
3446
- // Check Google Maps API Key
3447
- if (thirdPartyServices.googleMaps.patterns.some((p) => p.test(value))) {
3448
- return true;
3449
- }
3450
-
3451
- // Check Firebase
3452
- if (thirdPartyServices.firebase.patterns.some((p) => p.test(value))) {
3453
- return true;
3454
- }
3455
-
3456
- // Check AWS
3457
- if (thirdPartyServices.aws.patterns.some((p) => p.test(value))) {
3458
- return true;
3459
- }
504
+ // Skip if value looks like an enum value or constant
505
+ if (value === value.toUpperCase() && value.match(/^[A-Z_]+$/)) return true;
3460
506
 
3461
507
  return false;
3462
508
  }
3463
509
 
3464
- // IP Addresses
3465
- isIPAddress(value, context) {
3466
- if (typeof value !== "string") return false;
3467
-
3468
- const { ipAddresses } = this.configPatterns;
3469
-
3470
- // Check if it matches IP pattern
3471
- const isIP = ipAddresses.patterns.some((pattern) => pattern.test(value));
3472
- if (!isIP) return false;
3473
-
3474
- // Skip localhost and common development IPs
3475
- if (ipAddresses.privateRanges.some((range) => range.test(value))) {
3476
- // Only flag private IPs if in server/host context
3477
- return ipAddresses.keywords.some((keyword) => context.toLowerCase().includes(keyword));
3478
- }
3479
-
3480
- return true;
3481
- }
3482
-
3483
- // Internal Hostnames
3484
- isInternalHostname(value, context) {
3485
- if (typeof value !== "string") return false;
3486
- const lowerContext = context.toLowerCase();
3487
-
3488
- const matchesPattern = this.configPatterns.hostnames.patterns.some((pattern) => pattern.test(value));
3489
- if (!matchesPattern) return false;
3490
-
3491
- return this.configPatterns.hostnames.keywords.some((keyword) => lowerContext.includes(keyword));
3492
- }
3493
-
3494
- // Cron Schedules
3495
- isCronSchedule(value, context) {
3496
- if (typeof value !== "string") return false;
3497
- const lowerContext = context.toLowerCase();
3498
-
3499
- const matchesPattern = this.configPatterns.cronSchedules.patterns.some((pattern) => pattern.test(value));
3500
- if (!matchesPattern) return false;
3501
-
3502
- return this.configPatterns.cronSchedules.keywords.some((keyword) => lowerContext.includes(keyword));
3503
- }
3504
-
3505
- // Magic Numbers in Business Logic
3506
- isMagicNumber(value, context) {
3507
- if (typeof value !== "number") return false;
3508
-
3509
- // Skip common constants
3510
- const commonConstants = [0, 1, 2];
3511
- if (commonConstants.includes(value)) return false;
3512
-
3513
- // Skip HTTP status codes (200, 404, etc.)
3514
- if (value >= 100 && value < 600 && Number.isInteger(value)) {
3515
- // But allow decimal values like 0.1 (tax rate), 0.08 (VAT)
3516
- return false;
3517
- }
3518
-
3519
- const lowerContext = context.toLowerCase();
3520
-
3521
- // Check if it's in business logic context with specific keywords
3522
- const hasKeyword = this.configPatterns.magicNumbers.keywords.some((keyword) =>
3523
- lowerContext.includes(keyword)
3524
- );
3525
-
3526
- if (!hasKeyword) return false;
3527
-
3528
- // Additional check: look for business context patterns
3529
- const hasBusinessContext = this.configPatterns.magicNumbers.businessContexts.some((pattern) =>
3530
- pattern.test(context)
3531
- );
3532
-
3533
- return hasBusinessContext;
3534
- }
3535
-
3536
- // Email & SMS Templates
3537
- isMessageTemplate(value, context) {
3538
- if (typeof value !== "string") return false;
3539
- if (value.length < 10) return false;
3540
-
3541
- const lowerValue = value.toLowerCase();
3542
- const lowerContext = context.toLowerCase();
3543
-
3544
- // Skip error codes/enums (like "OAuthAccountNotLinked", "AccessDenied")
3545
- // These are constants, not templates!
3546
- if (/^[A-Z][a-zA-Z]+$/.test(value) && !value.includes(" ") && value.length < 30) {
3547
- return false;
3548
- }
3549
-
3550
- // Skip short strings without template markers
3551
- if (value.length < 20 && !value.includes("{{") && !value.includes("${")) {
3552
- return false;
3553
- }
3554
-
3555
- // Email subject/body - check both context AND value for keywords
3556
- const emailKeywordInContext = this.configPatterns.messageTemplates.email.keywords.some((k) =>
3557
- lowerContext.includes(k)
3558
- );
3559
- const emailKeywordInValue = ["welcome", "verify", "password", "reset", "order", "confirm"].some((k) =>
3560
- lowerValue.includes(k)
3561
- );
510
+ /**
511
+ * Check if number is a safe common value
512
+ */
513
+ isSafeNumber(value) {
514
+ // Common safe numbers
515
+ const safeNumbers = [
516
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
517
+ 100, 200, 201, 204, 301, 302, 304, 400, 401, 403, 404, 500, 502, 503, // HTTP status
518
+ 1000, // Common multiplier
519
+ -1, // Common sentinel
520
+ ];
3562
521
 
3563
- if (emailKeywordInContext || emailKeywordInValue) {
3564
- return this.configPatterns.messageTemplates.email.patterns.some((p) => p.test(value));
3565
- }
522
+ if (safeNumbers.includes(value)) return true;
3566
523
 
3567
- // SMS text - check for OTP, code, verification keywords
3568
- const smsKeywordInContext = this.configPatterns.messageTemplates.sms.keywords.some((k) =>
3569
- lowerContext.includes(k)
3570
- );
3571
- const smsKeywordInValue = ["otp", "code", "verification", "verify"].some((k) => lowerValue.includes(k));
524
+ // Skip small numbers (likely indices, counts)
525
+ if (value >= 0 && value <= 100) return true;
3572
526
 
3573
- if (smsKeywordInContext || smsKeywordInValue) {
3574
- return this.configPatterns.messageTemplates.sms.patterns.some((p) => p.test(value));
3575
- }
527
+ // Skip round numbers that are likely safe constants
528
+ if (value % 1000 === 0 && value <= 10000) return true;
3576
529
 
3577
530
  return false;
3578
531
  }
3579
532
 
3580
- // Version Numbers
3581
- isVersionNumber(value, context) {
3582
- if (typeof value !== "string") return false;
3583
- const lowerContext = context.toLowerCase();
3584
-
3585
- const matchesPattern = this.configPatterns.versions.patterns.some((pattern) => pattern.test(value));
3586
- if (!matchesPattern) return false;
3587
-
3588
- return this.configPatterns.versions.keywords.some((keyword) => lowerContext.includes(keyword));
3589
- }
3590
-
3591
- // Default Pagination
3592
- isPaginationDefault(value, context) {
3593
- if (typeof value !== "number") return false;
3594
- const lowerContext = context.toLowerCase();
3595
-
3596
- // Must be a common pagination value
3597
- if (!this.configPatterns.pagination.defaults.includes(value)) return false;
3598
-
3599
- // Must be in pagination context
3600
- return this.configPatterns.pagination.keywords.some((keyword) => lowerContext.includes(keyword));
3601
- }
3602
-
3603
- maskSensitiveValue(value) {
3604
- if (typeof value !== "string" || value.length <= 6) {
3605
- return value;
533
+ /**
534
+ * Mask sensitive values for display
535
+ */
536
+ maskValue(value) {
537
+ if (value.length <= 8) {
538
+ return "*".repeat(value.length);
3606
539
  }
3607
-
3608
- // Mask sensitive information but show some characters for context
3609
- const start = value.substring(0, 3);
3610
- const end = value.substring(value.length - 3);
3611
- const masked = "*".repeat(Math.min(value.length - 6, 10));
3612
- return `${start}${masked}${end}`;
540
+ return value.substring(0, 3) + "*".repeat(Math.min(value.length - 6, 20)) + value.substring(value.length - 3);
3613
541
  }
3614
542
 
543
+ /**
544
+ * Create violation message
545
+ */
3615
546
  createMessage(config) {
3616
- const baseMessage = "Configuration should not be hardcoded in source code.";
3617
-
3618
- switch (config.type) {
3619
- case "api_url":
3620
- return `${baseMessage} API URL '${config.value}' should be managed through environment variables or configuration files.`;
3621
- case "credential":
3622
- return `${baseMessage} Credential '${config.value}' must be stored in secure environment variables or a secrets vault.`;
3623
- case "connection_string":
3624
- return `${baseMessage} Database connection string should be loaded from environment variables.`;
3625
- case "timeout":
3626
- return `${baseMessage} Timeout value ${config.value}ms may need to differ between environments.`;
3627
- case "retry_interval":
3628
- return `${baseMessage} Retry interval ${config.value}ms should be configurable per environment.`;
3629
- case "batch_size":
3630
- return `${baseMessage} Batch size ${config.value} may vary between development and production environments.`;
3631
- case "threshold":
3632
- return `${baseMessage} Threshold value ${config.value} should be environment-configurable.`;
3633
- case "feature_flag":
3634
- return `${baseMessage} Feature flag should be managed through a feature flag system or configuration.`;
3635
- case "cors_origin":
3636
- return `${baseMessage} CORS origin '${config.value}' should be loaded from environment configuration.`;
3637
- case "session_config":
3638
- return `${baseMessage} Session configuration '${config.value}' should be environment-dependent.`;
3639
- case "cache_config":
3640
- return `${baseMessage} Cache configuration '${config.value}' should be managed through environment settings.`;
3641
- case "log_level":
3642
- return `${baseMessage} Log level '${config.value}' should be configurable per environment.`;
3643
- case "environment_name":
3644
- return `${baseMessage} Environment name '${config.value}' should not be hardcoded, use environment detection.`;
3645
- case "service_dependency":
3646
- return `${baseMessage} Service URL '${config.value}' should use service discovery or configuration management.`;
3647
- case "performance_config":
3648
- return `${baseMessage} Performance setting ${config.value} should be tuned per environment capacity.`;
3649
-
3650
- // Phase 2: Critical configurations
3651
- case "database_pool":
3652
- return `${baseMessage} Database pool size ${config.value} should be optimized per environment capacity.`;
3653
- case "query_config":
3654
- return `${baseMessage} Query configuration '${config.value}' should be environment-dependent.`;
3655
- case "schema_name":
3656
- return `${baseMessage} Database schema/table name '${config.value}' should be managed through configuration.`;
3657
- case "token_config":
3658
- return `${baseMessage} Token configuration '${config.value}' should be externalized for security.`;
3659
- case "password_policy":
3660
- return `${baseMessage} Password policy setting ${config.value} should be configurable per environment.`;
3661
- case "rate_limiting":
3662
- return `${baseMessage} Rate limit ${config.value} should vary by environment and tier.`;
3663
- case "encryption_config":
3664
- return `${baseMessage} Encryption algorithm '${config.value}' should be configurable for compliance requirements.`;
3665
- case "oauth_config":
3666
- return `${baseMessage} OAuth scope '${config.value}' should be managed through configuration.`;
3667
- case "directory":
3668
- return `${baseMessage} Directory path '${config.value}' should be environment-specific.`;
3669
- case "file_limit":
3670
- return `${baseMessage} File size limit ${config.value} bytes should be configurable per environment.`;
3671
- case "file_type":
3672
- return `${baseMessage} File type restriction '${config.value}' should be managed in configuration.`;
3673
- case "log_path":
3674
- return `${baseMessage} Log file path '${config.value}' should be environment-specific.`;
3675
-
3676
- // Phase 3: Important configurations
3677
- case "http_config":
3678
- return `${baseMessage} HTTP configuration '${config.value}' should be externalized.`;
3679
- case "network_timeout":
3680
- return `${baseMessage} Network timeout ${config.value}ms should be tuned per environment.`;
3681
- case "buffer_config":
3682
- return `${baseMessage} Buffer size ${config.value} bytes should be configurable.`;
3683
- case "keepalive_config":
3684
- return `${baseMessage} Keep-alive setting ${config.value} should be environment-dependent.`;
3685
- case "pricing":
3686
- return `${baseMessage} Price ${config.value} should be managed in pricing configuration system.`;
3687
- case "quota":
3688
- return `${baseMessage} Quota limit ${config.value} should be configurable per plan/tier.`;
3689
- case "discount":
3690
- return `${baseMessage} Discount rate ${config.value} should be managed in pricing configuration.`;
3691
- case "trial":
3692
- return `${baseMessage} Trial period ${config.value} days should be configurable.`;
3693
- case "metrics_config":
3694
- return `${baseMessage} Metrics interval ${config.value} should be environment-dependent.`;
3695
- case "alert_threshold":
3696
- return `${baseMessage} Alert threshold ${config.value} should be tuned per environment.`;
3697
- case "sampling_rate":
3698
- return `${baseMessage} Sampling rate ${config.value} should be configurable for observability tuning.`;
3699
- case "health_check":
3700
- return `${baseMessage} Health check interval ${config.value} should be environment-specific.`;
3701
-
3702
- // Phase 4: Enhancement configurations
3703
- case "queue_config":
3704
- return `${baseMessage} Queue configuration '${config.value}' should be externalized.`;
3705
- case "message_ttl":
3706
- return `${baseMessage} Message TTL ${config.value} should be configurable per environment.`;
3707
- case "consumer_config":
3708
- return `${baseMessage} Consumer configuration '${config.value}' should be managed externally.`;
3709
- case "resource_limit":
3710
- return `${baseMessage} Resource limit '${config.value}' should be environment-specific.`;
3711
- case "scaling_config":
3712
- return `${baseMessage} Scaling configuration ${config.value} should be tuned per environment.`;
3713
- case "region_config":
3714
- return `${baseMessage} Deployment region '${config.value}' should be determined at deployment time.`;
3715
- case "instance_type":
3716
- return `${baseMessage} Instance type '${config.value}' should be configurable per environment.`;
3717
- case "webhook_url":
3718
- return `${baseMessage} Webhook URL '${config.value}' should be environment-specific.`;
3719
- case "external_service":
3720
- return `${baseMessage} External service name '${config.value}' should be managed in configuration.`;
3721
- case "api_version":
3722
- return `${baseMessage} API version '${config.value}' should be configurable for version management.`;
3723
- case "channel_id":
3724
- return `${baseMessage} Channel ID '${config.value}' should be environment-dependent.`;
3725
- case "timezone":
3726
- return `${baseMessage} Timezone '${config.value}' should be user-configurable or environment-specific.`;
3727
- case "date_format":
3728
- return `${baseMessage} Date format '${config.value}' should be locale-configurable.`;
3729
- case "currency":
3730
- return `${baseMessage} Currency code '${config.value}' should be region-configurable.`;
3731
- case "locale":
3732
- return `${baseMessage} Locale '${config.value}' should be user-configurable.`;
3733
- case "number_format":
3734
- return `${baseMessage} Number format should be locale-configurable.`;
3735
-
3736
- // Additional critical configurations
3737
- case "environment_var":
3738
- return `${baseMessage} Environment variable name '${config.value}' is hardcoded and environment-specific.`;
3739
- case "third_party_service":
3740
- return `${baseMessage} Third-party service ID/key '${config.value}' is hardcoded (security risk).`;
3741
- case "ip_address":
3742
- return `${baseMessage} IP address '${config.value}' is hardcoded.`;
3743
- case "hostname":
3744
- return `${baseMessage} Internal hostname '${config.value}' is hardcoded.`;
3745
- case "cron_schedule":
3746
- return `${baseMessage} Cron schedule '${config.value}' is hardcoded.`;
3747
- case "magic_number":
3748
- return `${baseMessage} Magic number '${config.value}' in business logic should be a named constant.`;
3749
- case "message_template":
3750
- return `${baseMessage} Message template is hardcoded (should be in template system).`;
3751
- case "version":
3752
- return `${baseMessage} Version number '${config.value}' is hardcoded.`;
3753
- case "pagination_default":
3754
- return `${baseMessage} Pagination default '${config.value}' is hardcoded.`;
3755
-
3756
- case "property_config":
3757
- return `${baseMessage} Property '${config.propertyName}' contains value '${config.value}' that may differ between environments.`;
3758
- case "variable_config":
3759
- return `${baseMessage} Variable '${config.variableName}' contains value '${config.value}' that should be configurable.`;
3760
- default:
3761
- return `${baseMessage} Value '${config.value}' may need to differ between development, staging, and production environments.`;
3762
- }
547
+ const messages = {
548
+ api_url: `Hardcoded API URL '${config.value}' should be externalized to environment variables or config file`,
549
+ password: `Hardcoded password detected. Move to secure environment variable`,
550
+ api_key: `Hardcoded API key detected. Move to secure environment variable`,
551
+ secret: `Hardcoded secret detected. Move to secure environment variable`,
552
+ token: `Hardcoded token detected. Move to secure environment variable`,
553
+ private_key: `Hardcoded private key detected. Move to secure environment variable`,
554
+ connection_string: `Hardcoded connection string with credentials. Move to environment variable`,
555
+ jwt_token: `Hardcoded JWT token detected. This should be dynamically generated`,
556
+ aws_key: `Hardcoded AWS key detected. Move to secure environment variable`,
557
+ timeout: `Timeout value ${config.value}ms should be configurable via environment or config file`,
558
+ retry_config: `Retry configuration value ${config.value} should be externalized`,
559
+ batch_size: `Batch size ${config.value} should be configurable`,
560
+ };
561
+
562
+ return messages[config.type] || `Hardcoded configuration value should be externalized`;
3763
563
  }
3764
564
 
565
+ /**
566
+ * Get suggestion for fixing the violation
567
+ */
3765
568
  getSuggestion(type) {
3766
569
  const suggestions = {
3767
- api_url: 'Use process.env.API_BASE_URL or config.get("api.baseUrl")',
3768
- credential: "Use process.env.API_KEY or secure vault (e.g., AWS Secrets Manager, Azure Key Vault)",
3769
- connection_string: "Use process.env.DATABASE_URL or connection configuration",
3770
- timeout: 'Use process.env.REQUEST_TIMEOUT or config.get("timeouts.request")',
3771
- retry_interval: 'Use process.env.RETRY_INTERVAL or config.get("retry.interval")',
3772
- batch_size: 'Use process.env.BATCH_SIZE or config.get("processing.batchSize")',
3773
- threshold: 'Use process.env.MEMORY_THRESHOLD or config.get("limits.memory")',
3774
- feature_flag:
3775
- 'Use feature flag service (e.g., LaunchDarkly, ConfigCat) or config.get("features.enabled")',
3776
- cors_origin: 'Use process.env.CORS_ORIGINS or config.get("security.corsOrigins")',
3777
- session_config: 'Use process.env.JWT_EXPIRY or config.get("auth.sessionTimeout")',
3778
- cache_config: 'Use process.env.CACHE_TTL or config.get("cache.defaultTtl")',
3779
- log_level: 'Use process.env.LOG_LEVEL or config.get("logging.level")',
3780
- environment_name: "Use process.env.NODE_ENV or runtime environment detection",
3781
- service_dependency: 'Use service discovery or config.get("services.userService.url")',
3782
- performance_config: 'Use process.env.WORKER_THREADS or config.get("performance.workers")',
3783
-
3784
- // Phase 2: Critical configurations
3785
- database_pool: 'Use process.env.DB_POOL_SIZE or config.get("database.poolSize")',
3786
- query_config: 'Use process.env.QUERY_TIMEOUT or config.get("database.queryTimeout")',
3787
- schema_name: 'Use process.env.TABLE_PREFIX or config.get("database.schemaName")',
3788
- token_config: 'Use process.env.TOKEN_EXPIRY or config.get("auth.tokenExpiry")',
3789
- password_policy: 'Use process.env.MIN_PASSWORD_LENGTH or config.get("security.passwordPolicy")',
3790
- rate_limiting: 'Use process.env.RATE_LIMIT or config.get("security.rateLimit")',
3791
- encryption_config: 'Use process.env.ENCRYPTION_ALGORITHM or config.get("security.encryption")',
3792
- oauth_config: 'Use process.env.OAUTH_SCOPES or config.get("oauth.scopes")',
3793
- directory: 'Use process.env.UPLOAD_DIR or config.get("storage.uploadDirectory")',
3794
- file_limit: 'Use process.env.MAX_FILE_SIZE or config.get("upload.maxFileSize")',
3795
- file_type: 'Use process.env.ALLOWED_FILE_TYPES or config.get("upload.allowedTypes")',
3796
- log_path: 'Use process.env.LOG_PATH or config.get("logging.filePath")',
3797
-
3798
- // Phase 3: Important configurations
3799
- http_config: 'Use process.env.ALLOWED_METHODS or config.get("http.allowedMethods")',
3800
- network_timeout: 'Use process.env.CONNECT_TIMEOUT or config.get("network.timeout")',
3801
- buffer_config: 'Use process.env.BUFFER_SIZE or config.get("network.bufferSize")',
3802
- keepalive_config: 'Use process.env.KEEPALIVE_INTERVAL or config.get("network.keepAlive")',
3803
- pricing: 'Use pricing service or config.get("pricing.plans")',
3804
- quota: 'Use process.env.API_QUOTA or config.get("limits.quotas")',
3805
- discount: 'Use pricing service or config.get("pricing.discounts")',
3806
- trial: 'Use process.env.TRIAL_DAYS or config.get("subscription.trialPeriod")',
3807
- metrics_config: 'Use process.env.METRICS_INTERVAL or config.get("monitoring.metricsInterval")',
3808
- alert_threshold: 'Use process.env.ALERT_THRESHOLD or config.get("monitoring.alertThresholds")',
3809
- sampling_rate: 'Use process.env.TRACE_SAMPLING_RATE or config.get("observability.samplingRate")',
3810
- health_check: 'Use process.env.HEALTH_CHECK_INTERVAL or config.get("monitoring.healthCheck")',
3811
-
3812
- // Phase 4: Enhancement configurations
3813
- queue_config: 'Use process.env.QUEUE_SIZE or config.get("messaging.queueSize")',
3814
- message_ttl: 'Use process.env.MESSAGE_TTL or config.get("messaging.messageTtl")',
3815
- consumer_config: 'Use process.env.CONSUMER_GROUP or config.get("messaging.consumerGroup")',
3816
- resource_limit: 'Use Kubernetes limits or config.get("deployment.resources")',
3817
- scaling_config: 'Use process.env.MIN_REPLICAS or config.get("deployment.autoscaling")',
3818
- region_config: 'Use deployment configuration or config.get("infrastructure.region")',
3819
- instance_type: 'Use deployment configuration or config.get("infrastructure.instanceType")',
3820
- webhook_url: 'Use process.env.WEBHOOK_URL or config.get("integrations.webhookUrl")',
3821
- external_service: 'Use process.env.PAYMENT_PROVIDER or config.get("integrations.provider")',
3822
- api_version: 'Use process.env.API_VERSION or config.get("integrations.apiVersion")',
3823
- channel_id: 'Use process.env.SLACK_CHANNEL or config.get("notifications.channel")',
3824
- timezone: 'Use process.env.TZ or config.get("localization.timezone")',
3825
- date_format: 'Use config.get("localization.dateFormat") or user preferences',
3826
- currency: 'Use config.get("localization.currency") or user/region preferences',
3827
- locale: 'Use process.env.LOCALE or config.get("localization.locale")',
3828
- number_format: 'Use config.get("localization.numberFormat") or locale-based formatting',
3829
-
3830
- // Additional critical configurations
3831
- environment_var: "Use environment-agnostic names with NODE_ENV detection or config service",
3832
- third_party_service: "Use AWS Secrets Manager, Azure Key Vault, or .env with git-ignored secrets",
3833
- ip_address: 'Use service discovery (Consul, Kubernetes DNS) or config.get("services.host")',
3834
- hostname: 'Use service discovery or config.get("services.internalHostname")',
3835
- cron_schedule: 'Use process.env.CRON_SCHEDULE or config.get("scheduler.cron")',
3836
- magic_number: "Define as const BUSINESS_CONSTANT = value in constants file",
3837
- message_template: 'Use template management (i18n, email service) or config.get("templates.email")',
3838
- version: "Use package.json version or build-time injection (webpack.DefinePlugin)",
3839
- pagination_default: 'Use config.get("api.defaultPageSize") or API configuration',
3840
-
3841
- property_config: "Move to centralized configuration (config.ts, application.properties, or .env)",
3842
- variable_config: "Use environment variables or configuration service",
3843
- config_key: "Define configuration keys as constants in a dedicated config module",
570
+ api_url: "Use environment variable: process.env.API_URL or config.apiUrl",
571
+ password: "Use environment variable: process.env.DB_PASSWORD",
572
+ api_key: "Use environment variable: process.env.API_KEY",
573
+ secret: "Use environment variable: process.env.SECRET_KEY",
574
+ token: "Use environment variable or secure token storage",
575
+ private_key: "Use environment variable or secure key management",
576
+ connection_string: "Use environment variable: process.env.DATABASE_URL",
577
+ jwt_token: "Generate tokens dynamically, do not hardcode",
578
+ aws_key: "Use AWS credential provider or environment variables",
579
+ timeout: "Move to config: config.timeout or process.env.TIMEOUT",
580
+ retry_config: "Move to config: config.retryCount or process.env.MAX_RETRIES",
581
+ batch_size: "Move to config: config.batchSize or process.env.BATCH_SIZE",
3844
582
  };
3845
583
 
3846
- return (
3847
- suggestions[type] ||
3848
- "Move to environment variables, config files, or centralized configuration management"
3849
- );
584
+ return suggestions[type] || "Externalize to environment variable or config file";
3850
585
  }
3851
586
  }
3852
587