s3db.js 13.6.1 → 14.0.2

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.
Files changed (189) hide show
  1. package/README.md +56 -15
  2. package/dist/s3db.cjs +72446 -39022
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72172 -38790
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +85 -50
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/route-context.js +601 -0
  34. package/src/plugins/api/index.js +168 -40
  35. package/src/plugins/api/routes/auth-routes.js +198 -30
  36. package/src/plugins/api/routes/resource-routes.js +19 -4
  37. package/src/plugins/api/server/health-manager.class.js +163 -0
  38. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  39. package/src/plugins/api/server/router.class.js +472 -0
  40. package/src/plugins/api/server.js +280 -1303
  41. package/src/plugins/api/utils/custom-routes.js +17 -5
  42. package/src/plugins/api/utils/guards.js +76 -17
  43. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  44. package/src/plugins/api/utils/openapi-generator.js +7 -6
  45. package/src/plugins/audit.plugin.js +30 -8
  46. package/src/plugins/backup.plugin.js +110 -14
  47. package/src/plugins/cache/cache.class.js +22 -5
  48. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  49. package/src/plugins/cache/memory-cache.class.js +211 -57
  50. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  51. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  52. package/src/plugins/cache/redis-cache.class.js +552 -0
  53. package/src/plugins/cache/s3-cache.class.js +17 -8
  54. package/src/plugins/cache.plugin.js +176 -61
  55. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  56. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  57. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  58. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  59. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  60. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  62. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/index.js +29 -8
  66. package/src/plugins/cloud-inventory/registry.js +64 -42
  67. package/src/plugins/cloud-inventory.plugin.js +240 -138
  68. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  69. package/src/plugins/concerns/resource-names.js +100 -0
  70. package/src/plugins/consumers/index.js +10 -2
  71. package/src/plugins/consumers/sqs-consumer.js +12 -2
  72. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  73. package/src/plugins/cookie-farm.errors.js +73 -0
  74. package/src/plugins/cookie-farm.plugin.js +869 -0
  75. package/src/plugins/costs.plugin.js +7 -1
  76. package/src/plugins/eventual-consistency/analytics.js +94 -19
  77. package/src/plugins/eventual-consistency/config.js +15 -7
  78. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  79. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  80. package/src/plugins/eventual-consistency/helpers.js +39 -14
  81. package/src/plugins/eventual-consistency/install.js +21 -2
  82. package/src/plugins/eventual-consistency/utils.js +32 -10
  83. package/src/plugins/fulltext.plugin.js +38 -11
  84. package/src/plugins/geo.plugin.js +61 -9
  85. package/src/plugins/identity/concerns/config.js +61 -0
  86. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  87. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  88. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  89. package/src/plugins/identity/concerns/token-generator.js +29 -4
  90. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  91. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  92. package/src/plugins/identity/drivers/index.js +18 -0
  93. package/src/plugins/identity/drivers/password-driver.js +122 -0
  94. package/src/plugins/identity/email-service.js +17 -2
  95. package/src/plugins/identity/index.js +413 -69
  96. package/src/plugins/identity/oauth2-server.js +413 -30
  97. package/src/plugins/identity/oidc-discovery.js +16 -8
  98. package/src/plugins/identity/rsa-keys.js +115 -35
  99. package/src/plugins/identity/server.js +166 -45
  100. package/src/plugins/identity/session-manager.js +53 -7
  101. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  102. package/src/plugins/identity/ui/routes.js +363 -255
  103. package/src/plugins/importer/index.js +153 -20
  104. package/src/plugins/index.js +9 -2
  105. package/src/plugins/kubernetes-inventory/index.js +6 -0
  106. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  107. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  108. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  109. package/src/plugins/metrics.plugin.js +64 -16
  110. package/src/plugins/ml/base-model.class.js +25 -15
  111. package/src/plugins/ml/regression-model.class.js +1 -1
  112. package/src/plugins/ml.errors.js +57 -25
  113. package/src/plugins/ml.plugin.js +28 -4
  114. package/src/plugins/namespace.js +210 -0
  115. package/src/plugins/plugin.class.js +180 -8
  116. package/src/plugins/puppeteer/console-monitor.js +729 -0
  117. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  118. package/src/plugins/puppeteer/network-monitor.js +816 -0
  119. package/src/plugins/puppeteer/performance-manager.js +746 -0
  120. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  121. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  122. package/src/plugins/puppeteer.errors.js +81 -0
  123. package/src/plugins/puppeteer.plugin.js +1327 -0
  124. package/src/plugins/queue-consumer.plugin.js +69 -14
  125. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  126. package/src/plugins/recon/concerns/command-runner.js +148 -0
  127. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  128. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  129. package/src/plugins/recon/concerns/process-manager.js +338 -0
  130. package/src/plugins/recon/concerns/report-generator.js +478 -0
  131. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  132. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  133. package/src/plugins/recon/config/defaults.js +321 -0
  134. package/src/plugins/recon/config/resources.js +370 -0
  135. package/src/plugins/recon/index.js +778 -0
  136. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  137. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  138. package/src/plugins/recon/managers/storage-manager.js +745 -0
  139. package/src/plugins/recon/managers/target-manager.js +274 -0
  140. package/src/plugins/recon/stages/asn-stage.js +314 -0
  141. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  142. package/src/plugins/recon/stages/dns-stage.js +107 -0
  143. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  144. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  145. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  146. package/src/plugins/recon/stages/http-stage.js +89 -0
  147. package/src/plugins/recon/stages/latency-stage.js +148 -0
  148. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  149. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  150. package/src/plugins/recon/stages/ports-stage.js +169 -0
  151. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  152. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  153. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  154. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  155. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  156. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  157. package/src/plugins/recon/stages/whois-stage.js +349 -0
  158. package/src/plugins/recon.plugin.js +75 -0
  159. package/src/plugins/recon.plugin.js.backup +2635 -0
  160. package/src/plugins/relation.errors.js +87 -14
  161. package/src/plugins/replicator.plugin.js +514 -137
  162. package/src/plugins/replicators/base-replicator.class.js +89 -1
  163. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  164. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  165. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  166. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  167. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  168. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  169. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  170. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  171. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  172. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  173. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  174. package/src/plugins/s3-queue.plugin.js +464 -65
  175. package/src/plugins/scheduler.plugin.js +20 -6
  176. package/src/plugins/state-machine.plugin.js +40 -9
  177. package/src/plugins/tfstate/base-driver.js +28 -4
  178. package/src/plugins/tfstate/errors.js +65 -10
  179. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  180. package/src/plugins/tfstate/index.js +163 -90
  181. package/src/plugins/tfstate/s3-driver.js +64 -6
  182. package/src/plugins/ttl.plugin.js +72 -17
  183. package/src/plugins/vector/distances.js +18 -12
  184. package/src/plugins/vector/kmeans.js +26 -4
  185. package/src/resource.class.js +115 -19
  186. package/src/testing/factory.class.js +20 -3
  187. package/src/testing/seeder.class.js +7 -1
  188. package/src/clients/memory-client.md +0 -917
  189. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -0,0 +1,514 @@
