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