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