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,295 @@
1
+ /**
2
+ * SubdomainsStage
3
+ *
4
+ * Subdomain enumeration with multiple tools:
5
+ * - amass (OWASP, comprehensive)
6
+ * - subfinder (fast, API-based)
7
+ * - assetfinder (passive)
8
+ * - crt.sh (certificate transparency logs)
9
+ */
10
+
11
+ export class SubdomainsStage {
12
+ constructor(plugin) {
13
+ this.plugin = plugin;
14
+ this.commandRunner = plugin.commandRunner;
15
+ this.config = plugin.config;
16
+ }
17
+
18
+ async execute(target, featureConfig = {}) {
19
+ const aggregated = new Set();
20
+ const sources = {};
21
+
22
+ const executeCliCollector = async (name, command, args, parser) => {
23
+ if (!featureConfig[name]) {
24
+ return;
25
+ }
26
+ const run = await this.commandRunner.run(command, args, { timeout: 60000, maxBuffer: 8 * 1024 * 1024 });
27
+ if (!run.ok) {
28
+ sources[name] = {
29
+ status: run.error?.code === 'ENOENT' ? 'unavailable' : 'error',
30
+ message: run.error?.message || `${command} failed`,
31
+ stderr: run.stderr
32
+ };
33
+ return;
34
+ }
35
+ const items = parser(run.stdout, run.stderr);
36
+ items.forEach((item) => aggregated.add(item));
37
+ sources[name] = {
38
+ status: 'ok',
39
+ count: items.length,
40
+ sample: items.slice(0, 10)
41
+ };
42
+ if (this.config.storage.persistRawOutput) {
43
+ sources[name].raw = this._truncateOutput(run.stdout);
44
+ }
45
+ };
46
+
47
+ await executeCliCollector('amass', 'amass', ['enum', '-d', target.host, '-o', '-'], (stdout) =>
48
+ stdout
49
+ .split(/\r?\n/)
50
+ .map((line) => line.trim())
51
+ .filter(Boolean)
52
+ );
53
+
54
+ await executeCliCollector('subfinder', 'subfinder', ['-d', target.host, '-silent'], (stdout) =>
55
+ stdout
56
+ .split(/\r?\n/)
57
+ .map((line) => line.trim())
58
+ .filter(Boolean)
59
+ );
60
+
61
+ await executeCliCollector('assetfinder', 'assetfinder', ['--subs-only', target.host], (stdout) =>
62
+ stdout
63
+ .split(/\r?\n/)
64
+ .map((line) => line.trim())
65
+ .filter(Boolean)
66
+ );
67
+
68
+ if (featureConfig.crtsh) {
69
+ try {
70
+ const response = await fetch(`https://crt.sh/?q=%25.${target.host}&output=json`, {
71
+ headers: { 'User-Agent': this.config.curl.userAgent },
72
+ signal: AbortSignal.timeout ? AbortSignal.timeout(10000) : undefined
73
+ });
74
+ if (response.ok) {
75
+ const data = await response.json();
76
+ const entries = Array.isArray(data) ? data : [];
77
+ const hostnames = entries
78
+ .map((entry) => entry.name_value)
79
+ .filter(Boolean)
80
+ .flatMap((value) => value.split('\n'))
81
+ .map((value) => value.trim())
82
+ .filter(Boolean);
83
+ hostnames.forEach((hostname) => aggregated.add(hostname));
84
+ sources.crtsh = {
85
+ status: 'ok',
86
+ count: hostnames.length,
87
+ sample: hostnames.slice(0, 10)
88
+ };
89
+ } else {
90
+ sources.crtsh = {
91
+ status: 'error',
92
+ message: `crt.sh responded with status ${response.status}`
93
+ };
94
+ }
95
+ } catch (error) {
96
+ sources.crtsh = {
97
+ status: 'error',
98
+ message: error?.message || 'crt.sh lookup failed'
99
+ };
100
+ }
101
+ }
102
+
103
+ const list = Array.from(aggregated).sort();
104
+
105
+ // Check for subdomain takeover vulnerabilities
106
+ let takeoverResults = null;
107
+ if (featureConfig.checkTakeover && list.length > 0) {
108
+ takeoverResults = await this.checkSubdomainTakeover(list, featureConfig);
109
+ }
110
+
111
+ return {
112
+ _individual: sources,
113
+ _aggregated: {
114
+ status: list.length > 0 ? 'ok' : 'empty',
115
+ total: list.length,
116
+ list,
117
+ sources,
118
+ takeover: takeoverResults
119
+ },
120
+ status: list.length > 0 ? 'ok' : 'empty',
121
+ total: list.length,
122
+ list,
123
+ sources,
124
+ takeover: takeoverResults
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Check for subdomain takeover vulnerabilities
130
+ * @param {Array<string>} subdomains - List of subdomains
131
+ * @param {Object} options - Takeover check options
132
+ * @returns {Promise<Object>} Takeover check results
133
+ */
134
+ async checkSubdomainTakeover(subdomains, options = {}) {
135
+ const results = {
136
+ status: 'ok',
137
+ vulnerable: [],
138
+ checked: 0,
139
+ errors: []
140
+ };
141
+
142
+ // Known fingerprints for subdomain takeover
143
+ const takeoverFingerprints = {
144
+ 'github': {
145
+ cname: 'github.io',
146
+ response: ['There isn\'t a GitHub Pages site here', 'For root URLs'],
147
+ severity: 'high'
148
+ },
149
+ 'heroku': {
150
+ cname: 'herokuapp.com',
151
+ response: ['No such app', 'There\'s nothing here'],
152
+ severity: 'high'
153
+ },
154
+ 'aws-s3': {
155
+ cname: 's3.amazonaws.com',
156
+ response: ['NoSuchBucket', 'The specified bucket does not exist'],
157
+ severity: 'high'
158
+ },
159
+ 'aws-cloudfront': {
160
+ cname: 'cloudfront.net',
161
+ response: ['The request could not be satisfied', 'Bad request'],
162
+ severity: 'medium'
163
+ },
164
+ 'azure': {
165
+ cname: 'azurewebsites.net',
166
+ response: ['404 Web Site not found', 'Error 404'],
167
+ severity: 'high'
168
+ },
169
+ 'bitbucket': {
170
+ cname: 'bitbucket.io',
171
+ response: ['Repository not found'],
172
+ severity: 'high'
173
+ },
174
+ 'fastly': {
175
+ cname: 'fastly.net',
176
+ response: ['Fastly error: unknown domain'],
177
+ severity: 'medium'
178
+ },
179
+ 'shopify': {
180
+ cname: 'myshopify.com',
181
+ response: ['Sorry, this shop is currently unavailable'],
182
+ severity: 'high'
183
+ }
184
+ };
185
+
186
+ const maxSubdomains = options.maxSubdomains || 50;
187
+ const subdomainsToCheck = subdomains.slice(0, maxSubdomains);
188
+
189
+ for (const subdomain of subdomainsToCheck) {
190
+ try {
191
+ results.checked++;
192
+
193
+ // Check CNAME record
194
+ const cname = await this.resolveCNAME(subdomain);
195
+
196
+ if (cname) {
197
+ // Check if CNAME matches known vulnerable patterns
198
+ for (const [provider, fingerprint] of Object.entries(takeoverFingerprints)) {
199
+ if (cname.toLowerCase().includes(fingerprint.cname)) {
200
+ // Fetch the subdomain to check for error responses
201
+ const httpCheck = await this.checkHttpResponse(subdomain);
202
+
203
+ if (httpCheck && httpCheck.status >= 400) {
204
+ // Check if response contains takeover indicators
205
+ const isVulnerable = fingerprint.response.some(indicator =>
206
+ httpCheck.body?.toLowerCase().includes(indicator.toLowerCase())
207
+ );
208
+
209
+ if (isVulnerable) {
210
+ results.vulnerable.push({
211
+ subdomain,
212
+ provider,
213
+ cname,
214
+ severity: fingerprint.severity,
215
+ evidence: `CNAME points to ${cname} but returns ${httpCheck.status}`,
216
+ status: httpCheck.status,
217
+ recommendation: `Claim the ${provider} resource or remove the DNS record`
218
+ });
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+ } catch (error) {
225
+ results.errors.push({
226
+ subdomain,
227
+ error: error.message
228
+ });
229
+ }
230
+ }
231
+
232
+ if (results.vulnerable.length > 0) {
233
+ results.status = 'vulnerable';
234
+ }
235
+
236
+ return results;
237
+ }
238
+
239
+ /**
240
+ * Resolve CNAME record for a domain
241
+ * @param {string} domain - Domain to resolve
242
+ * @returns {Promise<string|null>} CNAME record or null
243
+ */
244
+ async resolveCNAME(domain) {
245
+ const run = await this.commandRunner.run('dig', ['+short', 'CNAME', domain], {
246
+ timeout: 5000
247
+ });
248
+
249
+ if (run.ok && run.stdout) {
250
+ const cname = run.stdout.trim().replace(/\.$/, ''); // Remove trailing dot
251
+ return cname || null;
252
+ }
253
+
254
+ return null;
255
+ }
256
+
257
+ /**
258
+ * Check HTTP response for a subdomain
259
+ * @param {string} subdomain - Subdomain to check
260
+ * @returns {Promise<Object|null>} HTTP response details
261
+ */
262
+ async checkHttpResponse(subdomain) {
263
+ try {
264
+ const run = await this.commandRunner.run('curl', [
265
+ '-sL',
266
+ '-w', '%{http_code}',
267
+ '-m', '10',
268
+ `https://${subdomain}`
269
+ ], {
270
+ timeout: 15000,
271
+ maxBuffer: 1024 * 1024 // 1MB max
272
+ });
273
+
274
+ if (run.ok) {
275
+ const output = run.stdout;
276
+ const statusMatch = output.match(/(\d{3})$/);
277
+ const status = statusMatch ? parseInt(statusMatch[1]) : 0;
278
+ const body = output.replace(/\d{3}$/, '');
279
+
280
+ return { status, body };
281
+ }
282
+ } catch (error) {
283
+ // Ignore errors, subdomain might not be accessible
284
+ }
285
+
286
+ return null;
287
+ }
288
+
289
+ _truncateOutput(text, maxLength = 10000) {
290
+ if (!text || text.length <= maxLength) {
291
+ return text;
292
+ }
293
+ return text.substring(0, maxLength) + '\n... (truncated)';
294
+ }
295
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * TlsAuditStage
3
+ *
4
+ * TLS/SSL security auditing:
5
+ * - openssl (basic TLS info)
6
+ * - sslyze (comprehensive TLS scanner)
7
+ * - testssl.sh (detailed SSL/TLS testing)
8
+ * - sslscan (fast SSL/TLS scanner)
9
+ */
10
+
11
+ export class TlsAuditStage {
12
+ constructor(plugin) {
13
+ this.plugin = plugin;
14
+ this.commandRunner = plugin.commandRunner;
15
+ this.config = plugin.config;
16
+ }
17
+
18
+ async execute(target, featureConfig = {}) {
19
+ const tools = {};
20
+ const port = target.port || 443;
21
+
22
+ const executeAudit = async (name, command, args) => {
23
+ const run = await this.commandRunner.run(command, args, {
24
+ timeout: featureConfig.timeout ?? 20000,
25
+ maxBuffer: 4 * 1024 * 1024
26
+ });
27
+ if (!run.ok) {
28
+ tools[name] = {
29
+ status: run.error?.code === 'ENOENT' ? 'unavailable' : 'error',
30
+ message: run.error?.message || `${command} failed`,
31
+ stderr: run.stderr
32
+ };
33
+ return;
34
+ }
35
+ tools[name] = {
36
+ status: 'ok'
37
+ };
38
+ if (this.config.storage.persistRawOutput) {
39
+ tools[name].raw = this._truncateOutput(run.stdout);
40
+ }
41
+ };
42
+
43
+ if (featureConfig.openssl) {
44
+ await executeAudit('openssl', 'openssl', ['s_client', '-servername', target.host, '-connect', `${target.host}:${port}`, '-brief']);
45
+ }
46
+
47
+ if (featureConfig.sslyze) {
48
+ await executeAudit('sslyze', 'sslyze', [target.host]);
49
+ }
50
+
51
+ if (featureConfig.testssl) {
52
+ await executeAudit('testssl', 'testssl.sh', ['--quiet', `${target.host}:${port}`]);
53
+ }
54
+
55
+ if (featureConfig.sslscan) {
56
+ await executeAudit('sslscan', 'sslscan', [`${target.host}:${port}`]);
57
+ }
58
+
59
+ if (Object.keys(tools).length === 0) {
60
+ return { status: 'skipped' };
61
+ }
62
+
63
+ return {
64
+ _individual: tools,
65
+ _aggregated: {
66
+ status: Object.values(tools).some((tool) => tool.status === 'ok') ? 'ok' : 'empty',
67
+ tools
68
+ },
69
+ status: Object.values(tools).some((tool) => tool.status === 'ok') ? 'ok' : 'empty',
70
+ tools
71
+ };
72
+ }
73
+
74
+ _truncateOutput(text, maxLength = 10000) {
75
+ if (!text || text.length <= maxLength) return text;
76
+ return text.substring(0, maxLength) + '\n... (truncated)';
77
+ }
78
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * VulnerabilityStage
3
+ *
4
+ * Vulnerability scanning:
5
+ * - nikto (web server scanner)
6
+ * - wpscan (WordPress scanner)
7
+ * - droopescan (Drupal/Joomla scanner)
8
+ */
9
+
10
+ export class VulnerabilityStage {
11
+ constructor(plugin) {
12
+ this.plugin = plugin;
13
+ this.commandRunner = plugin.commandRunner;
14
+ this.config = plugin.config;
15
+ }
16
+
17
+ async execute(target, featureConfig = {}) {
18
+ const tools = {};
19
+
20
+ const executeScanner = async (name, command, args) => {
21
+ const run = await this.commandRunner.run(command, args, {
22
+ timeout: featureConfig.timeout ?? 60000,
23
+ maxBuffer: 8 * 1024 * 1024
24
+ });
25
+ if (!run.ok) {
26
+ tools[name] = {
27
+ status: run.error?.code === 'ENOENT' ? 'unavailable' : 'error',
28
+ message: run.error?.message || `${command} failed`,
29
+ stderr: run.stderr
30
+ };
31
+ return;
32
+ }
33
+ tools[name] = {
34
+ status: 'ok'
35
+ };
36
+ if (this.config.storage.persistRawOutput) {
37
+ tools[name].raw = this._truncateOutput(run.stdout);
38
+ }
39
+ };
40
+
41
+ if (featureConfig.nikto) {
42
+ await executeScanner('nikto', 'nikto', ['-h', this._buildUrl(target), '-ask', 'no']);
43
+ }
44
+
45
+ if (featureConfig.wpscan) {
46
+ await executeScanner('wpscan', 'wpscan', ['--url', this._buildUrl(target), '--random-user-agent']);
47
+ }
48
+
49
+ if (featureConfig.droopescan) {
50
+ await executeScanner('droopescan', 'droopescan', ['scan', 'drupal', '-u', this._buildUrl(target)]);
51
+ }
52
+
53
+ if (Object.keys(tools).length === 0) {
54
+ return { status: 'skipped' };
55
+ }
56
+
57
+ return {
58
+ _individual: tools,
59
+ _aggregated: {
60
+ status: Object.values(tools).some((tool) => tool.status === 'ok') ? 'ok' : 'empty',
61
+ tools
62
+ },
63
+ status: Object.values(tools).some((tool) => tool.status === 'ok') ? 'ok' : 'empty',
64
+ tools
65
+ };
66
+ }
67
+
68
+ _buildUrl(target) {
69
+ const protocol = target.protocol || 'https';
70
+ const port = target.port && target.port !== (protocol === 'http' ? 80 : 443) ? `:${target.port}` : '';
71
+ return `${protocol}://${target.host}${port}${target.path || ''}`;
72
+ }
73
+
74
+ _truncateOutput(text, maxLength = 10000) {
75
+ if (!text || text.length <= maxLength) return text;
76
+ return text.substring(0, maxLength) + '\n... (truncated)';
77
+ }
78
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * WebDiscoveryStage
3
+ *
4
+ * Directory and endpoint fuzzing:
5
+ * - ffuf (fast, flexible)
6
+ * - feroxbuster (recursive)
7
+ * - gobuster (reliable)
8
+ */
9
+
10
+ export class WebDiscoveryStage {
11
+ constructor(plugin) {
12
+ this.plugin = plugin;
13
+ this.commandRunner = plugin.commandRunner;
14
+ this.config = plugin.config;
15
+ }
16
+
17
+ async execute(target, featureConfig = {}) {
18
+ if (!featureConfig) {
19
+ return { status: 'disabled' };
20
+ }
21
+
22
+ const tools = {};
23
+ const discovered = {};
24
+ const allPaths = new Set();
25
+ const wordlist = featureConfig.wordlist;
26
+ const threads = featureConfig.threads ?? 50;
27
+
28
+ const runDirBuster = async (name, command, args) => {
29
+ const run = await this.commandRunner.run(command, args, {
30
+ timeout: featureConfig.timeout ?? 60000,
31
+ maxBuffer: 8 * 1024 * 1024
32
+ });
33
+ if (!run.ok) {
34
+ tools[name] = {
35
+ status: run.error?.code === 'ENOENT' ? 'unavailable' : 'error',
36
+ message: run.error?.message || `${command} failed`,
37
+ stderr: run.stderr
38
+ };
39
+ return;
40
+ }
41
+ const findings = run.stdout
42
+ .split(/\r?\n/)
43
+ .map((line) => line.trim())
44
+ .filter(Boolean);
45
+ discovered[name] = findings;
46
+ findings.forEach((item) => allPaths.add(item));
47
+ tools[name] = {
48
+ status: 'ok',
49
+ count: findings.length,
50
+ sample: findings.slice(0, 10)
51
+ };
52
+ if (this.config.storage.persistRawOutput) {
53
+ tools[name].raw = this._truncateOutput(run.stdout);
54
+ }
55
+ };
56
+
57
+ if (featureConfig.ffuf && wordlist) {
58
+ await runDirBuster('ffuf', 'ffuf', ['-u', `${this._buildUrl(target)}/FUZZ`, '-w', wordlist, '-t', String(threads), '-mc', '200,204,301,302,307,401,403']);
59
+ }
60
+
61
+ if (featureConfig.feroxbuster && wordlist) {
62
+ await runDirBuster('feroxbuster', 'feroxbuster', ['-u', this._buildUrl(target), '-w', wordlist, '--threads', String(threads), '--silent']);
63
+ }
64
+
65
+ if (featureConfig.gobuster && wordlist) {
66
+ await runDirBuster('gobuster', 'gobuster', ['dir', '-u', this._buildUrl(target), '-w', wordlist, '-t', String(threads)]);
67
+ }
68
+
69
+ const total = Object.values(discovered).reduce((acc, list) => acc + list.length, 0);
70
+
71
+ if (!total) {
72
+ return {
73
+ status: wordlist ? 'empty' : 'skipped',
74
+ message: wordlist ? 'No endpoints discovered' : 'Wordlist not provided',
75
+ tools
76
+ };
77
+ }
78
+
79
+ const paths = Array.from(allPaths);
80
+
81
+ return {
82
+ _individual: tools,
83
+ _aggregated: {
84
+ status: 'ok',
85
+ total,
86
+ tools,
87
+ paths
88
+ },
89
+ status: 'ok',
90
+ total,
91
+ tools,
92
+ paths
93
+ };
94
+ }
95
+
96
+ _buildUrl(target) {
97
+ const protocol = target.protocol || 'https';
98
+ const port = target.port && target.port !== this._defaultPortForProtocol(protocol)
99
+ ? `:${target.port}`
100
+ : '';
101
+ const path = target.path || '';
102
+ return `${protocol}://${target.host}${port}${path}`;
103
+ }
104
+
105
+ _defaultPortForProtocol(protocol) {
106
+ return protocol === 'http' ? 80 : protocol === 'https' ? 443 : null;
107
+ }
108
+
109
+ _truncateOutput(text, maxLength = 10000) {
110
+ if (!text || text.length <= maxLength) return text;
111
+ return text.substring(0, maxLength) + '\n... (truncated)';
112
+ }
113
+ }