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,274 @@
1
+ /**
2
+ * TargetManager
3
+ *
4
+ * Handles dynamic target management:
5
+ * - CRUD operations for targets
6
+ * - Target normalization
7
+ * - Resource persistence
8
+ */
9
+
10
+ export class TargetManager {
11
+ constructor(plugin) {
12
+ this.plugin = plugin;
13
+ }
14
+
15
+ /**
16
+ * Add a new target to the monitoring list
17
+ */
18
+ async add(targetInput, options = {}) {
19
+ const normalized = this._normalizeTarget(targetInput);
20
+ const targetId = normalized.host;
21
+
22
+ // Check if target already exists
23
+ const existing = await this.get(targetInput);
24
+ if (existing) {
25
+ throw new Error(`Target "${targetId}" already exists. Use updateTarget() to modify it.`);
26
+ }
27
+
28
+ const targetRecord = {
29
+ id: targetId,
30
+ target: targetInput,
31
+ enabled: options.enabled !== false,
32
+ behavior: options.behavior || this.plugin.config.behavior,
33
+ features: options.features || {},
34
+ tools: options.tools || null,
35
+ schedule: options.schedule || null,
36
+ metadata: options.metadata || {},
37
+ lastScanAt: null,
38
+ lastScanStatus: null,
39
+ scanCount: 0,
40
+ addedBy: options.addedBy || 'manual',
41
+ tags: options.tags || [],
42
+ createdAt: new Date().toISOString(),
43
+ updatedAt: new Date().toISOString()
44
+ };
45
+
46
+ const targetsResource = await this._getResource();
47
+ await targetsResource.insert(targetRecord);
48
+
49
+ this.plugin.emit('recon:target-added', {
50
+ targetId,
51
+ target: targetInput,
52
+ enabled: targetRecord.enabled,
53
+ behavior: targetRecord.behavior
54
+ });
55
+
56
+ return targetRecord;
57
+ }
58
+
59
+ /**
60
+ * Remove a target from the monitoring list
61
+ */
62
+ async remove(targetInput) {
63
+ const normalized = this._normalizeTarget(targetInput);
64
+ const targetId = normalized.host;
65
+
66
+ const targetsResource = await this._getResource();
67
+ await targetsResource.delete(targetId);
68
+
69
+ this.plugin.emit('recon:target-removed', {
70
+ targetId,
71
+ target: targetInput
72
+ });
73
+
74
+ return { targetId, removed: true };
75
+ }
76
+
77
+ /**
78
+ * Update target configuration
79
+ */
80
+ async update(targetInput, updates) {
81
+ const normalized = this._normalizeTarget(targetInput);
82
+ const targetId = normalized.host;
83
+
84
+ const existing = await this.get(targetInput);
85
+ if (!existing) {
86
+ throw new Error(`Target "${targetId}" not found. Use addTarget() to create it.`);
87
+ }
88
+
89
+ const updatedRecord = {
90
+ ...existing,
91
+ ...updates,
92
+ updatedAt: new Date().toISOString()
93
+ };
94
+
95
+ const targetsResource = await this._getResource();
96
+ await targetsResource.update(targetId, updatedRecord);
97
+
98
+ this.plugin.emit('recon:target-updated', {
99
+ targetId,
100
+ updates
101
+ });
102
+
103
+ return updatedRecord;
104
+ }
105
+
106
+ /**
107
+ * List all configured targets
108
+ */
109
+ async list(options = {}) {
110
+ const includeDisabled = options.includeDisabled !== false;
111
+ const fromResource = options.fromResource !== false;
112
+ const limit = options.limit || 1000;
113
+
114
+ if (!fromResource) {
115
+ // Fallback to config targets (legacy mode)
116
+ return this._normalizeConfigTargets();
117
+ }
118
+
119
+ try {
120
+ const targetsResource = await this._getResource();
121
+ let targets = await targetsResource.list({ limit });
122
+
123
+ if (!includeDisabled) {
124
+ targets = targets.filter(t => t.enabled !== false);
125
+ }
126
+
127
+ return targets;
128
+ } catch (error) {
129
+ // Fallback to config targets if resource not available
130
+ return this._normalizeConfigTargets();
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Get details of a specific target
136
+ */
137
+ async get(targetInput) {
138
+ const normalized = this._normalizeTarget(targetInput);
139
+ const targetId = normalized.host;
140
+
141
+ try {
142
+ const targetsResource = await this._getResource();
143
+ return await targetsResource.get(targetId);
144
+ } catch (error) {
145
+ // Check if it exists in config targets (legacy mode)
146
+ const configTargets = this._normalizeConfigTargets();
147
+ return configTargets.find(t => t.id === targetId) || null;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Increment scan count and update last scan metadata
153
+ */
154
+ async updateScanMetadata(targetId, report) {
155
+ try {
156
+ const target = await this.get(targetId);
157
+ if (!target) {
158
+ return;
159
+ }
160
+
161
+ await this.update(targetId, {
162
+ lastScanAt: report.endedAt,
163
+ lastScanStatus: report.status,
164
+ scanCount: (target.scanCount || 0) + 1
165
+ });
166
+ } catch (error) {
167
+ // Ignore errors (target might not be in resource)
168
+ }
169
+ }
170
+
171
+ // ========================================
172
+ // Private Helper Methods
173
+ // ========================================
174
+
175
+ async _getResource() {
176
+ // Get namespaced targets resource
177
+ const namespace = this.plugin.namespace || '';
178
+ const resourceName = namespace === ''
179
+ ? 'plg_recon_targets'
180
+ : `plg_recon_${namespace}_targets`;
181
+
182
+ return await this.plugin.database.getResource(resourceName);
183
+ }
184
+
185
+ _normalizeTarget(targetInput) {
186
+ if (!targetInput || typeof targetInput !== 'string') {
187
+ throw new Error('Target must be a non-empty string');
188
+ }
189
+
190
+ let url;
191
+ try {
192
+ url = new URL(targetInput.includes('://') ? targetInput : `https://${targetInput}`);
193
+ } catch (error) {
194
+ url = new URL(`https://${targetInput}`);
195
+ }
196
+
197
+ const protocol = url.protocol ? url.protocol.replace(':', '') : null;
198
+ const host = url.hostname || targetInput;
199
+ const port = url.port ? Number(url.port) : this._defaultPortForProtocol(protocol);
200
+
201
+ return {
202
+ original: targetInput,
203
+ host,
204
+ protocol,
205
+ port,
206
+ path: url.pathname === '/' ? null : url.pathname
207
+ };
208
+ }
209
+
210
+ _defaultPortForProtocol(protocol) {
211
+ switch (protocol) {
212
+ case 'http':
213
+ return 80;
214
+ case 'https':
215
+ return 443;
216
+ case 'ftp':
217
+ return 21;
218
+ case 'ssh':
219
+ return 22;
220
+ default:
221
+ return null;
222
+ }
223
+ }
224
+
225
+ _normalizeConfigTargets() {
226
+ const targets = this.plugin.config.targets || [];
227
+ return targets.map((entry, index) => {
228
+ if (typeof entry === 'string') {
229
+ const normalized = this._normalizeTarget(entry);
230
+ return {
231
+ id: normalized.host,
232
+ target: entry,
233
+ enabled: true,
234
+ behavior: this.plugin.config.behavior,
235
+ features: {},
236
+ tools: null,
237
+ schedule: null,
238
+ metadata: {},
239
+ lastScanAt: null,
240
+ lastScanStatus: null,
241
+ scanCount: 0,
242
+ addedBy: 'config',
243
+ tags: [],
244
+ createdAt: new Date().toISOString(),
245
+ updatedAt: new Date().toISOString()
246
+ };
247
+ }
248
+
249
+ if (entry && typeof entry === 'object') {
250
+ const target = entry.target || entry.host || entry.domain;
251
+ const normalized = this._normalizeTarget(target);
252
+ return {
253
+ id: normalized.host,
254
+ target,
255
+ enabled: entry.enabled !== false,
256
+ behavior: entry.behavior || this.plugin.config.behavior,
257
+ features: entry.features || {},
258
+ tools: entry.tools || null,
259
+ schedule: entry.schedule || null,
260
+ metadata: entry.metadata || {},
261
+ lastScanAt: null,
262
+ lastScanStatus: null,
263
+ scanCount: 0,
264
+ addedBy: 'config',
265
+ tags: entry.tags || [],
266
+ createdAt: new Date().toISOString(),
267
+ updatedAt: new Date().toISOString()
268
+ };
269
+ }
270
+
271
+ throw new Error(`Invalid target configuration at index ${index}`);
272
+ });
273
+ }
274
+ }
@@ -0,0 +1,314 @@
1
+ /**
2
+ * ASNStage
3
+ *
4
+ * ASN (Autonomous System Number) and Network Intelligence
5
+ *
6
+ * Discovers:
7
+ * - ASN ownership and organization
8
+ * - IP ranges (CIDR blocks)
9
+ * - Network provider information
10
+ * - BGP routing data
11
+ *
12
+ * Uses 100% free APIs:
13
+ * - iptoasn.com (unlimited, free)
14
+ * - hackertarget.com (100 queries/day free)
15
+ */
16
+
17
+ export class ASNStage {
18
+ constructor(plugin) {
19
+ this.plugin = plugin;
20
+ this.commandRunner = plugin.commandRunner;
21
+ this.config = plugin.config;
22
+ }
23
+
24
+ /**
25
+ * Execute ASN lookup
26
+ * @param {Object} target - Target object with host property
27
+ * @param {Object} options - ASN options
28
+ * @returns {Promise<Object>} ASN results
29
+ */
30
+ async execute(target, options = {}) {
31
+ const result = {
32
+ status: 'ok',
33
+ host: target.host,
34
+ ipAddresses: [],
35
+ asns: [],
36
+ networks: [],
37
+ organizations: new Set(),
38
+ errors: {}
39
+ };
40
+
41
+ // Track individual tool results for artifact persistence
42
+ const individual = {
43
+ iptoasn: { status: 'ok', results: [] },
44
+ hackertarget: { status: 'ok', results: [] },
45
+ dig: { status: 'ok', ipv4: [], ipv6: [] }
46
+ };
47
+
48
+ // Step 1: Resolve host to IP addresses
49
+ const ipAddresses = await this.resolveHostToIPs(target.host, individual.dig);
50
+ result.ipAddresses = ipAddresses;
51
+
52
+ if (ipAddresses.length === 0) {
53
+ result.status = 'error';
54
+ result.errors.dns = 'Could not resolve host to IP addresses';
55
+ individual.dig.status = 'error';
56
+
57
+ return {
58
+ _individual: individual,
59
+ _aggregated: result,
60
+ ...result // Root level for compatibility
61
+ };
62
+ }
63
+
64
+ // Step 2: Lookup ASN for each IP
65
+ for (const ip of ipAddresses) {
66
+ try {
67
+ // Try iptoasn.com first (faster, unlimited)
68
+ let asnData = await this.lookupASNViaIPToASN(ip, options);
69
+
70
+ if (asnData) {
71
+ asnData._source = 'iptoasn';
72
+ individual.iptoasn.results.push({ ip, ...asnData });
73
+ result.asns.push(asnData);
74
+
75
+ if (asnData.network) {
76
+ result.networks.push(asnData.network);
77
+ }
78
+
79
+ if (asnData.organization) {
80
+ result.organizations.add(asnData.organization);
81
+ }
82
+ } else if (options.hackertarget !== false) {
83
+ // Fallback to hackertarget if iptoasn fails
84
+ asnData = await this.lookupASNViaHackerTarget(ip, options);
85
+
86
+ if (asnData) {
87
+ asnData._source = 'hackertarget';
88
+ individual.hackertarget.results.push({ ip, ...asnData });
89
+ result.asns.push(asnData);
90
+
91
+ if (asnData.network) {
92
+ result.networks.push(asnData.network);
93
+ }
94
+
95
+ if (asnData.organization) {
96
+ result.organizations.add(asnData.organization);
97
+ }
98
+ }
99
+ }
100
+
101
+ } catch (error) {
102
+ result.errors[ip] = error.message;
103
+ }
104
+ }
105
+
106
+ // Convert Set to Array for JSON serialization
107
+ result.organizations = Array.from(result.organizations);
108
+
109
+ // Deduplicate ASNs by ASN number
110
+ result.asns = this.deduplicateASNs(result.asns);
111
+
112
+ // Mark tools as unavailable if no results
113
+ if (individual.iptoasn.results.length === 0) {
114
+ individual.iptoasn.status = 'unavailable';
115
+ }
116
+ if (individual.hackertarget.results.length === 0 && options.hackertarget !== false) {
117
+ individual.hackertarget.status = 'unavailable';
118
+ }
119
+
120
+ return {
121
+ _individual: individual,
122
+ _aggregated: result,
123
+ ...result // Root level for compatibility
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Resolve host to IP addresses using dig
129
+ */
130
+ async resolveHostToIPs(host, digResults = null) {
131
+ const ips = [];
132
+
133
+ // Resolve A records (IPv4)
134
+ const aRun = await this.commandRunner.run('dig', ['+short', 'A', host], { timeout: 5000 });
135
+ if (aRun.ok && aRun.stdout) {
136
+ const ipv4s = aRun.stdout
137
+ .split('\n')
138
+ .map(line => line.trim())
139
+ .filter(line => /^\d+\.\d+\.\d+\.\d+$/.test(line));
140
+ ips.push(...ipv4s);
141
+
142
+ if (digResults) {
143
+ digResults.ipv4 = ipv4s;
144
+ if (this.config?.storage?.persistRawOutput) {
145
+ digResults.raw_ipv4 = aRun.stdout;
146
+ }
147
+ }
148
+ }
149
+
150
+ // Resolve AAAA records (IPv6)
151
+ const aaaaRun = await this.commandRunner.run('dig', ['+short', 'AAAA', host], { timeout: 5000 });
152
+ if (aaaaRun.ok && aaaaRun.stdout) {
153
+ const ipv6s = aaaaRun.stdout
154
+ .split('\n')
155
+ .map(line => line.trim())
156
+ .filter(line => /^[0-9a-f:]+$/i.test(line) && line.includes(':'));
157
+ ips.push(...ipv6s);
158
+
159
+ if (digResults) {
160
+ digResults.ipv6 = ipv6s;
161
+ if (this.config?.storage?.persistRawOutput) {
162
+ digResults.raw_ipv6 = aaaaRun.stdout;
163
+ }
164
+ }
165
+ }
166
+
167
+ return [...new Set(ips)]; // Deduplicate
168
+ }
169
+
170
+ /**
171
+ * Lookup ASN via iptoasn.com (100% free, unlimited)
172
+ * API: https://iptoasn.com/
173
+ */
174
+ async lookupASNViaIPToASN(ip, options = {}) {
175
+ try {
176
+ const url = `https://api.iptoasn.com/v1/as/ip/${encodeURIComponent(ip)}`;
177
+
178
+ const response = await fetch(url, {
179
+ headers: {
180
+ 'User-Agent': this.config.curl?.userAgent || 'ReconPlugin/1.0'
181
+ },
182
+ signal: AbortSignal.timeout ? AbortSignal.timeout(10000) : undefined
183
+ });
184
+
185
+ if (!response.ok) {
186
+ return null;
187
+ }
188
+
189
+ const data = await response.json();
190
+
191
+ // iptoasn.com response format:
192
+ // {
193
+ // "announced": true,
194
+ // "as_number": 15169,
195
+ // "as_country_code": "US",
196
+ // "as_description": "GOOGLE",
197
+ // "first_ip": "8.8.8.0",
198
+ // "last_ip": "8.8.8.255",
199
+ // "as_name": "GOOGLE"
200
+ // }
201
+
202
+ if (!data.announced || !data.as_number) {
203
+ return null;
204
+ }
205
+
206
+ return {
207
+ ip,
208
+ asn: `AS${data.as_number}`,
209
+ asnNumber: data.as_number,
210
+ organization: data.as_description || data.as_name,
211
+ country: data.as_country_code,
212
+ network: data.first_ip && data.last_ip
213
+ ? `${data.first_ip} - ${data.last_ip}`
214
+ : null,
215
+ source: 'iptoasn.com'
216
+ };
217
+
218
+ } catch (error) {
219
+ return null;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Lookup ASN via hackertarget.com (100 queries/day free)
225
+ * API: https://api.hackertarget.com/aslookup/
226
+ */
227
+ async lookupASNViaHackerTarget(ip, options = {}) {
228
+ try {
229
+ const url = `https://api.hackertarget.com/aslookup/?q=${encodeURIComponent(ip)}`;
230
+
231
+ const response = await fetch(url, {
232
+ headers: {
233
+ 'User-Agent': this.config.curl?.userAgent || 'ReconPlugin/1.0'
234
+ },
235
+ signal: AbortSignal.timeout ? AbortSignal.timeout(10000) : undefined
236
+ });
237
+
238
+ if (!response.ok) {
239
+ return null;
240
+ }
241
+
242
+ const text = await response.text();
243
+
244
+ // hackertarget response format (plain text):
245
+ // "15169","8.8.8.0/24","US","arin","GOOGLE"
246
+
247
+ if (text.includes('error') || !text.includes(',')) {
248
+ return null;
249
+ }
250
+
251
+ // Parse CSV format
252
+ const parts = text.split(',').map(p => p.replace(/"/g, '').trim());
253
+
254
+ if (parts.length < 3) {
255
+ return null;
256
+ }
257
+
258
+ const asnNumber = parseInt(parts[0]);
259
+ const network = parts[1] || null;
260
+ const country = parts[2] || null;
261
+ const organization = parts[4] || parts[3] || null;
262
+
263
+ return {
264
+ ip,
265
+ asn: `AS${asnNumber}`,
266
+ asnNumber,
267
+ organization,
268
+ country,
269
+ network,
270
+ source: 'hackertarget.com'
271
+ };
272
+
273
+ } catch (error) {
274
+ return null;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Deduplicate ASNs by ASN number
280
+ */
281
+ deduplicateASNs(asns) {
282
+ const seen = new Map();
283
+
284
+ for (const asn of asns) {
285
+ const key = asn.asnNumber;
286
+
287
+ if (!seen.has(key)) {
288
+ seen.set(key, asn);
289
+ } else {
290
+ // Merge data from multiple sources
291
+ const existing = seen.get(key);
292
+
293
+ // Prefer more detailed data
294
+ if (asn.network && !existing.network) {
295
+ existing.network = asn.network;
296
+ }
297
+
298
+ if (asn.organization && !existing.organization) {
299
+ existing.organization = asn.organization;
300
+ }
301
+
302
+ // Track multiple sources
303
+ if (!existing.sources) {
304
+ existing.sources = [existing.source];
305
+ }
306
+ if (!existing.sources.includes(asn.source)) {
307
+ existing.sources.push(asn.source);
308
+ }
309
+ }
310
+ }
311
+
312
+ return Array.from(seen.values());
313
+ }
314
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * CertificateStage
3
+ *
4
+ * TLS certificate inspection:
5
+ * - Subject and issuer details
6
+ * - Validity period
7
+ * - Fingerprint
8
+ * - Subject Alternative Names (SANs)
9
+ */
10
+
11
+ import tls from 'tls';
12
+
13
+ export class CertificateStage {
14
+ constructor(plugin) {
15
+ this.plugin = plugin;
16
+ }
17
+
18
+ async execute(target) {
19
+ const shouldCheckTls =
20
+ target.protocol === 'https' ||
21
+ (!target.protocol && (target.port === 443 || target.host.includes(':') === false));
22
+
23
+ if (!shouldCheckTls) {
24
+ return {
25
+ status: 'skipped',
26
+ message: 'TLS inspection skipped for non-HTTPS target'
27
+ };
28
+ }
29
+
30
+ const port = target.port || 443;
31
+
32
+ return new Promise((resolve) => {
33
+ const socket = tls.connect(
34
+ {
35
+ host: target.host,
36
+ port,
37
+ servername: target.host,
38
+ rejectUnauthorized: false,
39
+ timeout: 5000
40
+ },
41
+ () => {
42
+ const certificate = socket.getPeerCertificate(true);
43
+ socket.end();
44
+
45
+ if (!certificate || Object.keys(certificate).length === 0) {
46
+ resolve({
47
+ status: 'error',
48
+ message: 'No certificate information available'
49
+ });
50
+ return;
51
+ }
52
+
53
+ resolve({
54
+ status: 'ok',
55
+ subject: certificate.subject,
56
+ issuer: certificate.issuer,
57
+ validFrom: certificate.valid_from,
58
+ validTo: certificate.valid_to,
59
+ fingerprint: certificate.fingerprint256 || certificate.fingerprint,
60
+ subjectAltName: certificate.subjectaltname
61
+ ? certificate.subjectaltname.split(',').map((entry) => entry.trim())
62
+ : [],
63
+ raw: certificate
64
+ });
65
+ }
66
+ );
67
+
68
+ socket.on('error', (error) => {
69
+ resolve({
70
+ status: 'error',
71
+ message: error?.message || 'Unable to retrieve certificate'
72
+ });
73
+ });
74
+
75
+ socket.setTimeout(6000, () => {
76
+ socket.destroy();
77
+ resolve({
78
+ status: 'timeout',
79
+ message: 'TLS handshake timed out'
80
+ });
81
+ });
82
+ });
83
+ }
84
+ }