s3db.js 13.6.0 → 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 (193) hide show
  1. package/README.md +139 -43
  2. package/dist/s3db.cjs +72425 -38970
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72177 -38764
  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 +94 -49
  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/opengraph-helper.js +116 -0
  34. package/src/plugins/api/concerns/route-context.js +601 -0
  35. package/src/plugins/api/concerns/state-machine.js +288 -0
  36. package/src/plugins/api/index.js +180 -41
  37. package/src/plugins/api/routes/auth-routes.js +198 -30
  38. package/src/plugins/api/routes/resource-routes.js +19 -4
  39. package/src/plugins/api/server/health-manager.class.js +163 -0
  40. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  41. package/src/plugins/api/server/router.class.js +472 -0
  42. package/src/plugins/api/server.js +280 -1303
  43. package/src/plugins/api/utils/custom-routes.js +17 -5
  44. package/src/plugins/api/utils/guards.js +76 -17
  45. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  46. package/src/plugins/api/utils/openapi-generator.js +7 -6
  47. package/src/plugins/api/utils/template-engine.js +77 -3
  48. package/src/plugins/audit.plugin.js +30 -8
  49. package/src/plugins/backup.plugin.js +110 -14
  50. package/src/plugins/cache/cache.class.js +22 -5
  51. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  52. package/src/plugins/cache/memory-cache.class.js +211 -57
  53. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  54. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  55. package/src/plugins/cache/redis-cache.class.js +552 -0
  56. package/src/plugins/cache/s3-cache.class.js +17 -8
  57. package/src/plugins/cache.plugin.js +176 -61
  58. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  59. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  60. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  62. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  66. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  67. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  68. package/src/plugins/cloud-inventory/index.js +29 -8
  69. package/src/plugins/cloud-inventory/registry.js +64 -42
  70. package/src/plugins/cloud-inventory.plugin.js +240 -138
  71. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  72. package/src/plugins/concerns/resource-names.js +100 -0
  73. package/src/plugins/consumers/index.js +10 -2
  74. package/src/plugins/consumers/sqs-consumer.js +12 -2
  75. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  76. package/src/plugins/cookie-farm.errors.js +73 -0
  77. package/src/plugins/cookie-farm.plugin.js +869 -0
  78. package/src/plugins/costs.plugin.js +7 -1
  79. package/src/plugins/eventual-consistency/analytics.js +94 -19
  80. package/src/plugins/eventual-consistency/config.js +15 -7
  81. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  82. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  83. package/src/plugins/eventual-consistency/helpers.js +39 -14
  84. package/src/plugins/eventual-consistency/install.js +21 -2
  85. package/src/plugins/eventual-consistency/utils.js +32 -10
  86. package/src/plugins/fulltext.plugin.js +38 -11
  87. package/src/plugins/geo.plugin.js +61 -9
  88. package/src/plugins/identity/concerns/config.js +61 -0
  89. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  90. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  91. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  92. package/src/plugins/identity/concerns/token-generator.js +29 -4
  93. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  94. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  95. package/src/plugins/identity/drivers/index.js +18 -0
  96. package/src/plugins/identity/drivers/password-driver.js +122 -0
  97. package/src/plugins/identity/email-service.js +17 -2
  98. package/src/plugins/identity/index.js +413 -69
  99. package/src/plugins/identity/oauth2-server.js +413 -30
  100. package/src/plugins/identity/oidc-discovery.js +16 -8
  101. package/src/plugins/identity/rsa-keys.js +115 -35
  102. package/src/plugins/identity/server.js +166 -45
  103. package/src/plugins/identity/session-manager.js +53 -7
  104. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  105. package/src/plugins/identity/ui/routes.js +363 -255
  106. package/src/plugins/importer/index.js +153 -20
  107. package/src/plugins/index.js +9 -2
  108. package/src/plugins/kubernetes-inventory/index.js +6 -0
  109. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  110. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  111. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  112. package/src/plugins/metrics.plugin.js +64 -16
  113. package/src/plugins/ml/base-model.class.js +25 -15
  114. package/src/plugins/ml/regression-model.class.js +1 -1
  115. package/src/plugins/ml.errors.js +57 -25
  116. package/src/plugins/ml.plugin.js +28 -4
  117. package/src/plugins/namespace.js +210 -0
  118. package/src/plugins/plugin.class.js +180 -8
  119. package/src/plugins/puppeteer/console-monitor.js +729 -0
  120. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  121. package/src/plugins/puppeteer/network-monitor.js +816 -0
  122. package/src/plugins/puppeteer/performance-manager.js +746 -0
  123. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  124. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  125. package/src/plugins/puppeteer.errors.js +81 -0
  126. package/src/plugins/puppeteer.plugin.js +1327 -0
  127. package/src/plugins/queue-consumer.plugin.js +69 -14
  128. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  129. package/src/plugins/recon/concerns/command-runner.js +148 -0
  130. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  131. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  132. package/src/plugins/recon/concerns/process-manager.js +338 -0
  133. package/src/plugins/recon/concerns/report-generator.js +478 -0
  134. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  135. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  136. package/src/plugins/recon/config/defaults.js +321 -0
  137. package/src/plugins/recon/config/resources.js +370 -0
  138. package/src/plugins/recon/index.js +778 -0
  139. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  140. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  141. package/src/plugins/recon/managers/storage-manager.js +745 -0
  142. package/src/plugins/recon/managers/target-manager.js +274 -0
  143. package/src/plugins/recon/stages/asn-stage.js +314 -0
  144. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  145. package/src/plugins/recon/stages/dns-stage.js +107 -0
  146. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  147. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  148. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  149. package/src/plugins/recon/stages/http-stage.js +89 -0
  150. package/src/plugins/recon/stages/latency-stage.js +148 -0
  151. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  152. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  153. package/src/plugins/recon/stages/ports-stage.js +169 -0
  154. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  155. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  156. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  157. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  158. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  159. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  160. package/src/plugins/recon/stages/whois-stage.js +349 -0
  161. package/src/plugins/recon.plugin.js +75 -0
  162. package/src/plugins/recon.plugin.js.backup +2635 -0
  163. package/src/plugins/relation.errors.js +87 -14
  164. package/src/plugins/replicator.plugin.js +514 -137
  165. package/src/plugins/replicators/base-replicator.class.js +89 -1
  166. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  167. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  168. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  169. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  170. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  171. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  172. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  173. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  174. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  175. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  176. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  177. package/src/plugins/s3-queue.plugin.js +464 -65
  178. package/src/plugins/scheduler.plugin.js +20 -6
  179. package/src/plugins/state-machine.plugin.js +40 -9
  180. package/src/plugins/tfstate/README.md +126 -126
  181. package/src/plugins/tfstate/base-driver.js +28 -4
  182. package/src/plugins/tfstate/errors.js +65 -10
  183. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  184. package/src/plugins/tfstate/index.js +163 -90
  185. package/src/plugins/tfstate/s3-driver.js +64 -6
  186. package/src/plugins/ttl.plugin.js +72 -17
  187. package/src/plugins/vector/distances.js +18 -12
  188. package/src/plugins/vector/kmeans.js +26 -4
  189. package/src/resource.class.js +115 -19
  190. package/src/testing/factory.class.js +20 -3
  191. package/src/testing/seeder.class.js +7 -1
  192. package/src/clients/memory-client.md +0 -917
  193. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -0,0 +1,440 @@
