@sun-asterisk/sunlint 1.3.30 → 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.
- package/core/cli-action-handler.js +32 -4
- package/package.json +1 -1
- package/rules/common/C006_function_naming/analyzer.js +289 -145
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +335 -3599
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js.backup +3853 -0
- package/rules/security/S031_secure_session_cookies/analyzer.js +67 -112
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +0 -296
|
@@ -0,0 +1,3853 @@
|
|
|
1
|
+
// rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js
|
|
2
|
+
const { SyntaxKind, Project, Node } = require("ts-morph");
|
|
3
|
+
|
|
4
|
+
class C067SymbolBasedAnalyzer {
|
|
5
|
+
constructor(semanticEngine = null) {
|
|
6
|
+
this.semanticEngine = semanticEngine;
|
|
7
|
+
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
|
+
}
|
|
690
|
+
|
|
691
|
+
async initialize(semanticEngine = null) {
|
|
692
|
+
if (semanticEngine) {
|
|
693
|
+
this.semanticEngine = semanticEngine;
|
|
694
|
+
}
|
|
695
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
699
|
+
const violations = [];
|
|
700
|
+
|
|
701
|
+
try {
|
|
702
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
703
|
+
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
|
+
return await this.analyzeFileStandalone(filePath, options);
|
|
711
|
+
}
|
|
712
|
+
|
|
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
|
+
}
|
|
722
|
+
return violations;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Find hardcoded configuration values
|
|
726
|
+
const hardcodedConfigs = this.findHardcodedConfigs(sourceFile);
|
|
727
|
+
|
|
728
|
+
for (const config of hardcodedConfigs) {
|
|
729
|
+
violations.push({
|
|
730
|
+
ruleId: "C067",
|
|
731
|
+
message: this.createMessage(config),
|
|
732
|
+
filePath: filePath,
|
|
733
|
+
line: config.line,
|
|
734
|
+
column: config.column,
|
|
735
|
+
severity: "warning",
|
|
736
|
+
category: "configuration",
|
|
737
|
+
type: config.type,
|
|
738
|
+
value: config.value,
|
|
739
|
+
suggestion: this.getSuggestion(config.type),
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (this.verbose) {
|
|
744
|
+
console.log(`[DEBUG] 🔍 C067: Found ${violations.length} hardcoded config violations`);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return violations;
|
|
748
|
+
} catch (error) {
|
|
749
|
+
if (this.verbose) {
|
|
750
|
+
console.error(`[DEBUG] ❌ C067: Symbol analysis error: ${error.message}`);
|
|
751
|
+
}
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
async analyzeFileStandalone(filePath, options = {}) {
|
|
757
|
+
const violations = [];
|
|
758
|
+
|
|
759
|
+
try {
|
|
760
|
+
// Create a standalone ts-morph project for this analysis
|
|
761
|
+
const project = new Project({
|
|
762
|
+
compilerOptions: {
|
|
763
|
+
target: "ES2020",
|
|
764
|
+
module: "CommonJS",
|
|
765
|
+
allowJs: true,
|
|
766
|
+
allowSyntheticDefaultImports: true,
|
|
767
|
+
esModuleInterop: true,
|
|
768
|
+
skipLibCheck: true,
|
|
769
|
+
strict: false,
|
|
770
|
+
},
|
|
771
|
+
useInMemoryFileSystem: true,
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// Add the source file to the project
|
|
775
|
+
const fs = require("fs");
|
|
776
|
+
const path = require("path");
|
|
777
|
+
|
|
778
|
+
// Check if file exists first
|
|
779
|
+
if (!fs.existsSync(filePath)) {
|
|
780
|
+
throw new Error(`File not found on filesystem: ${filePath}`);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Read file content and create source file
|
|
784
|
+
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
785
|
+
const fileName = path.basename(filePath);
|
|
786
|
+
const sourceFile = project.createSourceFile(fileName, fileContent);
|
|
787
|
+
|
|
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
|
+
}
|
|
803
|
+
return violations;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Find hardcoded configuration values
|
|
807
|
+
const hardcodedConfigs = this.findHardcodedConfigs(sourceFile);
|
|
808
|
+
|
|
809
|
+
for (const config of hardcodedConfigs) {
|
|
810
|
+
violations.push({
|
|
811
|
+
ruleId: "C067",
|
|
812
|
+
message: this.createMessage(config),
|
|
813
|
+
filePath: filePath,
|
|
814
|
+
line: config.line,
|
|
815
|
+
column: config.column,
|
|
816
|
+
severity: "warning",
|
|
817
|
+
category: "configuration",
|
|
818
|
+
type: config.type,
|
|
819
|
+
value: config.value,
|
|
820
|
+
suggestion: this.getSuggestion(config.type),
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
|
|
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
|
+
return violations;
|
|
832
|
+
} catch (error) {
|
|
833
|
+
if (this.verbose) {
|
|
834
|
+
console.error(`[DEBUG] ❌ C067: Standalone analysis error: ${error.message}`);
|
|
835
|
+
}
|
|
836
|
+
throw error;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
isConfigOrTestFile(filePath) {
|
|
841
|
+
// Skip config files themselves and test files, including dummy/test data files
|
|
842
|
+
const fileName = filePath.toLowerCase();
|
|
843
|
+
const configPatterns = [
|
|
844
|
+
/config\.(ts|js|json)$/,
|
|
845
|
+
/\.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
|
|
850
|
+
/settings\.(ts|js)$/,
|
|
851
|
+
/defaults\.(ts|js)$/,
|
|
852
|
+
];
|
|
853
|
+
|
|
854
|
+
const testPatterns = [
|
|
855
|
+
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
|
856
|
+
/\/__tests__\//,
|
|
857
|
+
/\/test\//,
|
|
858
|
+
/\/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)
|
|
866
|
+
];
|
|
867
|
+
|
|
868
|
+
return (
|
|
869
|
+
configPatterns.some((pattern) => pattern.test(fileName)) ||
|
|
870
|
+
testPatterns.some((pattern) => pattern.test(fileName))
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
findHardcodedConfigs(sourceFile) {
|
|
875
|
+
const configs = [];
|
|
876
|
+
|
|
877
|
+
// Traverse all nodes in the source file
|
|
878
|
+
sourceFile.forEachDescendant((node) => {
|
|
879
|
+
// Check string literals for URLs, credentials, feature flags
|
|
880
|
+
if (node.getKind() === SyntaxKind.StringLiteral) {
|
|
881
|
+
const config = this.analyzeStringLiteral(node, sourceFile);
|
|
882
|
+
if (config) {
|
|
883
|
+
configs.push(config);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Check numeric literals for timeouts, ports, batch sizes
|
|
888
|
+
if (node.getKind() === SyntaxKind.NumericLiteral) {
|
|
889
|
+
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
|
+
}
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
return configs;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
analyzeStringLiteral(node, sourceFile) {
|
|
932
|
+
const value = node.getLiteralValue();
|
|
933
|
+
const position = sourceFile.getLineAndColumnAtPos(node.getStart());
|
|
934
|
+
|
|
935
|
+
// Skip short strings and common UI values
|
|
936
|
+
if (value.length < 3) return null;
|
|
937
|
+
|
|
938
|
+
const parentContext = this.getParentContext(node);
|
|
939
|
+
|
|
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)) {
|
|
997
|
+
return {
|
|
998
|
+
type: "credential",
|
|
999
|
+
value: this.maskSensitiveValue(value),
|
|
1000
|
+
line: position.line,
|
|
1001
|
+
column: position.column,
|
|
1002
|
+
node: node,
|
|
1003
|
+
context: parentContext,
|
|
1004
|
+
suggestion: "Move credentials to secure environment variables or vault",
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Check for connection strings - Rule C067 requirement
|
|
1009
|
+
if (this.configPatterns.connections.regex.test(value)) {
|
|
1010
|
+
return {
|
|
1011
|
+
type: "connection_string",
|
|
1012
|
+
value: this.maskSensitiveValue(value),
|
|
1013
|
+
line: position.line,
|
|
1014
|
+
column: position.column,
|
|
1015
|
+
node: node,
|
|
1016
|
+
suggestion: "Move connection strings to environment variables",
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Phase 1 extensions - Security & Infrastructure
|
|
1021
|
+
|
|
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
|
+
}
|
|
1033
|
+
|
|
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
|
+
}
|
|
1045
|
+
|
|
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
|
+
}
|
|
1057
|
+
|
|
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
|
+
}
|
|
1069
|
+
|
|
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
|
+
}
|
|
1081
|
+
|
|
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
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// ============ Phase 2: Critical String Configurations ============
|
|
1095
|
+
|
|
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
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
|
|
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
|
+
}
|
|
1119
|
+
|
|
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
|
+
}
|
|
1131
|
+
|
|
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
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
|
|
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
|
+
}
|
|
1155
|
+
|
|
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
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// ============ Phase 3: Important String Configurations ============
|
|
1169
|
+
|
|
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
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
|
|
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
|
+
}
|
|
1193
|
+
|
|
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
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// ============ Phase 4: Enhancement String Configurations ============
|
|
1207
|
+
|
|
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
|
+
}
|
|
1219
|
+
|
|
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
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
|
|
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
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
|
|
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
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
|
|
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
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
|
|
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
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// Check for channel IDs (Slack, etc.)
|
|
1281
|
+
if (this.isChannelId(value, parentContext)) {
|
|
1282
|
+
return {
|
|
1283
|
+
type: "channel_id",
|
|
1284
|
+
value: value,
|
|
1285
|
+
line: position.line,
|
|
1286
|
+
column: position.column,
|
|
1287
|
+
node: node,
|
|
1288
|
+
suggestion: "Move channel IDs to notification configuration",
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Check for timezones
|
|
1293
|
+
if (this.isTimezone(value, parentContext)) {
|
|
1294
|
+
return {
|
|
1295
|
+
type: "timezone",
|
|
1296
|
+
value: value,
|
|
1297
|
+
line: position.line,
|
|
1298
|
+
column: position.column,
|
|
1299
|
+
node: node,
|
|
1300
|
+
suggestion: "Move timezone to localization configuration",
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// Check for date formats
|
|
1305
|
+
if (this.isDateFormat(value, parentContext)) {
|
|
1306
|
+
return {
|
|
1307
|
+
type: "date_format",
|
|
1308
|
+
value: value,
|
|
1309
|
+
line: position.line,
|
|
1310
|
+
column: position.column,
|
|
1311
|
+
node: node,
|
|
1312
|
+
suggestion: "Move date formats to localization configuration",
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
|
|
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));
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
isBufferConfig(value, context) {
|
|
3066
|
+
const lowerContext = context.toLowerCase();
|
|
3067
|
+
|
|
3068
|
+
if (typeof value !== "number" || value <= 1024) return false;
|
|
3069
|
+
|
|
3070
|
+
return this.configPatterns.network.bufferConfig.patterns.some((pattern) => pattern.test(context));
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
isKeepAliveConfig(value, context) {
|
|
3074
|
+
const lowerContext = context.toLowerCase();
|
|
3075
|
+
|
|
3076
|
+
if (typeof value === "boolean") {
|
|
3077
|
+
return /keepalive|keep.*alive/i.test(context);
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
if (typeof value === "number" && value > 0) {
|
|
3081
|
+
return this.configPatterns.network.keepAlive.patterns.some((pattern) => pattern.test(context));
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
return false;
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
// Business Rules & Limits helpers
|
|
3088
|
+
isPricing(value, context) {
|
|
3089
|
+
const lowerContext = context.toLowerCase();
|
|
3090
|
+
|
|
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
|
+
);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
|
|
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;
|
|
3191
|
+
|
|
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
|
|
3207
|
+
];
|
|
3208
|
+
|
|
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
|
+
);
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
isScalingConfig(value, context) {
|
|
3241
|
+
const lowerContext = context.toLowerCase();
|
|
3242
|
+
|
|
3243
|
+
if (typeof value !== "number" || value <= 0) return false;
|
|
3244
|
+
|
|
3245
|
+
return this.configPatterns.deployment.scalingConfig.patterns.some((pattern) => pattern.test(context));
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
isRegionConfig(value, context) {
|
|
3249
|
+
if (typeof value !== "string") return false;
|
|
3250
|
+
const lowerContext = context.toLowerCase();
|
|
3251
|
+
|
|
3252
|
+
const matchesPattern = this.configPatterns.deployment.regionConfig.patterns.some((pattern) =>
|
|
3253
|
+
pattern.test(value)
|
|
3254
|
+
);
|
|
3255
|
+
if (!matchesPattern) return false;
|
|
3256
|
+
|
|
3257
|
+
return this.configPatterns.deployment.regionConfig.keywords.some((keyword) =>
|
|
3258
|
+
lowerContext.includes(keyword)
|
|
3259
|
+
);
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
isInstanceType(value, context) {
|
|
3263
|
+
if (typeof value !== "string") return false;
|
|
3264
|
+
const lowerContext = context.toLowerCase();
|
|
3265
|
+
|
|
3266
|
+
const matchesPattern = this.configPatterns.deployment.instanceTypes.patterns.some((pattern) =>
|
|
3267
|
+
pattern.test(value)
|
|
3268
|
+
);
|
|
3269
|
+
if (!matchesPattern) return false;
|
|
3270
|
+
|
|
3271
|
+
return this.configPatterns.deployment.instanceTypes.keywords.some((keyword) =>
|
|
3272
|
+
lowerContext.includes(keyword)
|
|
3273
|
+
);
|
|
3274
|
+
}
|
|
3275
|
+
|
|
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
|
|
3287
|
+
];
|
|
3288
|
+
|
|
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 ============
|
|
3412
|
+
|
|
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
|
+
}
|
|
3460
|
+
|
|
3461
|
+
return false;
|
|
3462
|
+
}
|
|
3463
|
+
|
|
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
|
+
);
|
|
3562
|
+
|
|
3563
|
+
if (emailKeywordInContext || emailKeywordInValue) {
|
|
3564
|
+
return this.configPatterns.messageTemplates.email.patterns.some((p) => p.test(value));
|
|
3565
|
+
}
|
|
3566
|
+
|
|
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));
|
|
3572
|
+
|
|
3573
|
+
if (smsKeywordInContext || smsKeywordInValue) {
|
|
3574
|
+
return this.configPatterns.messageTemplates.sms.patterns.some((p) => p.test(value));
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
return false;
|
|
3578
|
+
}
|
|
3579
|
+
|
|
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;
|
|
3606
|
+
}
|
|
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}`;
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
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
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
getSuggestion(type) {
|
|
3766
|
+
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",
|
|
3844
|
+
};
|
|
3845
|
+
|
|
3846
|
+
return (
|
|
3847
|
+
suggestions[type] ||
|
|
3848
|
+
"Move to environment variables, config files, or centralized configuration management"
|
|
3849
|
+
);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
module.exports = C067SymbolBasedAnalyzer;
|