@shipsafe/cli 0.1.2 → 0.2.0

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.
@@ -0,0 +1,1962 @@
1
+ /**
2
+ * ShipSafe Built-in Secret Scanner
3
+ *
4
+ * Pure TypeScript secret detection engine — zero external dependencies.
5
+ * Replaces Gitleaks with 100+ regex patterns, Shannon entropy checks,
6
+ * and context-aware validation for minimal false positives.
7
+ */
8
+ import { readdir, readFile, stat } from 'node:fs/promises';
9
+ import { join, extname, relative, basename } from 'node:path';
10
+ // ── Shannon Entropy ────────────────────────────────────────────────────────────
11
+ function shannonEntropy(s) {
12
+ if (s.length === 0)
13
+ return 0;
14
+ const freq = new Map();
15
+ for (const ch of s) {
16
+ freq.set(ch, (freq.get(ch) ?? 0) + 1);
17
+ }
18
+ let entropy = 0;
19
+ const len = s.length;
20
+ for (const count of freq.values()) {
21
+ const p = count / len;
22
+ entropy -= p * Math.log2(p);
23
+ }
24
+ return entropy;
25
+ }
26
+ // ── Placeholder / example detection ────────────────────────────────────────────
27
+ const PLACEHOLDER_PATTERNS = [
28
+ /^[x]{6,}$/i,
29
+ /^[*]{6,}$/,
30
+ /^<[^>]+>$/,
31
+ /^\$\{[^}]+\}$/,
32
+ /^%[^%]+%$/,
33
+ /^\{\{[^}]+\}\}$/,
34
+ /^your[_-]?/i,
35
+ /^example/i,
36
+ /^test[_-]?/i,
37
+ /^dummy/i,
38
+ /^fake/i,
39
+ /^replace[_-]?me/i,
40
+ /^insert[_-]?/i,
41
+ /^todo/i,
42
+ /^fixme/i,
43
+ /^changeme/i,
44
+ /^placeholder/i,
45
+ /^sample/i,
46
+ /^my[_-]?(api|secret|key|token|password)/i,
47
+ /^(xxx+|yyy+|zzz+|aaa+|bbb+|000+|111+|123+)/i,
48
+ ];
49
+ function isPlaceholder(value) {
50
+ const trimmed = value.trim().replace(/['"` ]/g, '');
51
+ if (trimmed.length < 8)
52
+ return true;
53
+ return PLACEHOLDER_PATTERNS.some((p) => p.test(trimmed));
54
+ }
55
+ // ── Comment / documentation line detection ─────────────────────────────────────
56
+ function isDocOrCommentExample(line) {
57
+ const trimmed = line.trim();
58
+ // Markdown code fence examples
59
+ if (trimmed.startsWith('```'))
60
+ return true;
61
+ // Lines that are clearly documentation
62
+ if (trimmed.startsWith('* @example'))
63
+ return true;
64
+ if (trimmed.startsWith('* @param'))
65
+ return true;
66
+ if (trimmed.startsWith('* @returns'))
67
+ return true;
68
+ // "e.g." or "for example" context
69
+ if (/\be\.g\.\b/i.test(trimmed))
70
+ return true;
71
+ if (/\bfor example\b/i.test(trimmed))
72
+ return true;
73
+ // URLs in comments pointing to docs
74
+ if (/^\s*[/*#]+\s*https?:\/\//.test(trimmed))
75
+ return true;
76
+ return false;
77
+ }
78
+ // ── File filtering ─────────────────────────────────────────────────────────────
79
+ const SKIP_DIRS = new Set([
80
+ 'node_modules',
81
+ '.git',
82
+ 'dist',
83
+ 'build',
84
+ 'coverage',
85
+ '.next',
86
+ '.nuxt',
87
+ '.svelte-kit',
88
+ '__pycache__',
89
+ '.pytest_cache',
90
+ 'vendor',
91
+ '.terraform',
92
+ '.cache',
93
+ '.turbo',
94
+ '.vercel',
95
+ '.output',
96
+ 'out',
97
+ '.parcel-cache',
98
+ ]);
99
+ const SKIP_EXTENSIONS = new Set([
100
+ // Minified / bundled
101
+ '.min.js',
102
+ '.min.css',
103
+ '.bundle.js',
104
+ '.chunk.js',
105
+ // Binaries & media
106
+ '.png',
107
+ '.jpg',
108
+ '.jpeg',
109
+ '.gif',
110
+ '.bmp',
111
+ '.ico',
112
+ '.svg',
113
+ '.webp',
114
+ '.avif',
115
+ '.mp3',
116
+ '.mp4',
117
+ '.mov',
118
+ '.avi',
119
+ '.mkv',
120
+ '.wav',
121
+ '.flac',
122
+ '.ogg',
123
+ '.woff',
124
+ '.woff2',
125
+ '.ttf',
126
+ '.eot',
127
+ '.otf',
128
+ '.pdf',
129
+ '.zip',
130
+ '.tar',
131
+ '.gz',
132
+ '.bz2',
133
+ '.7z',
134
+ '.rar',
135
+ '.exe',
136
+ '.dll',
137
+ '.so',
138
+ '.dylib',
139
+ '.o',
140
+ '.a',
141
+ '.class',
142
+ '.jar',
143
+ '.pyc',
144
+ '.pyo',
145
+ '.wasm',
146
+ // Lock files
147
+ '.lock',
148
+ // Maps
149
+ '.map',
150
+ ]);
151
+ const SKIP_FILENAMES = new Set([
152
+ 'package-lock.json',
153
+ 'yarn.lock',
154
+ 'pnpm-lock.yaml',
155
+ 'bun.lockb',
156
+ 'composer.lock',
157
+ 'Gemfile.lock',
158
+ 'Cargo.lock',
159
+ 'poetry.lock',
160
+ 'go.sum',
161
+ ]);
162
+ const MAX_FILE_SIZE = 1_048_576; // 1MB
163
+ function shouldSkipFile(filePath) {
164
+ const name = basename(filePath);
165
+ if (SKIP_FILENAMES.has(name))
166
+ return true;
167
+ const ext = extname(filePath).toLowerCase();
168
+ if (SKIP_EXTENSIONS.has(ext))
169
+ return true;
170
+ // Compound extensions like .min.js
171
+ if (filePath.endsWith('.min.js') || filePath.endsWith('.min.css'))
172
+ return true;
173
+ if (filePath.endsWith('.bundle.js') || filePath.endsWith('.chunk.js'))
174
+ return true;
175
+ return false;
176
+ }
177
+ // ── Directory walker ───────────────────────────────────────────────────────────
178
+ async function walkDirectory(dirPath) {
179
+ const files = [];
180
+ async function walk(currentPath) {
181
+ let entries;
182
+ try {
183
+ entries = await readdir(currentPath, { withFileTypes: true });
184
+ }
185
+ catch {
186
+ // Permission denied or other read error — skip silently
187
+ return;
188
+ }
189
+ const promises = [];
190
+ for (const entry of entries) {
191
+ const fullPath = join(currentPath, entry.name);
192
+ if (entry.isDirectory()) {
193
+ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith('.')) {
194
+ // Allow .env directories but skip other dot-dirs (except .github, .gitlab)
195
+ if (entry.name === '.env' ||
196
+ entry.name === '.github' ||
197
+ entry.name === '.gitlab' ||
198
+ entry.name === '.circleci' ||
199
+ !entry.name.startsWith('.')) {
200
+ promises.push(walk(fullPath));
201
+ }
202
+ }
203
+ continue;
204
+ }
205
+ if (entry.isFile() && !shouldSkipFile(fullPath)) {
206
+ files.push(fullPath);
207
+ }
208
+ }
209
+ await Promise.all(promises);
210
+ }
211
+ await walk(dirPath);
212
+ return files;
213
+ }
214
+ // ── Secret Patterns ────────────────────────────────────────────────────────────
215
+ // 100+ patterns organized by category
216
+ const SECRET_PATTERNS = [
217
+ // ─── Cloud Providers: AWS ──────────────────────────────────────────────────
218
+ {
219
+ id: 'aws-access-key-id',
220
+ regex: /(?:^|[^0-9A-Z])(AKIA[0-9A-Z]{16})(?:[^0-9A-Z]|$)/,
221
+ description: 'AWS Access Key ID detected',
222
+ severity: 'critical',
223
+ type: 'aws_access_key',
224
+ autoFixable: true,
225
+ },
226
+ {
227
+ id: 'aws-secret-access-key',
228
+ regex: /(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY|aws_secret|secret_access_key)\s*[=:]\s*['"]?([0-9a-zA-Z/+=]{40})['"]?/i,
229
+ description: 'AWS Secret Access Key detected',
230
+ severity: 'critical',
231
+ type: 'aws_secret_key',
232
+ autoFixable: true,
233
+ entropyCheck: true,
234
+ entropyThreshold: 4.0,
235
+ },
236
+ {
237
+ id: 'aws-session-token',
238
+ regex: /(?:aws_session_token|AWS_SESSION_TOKEN)\s*[=:]\s*['"]?([A-Za-z0-9/+=]{100,})['"]?/i,
239
+ description: 'AWS Session Token detected',
240
+ severity: 'critical',
241
+ type: 'aws_session_token',
242
+ autoFixable: true,
243
+ },
244
+ {
245
+ id: 'aws-mws-key',
246
+ regex: /amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/,
247
+ description: 'Amazon MWS Auth Token detected',
248
+ severity: 'critical',
249
+ type: 'aws_mws_token',
250
+ autoFixable: true,
251
+ },
252
+ {
253
+ id: 'aws-arn-with-secret',
254
+ regex: /arn:aws:[a-z0-9-]+:[a-z0-9-]*:\d{12}:secret:[A-Za-z0-9/_+=.@-]+/,
255
+ description: 'AWS Secrets Manager ARN detected',
256
+ severity: 'medium',
257
+ type: 'aws_secret_arn',
258
+ autoFixable: false,
259
+ },
260
+ // ─── Cloud Providers: Google ───────────────────────────────────────────────
261
+ {
262
+ id: 'google-api-key',
263
+ regex: /AIza[0-9A-Za-z\-_]{35}/,
264
+ description: 'Google API Key detected',
265
+ severity: 'critical',
266
+ type: 'google_api_key',
267
+ autoFixable: true,
268
+ },
269
+ {
270
+ id: 'google-oauth-client-secret',
271
+ regex: /(?:client_secret|GOOGLE_CLIENT_SECRET|google_client_secret)\s*[=:]\s*['"]?([A-Za-z0-9_-]{24,})['"]?/i,
272
+ description: 'Google OAuth Client Secret detected',
273
+ severity: 'critical',
274
+ type: 'google_oauth_secret',
275
+ autoFixable: true,
276
+ skipPlaceholders: true,
277
+ },
278
+ {
279
+ id: 'google-cloud-service-account-key',
280
+ regex: /"private_key"\s*:\s*"-----BEGIN (?:RSA )?PRIVATE KEY-----/,
281
+ description: 'Google Cloud Service Account private key detected',
282
+ severity: 'critical',
283
+ type: 'gcp_service_account_key',
284
+ autoFixable: true,
285
+ },
286
+ {
287
+ id: 'google-oauth-access-token',
288
+ regex: /ya29\.[0-9A-Za-z_-]{20,}/,
289
+ description: 'Google OAuth Access Token detected',
290
+ severity: 'critical',
291
+ type: 'google_oauth_token',
292
+ autoFixable: true,
293
+ },
294
+ {
295
+ id: 'google-cloud-api-key',
296
+ regex: /(?:GOOGLE_CLOUD_API_KEY|GCLOUD_API_KEY)\s*[=:]\s*['"]?([A-Za-z0-9_-]{20,})['"]?/i,
297
+ description: 'Google Cloud API key in environment variable detected',
298
+ severity: 'critical',
299
+ type: 'google_cloud_api_key',
300
+ autoFixable: true,
301
+ skipPlaceholders: true,
302
+ },
303
+ // ─── Cloud Providers: Azure ────────────────────────────────────────────────
304
+ {
305
+ id: 'azure-storage-account-key',
306
+ regex: /(?:AccountKey|azure_storage_key|AZURE_STORAGE_KEY)\s*[=:]\s*['"]?([A-Za-z0-9+/]{86}==)['"]?/i,
307
+ description: 'Azure Storage Account Key detected',
308
+ severity: 'critical',
309
+ type: 'azure_storage_key',
310
+ autoFixable: true,
311
+ },
312
+ {
313
+ id: 'azure-connection-string',
314
+ regex: /DefaultEndpointsProtocol=https?;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/]{86}==/,
315
+ description: 'Azure Storage Connection String detected',
316
+ severity: 'critical',
317
+ type: 'azure_connection_string',
318
+ autoFixable: true,
319
+ },
320
+ {
321
+ id: 'azure-ad-client-secret',
322
+ regex: /(?:AZURE_CLIENT_SECRET|azure_client_secret|client_secret)\s*[=:]\s*['"]?([A-Za-z0-9~._-]{34,})['"]?/i,
323
+ description: 'Azure AD Client Secret detected',
324
+ severity: 'critical',
325
+ type: 'azure_ad_secret',
326
+ autoFixable: true,
327
+ skipPlaceholders: true,
328
+ contextKeywords: ['azure', 'AZURE', 'tenant', 'client_id'],
329
+ },
330
+ {
331
+ id: 'azure-sas-token',
332
+ regex: /[?&]sig=[A-Za-z0-9%+/=]{40,}/,
333
+ description: 'Azure SAS Token detected',
334
+ severity: 'high',
335
+ type: 'azure_sas_token',
336
+ autoFixable: true,
337
+ contextKeywords: ['blob.core.windows.net', 'queue.core.windows.net', 'table.core.windows.net', 'file.core.windows.net'],
338
+ },
339
+ {
340
+ id: 'azure-devops-pat',
341
+ regex: /(?:azure_devops_pat|AZURE_DEVOPS_PAT|ADO_PAT)\s*[=:]\s*['"]?([a-z0-9]{52})['"]?/i,
342
+ description: 'Azure DevOps Personal Access Token detected',
343
+ severity: 'critical',
344
+ type: 'azure_devops_pat',
345
+ autoFixable: true,
346
+ },
347
+ // ─── Cloud Providers: DigitalOcean ─────────────────────────────────────────
348
+ {
349
+ id: 'digitalocean-pat',
350
+ regex: /dop_v1_[a-f0-9]{64}/,
351
+ description: 'DigitalOcean Personal Access Token detected',
352
+ severity: 'critical',
353
+ type: 'digitalocean_token',
354
+ autoFixable: true,
355
+ },
356
+ {
357
+ id: 'digitalocean-oauth-token',
358
+ regex: /doo_v1_[a-f0-9]{64}/,
359
+ description: 'DigitalOcean OAuth Token detected',
360
+ severity: 'critical',
361
+ type: 'digitalocean_oauth_token',
362
+ autoFixable: true,
363
+ },
364
+ {
365
+ id: 'digitalocean-refresh-token',
366
+ regex: /dor_v1_[a-f0-9]{64}/,
367
+ description: 'DigitalOcean Refresh Token detected',
368
+ severity: 'critical',
369
+ type: 'digitalocean_refresh_token',
370
+ autoFixable: true,
371
+ },
372
+ {
373
+ id: 'digitalocean-spaces-key',
374
+ regex: /(?:SPACES_ACCESS_KEY_ID|DO_SPACES_KEY)\s*[=:]\s*['"]?([A-Z0-9]{20})['"]?/i,
375
+ description: 'DigitalOcean Spaces Access Key detected',
376
+ severity: 'critical',
377
+ type: 'digitalocean_spaces_key',
378
+ autoFixable: true,
379
+ skipPlaceholders: true,
380
+ },
381
+ // ─── Cloud Providers: Heroku ───────────────────────────────────────────────
382
+ {
383
+ id: 'heroku-api-key',
384
+ regex: /(?:HEROKU_API_KEY|heroku_api_key)\s*[=:]\s*['"]?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})['"]?/i,
385
+ description: 'Heroku API Key detected',
386
+ severity: 'critical',
387
+ type: 'heroku_api_key',
388
+ autoFixable: true,
389
+ },
390
+ {
391
+ id: 'heroku-api-key-direct',
392
+ regex: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/,
393
+ description: 'Heroku API Key (UUID format) detected',
394
+ severity: 'high',
395
+ type: 'heroku_api_key',
396
+ autoFixable: true,
397
+ contextKeywords: ['heroku', 'HEROKU'],
398
+ },
399
+ // ─── Cloud Providers: Cloudflare ───────────────────────────────────────────
400
+ {
401
+ id: 'cloudflare-api-key',
402
+ regex: /(?:CLOUDFLARE_API_KEY|CF_API_KEY|cloudflare_api_key)\s*[=:]\s*['"]?([a-f0-9]{37})['"]?/i,
403
+ description: 'Cloudflare API Key detected',
404
+ severity: 'critical',
405
+ type: 'cloudflare_api_key',
406
+ autoFixable: true,
407
+ },
408
+ {
409
+ id: 'cloudflare-api-token',
410
+ regex: /(?:CLOUDFLARE_API_TOKEN|CF_API_TOKEN)\s*[=:]\s*['"]?([A-Za-z0-9_-]{40,})['"]?/i,
411
+ description: 'Cloudflare API Token detected',
412
+ severity: 'critical',
413
+ type: 'cloudflare_api_token',
414
+ autoFixable: true,
415
+ skipPlaceholders: true,
416
+ },
417
+ {
418
+ id: 'cloudflare-origin-ca-key',
419
+ regex: /v1\.0-[a-f0-9]{24}-[a-f0-9]{146}/,
420
+ description: 'Cloudflare Origin CA Key detected',
421
+ severity: 'critical',
422
+ type: 'cloudflare_origin_ca',
423
+ autoFixable: true,
424
+ },
425
+ // ─── Cloud Providers: Vercel ───────────────────────────────────────────────
426
+ {
427
+ id: 'vercel-access-token',
428
+ regex: /(?:VERCEL_TOKEN|vercel_token|VERCEL_ACCESS_TOKEN)\s*[=:]\s*['"]?([A-Za-z0-9]{24,})['"]?/i,
429
+ description: 'Vercel Access Token detected',
430
+ severity: 'critical',
431
+ type: 'vercel_token',
432
+ autoFixable: true,
433
+ skipPlaceholders: true,
434
+ },
435
+ // ─── Cloud Providers: Alibaba Cloud ────────────────────────────────────────
436
+ {
437
+ id: 'alibaba-access-key',
438
+ regex: /(?:^|[^0-9A-Z])(LTAI[0-9A-Za-z]{12,20})(?:[^0-9A-Za-z]|$)/,
439
+ description: 'Alibaba Cloud Access Key ID detected',
440
+ severity: 'critical',
441
+ type: 'alibaba_access_key',
442
+ autoFixable: true,
443
+ },
444
+ // ─── Cloud Providers: IBM ──────────────────────────────────────────────────
445
+ {
446
+ id: 'ibm-cloud-api-key',
447
+ regex: /(?:IBM_CLOUD_API_KEY|IBMCLOUD_API_KEY|ibm_api_key)\s*[=:]\s*['"]?([A-Za-z0-9_-]{44})['"]?/i,
448
+ description: 'IBM Cloud API Key detected',
449
+ severity: 'critical',
450
+ type: 'ibm_cloud_api_key',
451
+ autoFixable: true,
452
+ skipPlaceholders: true,
453
+ },
454
+ // ─── Payment: Stripe ──────────────────────────────────────────────────────
455
+ {
456
+ id: 'stripe-secret-key',
457
+ regex: /sk_live_[0-9a-zA-Z]{24,}/,
458
+ description: 'Stripe Live Secret Key detected',
459
+ severity: 'critical',
460
+ type: 'stripe_secret_key',
461
+ autoFixable: true,
462
+ },
463
+ {
464
+ id: 'stripe-publishable-key',
465
+ regex: /pk_live_[0-9a-zA-Z]{24,}/,
466
+ description: 'Stripe Live Publishable Key detected',
467
+ severity: 'high',
468
+ type: 'stripe_publishable_key',
469
+ autoFixable: true,
470
+ },
471
+ {
472
+ id: 'stripe-test-secret-key',
473
+ regex: /sk_test_[0-9a-zA-Z]{24,}/,
474
+ description: 'Stripe Test Secret Key detected (still sensitive)',
475
+ severity: 'high',
476
+ type: 'stripe_test_secret_key',
477
+ autoFixable: true,
478
+ },
479
+ {
480
+ id: 'stripe-test-publishable-key',
481
+ regex: /pk_test_[0-9a-zA-Z]{24,}/,
482
+ description: 'Stripe Test Publishable Key detected',
483
+ severity: 'medium',
484
+ type: 'stripe_test_publishable_key',
485
+ autoFixable: true,
486
+ },
487
+ {
488
+ id: 'stripe-webhook-secret',
489
+ regex: /whsec_[0-9a-zA-Z]{32,}/,
490
+ description: 'Stripe Webhook Secret detected',
491
+ severity: 'critical',
492
+ type: 'stripe_webhook_secret',
493
+ autoFixable: true,
494
+ },
495
+ {
496
+ id: 'stripe-restricted-key',
497
+ regex: /rk_live_[0-9a-zA-Z]{24,}/,
498
+ description: 'Stripe Restricted Key detected',
499
+ severity: 'critical',
500
+ type: 'stripe_restricted_key',
501
+ autoFixable: true,
502
+ },
503
+ // ─── Payment: PayPal ──────────────────────────────────────────────────────
504
+ {
505
+ id: 'paypal-client-secret',
506
+ regex: /(?:PAYPAL_CLIENT_SECRET|PAYPAL_SECRET|paypal_client_secret)\s*[=:]\s*['"]?([A-Za-z0-9_-]{20,80})['"]?/i,
507
+ description: 'PayPal Client Secret detected',
508
+ severity: 'critical',
509
+ type: 'paypal_secret',
510
+ autoFixable: true,
511
+ skipPlaceholders: true,
512
+ },
513
+ {
514
+ id: 'paypal-braintree-access-token',
515
+ regex: /access_token\$production\$[a-z0-9]{16}\$[a-f0-9]{32}/,
516
+ description: 'PayPal/Braintree Access Token detected',
517
+ severity: 'critical',
518
+ type: 'paypal_braintree_token',
519
+ autoFixable: true,
520
+ },
521
+ // ─── Payment: Square ──────────────────────────────────────────────────────
522
+ {
523
+ id: 'square-access-token',
524
+ regex: /sq0atp-[0-9A-Za-z\-_]{22}/,
525
+ description: 'Square Access Token detected',
526
+ severity: 'critical',
527
+ type: 'square_access_token',
528
+ autoFixable: true,
529
+ },
530
+ {
531
+ id: 'square-oauth-secret',
532
+ regex: /sq0csp-[0-9A-Za-z\-_]{43}/,
533
+ description: 'Square OAuth Secret detected',
534
+ severity: 'critical',
535
+ type: 'square_oauth_secret',
536
+ autoFixable: true,
537
+ },
538
+ // ─── Auth & Identity: JWT ──────────────────────────────────────────────────
539
+ {
540
+ id: 'jwt-token',
541
+ regex: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
542
+ description: 'JSON Web Token (JWT) detected',
543
+ severity: 'high',
544
+ type: 'jwt_token',
545
+ autoFixable: true,
546
+ },
547
+ {
548
+ id: 'jwt-secret',
549
+ regex: /(?:JWT_SECRET|jwt_secret|JWT_SIGNING_KEY)\s*[=:]\s*['"]?([A-Za-z0-9/+_=-]{16,})['"]?/i,
550
+ description: 'JWT signing secret detected',
551
+ severity: 'critical',
552
+ type: 'jwt_secret',
553
+ autoFixable: true,
554
+ skipPlaceholders: true,
555
+ },
556
+ // ─── Auth & Identity: GitHub ───────────────────────────────────────────────
557
+ {
558
+ id: 'github-pat',
559
+ regex: /ghp_[0-9a-zA-Z]{36}/,
560
+ description: 'GitHub Personal Access Token detected',
561
+ severity: 'critical',
562
+ type: 'github_pat',
563
+ autoFixable: true,
564
+ },
565
+ {
566
+ id: 'github-oauth-token',
567
+ regex: /gho_[0-9a-zA-Z]{36}/,
568
+ description: 'GitHub OAuth Access Token detected',
569
+ severity: 'critical',
570
+ type: 'github_oauth_token',
571
+ autoFixable: true,
572
+ },
573
+ {
574
+ id: 'github-app-token',
575
+ regex: /ghs_[0-9a-zA-Z]{36}/,
576
+ description: 'GitHub App Installation Token detected',
577
+ severity: 'critical',
578
+ type: 'github_app_token',
579
+ autoFixable: true,
580
+ },
581
+ {
582
+ id: 'github-refresh-token',
583
+ regex: /ghr_[0-9a-zA-Z]{36}/,
584
+ description: 'GitHub Refresh Token detected',
585
+ severity: 'critical',
586
+ type: 'github_refresh_token',
587
+ autoFixable: true,
588
+ },
589
+ {
590
+ id: 'github-fine-grained-pat',
591
+ regex: /github_pat_[0-9a-zA-Z_]{82}/,
592
+ description: 'GitHub Fine-Grained Personal Access Token detected',
593
+ severity: 'critical',
594
+ type: 'github_fine_grained_pat',
595
+ autoFixable: true,
596
+ },
597
+ // ─── Auth & Identity: GitLab ───────────────────────────────────────────────
598
+ {
599
+ id: 'gitlab-pat',
600
+ regex: /glpat-[0-9a-zA-Z\-_]{20,}/,
601
+ description: 'GitLab Personal Access Token detected',
602
+ severity: 'critical',
603
+ type: 'gitlab_pat',
604
+ autoFixable: true,
605
+ },
606
+ {
607
+ id: 'gitlab-pipeline-trigger',
608
+ regex: /glptt-[0-9a-zA-Z\-_]{20,}/,
609
+ description: 'GitLab Pipeline Trigger Token detected',
610
+ severity: 'high',
611
+ type: 'gitlab_pipeline_token',
612
+ autoFixable: true,
613
+ },
614
+ {
615
+ id: 'gitlab-runner-token',
616
+ regex: /glrt-[0-9a-zA-Z\-_]{20,}/,
617
+ description: 'GitLab Runner Registration Token detected',
618
+ severity: 'critical',
619
+ type: 'gitlab_runner_token',
620
+ autoFixable: true,
621
+ },
622
+ // ─── Auth & Identity: Slack ────────────────────────────────────────────────
623
+ {
624
+ id: 'slack-bot-token',
625
+ regex: /xoxb-[0-9]{10,}-[0-9a-zA-Z]{24,}/,
626
+ description: 'Slack Bot Token detected',
627
+ severity: 'critical',
628
+ type: 'slack_bot_token',
629
+ autoFixable: true,
630
+ },
631
+ {
632
+ id: 'slack-user-token',
633
+ regex: /xoxp-[0-9]{10,}-[0-9]{10,}-[0-9a-zA-Z]{24,}/,
634
+ description: 'Slack User Token detected',
635
+ severity: 'critical',
636
+ type: 'slack_user_token',
637
+ autoFixable: true,
638
+ },
639
+ {
640
+ id: 'slack-app-token',
641
+ regex: /xapp-[0-9]-[A-Za-z0-9]{10,}-[0-9]{10,}-[a-f0-9]{64}/,
642
+ description: 'Slack App-Level Token detected',
643
+ severity: 'critical',
644
+ type: 'slack_app_token',
645
+ autoFixable: true,
646
+ },
647
+ {
648
+ id: 'slack-webhook-url',
649
+ regex: /https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]{8,}\/B[A-Z0-9]{8,}\/[a-zA-Z0-9]{24,}/,
650
+ description: 'Slack Incoming Webhook URL detected',
651
+ severity: 'high',
652
+ type: 'slack_webhook',
653
+ autoFixable: true,
654
+ },
655
+ {
656
+ id: 'slack-config-token',
657
+ regex: /xoxe\.xoxp-[0-9]-[A-Za-z0-9]{146,}/,
658
+ description: 'Slack Configuration Token detected',
659
+ severity: 'critical',
660
+ type: 'slack_config_token',
661
+ autoFixable: true,
662
+ },
663
+ // ─── Auth & Identity: Discord ──────────────────────────────────────────────
664
+ {
665
+ id: 'discord-bot-token',
666
+ regex: /(?:DISCORD_TOKEN|DISCORD_BOT_TOKEN|discord_token)\s*[=:]\s*['"]?([A-Za-z0-9_-]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,})['"]?/i,
667
+ description: 'Discord Bot Token detected',
668
+ severity: 'critical',
669
+ type: 'discord_bot_token',
670
+ autoFixable: true,
671
+ },
672
+ {
673
+ id: 'discord-webhook-url',
674
+ regex: /https:\/\/discord(?:app)?\.com\/api\/webhooks\/\d+\/[A-Za-z0-9_-]+/,
675
+ description: 'Discord Webhook URL detected',
676
+ severity: 'high',
677
+ type: 'discord_webhook',
678
+ autoFixable: true,
679
+ },
680
+ // ─── Auth & Identity: Auth0 ────────────────────────────────────────────────
681
+ {
682
+ id: 'auth0-client-secret',
683
+ regex: /(?:AUTH0_CLIENT_SECRET|auth0_client_secret)\s*[=:]\s*['"]?([A-Za-z0-9_-]{32,})['"]?/i,
684
+ description: 'Auth0 Client Secret detected',
685
+ severity: 'critical',
686
+ type: 'auth0_client_secret',
687
+ autoFixable: true,
688
+ skipPlaceholders: true,
689
+ },
690
+ {
691
+ id: 'auth0-management-api-token',
692
+ regex: /(?:AUTH0_MANAGEMENT_API_TOKEN|AUTH0_API_TOKEN)\s*[=:]\s*['"]?([A-Za-z0-9_-]{30,})['"]?/i,
693
+ description: 'Auth0 Management API Token detected',
694
+ severity: 'critical',
695
+ type: 'auth0_mgmt_token',
696
+ autoFixable: true,
697
+ skipPlaceholders: true,
698
+ },
699
+ // ─── Auth & Identity: Firebase ─────────────────────────────────────────────
700
+ {
701
+ id: 'firebase-api-key',
702
+ regex: /(?:FIREBASE_API_KEY|NEXT_PUBLIC_FIREBASE_API_KEY|REACT_APP_FIREBASE_API_KEY|firebase_api_key)\s*[=:]\s*['"]?(AIza[0-9A-Za-z\-_]{35})['"]?/i,
703
+ description: 'Firebase API Key detected',
704
+ severity: 'high',
705
+ type: 'firebase_api_key',
706
+ autoFixable: true,
707
+ },
708
+ {
709
+ id: 'firebase-admin-sdk-json',
710
+ regex: /"type"\s*:\s*"service_account"[^}]*"private_key"\s*:/,
711
+ description: 'Firebase Admin SDK service account JSON detected',
712
+ severity: 'critical',
713
+ type: 'firebase_service_account',
714
+ autoFixable: true,
715
+ },
716
+ // ─── Auth & Identity: Supabase ─────────────────────────────────────────────
717
+ {
718
+ id: 'supabase-anon-key',
719
+ regex: /(?:SUPABASE_ANON_KEY|NEXT_PUBLIC_SUPABASE_ANON_KEY|SUPABASE_KEY)\s*[=:]\s*['"]?(eyJ[A-Za-z0-9_-]{100,})['"]?/i,
720
+ description: 'Supabase anon/public key detected',
721
+ severity: 'medium',
722
+ type: 'supabase_anon_key',
723
+ autoFixable: true,
724
+ },
725
+ {
726
+ id: 'supabase-service-role-key',
727
+ regex: /(?:SUPABASE_SERVICE_ROLE_KEY|SUPABASE_SERVICE_KEY)\s*[=:]\s*['"]?(eyJ[A-Za-z0-9_-]{100,})['"]?/i,
728
+ description: 'Supabase service role key detected (admin access!)',
729
+ severity: 'critical',
730
+ type: 'supabase_service_key',
731
+ autoFixable: true,
732
+ },
733
+ // ─── Auth & Identity: Clerk ────────────────────────────────────────────────
734
+ {
735
+ id: 'clerk-secret-key',
736
+ regex: /sk_live_[A-Za-z0-9]{40,}/,
737
+ description: 'Clerk Secret Key detected',
738
+ severity: 'critical',
739
+ type: 'clerk_secret_key',
740
+ autoFixable: true,
741
+ },
742
+ {
743
+ id: 'clerk-publishable-key',
744
+ regex: /pk_live_[A-Za-z0-9]{40,}/,
745
+ description: 'Clerk Publishable Key detected',
746
+ severity: 'medium',
747
+ type: 'clerk_publishable_key',
748
+ autoFixable: true,
749
+ },
750
+ // ─── Auth & Identity: OAuth generic ────────────────────────────────────────
751
+ {
752
+ id: 'generic-oauth-client-secret',
753
+ regex: /(?:OAUTH_CLIENT_SECRET|oauth_client_secret|CLIENT_SECRET)\s*[=:]\s*['"]?([A-Za-z0-9_-]{20,})['"]?/i,
754
+ description: 'OAuth Client Secret detected',
755
+ severity: 'high',
756
+ type: 'oauth_client_secret',
757
+ autoFixable: true,
758
+ skipPlaceholders: true,
759
+ entropyCheck: true,
760
+ entropyThreshold: 3.0,
761
+ },
762
+ // ─── Auth & Identity: Okta ─────────────────────────────────────────────────
763
+ {
764
+ id: 'okta-api-token',
765
+ regex: /(?:OKTA_API_TOKEN|OKTA_TOKEN|okta_token)\s*[=:]\s*['"]?([0-9a-zA-Z_-]{42})['"]?/i,
766
+ description: 'Okta API Token detected',
767
+ severity: 'critical',
768
+ type: 'okta_api_token',
769
+ autoFixable: true,
770
+ skipPlaceholders: true,
771
+ },
772
+ // ─── Databases ─────────────────────────────────────────────────────────────
773
+ {
774
+ id: 'postgres-connection-string',
775
+ regex: /postgres(?:ql)?:\/\/[^:]+:[^@]{3,}@[^/\s]+\/[^\s'"]+/i,
776
+ description: 'PostgreSQL connection string with credentials detected',
777
+ severity: 'critical',
778
+ type: 'database_url',
779
+ autoFixable: true,
780
+ skipPlaceholders: true,
781
+ },
782
+ {
783
+ id: 'mysql-connection-string',
784
+ regex: /mysql:\/\/[^:]+:[^@]{3,}@[^/\s]+\/[^\s'"]+/i,
785
+ description: 'MySQL connection string with credentials detected',
786
+ severity: 'critical',
787
+ type: 'database_url',
788
+ autoFixable: true,
789
+ skipPlaceholders: true,
790
+ },
791
+ {
792
+ id: 'mongodb-connection-string',
793
+ regex: /mongodb(?:\+srv)?:\/\/[^:]+:[^@]{3,}@[^\s'"]+/i,
794
+ description: 'MongoDB connection string with credentials detected',
795
+ severity: 'critical',
796
+ type: 'database_url',
797
+ autoFixable: true,
798
+ skipPlaceholders: true,
799
+ },
800
+ {
801
+ id: 'redis-connection-string',
802
+ regex: /redis(?:s)?:\/\/[^:]*:[^@]{3,}@[^\s'"]+/i,
803
+ description: 'Redis connection string with credentials detected',
804
+ severity: 'critical',
805
+ type: 'database_url',
806
+ autoFixable: true,
807
+ skipPlaceholders: true,
808
+ },
809
+ {
810
+ id: 'database-url-generic',
811
+ regex: /(?:DATABASE_URL|DB_URL|DATABASE_CONNECTION)\s*[=:]\s*['"]?(\w+:\/\/[^:]+:[^@]{3,}@[^\s'"]+)['"]?/i,
812
+ description: 'Database connection URL with credentials detected',
813
+ severity: 'critical',
814
+ type: 'database_url',
815
+ autoFixable: true,
816
+ skipPlaceholders: true,
817
+ },
818
+ {
819
+ id: 'database-password',
820
+ regex: /(?:DB_PASSWORD|DATABASE_PASSWORD|DB_PASS|MYSQL_PASSWORD|POSTGRES_PASSWORD|PGPASSWORD|MONGO_PASSWORD)\s*[=:]\s*['"]?([^\s'"]{8,})['"]?/i,
821
+ description: 'Database password detected',
822
+ severity: 'critical',
823
+ type: 'database_password',
824
+ autoFixable: true,
825
+ skipPlaceholders: true,
826
+ },
827
+ // ─── Communication: Twilio ─────────────────────────────────────────────────
828
+ {
829
+ id: 'twilio-account-sid',
830
+ regex: /AC[0-9a-f]{32}/,
831
+ description: 'Twilio Account SID detected',
832
+ severity: 'high',
833
+ type: 'twilio_account_sid',
834
+ autoFixable: true,
835
+ },
836
+ {
837
+ id: 'twilio-auth-token',
838
+ regex: /(?:TWILIO_AUTH_TOKEN|twilio_auth_token)\s*[=:]\s*['"]?([a-f0-9]{32})['"]?/i,
839
+ description: 'Twilio Auth Token detected',
840
+ severity: 'critical',
841
+ type: 'twilio_auth_token',
842
+ autoFixable: true,
843
+ skipPlaceholders: true,
844
+ },
845
+ {
846
+ id: 'twilio-api-key',
847
+ regex: /SK[0-9a-f]{32}/,
848
+ description: 'Twilio API Key detected',
849
+ severity: 'critical',
850
+ type: 'twilio_api_key',
851
+ autoFixable: true,
852
+ contextKeywords: ['twilio', 'TWILIO'],
853
+ },
854
+ // ─── Communication: SendGrid ───────────────────────────────────────────────
855
+ {
856
+ id: 'sendgrid-api-key',
857
+ regex: /SG\.[0-9A-Za-z_-]{22}\.[0-9A-Za-z_-]{43}/,
858
+ description: 'SendGrid API Key detected',
859
+ severity: 'critical',
860
+ type: 'sendgrid_api_key',
861
+ autoFixable: true,
862
+ },
863
+ // ─── Communication: Mailgun ────────────────────────────────────────────────
864
+ {
865
+ id: 'mailgun-api-key',
866
+ regex: /key-[0-9a-f]{32}/,
867
+ description: 'Mailgun API Key detected',
868
+ severity: 'critical',
869
+ type: 'mailgun_api_key',
870
+ autoFixable: true,
871
+ contextKeywords: ['mailgun', 'MAILGUN'],
872
+ },
873
+ {
874
+ id: 'mailgun-api-key-env',
875
+ regex: /(?:MAILGUN_API_KEY|mailgun_api_key)\s*[=:]\s*['"]?(key-[0-9a-f]{32})['"]?/i,
876
+ description: 'Mailgun API Key in env variable detected',
877
+ severity: 'critical',
878
+ type: 'mailgun_api_key',
879
+ autoFixable: true,
880
+ },
881
+ // ─── Communication: Postmark ───────────────────────────────────────────────
882
+ {
883
+ id: 'postmark-server-token',
884
+ regex: /(?:POSTMARK_SERVER_TOKEN|POSTMARK_API_TOKEN|postmark_token)\s*[=:]\s*['"]?([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})['"]?/i,
885
+ description: 'Postmark Server Token detected',
886
+ severity: 'critical',
887
+ type: 'postmark_token',
888
+ autoFixable: true,
889
+ },
890
+ // ─── Communication: Messagebird ────────────────────────────────────────────
891
+ {
892
+ id: 'messagebird-api-key',
893
+ regex: /(?:MESSAGEBIRD_API_KEY|messagebird_api_key)\s*[=:]\s*['"]?([a-zA-Z0-9]{25})['"]?/i,
894
+ description: 'MessageBird API Key detected',
895
+ severity: 'critical',
896
+ type: 'messagebird_api_key',
897
+ autoFixable: true,
898
+ skipPlaceholders: true,
899
+ },
900
+ // ─── Infrastructure: SSH / PGP ─────────────────────────────────────────────
901
+ {
902
+ id: 'ssh-rsa-private-key',
903
+ regex: /-----BEGIN RSA PRIVATE KEY-----/,
904
+ description: 'SSH RSA Private Key detected',
905
+ severity: 'critical',
906
+ type: 'ssh_private_key',
907
+ autoFixable: false,
908
+ },
909
+ {
910
+ id: 'ssh-dsa-private-key',
911
+ regex: /-----BEGIN DSA PRIVATE KEY-----/,
912
+ description: 'SSH DSA Private Key detected',
913
+ severity: 'critical',
914
+ type: 'ssh_private_key',
915
+ autoFixable: false,
916
+ },
917
+ {
918
+ id: 'ssh-ec-private-key',
919
+ regex: /-----BEGIN EC PRIVATE KEY-----/,
920
+ description: 'SSH EC Private Key detected',
921
+ severity: 'critical',
922
+ type: 'ssh_private_key',
923
+ autoFixable: false,
924
+ },
925
+ {
926
+ id: 'ssh-openssh-private-key',
927
+ regex: /-----BEGIN OPENSSH PRIVATE KEY-----/,
928
+ description: 'OpenSSH Private Key detected',
929
+ severity: 'critical',
930
+ type: 'ssh_private_key',
931
+ autoFixable: false,
932
+ },
933
+ {
934
+ id: 'generic-private-key',
935
+ regex: /-----BEGIN PRIVATE KEY-----/,
936
+ description: 'Private Key (PKCS#8) detected',
937
+ severity: 'critical',
938
+ type: 'private_key',
939
+ autoFixable: false,
940
+ },
941
+ {
942
+ id: 'encrypted-private-key',
943
+ regex: /-----BEGIN ENCRYPTED PRIVATE KEY-----/,
944
+ description: 'Encrypted Private Key detected',
945
+ severity: 'high',
946
+ type: 'encrypted_private_key',
947
+ autoFixable: false,
948
+ },
949
+ {
950
+ id: 'pgp-private-key',
951
+ regex: /-----BEGIN PGP PRIVATE KEY BLOCK-----/,
952
+ description: 'PGP Private Key Block detected',
953
+ severity: 'critical',
954
+ type: 'pgp_private_key',
955
+ autoFixable: false,
956
+ },
957
+ {
958
+ id: 'x509-certificate-private-key',
959
+ regex: /-----BEGIN CERTIFICATE-----/,
960
+ description: 'X.509 Certificate detected (may contain private material)',
961
+ severity: 'medium',
962
+ type: 'x509_certificate',
963
+ autoFixable: false,
964
+ },
965
+ // ─── Infrastructure: Docker ────────────────────────────────────────────────
966
+ {
967
+ id: 'docker-registry-password',
968
+ regex: /(?:DOCKER_PASSWORD|DOCKER_REGISTRY_PASSWORD|DOCKER_AUTH)\s*[=:]\s*['"]?([^\s'"]{8,})['"]?/i,
969
+ description: 'Docker Registry Password detected',
970
+ severity: 'critical',
971
+ type: 'docker_password',
972
+ autoFixable: true,
973
+ skipPlaceholders: true,
974
+ },
975
+ {
976
+ id: 'docker-config-auth',
977
+ regex: /"auth"\s*:\s*"[A-Za-z0-9+/=]{20,}"/,
978
+ description: 'Docker config.json auth token detected',
979
+ severity: 'critical',
980
+ type: 'docker_auth_token',
981
+ autoFixable: true,
982
+ contextKeywords: ['docker', '.docker', 'registry'],
983
+ },
984
+ // ─── Infrastructure: npm ───────────────────────────────────────────────────
985
+ {
986
+ id: 'npm-token',
987
+ regex: /npm_[0-9a-zA-Z]{36}/,
988
+ description: 'npm Access Token detected',
989
+ severity: 'critical',
990
+ type: 'npm_token',
991
+ autoFixable: true,
992
+ },
993
+ {
994
+ id: 'npm-token-legacy',
995
+ regex: /\/\/registry\.npmjs\.org\/:_authToken=[0-9a-f-]{36,}/,
996
+ description: 'npm legacy auth token in .npmrc detected',
997
+ severity: 'critical',
998
+ type: 'npm_token',
999
+ autoFixable: true,
1000
+ },
1001
+ // ─── Infrastructure: PyPI ──────────────────────────────────────────────────
1002
+ {
1003
+ id: 'pypi-token',
1004
+ regex: /pypi-[0-9a-zA-Z_-]{50,}/,
1005
+ description: 'PyPI API Token detected',
1006
+ severity: 'critical',
1007
+ type: 'pypi_token',
1008
+ autoFixable: true,
1009
+ },
1010
+ // ─── Infrastructure: NuGet ─────────────────────────────────────────────────
1011
+ {
1012
+ id: 'nuget-api-key',
1013
+ regex: /oy2[A-Za-z0-9]{43}/,
1014
+ description: 'NuGet API Key detected',
1015
+ severity: 'critical',
1016
+ type: 'nuget_api_key',
1017
+ autoFixable: true,
1018
+ },
1019
+ // ─── Infrastructure: RubyGems ──────────────────────────────────────────────
1020
+ {
1021
+ id: 'rubygems-api-key',
1022
+ regex: /rubygems_[0-9a-f]{48}/,
1023
+ description: 'RubyGems API Key detected',
1024
+ severity: 'critical',
1025
+ type: 'rubygems_api_key',
1026
+ autoFixable: true,
1027
+ },
1028
+ // ─── Infrastructure: Terraform ─────────────────────────────────────────────
1029
+ {
1030
+ id: 'terraform-cloud-token',
1031
+ regex: /(?:TFE_TOKEN|TF_API_TOKEN|TERRAFORM_TOKEN)\s*[=:]\s*['"]?([A-Za-z0-9.]{14,})['"]?/i,
1032
+ description: 'Terraform Cloud/Enterprise Token detected',
1033
+ severity: 'critical',
1034
+ type: 'terraform_token',
1035
+ autoFixable: true,
1036
+ skipPlaceholders: true,
1037
+ },
1038
+ // ─── Infrastructure: Vault ─────────────────────────────────────────────────
1039
+ {
1040
+ id: 'hashicorp-vault-token',
1041
+ regex: /(?:VAULT_TOKEN|vault_token)\s*[=:]\s*['"]?(hvs\.[A-Za-z0-9_-]{24,})['"]?/i,
1042
+ description: 'HashiCorp Vault Token detected',
1043
+ severity: 'critical',
1044
+ type: 'vault_token',
1045
+ autoFixable: true,
1046
+ },
1047
+ {
1048
+ id: 'hashicorp-vault-token-direct',
1049
+ regex: /hvs\.[A-Za-z0-9_-]{24,}/,
1050
+ description: 'HashiCorp Vault Token detected',
1051
+ severity: 'critical',
1052
+ type: 'vault_token',
1053
+ autoFixable: true,
1054
+ },
1055
+ // ─── CI/CD: CircleCI ──────────────────────────────────────────────────────
1056
+ {
1057
+ id: 'circleci-token',
1058
+ regex: /(?:CIRCLECI_TOKEN|CIRCLE_TOKEN)\s*[=:]\s*['"]?([a-f0-9]{40})['"]?/i,
1059
+ description: 'CircleCI Token detected',
1060
+ severity: 'critical',
1061
+ type: 'circleci_token',
1062
+ autoFixable: true,
1063
+ skipPlaceholders: true,
1064
+ },
1065
+ // ─── CI/CD: Travis CI ─────────────────────────────────────────────────────
1066
+ {
1067
+ id: 'travis-ci-token',
1068
+ regex: /(?:TRAVIS_TOKEN|TRAVIS_API_TOKEN)\s*[=:]\s*['"]?([A-Za-z0-9_-]{22,})['"]?/i,
1069
+ description: 'Travis CI Token detected',
1070
+ severity: 'critical',
1071
+ type: 'travis_token',
1072
+ autoFixable: true,
1073
+ skipPlaceholders: true,
1074
+ },
1075
+ // ─── Monitoring: Datadog ───────────────────────────────────────────────────
1076
+ {
1077
+ id: 'datadog-api-key',
1078
+ regex: /(?:DD_API_KEY|DATADOG_API_KEY|datadog_api_key)\s*[=:]\s*['"]?([a-f0-9]{32})['"]?/i,
1079
+ description: 'Datadog API Key detected',
1080
+ severity: 'critical',
1081
+ type: 'datadog_api_key',
1082
+ autoFixable: true,
1083
+ skipPlaceholders: true,
1084
+ },
1085
+ {
1086
+ id: 'datadog-app-key',
1087
+ regex: /(?:DD_APP_KEY|DATADOG_APP_KEY|datadog_app_key)\s*[=:]\s*['"]?([a-f0-9]{40})['"]?/i,
1088
+ description: 'Datadog Application Key detected',
1089
+ severity: 'critical',
1090
+ type: 'datadog_app_key',
1091
+ autoFixable: true,
1092
+ skipPlaceholders: true,
1093
+ },
1094
+ // ─── Monitoring: New Relic ─────────────────────────────────────────────────
1095
+ {
1096
+ id: 'newrelic-license-key',
1097
+ regex: /(?:NEW_RELIC_LICENSE_KEY|NEWRELIC_LICENSE_KEY|newrelic_key)\s*[=:]\s*['"]?([a-f0-9]{40})['"]?/i,
1098
+ description: 'New Relic License Key detected',
1099
+ severity: 'high',
1100
+ type: 'newrelic_license_key',
1101
+ autoFixable: true,
1102
+ skipPlaceholders: true,
1103
+ },
1104
+ {
1105
+ id: 'newrelic-api-key',
1106
+ regex: /NRAK-[A-Z0-9]{27}/,
1107
+ description: 'New Relic API Key detected',
1108
+ severity: 'critical',
1109
+ type: 'newrelic_api_key',
1110
+ autoFixable: true,
1111
+ },
1112
+ // ─── Monitoring: Sentry ────────────────────────────────────────────────────
1113
+ {
1114
+ id: 'sentry-dsn',
1115
+ regex: /https:\/\/[a-f0-9]{32}@[a-z0-9.]+\.sentry\.io\/\d+/,
1116
+ description: 'Sentry DSN detected',
1117
+ severity: 'medium',
1118
+ type: 'sentry_dsn',
1119
+ autoFixable: true,
1120
+ },
1121
+ {
1122
+ id: 'sentry-auth-token',
1123
+ regex: /(?:SENTRY_AUTH_TOKEN|sentry_auth_token)\s*[=:]\s*['"]?([a-f0-9]{64})['"]?/i,
1124
+ description: 'Sentry Auth Token detected',
1125
+ severity: 'critical',
1126
+ type: 'sentry_auth_token',
1127
+ autoFixable: true,
1128
+ skipPlaceholders: true,
1129
+ },
1130
+ // ─── Analytics: Segment ────────────────────────────────────────────────────
1131
+ {
1132
+ id: 'segment-write-key',
1133
+ regex: /(?:SEGMENT_WRITE_KEY|segment_write_key)\s*[=:]\s*['"]?([A-Za-z0-9]{32,})['"]?/i,
1134
+ description: 'Segment Write Key detected',
1135
+ severity: 'high',
1136
+ type: 'segment_write_key',
1137
+ autoFixable: true,
1138
+ skipPlaceholders: true,
1139
+ },
1140
+ // ─── Analytics: Mixpanel ───────────────────────────────────────────────────
1141
+ {
1142
+ id: 'mixpanel-token',
1143
+ regex: /(?:MIXPANEL_TOKEN|MIXPANEL_PROJECT_TOKEN|mixpanel_token)\s*[=:]\s*['"]?([a-f0-9]{32})['"]?/i,
1144
+ description: 'Mixpanel Project Token detected',
1145
+ severity: 'medium',
1146
+ type: 'mixpanel_token',
1147
+ autoFixable: true,
1148
+ skipPlaceholders: true,
1149
+ },
1150
+ // ─── Messaging: Telegram ───────────────────────────────────────────────────
1151
+ {
1152
+ id: 'telegram-bot-token',
1153
+ regex: /\d{8,10}:[A-Za-z0-9_-]{35}/,
1154
+ description: 'Telegram Bot Token detected',
1155
+ severity: 'critical',
1156
+ type: 'telegram_bot_token',
1157
+ autoFixable: true,
1158
+ contextKeywords: ['telegram', 'TELEGRAM', 'bot', 'BOT_TOKEN'],
1159
+ },
1160
+ // ─── Social: Twitter ──────────────────────────────────────────────────────
1161
+ {
1162
+ id: 'twitter-api-key',
1163
+ regex: /(?:TWITTER_API_KEY|TWITTER_CONSUMER_KEY|twitter_api_key)\s*[=:]\s*['"]?([A-Za-z0-9]{25})['"]?/i,
1164
+ description: 'Twitter/X API Key detected',
1165
+ severity: 'critical',
1166
+ type: 'twitter_api_key',
1167
+ autoFixable: true,
1168
+ skipPlaceholders: true,
1169
+ },
1170
+ {
1171
+ id: 'twitter-bearer-token',
1172
+ regex: /(?:TWITTER_BEARER_TOKEN|twitter_bearer)\s*[=:]\s*['"]?(AAAAAAAAAAAAAAAAAAA[A-Za-z0-9%]+)['"]?/i,
1173
+ description: 'Twitter/X Bearer Token detected',
1174
+ severity: 'critical',
1175
+ type: 'twitter_bearer_token',
1176
+ autoFixable: true,
1177
+ },
1178
+ // ─── Social: Facebook ─────────────────────────────────────────────────────
1179
+ {
1180
+ id: 'facebook-access-token',
1181
+ regex: /EAA[A-Za-z0-9]{100,}/,
1182
+ description: 'Facebook Access Token detected',
1183
+ severity: 'critical',
1184
+ type: 'facebook_access_token',
1185
+ autoFixable: true,
1186
+ },
1187
+ {
1188
+ id: 'facebook-app-secret',
1189
+ regex: /(?:FACEBOOK_APP_SECRET|FB_APP_SECRET|facebook_secret)\s*[=:]\s*['"]?([a-f0-9]{32})['"]?/i,
1190
+ description: 'Facebook App Secret detected',
1191
+ severity: 'critical',
1192
+ type: 'facebook_app_secret',
1193
+ autoFixable: true,
1194
+ skipPlaceholders: true,
1195
+ },
1196
+ // ─── Maps / Location ──────────────────────────────────────────────────────
1197
+ {
1198
+ id: 'mapbox-access-token',
1199
+ regex: /(?:pk|sk)\.eyJ[A-Za-z0-9_-]{50,}\.[A-Za-z0-9_-]{20,}/,
1200
+ description: 'Mapbox Access Token detected',
1201
+ severity: 'high',
1202
+ type: 'mapbox_token',
1203
+ autoFixable: true,
1204
+ },
1205
+ // ─── E-commerce: Shopify ───────────────────────────────────────────────────
1206
+ {
1207
+ id: 'shopify-private-app-password',
1208
+ regex: /shppa_[a-f0-9]{32}/,
1209
+ description: 'Shopify Private App Password detected',
1210
+ severity: 'critical',
1211
+ type: 'shopify_private_app',
1212
+ autoFixable: true,
1213
+ },
1214
+ {
1215
+ id: 'shopify-access-token',
1216
+ regex: /shpat_[a-f0-9]{32}/,
1217
+ description: 'Shopify Admin Access Token detected',
1218
+ severity: 'critical',
1219
+ type: 'shopify_access_token',
1220
+ autoFixable: true,
1221
+ },
1222
+ {
1223
+ id: 'shopify-shared-secret',
1224
+ regex: /shpss_[a-f0-9]{32}/,
1225
+ description: 'Shopify Shared Secret detected',
1226
+ severity: 'critical',
1227
+ type: 'shopify_shared_secret',
1228
+ autoFixable: true,
1229
+ },
1230
+ {
1231
+ id: 'shopify-custom-app-token',
1232
+ regex: /shpca_[a-f0-9]{32}/,
1233
+ description: 'Shopify Custom App Access Token detected',
1234
+ severity: 'critical',
1235
+ type: 'shopify_custom_app',
1236
+ autoFixable: true,
1237
+ },
1238
+ // ─── Atlassian: Jira / Confluence ──────────────────────────────────────────
1239
+ {
1240
+ id: 'atlassian-api-token',
1241
+ regex: /(?:ATLASSIAN_API_TOKEN|JIRA_API_TOKEN|CONFLUENCE_TOKEN)\s*[=:]\s*['"]?([A-Za-z0-9]{24,})['"]?/i,
1242
+ description: 'Atlassian API Token detected',
1243
+ severity: 'critical',
1244
+ type: 'atlassian_api_token',
1245
+ autoFixable: true,
1246
+ skipPlaceholders: true,
1247
+ },
1248
+ // ─── Bitbucket ─────────────────────────────────────────────────────────────
1249
+ {
1250
+ id: 'bitbucket-app-password',
1251
+ regex: /(?:BITBUCKET_APP_PASSWORD|BITBUCKET_PASSWORD)\s*[=:]\s*['"]?([A-Za-z0-9]{18,})['"]?/i,
1252
+ description: 'Bitbucket App Password detected',
1253
+ severity: 'critical',
1254
+ type: 'bitbucket_app_password',
1255
+ autoFixable: true,
1256
+ skipPlaceholders: true,
1257
+ },
1258
+ // ─── Snyk ──────────────────────────────────────────────────────────────────
1259
+ {
1260
+ id: 'snyk-api-token',
1261
+ regex: /(?:SNYK_TOKEN|SNYK_API_TOKEN)\s*[=:]\s*['"]?([a-f0-9-]{36,})['"]?/i,
1262
+ description: 'Snyk API Token detected',
1263
+ severity: 'critical',
1264
+ type: 'snyk_api_token',
1265
+ autoFixable: true,
1266
+ skipPlaceholders: true,
1267
+ },
1268
+ // ─── OpenAI / AI Providers ─────────────────────────────────────────────────
1269
+ {
1270
+ id: 'openai-api-key',
1271
+ regex: /sk-[A-Za-z0-9]{20}T3BlbkFJ[A-Za-z0-9]{20}/,
1272
+ description: 'OpenAI API Key detected',
1273
+ severity: 'critical',
1274
+ type: 'openai_api_key',
1275
+ autoFixable: true,
1276
+ },
1277
+ {
1278
+ id: 'openai-api-key-v2',
1279
+ regex: /sk-proj-[A-Za-z0-9_-]{40,}/,
1280
+ description: 'OpenAI Project API Key detected',
1281
+ severity: 'critical',
1282
+ type: 'openai_api_key',
1283
+ autoFixable: true,
1284
+ },
1285
+ {
1286
+ id: 'openai-api-key-env',
1287
+ regex: /(?:OPENAI_API_KEY|openai_api_key)\s*[=:]\s*['"]?(sk-[A-Za-z0-9_-]{20,})['"]?/i,
1288
+ description: 'OpenAI API Key in environment variable detected',
1289
+ severity: 'critical',
1290
+ type: 'openai_api_key',
1291
+ autoFixable: true,
1292
+ },
1293
+ {
1294
+ id: 'anthropic-api-key',
1295
+ regex: /sk-ant-[A-Za-z0-9_-]{90,}/,
1296
+ description: 'Anthropic API Key detected',
1297
+ severity: 'critical',
1298
+ type: 'anthropic_api_key',
1299
+ autoFixable: true,
1300
+ },
1301
+ {
1302
+ id: 'anthropic-api-key-env',
1303
+ regex: /(?:ANTHROPIC_API_KEY|anthropic_api_key)\s*[=:]\s*['"]?(sk-ant-[A-Za-z0-9_-]{20,})['"]?/i,
1304
+ description: 'Anthropic API Key in environment variable detected',
1305
+ severity: 'critical',
1306
+ type: 'anthropic_api_key',
1307
+ autoFixable: true,
1308
+ },
1309
+ {
1310
+ id: 'cohere-api-key',
1311
+ regex: /(?:COHERE_API_KEY|cohere_api_key|CO_API_KEY)\s*[=:]\s*['"]?([A-Za-z0-9]{40})['"]?/i,
1312
+ description: 'Cohere API Key detected',
1313
+ severity: 'critical',
1314
+ type: 'cohere_api_key',
1315
+ autoFixable: true,
1316
+ skipPlaceholders: true,
1317
+ },
1318
+ {
1319
+ id: 'huggingface-token',
1320
+ regex: /hf_[A-Za-z0-9]{34}/,
1321
+ description: 'Hugging Face Access Token detected',
1322
+ severity: 'critical',
1323
+ type: 'huggingface_token',
1324
+ autoFixable: true,
1325
+ },
1326
+ {
1327
+ id: 'replicate-api-token',
1328
+ regex: /r8_[A-Za-z0-9]{38}/,
1329
+ description: 'Replicate API Token detected',
1330
+ severity: 'critical',
1331
+ type: 'replicate_api_token',
1332
+ autoFixable: true,
1333
+ },
1334
+ // ─── Encryption / Signing ──────────────────────────────────────────────────
1335
+ {
1336
+ id: 'encryption-key',
1337
+ regex: /(?:ENCRYPTION_KEY|ENCRYPT_KEY|encryption_key)\s*[=:]\s*['"]?([A-Za-z0-9/+=]{32,})['"]?/i,
1338
+ description: 'Encryption key detected',
1339
+ severity: 'critical',
1340
+ type: 'encryption_key',
1341
+ autoFixable: true,
1342
+ skipPlaceholders: true,
1343
+ entropyCheck: true,
1344
+ entropyThreshold: 3.5,
1345
+ },
1346
+ {
1347
+ id: 'signing-secret',
1348
+ regex: /(?:SIGNING_SECRET|signing_secret|WEBHOOK_SECRET|HMAC_SECRET)\s*[=:]\s*['"]?([A-Za-z0-9/+=_-]{16,})['"]?/i,
1349
+ description: 'Signing secret detected',
1350
+ severity: 'critical',
1351
+ type: 'signing_secret',
1352
+ autoFixable: true,
1353
+ skipPlaceholders: true,
1354
+ entropyCheck: true,
1355
+ entropyThreshold: 3.0,
1356
+ },
1357
+ // ─── Session / Cookie Secrets ──────────────────────────────────────────────
1358
+ {
1359
+ id: 'session-secret',
1360
+ regex: /(?:SESSION_SECRET|session_secret|COOKIE_SECRET|cookie_secret|EXPRESS_SESSION_SECRET)\s*[=:]\s*['"]?([^\s'"]{16,})['"]?/i,
1361
+ description: 'Session/cookie secret detected',
1362
+ severity: 'high',
1363
+ type: 'session_secret',
1364
+ autoFixable: true,
1365
+ skipPlaceholders: true,
1366
+ entropyCheck: true,
1367
+ entropyThreshold: 3.0,
1368
+ },
1369
+ // ─── Plaid ─────────────────────────────────────────────────────────────────
1370
+ {
1371
+ id: 'plaid-client-id',
1372
+ regex: /(?:PLAID_CLIENT_ID|plaid_client_id)\s*[=:]\s*['"]?([a-f0-9]{24})['"]?/i,
1373
+ description: 'Plaid Client ID detected',
1374
+ severity: 'high',
1375
+ type: 'plaid_client_id',
1376
+ autoFixable: true,
1377
+ skipPlaceholders: true,
1378
+ },
1379
+ {
1380
+ id: 'plaid-secret',
1381
+ regex: /(?:PLAID_SECRET|plaid_secret)\s*[=:]\s*['"]?([a-f0-9]{30})['"]?/i,
1382
+ description: 'Plaid Secret detected',
1383
+ severity: 'critical',
1384
+ type: 'plaid_secret',
1385
+ autoFixable: true,
1386
+ skipPlaceholders: true,
1387
+ },
1388
+ // ─── Airtable ──────────────────────────────────────────────────────────────
1389
+ {
1390
+ id: 'airtable-api-key',
1391
+ regex: /(?:AIRTABLE_API_KEY|airtable_api_key)\s*[=:]\s*['"]?(key[A-Za-z0-9]{14})['"]?/i,
1392
+ description: 'Airtable API Key detected',
1393
+ severity: 'critical',
1394
+ type: 'airtable_api_key',
1395
+ autoFixable: true,
1396
+ },
1397
+ {
1398
+ id: 'airtable-pat',
1399
+ regex: /pat[A-Za-z0-9]{14}\.[a-f0-9]{64}/,
1400
+ description: 'Airtable Personal Access Token detected',
1401
+ severity: 'critical',
1402
+ type: 'airtable_pat',
1403
+ autoFixable: true,
1404
+ },
1405
+ // ─── Algolia ───────────────────────────────────────────────────────────────
1406
+ {
1407
+ id: 'algolia-admin-api-key',
1408
+ regex: /(?:ALGOLIA_ADMIN_API_KEY|ALGOLIA_API_KEY|algolia_admin_key)\s*[=:]\s*['"]?([a-f0-9]{32})['"]?/i,
1409
+ description: 'Algolia Admin API Key detected',
1410
+ severity: 'critical',
1411
+ type: 'algolia_api_key',
1412
+ autoFixable: true,
1413
+ skipPlaceholders: true,
1414
+ },
1415
+ // ─── Zendesk ───────────────────────────────────────────────────────────────
1416
+ {
1417
+ id: 'zendesk-api-token',
1418
+ regex: /(?:ZENDESK_API_TOKEN|zendesk_token)\s*[=:]\s*['"]?([A-Za-z0-9]{40})['"]?/i,
1419
+ description: 'Zendesk API Token detected',
1420
+ severity: 'critical',
1421
+ type: 'zendesk_token',
1422
+ autoFixable: true,
1423
+ skipPlaceholders: true,
1424
+ },
1425
+ // ─── Intercom ──────────────────────────────────────────────────────────────
1426
+ {
1427
+ id: 'intercom-access-token',
1428
+ regex: /(?:INTERCOM_ACCESS_TOKEN|intercom_token)\s*[=:]\s*['"]?([A-Za-z0-9=]{40,})['"]?/i,
1429
+ description: 'Intercom Access Token detected',
1430
+ severity: 'critical',
1431
+ type: 'intercom_token',
1432
+ autoFixable: true,
1433
+ skipPlaceholders: true,
1434
+ },
1435
+ // ─── LaunchDarkly ──────────────────────────────────────────────────────────
1436
+ {
1437
+ id: 'launchdarkly-sdk-key',
1438
+ regex: /sdk-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/,
1439
+ description: 'LaunchDarkly SDK Key detected',
1440
+ severity: 'high',
1441
+ type: 'launchdarkly_sdk_key',
1442
+ autoFixable: true,
1443
+ },
1444
+ // ─── Doppler ───────────────────────────────────────────────────────────────
1445
+ {
1446
+ id: 'doppler-token',
1447
+ regex: /dp\.st\.[a-z0-9_-]{40,}/,
1448
+ description: 'Doppler Service Token detected',
1449
+ severity: 'critical',
1450
+ type: 'doppler_token',
1451
+ autoFixable: true,
1452
+ },
1453
+ // ─── Linear ────────────────────────────────────────────────────────────────
1454
+ {
1455
+ id: 'linear-api-key',
1456
+ regex: /lin_api_[A-Za-z0-9]{40}/,
1457
+ description: 'Linear API Key detected',
1458
+ severity: 'critical',
1459
+ type: 'linear_api_key',
1460
+ autoFixable: true,
1461
+ },
1462
+ // ─── Notion ────────────────────────────────────────────────────────────────
1463
+ {
1464
+ id: 'notion-integration-token',
1465
+ regex: /(?:secret_|ntn_)[A-Za-z0-9]{43}/,
1466
+ description: 'Notion Integration Token detected',
1467
+ severity: 'critical',
1468
+ type: 'notion_token',
1469
+ autoFixable: true,
1470
+ contextKeywords: ['notion', 'NOTION'],
1471
+ },
1472
+ // ─── Figma ─────────────────────────────────────────────────────────────────
1473
+ {
1474
+ id: 'figma-pat',
1475
+ regex: /figd_[A-Za-z0-9_-]{40,}/,
1476
+ description: 'Figma Personal Access Token detected',
1477
+ severity: 'critical',
1478
+ type: 'figma_pat',
1479
+ autoFixable: true,
1480
+ },
1481
+ // ─── Fly.io ────────────────────────────────────────────────────────────────
1482
+ {
1483
+ id: 'flyio-access-token',
1484
+ regex: /FlyV1\s+fm[12]_[A-Za-z0-9_]+/,
1485
+ description: 'Fly.io Access Token detected',
1486
+ severity: 'critical',
1487
+ type: 'flyio_token',
1488
+ autoFixable: true,
1489
+ },
1490
+ // ─── Netlify ───────────────────────────────────────────────────────────────
1491
+ {
1492
+ id: 'netlify-access-token',
1493
+ regex: /(?:NETLIFY_AUTH_TOKEN|NETLIFY_TOKEN|netlify_token)\s*[=:]\s*['"]?([A-Za-z0-9_-]{40,})['"]?/i,
1494
+ description: 'Netlify Access Token detected',
1495
+ severity: 'critical',
1496
+ type: 'netlify_token',
1497
+ autoFixable: true,
1498
+ skipPlaceholders: true,
1499
+ },
1500
+ // ─── Supabase / PostgREST ──────────────────────────────────────────────────
1501
+ {
1502
+ id: 'supabase-url-with-key',
1503
+ regex: /https:\/\/[a-z0-9]+\.supabase\.co\/rest\/v1\/[^\s]*\?apikey=[A-Za-z0-9._-]+/,
1504
+ description: 'Supabase REST URL with API key detected',
1505
+ severity: 'high',
1506
+ type: 'supabase_url_with_key',
1507
+ autoFixable: true,
1508
+ },
1509
+ // ─── Generic: Password assignments ─────────────────────────────────────────
1510
+ {
1511
+ id: 'password-assignment-double-quote',
1512
+ regex: /(?:password|passwd|pwd|pass)\s*[=:]\s*"([^"]{8,})"/i,
1513
+ description: 'Hardcoded password in double-quoted string detected',
1514
+ severity: 'high',
1515
+ type: 'hardcoded_password',
1516
+ autoFixable: true,
1517
+ skipPlaceholders: true,
1518
+ entropyCheck: true,
1519
+ entropyThreshold: 2.5,
1520
+ },
1521
+ {
1522
+ id: 'password-assignment-single-quote',
1523
+ regex: /(?:password|passwd|pwd|pass)\s*[=:]\s*'([^']{8,})'/i,
1524
+ description: 'Hardcoded password in single-quoted string detected',
1525
+ severity: 'high',
1526
+ type: 'hardcoded_password',
1527
+ autoFixable: true,
1528
+ skipPlaceholders: true,
1529
+ entropyCheck: true,
1530
+ entropyThreshold: 2.5,
1531
+ },
1532
+ {
1533
+ id: 'secret-assignment-double-quote',
1534
+ regex: /(?:secret|SECRET)\s*[=:]\s*"([^"]{8,})"/,
1535
+ description: 'Hardcoded secret in double-quoted string detected',
1536
+ severity: 'high',
1537
+ type: 'hardcoded_secret',
1538
+ autoFixable: true,
1539
+ skipPlaceholders: true,
1540
+ entropyCheck: true,
1541
+ entropyThreshold: 3.0,
1542
+ },
1543
+ {
1544
+ id: 'secret-assignment-single-quote',
1545
+ regex: /(?:secret|SECRET)\s*[=:]\s*'([^']{8,})'/,
1546
+ description: 'Hardcoded secret in single-quoted string detected',
1547
+ severity: 'high',
1548
+ type: 'hardcoded_secret',
1549
+ autoFixable: true,
1550
+ skipPlaceholders: true,
1551
+ entropyCheck: true,
1552
+ entropyThreshold: 3.0,
1553
+ },
1554
+ // ─── Generic: API key assignments ──────────────────────────────────────────
1555
+ {
1556
+ id: 'api-key-assignment-double-quote',
1557
+ regex: /(?:api_key|apikey|api-key|API_KEY|APIKEY)\s*[=:]\s*"([^"]{16,})"/i,
1558
+ description: 'Hardcoded API key in double-quoted string detected',
1559
+ severity: 'high',
1560
+ type: 'hardcoded_api_key',
1561
+ autoFixable: true,
1562
+ skipPlaceholders: true,
1563
+ entropyCheck: true,
1564
+ entropyThreshold: 3.0,
1565
+ },
1566
+ {
1567
+ id: 'api-key-assignment-single-quote',
1568
+ regex: /(?:api_key|apikey|api-key|API_KEY|APIKEY)\s*[=:]\s*'([^']{16,})'/i,
1569
+ description: 'Hardcoded API key in single-quoted string detected',
1570
+ severity: 'high',
1571
+ type: 'hardcoded_api_key',
1572
+ autoFixable: true,
1573
+ skipPlaceholders: true,
1574
+ entropyCheck: true,
1575
+ entropyThreshold: 3.0,
1576
+ },
1577
+ {
1578
+ id: 'access-token-assignment-double-quote',
1579
+ regex: /(?:access_token|ACCESS_TOKEN|accessToken)\s*[=:]\s*"([^"]{16,})"/i,
1580
+ description: 'Hardcoded access token in double-quoted string detected',
1581
+ severity: 'high',
1582
+ type: 'hardcoded_access_token',
1583
+ autoFixable: true,
1584
+ skipPlaceholders: true,
1585
+ entropyCheck: true,
1586
+ entropyThreshold: 3.0,
1587
+ },
1588
+ {
1589
+ id: 'access-token-assignment-single-quote',
1590
+ regex: /(?:access_token|ACCESS_TOKEN|accessToken)\s*[=:]\s*'([^']{16,})'/i,
1591
+ description: 'Hardcoded access token in single-quoted string detected',
1592
+ severity: 'high',
1593
+ type: 'hardcoded_access_token',
1594
+ autoFixable: true,
1595
+ skipPlaceholders: true,
1596
+ entropyCheck: true,
1597
+ entropyThreshold: 3.0,
1598
+ },
1599
+ {
1600
+ id: 'secret-key-assignment-double-quote',
1601
+ regex: /(?:secret_key|SECRET_KEY|secretKey)\s*[=:]\s*"([^"]{16,})"/i,
1602
+ description: 'Hardcoded secret key in double-quoted string detected',
1603
+ severity: 'high',
1604
+ type: 'hardcoded_secret_key',
1605
+ autoFixable: true,
1606
+ skipPlaceholders: true,
1607
+ entropyCheck: true,
1608
+ entropyThreshold: 3.0,
1609
+ },
1610
+ {
1611
+ id: 'secret-key-assignment-single-quote',
1612
+ regex: /(?:secret_key|SECRET_KEY|secretKey)\s*[=:]\s*'([^']{16,})'/i,
1613
+ description: 'Hardcoded secret key in single-quoted string detected',
1614
+ severity: 'high',
1615
+ type: 'hardcoded_secret_key',
1616
+ autoFixable: true,
1617
+ skipPlaceholders: true,
1618
+ entropyCheck: true,
1619
+ entropyThreshold: 3.0,
1620
+ },
1621
+ {
1622
+ id: 'private-key-assignment',
1623
+ regex: /(?:private_key|PRIVATE_KEY|privateKey)\s*[=:]\s*['"]([^'"]{16,})['"]/i,
1624
+ description: 'Hardcoded private key value detected',
1625
+ severity: 'critical',
1626
+ type: 'hardcoded_private_key',
1627
+ autoFixable: true,
1628
+ skipPlaceholders: true,
1629
+ entropyCheck: true,
1630
+ entropyThreshold: 3.5,
1631
+ },
1632
+ {
1633
+ id: 'auth-token-assignment',
1634
+ regex: /(?:auth_token|AUTH_TOKEN|authToken|authorization_token)\s*[=:]\s*['"]([^'"]{16,})['"]/i,
1635
+ description: 'Hardcoded auth token detected',
1636
+ severity: 'high',
1637
+ type: 'hardcoded_auth_token',
1638
+ autoFixable: true,
1639
+ skipPlaceholders: true,
1640
+ entropyCheck: true,
1641
+ entropyThreshold: 3.0,
1642
+ },
1643
+ // ─── Generic: Bearer tokens ────────────────────────────────────────────────
1644
+ {
1645
+ id: 'bearer-token-in-string',
1646
+ regex: /['"]Bearer\s+([A-Za-z0-9_-]{20,})['"]/,
1647
+ description: 'Hardcoded Bearer token in string detected',
1648
+ severity: 'high',
1649
+ type: 'bearer_token',
1650
+ autoFixable: true,
1651
+ entropyCheck: true,
1652
+ entropyThreshold: 3.5,
1653
+ },
1654
+ {
1655
+ id: 'basic-auth-in-string',
1656
+ regex: /['"]Basic\s+([A-Za-z0-9+/=]{20,})['"]/,
1657
+ description: 'Hardcoded Basic Auth credential in string detected',
1658
+ severity: 'high',
1659
+ type: 'basic_auth',
1660
+ autoFixable: true,
1661
+ entropyCheck: true,
1662
+ entropyThreshold: 3.0,
1663
+ },
1664
+ // ─── Generic: Authorization headers ────────────────────────────────────────
1665
+ {
1666
+ id: 'authorization-header-bearer',
1667
+ regex: /[Aa]uthorization['":\s]+['"]?Bearer\s+([A-Za-z0-9._~+/=-]{20,})/,
1668
+ description: 'Authorization header with Bearer token detected',
1669
+ severity: 'high',
1670
+ type: 'authorization_header',
1671
+ autoFixable: true,
1672
+ entropyCheck: true,
1673
+ entropyThreshold: 3.5,
1674
+ },
1675
+ // ─── Generic: High-entropy base64 strings with context ─────────────────────
1676
+ {
1677
+ id: 'high-entropy-base64-with-key-context',
1678
+ regex: /(?:key|token|secret|credential|auth)\s*[=:]\s*['"]([A-Za-z0-9+/]{40,}={0,2})['"]/i,
1679
+ description: 'High-entropy base64 string in secret context detected',
1680
+ severity: 'medium',
1681
+ type: 'high_entropy_secret',
1682
+ autoFixable: true,
1683
+ skipPlaceholders: true,
1684
+ entropyCheck: true,
1685
+ entropyThreshold: 4.0,
1686
+ },
1687
+ // ─── .env file patterns ────────────────────────────────────────────────────
1688
+ {
1689
+ id: 'env-file-generic-secret',
1690
+ regex: /^[A-Z_]*(?:SECRET|TOKEN|KEY|PASSWORD|CREDENTIAL|AUTH)[A-Z_]*\s*=\s*['"]?([^\s'"#]{12,})['"]?\s*(?:#.*)?$/i,
1691
+ description: 'Secret value in .env file detected',
1692
+ severity: 'high',
1693
+ type: 'env_file_secret',
1694
+ autoFixable: true,
1695
+ skipPlaceholders: true,
1696
+ entropyCheck: true,
1697
+ entropyThreshold: 3.0,
1698
+ contextKeywords: ['.env'],
1699
+ },
1700
+ // ─── Webhook URLs ──────────────────────────────────────────────────────────
1701
+ {
1702
+ id: 'generic-webhook-url',
1703
+ regex: /https?:\/\/[^\s'"]*\/webhook[s]?\/[A-Za-z0-9_-]{20,}/i,
1704
+ description: 'Webhook URL with token detected',
1705
+ severity: 'medium',
1706
+ type: 'webhook_url',
1707
+ autoFixable: true,
1708
+ entropyCheck: true,
1709
+ entropyThreshold: 3.0,
1710
+ },
1711
+ // ─── Connection strings with inline creds ──────────────────────────────────
1712
+ {
1713
+ id: 'jdbc-connection-string',
1714
+ regex: /jdbc:[a-z]+:\/\/[^:]+:[^@]{3,}@[^\s'"]+/i,
1715
+ description: 'JDBC connection string with credentials detected',
1716
+ severity: 'critical',
1717
+ type: 'database_url',
1718
+ autoFixable: true,
1719
+ skipPlaceholders: true,
1720
+ },
1721
+ {
1722
+ id: 'amqp-connection-string',
1723
+ regex: /amqps?:\/\/[^:]+:[^@]{3,}@[^\s'"]+/i,
1724
+ description: 'AMQP connection string with credentials detected',
1725
+ severity: 'critical',
1726
+ type: 'amqp_connection_string',
1727
+ autoFixable: true,
1728
+ skipPlaceholders: true,
1729
+ },
1730
+ // ─── Grafana ───────────────────────────────────────────────────────────────
1731
+ {
1732
+ id: 'grafana-api-key',
1733
+ regex: /eyJrIjoi[A-Za-z0-9_-]{30,}/,
1734
+ description: 'Grafana API Key / Service Account Token detected',
1735
+ severity: 'critical',
1736
+ type: 'grafana_api_key',
1737
+ autoFixable: true,
1738
+ },
1739
+ // ─── Contentful ────────────────────────────────────────────────────────────
1740
+ {
1741
+ id: 'contentful-delivery-token',
1742
+ regex: /(?:CONTENTFUL_ACCESS_TOKEN|CONTENTFUL_DELIVERY_TOKEN)\s*[=:]\s*['"]?([A-Za-z0-9_-]{43,})['"]?/i,
1743
+ description: 'Contentful Delivery/Preview Token detected',
1744
+ severity: 'high',
1745
+ type: 'contentful_token',
1746
+ autoFixable: true,
1747
+ skipPlaceholders: true,
1748
+ },
1749
+ // ─── Pulumi ────────────────────────────────────────────────────────────────
1750
+ {
1751
+ id: 'pulumi-access-token',
1752
+ regex: /pul-[A-Za-z0-9]{40}/,
1753
+ description: 'Pulumi Access Token detected',
1754
+ severity: 'critical',
1755
+ type: 'pulumi_token',
1756
+ autoFixable: true,
1757
+ },
1758
+ // ─── Fastly ────────────────────────────────────────────────────────────────
1759
+ {
1760
+ id: 'fastly-api-token',
1761
+ regex: /(?:FASTLY_API_TOKEN|FASTLY_KEY|fastly_api_token)\s*[=:]\s*['"]?([A-Za-z0-9_-]{32})['"]?/i,
1762
+ description: 'Fastly API Token detected',
1763
+ severity: 'critical',
1764
+ type: 'fastly_api_token',
1765
+ autoFixable: true,
1766
+ skipPlaceholders: true,
1767
+ },
1768
+ // ─── Lob ───────────────────────────────────────────────────────────────────
1769
+ {
1770
+ id: 'lob-api-key',
1771
+ regex: /(?:live|test)_[a-f0-9]{35}/,
1772
+ description: 'Lob API Key detected',
1773
+ severity: 'high',
1774
+ type: 'lob_api_key',
1775
+ autoFixable: true,
1776
+ contextKeywords: ['lob', 'LOB'],
1777
+ },
1778
+ // ─── Dynatrace ─────────────────────────────────────────────────────────────
1779
+ {
1780
+ id: 'dynatrace-api-token',
1781
+ regex: /dt0c01\.[A-Z0-9]{24}\.[A-Z0-9]{64}/,
1782
+ description: 'Dynatrace API Token detected',
1783
+ severity: 'critical',
1784
+ type: 'dynatrace_api_token',
1785
+ autoFixable: true,
1786
+ },
1787
+ // ─── Elastic / Kibana ──────────────────────────────────────────────────────
1788
+ {
1789
+ id: 'elastic-cloud-api-key',
1790
+ regex: /(?:ELASTIC_API_KEY|ELASTICSEARCH_API_KEY)\s*[=:]\s*['"]?([A-Za-z0-9_-]{40,})['"]?/i,
1791
+ description: 'Elastic Cloud API Key detected',
1792
+ severity: 'critical',
1793
+ type: 'elastic_api_key',
1794
+ autoFixable: true,
1795
+ skipPlaceholders: true,
1796
+ },
1797
+ // ─── Age encryption key ────────────────────────────────────────────────────
1798
+ {
1799
+ id: 'age-secret-key',
1800
+ regex: /AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}/,
1801
+ description: 'age encryption secret key detected',
1802
+ severity: 'critical',
1803
+ type: 'age_secret_key',
1804
+ autoFixable: false,
1805
+ },
1806
+ // ─── PKCS12 / PFX ─────────────────────────────────────────────────────────
1807
+ {
1808
+ id: 'pkcs12-password',
1809
+ regex: /(?:PKCS12_PASSWORD|PFX_PASSWORD|pkcs12_pass|pfx_pass)\s*[=:]\s*['"]?([^\s'"]{6,})['"]?/i,
1810
+ description: 'PKCS12/PFX password detected',
1811
+ severity: 'critical',
1812
+ type: 'pkcs12_password',
1813
+ autoFixable: true,
1814
+ skipPlaceholders: true,
1815
+ },
1816
+ ];
1817
+ // ── Pattern count export ───────────────────────────────────────────────────────
1818
+ export function getSecretPatternCount() {
1819
+ return SECRET_PATTERNS.length;
1820
+ }
1821
+ // ── Severity ordering (for sorting) ────────────────────────────────────────────
1822
+ const SEVERITY_RANK = {
1823
+ critical: 0,
1824
+ high: 1,
1825
+ medium: 2,
1826
+ low: 3,
1827
+ info: 4,
1828
+ };
1829
+ // ── Main scanner ───────────────────────────────────────────────────────────────
1830
+ export async function scanSecrets(targetPath, files) {
1831
+ // 1. Determine file list
1832
+ let filesToScan;
1833
+ if (files && files.length > 0) {
1834
+ // Map relative paths to absolute
1835
+ filesToScan = files.map((f) => f.startsWith('/') ? f : join(targetPath, f));
1836
+ }
1837
+ else {
1838
+ filesToScan = await walkDirectory(targetPath);
1839
+ }
1840
+ // 2. Scan all files concurrently in controlled batches
1841
+ const findings = [];
1842
+ const BATCH_SIZE = 50;
1843
+ for (let i = 0; i < filesToScan.length; i += BATCH_SIZE) {
1844
+ const batch = filesToScan.slice(i, i + BATCH_SIZE);
1845
+ const batchResults = await Promise.all(batch.map((filePath) => scanFile(filePath, targetPath)));
1846
+ for (const result of batchResults) {
1847
+ findings.push(...result);
1848
+ }
1849
+ }
1850
+ // 3. Deduplicate: same pattern + same file + same line
1851
+ const seen = new Set();
1852
+ const deduped = [];
1853
+ for (const finding of findings) {
1854
+ const key = `${finding.id}:${finding.file}:${finding.line}`;
1855
+ if (!seen.has(key)) {
1856
+ seen.add(key);
1857
+ deduped.push(finding);
1858
+ }
1859
+ }
1860
+ // 4. Sort by severity (critical first), then file, then line
1861
+ deduped.sort((a, b) => {
1862
+ const sevDiff = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
1863
+ if (sevDiff !== 0)
1864
+ return sevDiff;
1865
+ const fileDiff = a.file.localeCompare(b.file);
1866
+ if (fileDiff !== 0)
1867
+ return fileDiff;
1868
+ return a.line - b.line;
1869
+ });
1870
+ return deduped;
1871
+ }
1872
+ // ── Single-file scanner ────────────────────────────────────────────────────────
1873
+ async function scanFile(filePath, basePath) {
1874
+ // Check file size — skip files > 1MB
1875
+ let fileStat;
1876
+ try {
1877
+ fileStat = await stat(filePath);
1878
+ }
1879
+ catch {
1880
+ return [];
1881
+ }
1882
+ if (fileStat.size > MAX_FILE_SIZE)
1883
+ return [];
1884
+ if (!fileStat.isFile())
1885
+ return [];
1886
+ // Read file contents
1887
+ let content;
1888
+ try {
1889
+ content = await readFile(filePath, 'utf-8');
1890
+ }
1891
+ catch {
1892
+ // Binary file or permission error — skip
1893
+ return [];
1894
+ }
1895
+ // Quick binary check: if file contains null bytes in first 8KB, skip
1896
+ const sample = content.slice(0, 8192);
1897
+ if (sample.includes('\0'))
1898
+ return [];
1899
+ const relativePath = relative(basePath, filePath);
1900
+ const isEnvFile = basename(filePath).startsWith('.env');
1901
+ const lines = content.split('\n');
1902
+ const findings = [];
1903
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
1904
+ const line = lines[lineIdx];
1905
+ // Skip empty lines and very short lines
1906
+ if (line.length < 8)
1907
+ continue;
1908
+ // Skip documentation / example comment lines
1909
+ if (isDocOrCommentExample(line))
1910
+ continue;
1911
+ for (const pattern of SECRET_PATTERNS) {
1912
+ // Context keyword filter for .env patterns
1913
+ if (pattern.contextKeywords?.includes('.env') &&
1914
+ !isEnvFile) {
1915
+ continue;
1916
+ }
1917
+ const match = pattern.regex.exec(line);
1918
+ if (!match)
1919
+ continue;
1920
+ // Extract the captured group (secret value) or the full match
1921
+ const secretValue = match[1] ?? match[0];
1922
+ // Placeholder check
1923
+ if (pattern.skipPlaceholders && isPlaceholder(secretValue)) {
1924
+ continue;
1925
+ }
1926
+ // Entropy check
1927
+ if (pattern.entropyCheck) {
1928
+ const threshold = pattern.entropyThreshold ?? 3.5;
1929
+ const entropy = shannonEntropy(secretValue);
1930
+ if (entropy < threshold)
1931
+ continue;
1932
+ }
1933
+ // Context keyword validation (for patterns that need nearby keywords)
1934
+ if (pattern.contextKeywords &&
1935
+ !pattern.contextKeywords.includes('.env')) {
1936
+ const contextWindow = getContextWindow(lines, lineIdx, 3);
1937
+ const hasContext = pattern.contextKeywords.some((kw) => contextWindow.toLowerCase().includes(kw.toLowerCase()));
1938
+ if (!hasContext)
1939
+ continue;
1940
+ }
1941
+ findings.push({
1942
+ id: `builtin_${pattern.id}_${lineIdx + 1}`,
1943
+ engine: 'pattern',
1944
+ severity: pattern.severity,
1945
+ type: pattern.type,
1946
+ file: relativePath,
1947
+ line: lineIdx + 1,
1948
+ description: pattern.description,
1949
+ fix_suggestion: 'Move this secret to an environment variable or secrets manager. Never commit secrets to source control.',
1950
+ auto_fixable: pattern.autoFixable,
1951
+ });
1952
+ }
1953
+ }
1954
+ return findings;
1955
+ }
1956
+ // ── Context window helper ──────────────────────────────────────────────────────
1957
+ function getContextWindow(lines, lineIdx, radius) {
1958
+ const start = Math.max(0, lineIdx - radius);
1959
+ const end = Math.min(lines.length - 1, lineIdx + radius);
1960
+ return lines.slice(start, end + 1).join('\n');
1961
+ }
1962
+ //# sourceMappingURL=secrets.js.map