1
+ /**
2
+ * GoogleDorksStage
3
+ *
4
+ * Search engine reconnaissance using Google Dorks
5
+ *
6
+ * Discovers:
7
+ * - GitHub repositories
8
+ * - Pastebin leaks
9
+ * - LinkedIn employees
10
+ * - Exposed files (PDF, DOC, XLS)
11
+ * - Subdomains
12
+ * - Login pages
13
+ * - Exposed configs
14
+ *
15
+ * Uses 100% free web scraping (no API key required)
16
+ */
17
+
18
+ export class GoogleDorksStage {
19
+ constructor(plugin) {
20
+ this.plugin = plugin;
21
+ this.commandRunner = plugin.commandRunner;
22
+ this.config = plugin.config;
23
+ }
24
+
25
+ /**
26
+ * Execute Google Dorks search
27
+ * @param {Object} target - Target object with host property
28
+ * @param {Object} options - Google Dorks options
29
+ * @returns {Promise<Object>} Search results
30
+ */
31
+ async execute(target, options = {}) {
32
+ const domain = this.extractBaseDomain(target.host);
33
+ const companyName = this.extractCompanyName(domain);
34
+
35
+ const result = {
36
+ status: 'ok',
37
+ domain,
38
+ companyName,
39
+ categories: {
40
+ github: null, // GitHub repos/code
41
+ pastebin: null, // Pastebin leaks
42
+ linkedin: null, // LinkedIn employees
43
+ documents: null, // Exposed docs (PDF, DOC, XLS)
44
+ subdomains: null, // Subdomains via Google
45
+ loginPages: null, // Login/admin pages
46
+ configs: null, // Config files
47
+ errors: null // Error pages revealing info
48
+ },
49
+ summary: {
50
+ totalResults: 0,
51
+ totalCategories: 0
52
+ }
53
+ };
54
+
55
+ // Track individual category results for artifact persistence
56
+ const individual = {};
57
+
58
+ const enabledCategories = options.categories || [
59
+ 'github', 'pastebin', 'linkedin', 'documents',
60
+ 'subdomains', 'loginPages', 'configs', 'errors'
61
+ ];
62
+
63
+ // Execute each category
64
+ for (const category of enabledCategories) {
65
+ try {
66
+ let categoryData = null;
67
+
68
+ switch (category) {
69
+ case 'github':
70
+ categoryData = await this.searchGitHub(domain, companyName, options);
71
+ break;
72
+ case 'pastebin':
73
+ categoryData = await this.searchPastebin(domain, companyName, options);
74
+ break;
75
+ case 'linkedin':
76
+ categoryData = await this.searchLinkedIn(domain, companyName, options);
77
+ break;
78
+ case 'documents':
79
+ categoryData = await this.searchDocuments(domain, options);
80
+ break;
81
+ case 'subdomains':
82
+ categoryData = await this.searchSubdomains(domain, options);
83
+ break;
84
+ case 'loginPages':
85
+ categoryData = await this.searchLoginPages(domain, options);
86
+ break;
87
+ case 'configs':
88
+ categoryData = await this.searchConfigs(domain, options);
89
+ break;
90
+ case 'errors':
91
+ categoryData = await this.searchErrors(domain, options);
92
+ break;
93
+ }
94
+
95
+ result.categories[category] = categoryData;
96
+ individual[category] = categoryData; // Store in individual too
97
+
98
+ } catch (error) {
99
+ result.categories[category] = {
100
+ status: 'error',
101
+ message: error.message
102
+ };
103
+ individual[category] = {
104
+ status: 'error',
105
+ message: error.message
106
+ };
107
+ }
108
+ }
109
+
110
+ // Calculate summary
111
+ let totalResults = 0;
112
+ let totalCategories = 0;
113
+
114
+ for (const [category, data] of Object.entries(result.categories)) {
115
+ if (data && data.status === 'ok') {
116
+ totalCategories++;
117
+ if (data.results) {
118
+ totalResults += data.results.length;
119
+ }
120
+ }
121
+ }
122
+
123
+ result.summary.totalResults = totalResults;
124
+ result.summary.totalCategories = totalCategories;
125
+
126
+ return {
127
+ _individual: individual,
128
+ _aggregated: result,
129
+ ...result // Root level for compatibility
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Search GitHub for company repos/code
135
+ * Dork: site:github.com "companyname"
136
+ */
137
+ async searchGitHub(domain, companyName, options = {}) {
138
+ const queries = [
139
+ `site:github.com "${companyName}"`,
140
+ `site:github.com "${domain}"`,
141
+ `site:github.com "api" "${domain}"`,
142
+ `site:github.com "config" "${domain}"`
143
+ ];
144
+
145
+ const results = [];
146
+
147
+ for (const query of queries) {
148
+ const urls = await this.performGoogleSearch(query, options);
149
+ results.push(...urls.map(url => ({ url, query })));
150
+
151
+ // Rate limit
152
+ await this.sleep(2000);
153
+ }
154
+
155
+ return {
156
+ status: 'ok',
157
+ results: this.deduplicateResults(results),
158
+ count: results.length
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Search Pastebin for leaks
164
+ * Dork: site:pastebin.com "domain.com"
165
+ */
166
+ async searchPastebin(domain, companyName, options = {}) {
167
+ const queries = [
168
+ `site:pastebin.com "${domain}"`,
169
+ `site:pastebin.com "${companyName}"`,
170
+ `site:paste2.org "${domain}"`,
171
+ `site:slexy.org "${domain}"`
172
+ ];
173
+
174
+ const results = [];
175
+
176
+ for (const query of queries) {
177
+ const urls = await this.performGoogleSearch(query, options);
178
+ results.push(...urls.map(url => ({ url, query })));
179
+
180
+ await this.sleep(2000);
181
+ }
182
+
183
+ return {
184
+ status: 'ok',
185
+ results: this.deduplicateResults(results),
186
+ count: results.length
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Search LinkedIn for employees
192
+ * Dork: site:linkedin.com/in "companyname"
193
+ */
194
+ async searchLinkedIn(domain, companyName, options = {}) {
195
+ const queries = [
196
+ `site:linkedin.com/in "${companyName}"`,
197
+ `site:linkedin.com/company/${companyName.toLowerCase().replace(/\s+/g, '-')}`
198
+ ];
199
+
200
+ const results = [];
201
+
202
+ for (const query of queries) {
203
+ const urls = await this.performGoogleSearch(query, options);
204
+ results.push(...urls.map(url => ({ url, query })));
205
+
206
+ await this.sleep(2000);
207
+ }
208
+
209
+ return {
210
+ status: 'ok',
211
+ results: this.deduplicateResults(results),
212
+ count: results.length
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Search for exposed documents
218
+ * Dork: site:domain.com filetype:pdf|doc|xls
219
+ */
220
+ async searchDocuments(domain, options = {}) {
221
+ const filetypes = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
222
+ const results = [];
223
+
224
+ for (const filetype of filetypes) {
225
+ const query = `site:${domain} filetype:${filetype}`;
226
+ const urls = await this.performGoogleSearch(query, options);
227
+ results.push(...urls.map(url => ({ url, filetype, query })));
228
+
229
+ await this.sleep(2000);
230
+ }
231
+
232
+ return {
233
+ status: 'ok',
234
+ results: this.deduplicateResults(results),
235
+ count: results.length
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Search for subdomains
241
+ * Dork: site:*.domain.com
242
+ */
243
+ async searchSubdomains(domain, options = {}) {
244
+ const query = `site:*.${domain}`;
245
+ const urls = await this.performGoogleSearch(query, options);
246
+
247
+ const subdomains = urls.map(url => {
248
+ const match = url.match(/https?:\/\/([^\/]+)/);
249
+ return match ? match[1] : null;
250
+ }).filter(Boolean);
251
+
252
+ return {
253
+ status: 'ok',
254
+ results: [...new Set(subdomains)].map(subdomain => ({ subdomain })),
255
+ count: subdomains.length
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Search for login/admin pages
261
+ * Dork: site:domain.com inurl:login|admin|dashboard
262
+ */
263
+ async searchLoginPages(domain, options = {}) {
264
+ const queries = [
265
+ `site:${domain} inurl:login`,
266
+ `site:${domain} inurl:admin`,
267
+ `site:${domain} inurl:dashboard`,
268
+ `site:${domain} inurl:portal`,
269
+ `site:${domain} intitle:"login" OR intitle:"sign in"`
270
+ ];
271
+
272
+ const results = [];
273
+
274
+ for (const query of queries) {
275
+ const urls = await this.performGoogleSearch(query, options);
276
+ results.push(...urls.map(url => ({ url, query })));
277
+
278
+ await this.sleep(2000);
279
+ }
280
+
281
+ return {
282
+ status: 'ok',
283
+ results: this.deduplicateResults(results),
284
+ count: results.length
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Search for config files
290
+ * Dork: site:domain.com ext:env|config|ini|yml
291
+ */
292
+ async searchConfigs(domain, options = {}) {
293
+ const queries = [
294
+ `site:${domain} ext:env`,
295
+ `site:${domain} ext:config`,
296
+ `site:${domain} ext:ini`,
297
+ `site:${domain} ext:yml`,
298
+ `site:${domain} ext:yaml`,
299
+ `site:${domain} inurl:config`,
300
+ `site:${domain} intitle:"index of" "config"`
301
+ ];
302
+
303
+ const results = [];
304
+
305
+ for (const query of queries) {
306
+ const urls = await this.performGoogleSearch(query, options);
307
+ results.push(...urls.map(url => ({ url, query })));
308
+
309
+ await this.sleep(2000);
310
+ }
311
+
312
+ return {
313
+ status: 'ok',
314
+ results: this.deduplicateResults(results),
315
+ count: results.length
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Search for error pages (can reveal paths, versions, etc.)
321
+ * Dork: site:domain.com intext:"error" OR intext:"warning"
322
+ */
323
+ async searchErrors(domain, options = {}) {
324
+ const queries = [
325
+ `site:${domain} intext:"error" OR intext:"exception"`,
326
+ `site:${domain} intext:"stack trace"`,
327
+ `site:${domain} intext:"warning" intitle:"error"`,
328
+ `site:${domain} intext:"mysql" intext:"error"`,
329
+ `site:${domain} intext:"fatal error"`
330
+ ];
331
+
332
+ const results = [];
333
+
334
+ for (const query of queries) {
335
+ const urls = await this.performGoogleSearch(query, options);
336
+ results.push(...urls.map(url => ({ url, query })));
337
+
338
+ await this.sleep(2000);
339
+ }
340
+
341
+ return {
342
+ status: 'ok',
343
+ results: this.deduplicateResults(results),
344
+ count: results.length
345
+ };
346
+ }
347
+
348
+ /**
349
+ * Perform actual Google search
350
+ * Note: Google blocks automated queries, so this uses DuckDuckGo as fallback
351
+ */
352
+ async performGoogleSearch(query, options = {}) {
353
+ const maxResults = options.maxResults || 10;
354
+ const results = [];
355
+
356
+ try {
357
+ // Try DuckDuckGo HTML search (more permissive than Google)
358
+ const encodedQuery = encodeURIComponent(query);
359
+ const url = `https://html.duckduckgo.com/html/?q=${encodedQuery}`;
360
+
361
+ const response = await fetch(url, {
362
+ headers: {
363
+ 'User-Agent': this.config.curl?.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
364
+ },
365
+ signal: AbortSignal.timeout ? AbortSignal.timeout(15000) : undefined
366
+ });
367
+
368
+ if (!response.ok) {
369
+ return results;
370
+ }
371
+
372
+ const html = await response.text();
373
+
374
+ // Parse DuckDuckGo results
375
+ // Format: <a class="result__a" href="//duckduckgo.com/l/?uddg=URL&rut=...">
376
+ const urlMatches = html.matchAll(/class="result__url"[^>]*>([^<]+)</g);
377
+
378
+ for (const match of urlMatches) {
379
+ let resultUrl = match[1].trim();
380
+
381
+ // Clean up URL
382
+ if (resultUrl.startsWith('//')) {
383
+ resultUrl = 'https:' + resultUrl;
384
+ }
385
+
386
+ results.push(resultUrl);
387
+
388
+ if (results.length >= maxResults) {
389
+ break;
390
+ }
391
+ }
392
+
393
+ } catch (error) {
394
+ // Silently fail
395
+ }
396
+
397
+ return results;
398
+ }
399
+
400
+ /**
401
+ * Deduplicate results by URL
402
+ */
403
+ deduplicateResults(results) {
404
+ const seen = new Set();
405
+ return results.filter(item => {
406
+ const url = item.url || item.subdomain;
407
+ if (seen.has(url)) {
408
+ return false;
409
+ }
410
+ seen.add(url);
411
+ return true;
412
+ });
413
+ }
414
+
415
+ /**
416
+ * Extract base domain from host
417
+ */
418
+ extractBaseDomain(host) {
419
+ const parts = host.split('.');
420
+ if (parts.length > 2) {
421
+ return parts.slice(-2).join('.');
422
+ }
423
+ return host;
424
+ }
425
+
426
+ /**
427
+ * Extract company name from domain
428
+ */
429
+ extractCompanyName(domain) {
430
+ const parts = domain.split('.');
431
+ return parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
432
+ }
433
+
434
+ /**
435
+ * Sleep helper
436
+ */
437
+ async sleep(ms) {
438
+ return new Promise(resolve => setTimeout(resolve, ms));
439
+ }
440
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * HttpStage
3
+ *
4
+ * HTTP header analysis:
5
+ * - Server identification
6
+ * - Security headers
7
+ * - Technology fingerprinting from headers
8
+ */
9
+
10
+ export class HttpStage {
11
+ constructor(plugin) {
12
+ this.plugin = plugin;
13
+ this.commandRunner = plugin.commandRunner;
14
+ this.config = plugin.config;
15
+ }
16
+
17
+ async execute(target) {
18
+ const url = this._buildUrl(target);
19
+ const args = [
20
+ '-I',
21
+ '-sS',
22
+ '-L',
23
+ '--max-time',
24
+ String(Math.ceil(this.config.curl.timeout / 1000)),
25
+ '--user-agent',
26
+ this.config.curl.userAgent,
27
+ url
28
+ ];
29
+
30
+ const result = await this.commandRunner.run('curl', args, {
31
+ timeout: this.config.curl.timeout
32
+ });
33
+
34
+ if (!result.ok) {
35
+ return {
36
+ status: 'unavailable',
37
+ message: result.error?.message || 'curl failed',
38
+ stderr: result.stderr
39
+ };
40
+ }
41
+
42
+ const headers = this._parseCurlHeaders(result.stdout);
43
+
44
+ return {
45
+ status: 'ok',
46
+ url,
47
+ headers,
48
+ raw: this.config.storage.persistRawOutput ? this._truncateOutput(result.stdout) : undefined
49
+ };
50
+ }
51
+
52
+ _buildUrl(target) {
53
+ const protocol = target.protocol || 'https';
54
+ const port = target.port && target.port !== this._defaultPortForProtocol(protocol)
55
+ ? `:${target.port}`
56
+ : '';
57
+ const path = target.path || '';
58
+ return `${protocol}://${target.host}${port}${path}`;
59
+ }
60
+
61
+ _defaultPortForProtocol(protocol) {
62
+ switch (protocol) {
63
+ case 'http':
64
+ return 80;
65
+ case 'https':
66
+ return 443;
67
+ default:
68
+ return null;
69
+ }
70
+ }
71
+
72
+ _parseCurlHeaders(raw) {
73
+ const lines = raw.split(/\r?\n/).filter(Boolean);
74
+ const headers = {};
75
+ for (const line of lines) {
76
+ if (!line.includes(':')) continue;
77
+ const [key, ...rest] = line.split(':');
78
+ headers[key.trim().toLowerCase()] = rest.join(':').trim();
79
+ }
80
+ return headers;
81
+ }
82
+
83
+ _truncateOutput(text, maxLength = 10000) {
84
+ if (!text || text.length <= maxLength) {
85
+ return text;
86
+ }
87
+ return text.substring(0, maxLength) + '\n... (truncated)';
88
+ }
89
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * LatencyStage
3
+ *
4
+ * Network latency measurement:
5
+ * - Ping (ICMP echo) with metrics
6
+ * - Traceroute (mtr or traceroute)
7
+ * - Hop analysis
8
+ */
9
+
10
+ export class LatencyStage {
11
+ constructor(plugin) {
12
+ this.plugin = plugin;
13
+ this.commandRunner = plugin.commandRunner;
14
+ this.config = plugin.config;
15
+ }
16
+
17
+ async execute(target, options = {}) {
18
+ const config = { ...this.config.latency, ...options };
19
+ const results = {};
20
+
21
+ // Execute ping if enabled
22
+ if (config.ping !== false) {
23
+ results.ping = await this.executePing(target);
24
+ }
25
+
26
+ // Execute traceroute if enabled
27
+ if (config.traceroute !== false) {
28
+ results.traceroute = await this.executeTraceroute(target);
29
+ }
30
+
31
+ return results;
32
+ }
33
+
34
+ async executePing(target) {
35
+ const args = ['-n', '-c', String(this.config.ping.count), target.host];
36
+ const run = await this.commandRunner.run('ping', args, {
37
+ timeout: this.config.ping.timeout
38
+ });
39
+
40
+ if (!run.ok) {
41
+ return {
42
+ status: 'unavailable',
43
+ message: run.error?.message || 'Ping failed',
44
+ stderr: run.stderr
45
+ };
46
+ }
47
+
48
+ const metrics = this._parsePingOutput(run.stdout);
49
+
50
+ return {
51
+ status: 'ok',
52
+ stdout: run.stdout,
53
+ metrics
54
+ };
55
+ }
56
+
57
+ async executeTraceroute(target) {
58
+ if (await this.commandRunner.isAvailable('mtr')) {
59
+ const args = [
60
+ '--report',
61
+ '--report-cycles',
62
+ String(this.config.traceroute.cycles),
63
+ '--json',
64
+ target.host
65
+ ];
66
+ const mtrResult = await this.commandRunner.run('mtr', args, {
67
+ timeout: this.config.traceroute.timeout,
68
+ maxBuffer: 4 * 1024 * 1024
69
+ });
70
+
71
+ if (mtrResult.ok) {
72
+ try {
73
+ const parsed = JSON.parse(mtrResult.stdout);
74
+ return {
75
+ status: 'ok',
76
+ type: 'mtr',
77
+ report: parsed
78
+ };
79
+ } catch (error) {
80
+ // Fallback to plain text interpretation
81
+ return {
82
+ status: 'ok',
83
+ type: 'mtr',
84
+ stdout: mtrResult.stdout
85
+ };
86
+ }
87
+ }
88
+ }
89
+
90
+ if (await this.commandRunner.isAvailable('traceroute')) {
91
+ const tracerouteResult = await this.commandRunner.run(
92
+ 'traceroute',
93
+ ['-n', target.host],
94
+ {
95
+ timeout: this.config.traceroute.timeout
96
+ }
97
+ );
98
+
99
+ if (tracerouteResult.ok) {
100
+ return {
101
+ status: 'ok',
102
+ type: 'traceroute',
103
+ stdout: tracerouteResult.stdout
104
+ };
105
+ }
106
+ }
107
+
108
+ return {
109
+ status: 'unavailable',
110
+ message: 'Neither mtr nor traceroute is available'
111
+ };
112
+ }
113
+
114
+ _parsePingOutput(text) {
115
+ const metrics = {
116
+ packetsTransmitted: null,
117
+ packetsReceived: null,
118
+ packetLoss: null,
119
+ min: null,
120
+ avg: null,
121
+ max: null,
122
+ stdDev: null
123
+ };
124
+
125
+ const packetLine = text.split('\n').find((line) => line.includes('packets transmitted'));
126
+ if (packetLine) {
127
+ const match = packetLine.match(/(\d+)\s+packets transmitted,\s+(\d+)\s+received,.*?([\d.]+)% packet loss/);
128
+ if (match) {
129
+ metrics.packetsTransmitted = Number(match[1]);
130
+ metrics.packetsReceived = Number(match[2]);
131
+ metrics.packetLoss = Number(match[3]);
132
+ }
133
+ }
134
+
135
+ const statsLine = text.split('\n').find((line) => line.includes('min/avg/max'));
136
+ if (statsLine) {
137
+ const match = statsLine.match(/=\s*([\d.]+)\/([\d.]+)\/([\d.]+)\/([\d.]+)/);
138
+ if (match) {
139
+ metrics.min = Number(match[1]);
140
+ metrics.avg = Number(match[2]);
141
+ metrics.max = Number(match[3]);
142
+ metrics.stdDev = Number(match[4]);
143
+ }
144
+ }
145
+
146
+ return metrics;
147
+ }
148
+ }