1
+ /**
2
+ * SecretsStage
3
+ *
4
+ * Secrets detection stage using multiple methods:
5
+ * - Gitleaks: Industry-standard secrets scanner
6
+ * - TruffleHog: Git repository secret scanning
7
+ * - Regex patterns: Common API keys, tokens, credentials
8
+ * - JS file analysis: Extract endpoints and potential secrets
9
+ *
10
+ * Scans:
11
+ * - JavaScript files from HTTP responses
12
+ * - Wayback/historical URLs
13
+ * - Git repositories (if accessible)
14
+ * - Configuration files
15
+ * - Environment variables in responses
16
+ */
17
+
18
+ import { exec } from 'child_process';
19
+ import { promisify } from 'util';
20
+ import { writeFile, unlink, mkdir } from 'fs/promises';
21
+ import { join } from 'path';
22
+ import { tmpdir } from 'os';
23
+ import { randomBytes } from 'crypto';
24
+
25
+ const execAsync = promisify(exec);
26
+
27
+ export class SecretsStage {
28
+ constructor(plugin) {
29
+ this.plugin = plugin;
30
+ this.timeout = 30000; // 30 second timeout for scans
31
+ }
32
+
33
+ /**
34
+ * Execute secrets detection scan
35
+ * @param {Object} target - Target object with host property
36
+ * @param {Object} options - Scan options
37
+ * @returns {Object} Secrets findings
38
+ */
39
+ async execute(target, options = {}) {
40
+ const result = {
41
+ status: 'ok',
42
+ findings: [],
43
+ summary: {
44
+ total: 0,
45
+ highSeverity: 0,
46
+ mediumSeverity: 0,
47
+ lowSeverity: 0,
48
+ byType: {}
49
+ },
50
+ scanners: {},
51
+ errors: {}
52
+ };
53
+
54
+ try {
55
+ // Check which scanners are available
56
+ const availableScanners = await this.checkAvailableScanners();
57
+
58
+ // Collect URLs to scan (from HTTP stage if available)
59
+ const urlsToScan = this.collectUrlsToScan(target, options);
60
+
61
+ // Run Gitleaks if available
62
+ if (availableScanners.gitleaks && options.gitleaks !== false) {
63
+ try {
64
+ const gitleaksFindings = await this.runGitleaks(urlsToScan, options);
65
+ result.scanners.gitleaks = gitleaksFindings;
66
+ result.findings.push(...gitleaksFindings.findings);
67
+ } catch (error) {
68
+ result.errors.gitleaks = error.message;
69
+ }
70
+ }
71
+
72
+ // Run regex-based pattern matching (always available)
73
+ if (options.patterns !== false) {
74
+ try {
75
+ const patternFindings = await this.runPatternMatching(urlsToScan, options);
76
+ result.scanners.patterns = patternFindings;
77
+ result.findings.push(...patternFindings.findings);
78
+ } catch (error) {
79
+ result.errors.patterns = error.message;
80
+ }
81
+ }
82
+
83
+ // Deduplicate and categorize findings
84
+ result.findings = this.deduplicateFindings(result.findings);
85
+ result.summary = this.buildSummary(result.findings);
86
+
87
+ if (result.findings.length === 0) {
88
+ result.status = 'clean';
89
+ } else if (result.summary.highSeverity > 0) {
90
+ result.status = 'critical';
91
+ } else if (result.summary.mediumSeverity > 0) {
92
+ result.status = 'warning';
93
+ }
94
+
95
+ } catch (error) {
96
+ result.status = 'error';
97
+ result.message = error?.message || 'Secrets scan failed';
98
+ result.errors.scan = error?.message;
99
+ }
100
+
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * Check which secret scanners are available
106
+ * @returns {Promise<Object>} Available scanners
107
+ */
108
+ async checkAvailableScanners() {
109
+ const scanners = {
110
+ gitleaks: false,
111
+ trufflehog: false
112
+ };
113
+
114
+ try {
115
+ await execAsync('which gitleaks', { timeout: 2000 });
116
+ scanners.gitleaks = true;
117
+ } catch (error) {
118
+ // Gitleaks not available
119
+ }
120
+
121
+ try {
122
+ await execAsync('which trufflehog', { timeout: 2000 });
123
+ scanners.trufflehog = true;
124
+ } catch (error) {
125
+ // TruffleHog not available
126
+ }
127
+
128
+ return scanners;
129
+ }
130
+
131
+ /**
132
+ * Collect URLs to scan from target and previous stages
133
+ * @param {Object} target - Target object
134
+ * @param {Object} options - Scan options
135
+ * @returns {Array<string>} URLs to scan
136
+ */
137
+ collectUrlsToScan(target, options) {
138
+ const urls = [];
139
+
140
+ // Add main target URL
141
+ const protocol = target.protocol || 'https';
142
+ const port = target.port ? `:${target.port}` : '';
143
+ urls.push(`${protocol}://${target.host}${port}`);
144
+
145
+ // Add common paths that might contain secrets
146
+ const commonPaths = [
147
+ '/',
148
+ '/robots.txt',
149
+ '/sitemap.xml',
150
+ '/.git/config',
151
+ '/.env',
152
+ '/config.json',
153
+ '/package.json',
154
+ '/composer.json',
155
+ '/app.js',
156
+ '/main.js',
157
+ '/bundle.js',
158
+ '/vendor.js'
159
+ ];
160
+
161
+ for (const path of commonPaths) {
162
+ urls.push(`${protocol}://${target.host}${port}${path}`);
163
+ }
164
+
165
+ // Add custom URLs if provided
166
+ if (options.urls && Array.isArray(options.urls)) {
167
+ urls.push(...options.urls);
168
+ }
169
+
170
+ return [...new Set(urls)]; // Deduplicate
171
+ }
172
+
173
+ /**
174
+ * Run Gitleaks scanner
175
+ * @param {Array<string>} urls - URLs to scan
176
+ * @param {Object} options - Scan options
177
+ * @returns {Promise<Object>} Gitleaks findings
178
+ */
179
+ async runGitleaks(urls, options) {
180
+ const findings = {
181
+ status: 'ok',
182
+ findings: [],
183
+ scannedUrls: urls.length
184
+ };
185
+
186
+ // Create temporary directory for scan
187
+ const tmpDir = join(tmpdir(), `gitleaks-${randomBytes(8).toString('hex')}`);
188
+ await mkdir(tmpDir, { recursive: true });
189
+
190
+ try {
191
+ // Fetch content from URLs
192
+ const contentFiles = [];
193
+
194
+ for (const url of urls.slice(0, options.maxUrls || 20)) {
195
+ try {
196
+ const response = await this.fetchUrl(url);
197
+ if (response && response.body) {
198
+ const filename = join(tmpDir, `${randomBytes(4).toString('hex')}.txt`);
199
+ await writeFile(filename, response.body);
200
+ contentFiles.push({ filename, url });
201
+ }
202
+ } catch (error) {
203
+ // Skip URLs that fail to fetch
204
+ }
205
+ }
206
+
207
+ // Run Gitleaks detect on each file
208
+ for (const { filename, url } of contentFiles) {
209
+ try {
210
+ const gitleaksOutput = await this.executeGitleaks(filename, options);
211
+
212
+ if (gitleaksOutput && gitleaksOutput.length > 0) {
213
+ for (const finding of gitleaksOutput) {
214
+ findings.findings.push({
215
+ type: finding.RuleID || 'unknown',
216
+ description: finding.Description || finding.RuleID,
217
+ severity: this.mapGitleaksSeverity(finding),
218
+ location: url,
219
+ line: finding.StartLine,
220
+ match: finding.Match || finding.Secret?.substring(0, 50),
221
+ file: finding.File,
222
+ scanner: 'gitleaks'
223
+ });
224
+ }
225
+ }
226
+ } catch (error) {
227
+ // Continue with next file
228
+ }
229
+ }
230
+
231
+ // Cleanup temporary files
232
+ for (const { filename } of contentFiles) {
233
+ try {
234
+ await unlink(filename);
235
+ } catch (error) {
236
+ // Ignore cleanup errors
237
+ }
238
+ }
239
+
240
+ } finally {
241
+ // Cleanup temporary directory
242
+ try {
243
+ await execAsync(`rm -rf "${tmpDir}"`);
244
+ } catch (error) {
245
+ // Ignore cleanup errors
246
+ }
247
+ }
248
+
249
+ return findings;
250
+ }
251
+
252
+ /**
253
+ * Execute Gitleaks command
254
+ * @param {string} filepath - File to scan
255
+ * @param {Object} options - Scan options
256
+ * @returns {Promise<Array>} Gitleaks findings
257
+ */
258
+ async executeGitleaks(filepath, options) {
259
+ try {
260
+ const { stdout } = await execAsync(
261
+ `gitleaks detect --no-git --source "${filepath}" --report-format json --report-path /dev/stdout`,
262
+ {
263
+ timeout: this.timeout,
264
+ maxBuffer: 5 * 1024 * 1024 // 5MB buffer
265
+ }
266
+ );
267
+
268
+ if (!stdout || stdout.trim() === '') {
269
+ return [];
270
+ }
271
+
272
+ return JSON.parse(stdout);
273
+ } catch (error) {
274
+ // Gitleaks exits with code 1 if leaks found
275
+ if (error.stdout) {
276
+ try {
277
+ return JSON.parse(error.stdout);
278
+ } catch (parseError) {
279
+ return [];
280
+ }
281
+ }
282
+ throw error;
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Map Gitleaks severity to our scale
288
+ * @param {Object} finding - Gitleaks finding
289
+ * @returns {string} Severity level
290
+ */
291
+ mapGitleaksSeverity(finding) {
292
+ // Gitleaks doesn't provide severity, so we classify by rule type
293
+ const highSeverityRules = [
294
+ 'aws-access-token',
295
+ 'aws-secret-key',
296
+ 'github-pat',
297
+ 'github-oauth',
298
+ 'private-key',
299
+ 'slack-token',
300
+ 'stripe-api-key'
301
+ ];
302
+
303
+ const ruleId = (finding.RuleID || '').toLowerCase();
304
+
305
+ if (highSeverityRules.some(rule => ruleId.includes(rule))) {
306
+ return 'high';
307
+ }
308
+
309
+ if (ruleId.includes('api') || ruleId.includes('token') || ruleId.includes('key')) {
310
+ return 'medium';
311
+ }
312
+
313
+ return 'low';
314
+ }
315
+
316
+ /**
317
+ * Run regex-based pattern matching
318
+ * @param {Array<string>} urls - URLs to scan
319
+ * @param {Object} options - Scan options
320
+ * @returns {Promise<Object>} Pattern findings
321
+ */
322
+ async runPatternMatching(urls, options) {
323
+ const findings = {
324
+ status: 'ok',
325
+ findings: [],
326
+ scannedUrls: 0
327
+ };
328
+
329
+ // Common secret patterns
330
+ const patterns = [
331
+ {
332
+ name: 'AWS Access Key',
333
+ regex: /AKIA[0-9A-Z]{16}/g,
334
+ severity: 'high',
335
+ description: 'AWS Access Key ID found'
336
+ },
337
+ {
338
+ name: 'AWS Secret Key',
339
+ regex: /aws(.{0,20})?['\"][0-9a-zA-Z\/+]{40}['\"]?/gi,
340
+ severity: 'high',
341
+ description: 'Possible AWS Secret Access Key'
342
+ },
343
+ {
344
+ name: 'GitHub Token',
345
+ regex: /gh[pousr]_[0-9a-zA-Z]{36}/g,
346
+ severity: 'high',
347
+ description: 'GitHub Personal Access Token'
348
+ },
349
+ {
350
+ name: 'Generic API Key',
351
+ regex: /api[_-]?key['\"]?\s*[:=]\s*['\"]?([0-9a-zA-Z\-_]{20,})['\"]?/gi,
352
+ severity: 'medium',
353
+ description: 'Generic API key pattern'
354
+ },
355
+ {
356
+ name: 'Slack Token',
357
+ regex: /xox[baprs]-[0-9a-zA-Z\-]+/g,
358
+ severity: 'high',
359
+ description: 'Slack token found'
360
+ },
361
+ {
362
+ name: 'Stripe API Key',
363
+ regex: /sk_live_[0-9a-zA-Z]{24,}/g,
364
+ severity: 'high',
365
+ description: 'Stripe live API key'
366
+ },
367
+ {
368
+ name: 'Private Key',
369
+ regex: /-----BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----/g,
370
+ severity: 'high',
371
+ description: 'Private key detected'
372
+ },
373
+ {
374
+ name: 'JWT Token',
375
+ regex: /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g,
376
+ severity: 'medium',
377
+ description: 'JWT token found'
378
+ },
379
+ {
380
+ name: 'Database URL',
381
+ regex: /(postgres|mysql|mongodb):\/\/[^\s]+/gi,
382
+ severity: 'medium',
383
+ description: 'Database connection string'
384
+ }
385
+ ];
386
+
387
+ // Scan URLs
388
+ for (const url of urls.slice(0, options.maxUrls || 20)) {
389
+ try {
390
+ const response = await this.fetchUrl(url);
391
+
392
+ if (response && response.body) {
393
+ findings.scannedUrls++;
394
+
395
+ // Test each pattern
396
+ for (const pattern of patterns) {
397
+ const matches = response.body.matchAll(pattern.regex);
398
+
399
+ for (const match of matches) {
400
+ findings.findings.push({
401
+ type: pattern.name,
402
+ description: pattern.description,
403
+ severity: pattern.severity,
404
+ location: url,
405
+ match: match[0].substring(0, 100), // Truncate long matches
406
+ context: this.extractContext(response.body, match.index, 50),
407
+ scanner: 'regex-patterns'
408
+ });
409
+ }
410
+ }
411
+ }
412
+ } catch (error) {
413
+ // Continue with next URL
414
+ }
415
+ }
416
+
417
+ return findings;
418
+ }
419
+
420
+ /**
421
+ * Fetch URL content
422
+ * @param {string} url - URL to fetch
423
+ * @returns {Promise<Object>} Response with body
424
+ */
425
+ async fetchUrl(url) {
426
+ try {
427
+ const { stdout, stderr } = await execAsync(
428
+ `curl -sL -m 10 "${url}"`,
429
+ {
430
+ timeout: 15000,
431
+ maxBuffer: 2 * 1024 * 1024 // 2MB max response
432
+ }
433
+ );
434
+
435
+ return {
436
+ body: stdout,
437
+ error: stderr
438
+ };
439
+ } catch (error) {
440
+ return null;
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Extract context around a match
446
+ * @param {string} text - Full text
447
+ * @param {number} index - Match index
448
+ * @param {number} contextLength - Characters before/after
449
+ * @returns {string} Context string
450
+ */
451
+ extractContext(text, index, contextLength) {
452
+ const start = Math.max(0, index - contextLength);
453
+ const end = Math.min(text.length, index + contextLength);
454
+
455
+ let context = text.substring(start, end);
456
+
457
+ if (start > 0) context = '...' + context;
458
+ if (end < text.length) context = context + '...';
459
+
460
+ return context.replace(/\s+/g, ' ').trim();
461
+ }
462
+
463
+ /**
464
+ * Deduplicate findings by match content
465
+ * @param {Array} findings - All findings
466
+ * @returns {Array} Deduplicated findings
467
+ */
468
+ deduplicateFindings(findings) {
469
+ const seen = new Set();
470
+ const unique = [];
471
+
472
+ for (const finding of findings) {
473
+ const key = `${finding.type}:${finding.match}`;
474
+
475
+ if (!seen.has(key)) {
476
+ seen.add(key);
477
+ unique.push(finding);
478
+ }
479
+ }
480
+
481
+ return unique;
482
+ }
483
+
484
+ /**
485
+ * Build summary statistics
486
+ * @param {Array} findings - All findings
487
+ * @returns {Object} Summary object
488
+ */
489
+ buildSummary(findings) {
490
+ const summary = {
491
+ total: findings.length,
492
+ highSeverity: 0,
493
+ mediumSeverity: 0,
494
+ lowSeverity: 0,
495
+ byType: {}
496
+ };
497
+
498
+ for (const finding of findings) {
499
+ // Count by severity
500
+ if (finding.severity === 'high') {
501
+ summary.highSeverity++;
502
+ } else if (finding.severity === 'medium') {
503
+ summary.mediumSeverity++;
504
+ } else {
505
+ summary.lowSeverity++;
506
+ }
507
+
508
+ // Count by type
509
+ summary.byType[finding.type] = (summary.byType[finding.type] || 0) + 1;
510
+ }
511
+
512
+ return summary;
513
+ }
514
+ }