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,307 @@
1
+ /**
2
+ * FingerprintBuilder
3
+ *
4
+ * Aggregates data from multiple stage results to build a consolidated fingerprint:
5
+ * - DNS records (IPs, nameservers, mail servers)
6
+ * - Open ports and services
7
+ * - Subdomains
8
+ * - Technology stack
9
+ * - TLS/SSL configuration
10
+ * - HTTP headers and server info
11
+ */
12
+
13
+ export class FingerprintBuilder {
14
+ /**
15
+ * Build consolidated fingerprint from stage results
16
+ */
17
+ static build(stageResults) {
18
+ const fingerprint = {
19
+ infrastructure: this._buildInfrastructure(stageResults),
20
+ attackSurface: this._buildAttackSurface(stageResults),
21
+ technologies: this._buildTechnologies(stageResults),
22
+ security: this._buildSecurity(stageResults)
23
+ };
24
+
25
+ return fingerprint;
26
+ }
27
+
28
+ /**
29
+ * Extract infrastructure data (DNS, IPs, certificates)
30
+ */
31
+ static _buildInfrastructure(stageResults) {
32
+ const infrastructure = {};
33
+
34
+ // DNS data
35
+ if (stageResults.dns?.status === 'ok') {
36
+ infrastructure.ips = {
37
+ ipv4: stageResults.dns.records.A || [],
38
+ ipv6: stageResults.dns.records.AAAA || []
39
+ };
40
+ infrastructure.nameservers = stageResults.dns.records.NS || [];
41
+ infrastructure.mailServers = stageResults.dns.records.MX || [];
42
+ infrastructure.txtRecords = stageResults.dns.records.TXT || [];
43
+ }
44
+
45
+ // Certificate data
46
+ if (stageResults.certificate?.status === 'ok') {
47
+ infrastructure.certificate = {
48
+ issuer: stageResults.certificate.issuer,
49
+ subject: stageResults.certificate.subject,
50
+ validFrom: stageResults.certificate.validFrom,
51
+ validTo: stageResults.certificate.validTo,
52
+ fingerprint: stageResults.certificate.fingerprint,
53
+ sans: stageResults.certificate.subjectAltName || []
54
+ };
55
+ }
56
+
57
+ // Latency/connectivity
58
+ if (stageResults.latency?.status === 'ok') {
59
+ infrastructure.latency = {
60
+ ping: stageResults.latency.ping,
61
+ traceroute: stageResults.latency.traceroute
62
+ };
63
+ }
64
+
65
+ return infrastructure;
66
+ }
67
+
68
+ /**
69
+ * Extract attack surface data (ports, subdomains, paths)
70
+ */
71
+ static _buildAttackSurface(stageResults) {
72
+ const attackSurface = {};
73
+
74
+ // Open ports
75
+ if (stageResults.ports?.status === 'ok') {
76
+ attackSurface.openPorts = stageResults.ports.openPorts || [];
77
+ attackSurface.portScanners = Object.keys(stageResults.ports.scanners || {});
78
+ }
79
+
80
+ // Subdomains
81
+ if (stageResults.subdomains?.status === 'ok') {
82
+ attackSurface.subdomains = {
83
+ total: stageResults.subdomains.total || 0,
84
+ list: stageResults.subdomains.list || [],
85
+ sources: Object.keys(stageResults.subdomains.sources || {})
86
+ };
87
+ }
88
+
89
+ // Web discovery (paths/endpoints)
90
+ if (stageResults.webDiscovery?.status === 'ok') {
91
+ const paths = new Set();
92
+ Object.values(stageResults.webDiscovery.tools || {}).forEach(tool => {
93
+ if (tool.status === 'ok' && tool.paths) {
94
+ tool.paths.forEach(path => paths.add(path));
95
+ }
96
+ });
97
+ attackSurface.discoveredPaths = {
98
+ total: paths.size,
99
+ list: Array.from(paths).sort()
100
+ };
101
+ }
102
+
103
+ return attackSurface;
104
+ }
105
+
106
+ /**
107
+ * Extract technology stack data (fingerprinting)
108
+ */
109
+ static _buildTechnologies(stageResults) {
110
+ const technologies = {};
111
+
112
+ // HTTP headers
113
+ if (stageResults.http?.status === 'ok') {
114
+ technologies.server = stageResults.http.headers?.server;
115
+ technologies.poweredBy = stageResults.http.headers?.['x-powered-by'];
116
+ technologies.httpHeaders = stageResults.http.headers;
117
+ }
118
+
119
+ // Technology fingerprinting
120
+ if (stageResults.fingerprint?.status === 'ok') {
121
+ technologies.detected = stageResults.fingerprint.technologies || [];
122
+ technologies.cms = stageResults.fingerprint.cms;
123
+ technologies.frameworks = stageResults.fingerprint.frameworks || [];
124
+ }
125
+
126
+ // OSINT data
127
+ if (stageResults.osint?.status === 'ok') {
128
+ const osintData = {};
129
+ Object.entries(stageResults.osint.tools || {}).forEach(([tool, result]) => {
130
+ if (result.status === 'ok') {
131
+ osintData[tool] = result;
132
+ }
133
+ });
134
+ if (Object.keys(osintData).length > 0) {
135
+ technologies.osint = osintData;
136
+ }
137
+ }
138
+
139
+ return technologies;
140
+ }
141
+
142
+ /**
143
+ * Extract security data (TLS audit, vulnerabilities)
144
+ */
145
+ static _buildSecurity(stageResults) {
146
+ const security = {};
147
+
148
+ // TLS audit
149
+ if (stageResults.tlsAudit?.status === 'ok') {
150
+ const tlsData = {};
151
+ Object.entries(stageResults.tlsAudit.tools || {}).forEach(([tool, result]) => {
152
+ if (result.status === 'ok') {
153
+ tlsData[tool] = result;
154
+ }
155
+ });
156
+ if (Object.keys(tlsData).length > 0) {
157
+ security.tls = tlsData;
158
+ }
159
+ }
160
+
161
+ // Vulnerabilities
162
+ if (stageResults.vulnerability?.status === 'ok') {
163
+ const vulns = {};
164
+ Object.entries(stageResults.vulnerability.tools || {}).forEach(([tool, result]) => {
165
+ if (result.status === 'ok') {
166
+ vulns[tool] = result;
167
+ }
168
+ });
169
+ if (Object.keys(vulns).length > 0) {
170
+ security.vulnerabilities = vulns;
171
+ }
172
+ }
173
+
174
+ // Security headers from HTTP
175
+ if (stageResults.http?.status === 'ok') {
176
+ const headers = stageResults.http.headers || {};
177
+ security.headers = {
178
+ hsts: headers['strict-transport-security'],
179
+ csp: headers['content-security-policy'],
180
+ xFrameOptions: headers['x-frame-options'],
181
+ xContentTypeOptions: headers['x-content-type-options'],
182
+ xXssProtection: headers['x-xss-protection'],
183
+ referrerPolicy: headers['referrer-policy']
184
+ };
185
+ }
186
+
187
+ return security;
188
+ }
189
+
190
+ /**
191
+ * Calculate fingerprint summary statistics
192
+ */
193
+ static buildSummary(fingerprint) {
194
+ return {
195
+ totalIPs: (fingerprint.infrastructure?.ips?.ipv4?.length || 0) +
196
+ (fingerprint.infrastructure?.ips?.ipv6?.length || 0),
197
+ totalPorts: fingerprint.attackSurface?.openPorts?.length || 0,
198
+ totalSubdomains: fingerprint.attackSurface?.subdomains?.total || 0,
199
+ totalPaths: fingerprint.attackSurface?.discoveredPaths?.total || 0,
200
+ hasCertificate: !!fingerprint.infrastructure?.certificate,
201
+ hasTLSAudit: !!fingerprint.security?.tls,
202
+ hasVulnerabilities: !!fingerprint.security?.vulnerabilities,
203
+ detectedTechnologies: fingerprint.technologies?.detected?.length || 0
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Compare two fingerprints and return diff
209
+ */
210
+ static diff(oldFingerprint, newFingerprint) {
211
+ const changes = {
212
+ infrastructure: this._diffInfrastructure(oldFingerprint.infrastructure, newFingerprint.infrastructure),
213
+ attackSurface: this._diffAttackSurface(oldFingerprint.attackSurface, newFingerprint.attackSurface),
214
+ technologies: this._diffTechnologies(oldFingerprint.technologies, newFingerprint.technologies),
215
+ security: this._diffSecurity(oldFingerprint.security, newFingerprint.security)
216
+ };
217
+
218
+ return changes;
219
+ }
220
+
221
+ static _diffInfrastructure(oldInfra, newInfra) {
222
+ const diff = {};
223
+
224
+ // Compare IPs
225
+ const oldIPv4 = new Set(oldInfra?.ips?.ipv4 || []);
226
+ const newIPv4 = new Set(newInfra?.ips?.ipv4 || []);
227
+ const addedIPv4 = [...newIPv4].filter(ip => !oldIPv4.has(ip));
228
+ const removedIPv4 = [...oldIPv4].filter(ip => !newIPv4.has(ip));
229
+ if (addedIPv4.length > 0 || removedIPv4.length > 0) {
230
+ diff.ipv4 = { added: addedIPv4, removed: removedIPv4 };
231
+ }
232
+
233
+ // Certificate changes
234
+ if (oldInfra?.certificate?.fingerprint !== newInfra?.certificate?.fingerprint) {
235
+ diff.certificateChanged = true;
236
+ }
237
+
238
+ return Object.keys(diff).length > 0 ? diff : null;
239
+ }
240
+
241
+ static _diffAttackSurface(oldSurface, newSurface) {
242
+ const diff = {};
243
+
244
+ // Compare ports
245
+ const oldPorts = new Set((oldSurface?.openPorts || []).map(p => p.port));
246
+ const newPorts = new Set((newSurface?.openPorts || []).map(p => p.port));
247
+ const addedPorts = [...newPorts].filter(p => !oldPorts.has(p));
248
+ const removedPorts = [...oldPorts].filter(p => !newPorts.has(p));
249
+ if (addedPorts.length > 0 || removedPorts.length > 0) {
250
+ diff.ports = { added: addedPorts, removed: removedPorts };
251
+ }
252
+
253
+ // Compare subdomains
254
+ const oldSubs = new Set(oldSurface?.subdomains?.list || []);
255
+ const newSubs = new Set(newSurface?.subdomains?.list || []);
256
+ const addedSubs = [...newSubs].filter(s => !oldSubs.has(s));
257
+ const removedSubs = [...oldSubs].filter(s => !newSubs.has(s));
258
+ if (addedSubs.length > 0 || removedSubs.length > 0) {
259
+ diff.subdomains = { added: addedSubs, removed: removedSubs };
260
+ }
261
+
262
+ // Compare paths
263
+ const oldPaths = new Set(oldSurface?.discoveredPaths?.list || []);
264
+ const newPaths = new Set(newSurface?.discoveredPaths?.list || []);
265
+ const addedPaths = [...newPaths].filter(p => !oldPaths.has(p));
266
+ const removedPaths = [...oldPaths].filter(p => !newPaths.has(p));
267
+ if (addedPaths.length > 0 || removedPaths.length > 0) {
268
+ diff.paths = { added: addedPaths, removed: removedPaths };
269
+ }
270
+
271
+ return Object.keys(diff).length > 0 ? diff : null;
272
+ }
273
+
274
+ static _diffTechnologies(oldTech, newTech) {
275
+ const diff = {};
276
+
277
+ const oldDetected = new Set(oldTech?.detected || []);
278
+ const newDetected = new Set(newTech?.detected || []);
279
+ const addedTech = [...newDetected].filter(t => !oldDetected.has(t));
280
+ const removedTech = [...oldDetected].filter(t => !newDetected.has(t));
281
+ if (addedTech.length > 0 || removedTech.length > 0) {
282
+ diff.detected = { added: addedTech, removed: removedTech };
283
+ }
284
+
285
+ return Object.keys(diff).length > 0 ? diff : null;
286
+ }
287
+
288
+ static _diffSecurity(oldSec, newSec) {
289
+ const diff = {};
290
+
291
+ // TLS changes
292
+ const oldTLS = Object.keys(oldSec?.tls || {});
293
+ const newTLS = Object.keys(newSec?.tls || {});
294
+ if (JSON.stringify(oldTLS.sort()) !== JSON.stringify(newTLS.sort())) {
295
+ diff.tlsChanged = true;
296
+ }
297
+
298
+ // Vulnerability changes
299
+ const oldVulns = Object.keys(oldSec?.vulnerabilities || {});
300
+ const newVulns = Object.keys(newSec?.vulnerabilities || {});
301
+ if (JSON.stringify(oldVulns.sort()) !== JSON.stringify(newVulns.sort())) {
302
+ diff.vulnerabilitiesChanged = true;
303
+ }
304
+
305
+ return Object.keys(diff).length > 0 ? diff : null;
306
+ }
307
+ }
@@ -0,0 +1,338 @@
1
+ /**
2
+ * ProcessManager
3
+ *
4
+ * Manages child processes and cleanup for ReconPlugin.
5
+ * Ensures all spawned processes (Chrome/Puppeteer, external tools) are properly terminated
6
+ * when the parent process exits or when operations complete.
7
+ *
8
+ * Key Features:
9
+ * - Tracks all spawned child processes
10
+ * - Automatic cleanup on process exit (SIGINT, SIGTERM, uncaughtException)
11
+ * - Force kill orphaned processes
12
+ * - Cleanup temporary directories (Puppeteer profiles, etc.)
13
+ * - Prevents zombie processes
14
+ *
15
+ * Usage:
16
+ * const processManager = new ProcessManager();
17
+ * processManager.track(childProcess);
18
+ * processManager.cleanup(); // Manual cleanup
19
+ */
20
+
21
+ import { spawn, exec } from 'child_process';
22
+ import { promisify } from 'util';
23
+ import fs from 'fs/promises';
24
+ import path from 'path';
25
+ import os from 'os';
26
+
27
+ const execAsync = promisify(exec);
28
+
29
+ export class ProcessManager {
30
+ constructor() {
31
+ this.processes = new Set();
32
+ this.tempDirs = new Set();
33
+ this.cleanupHandlersRegistered = false;
34
+ this._setupCleanupHandlers();
35
+ }
36
+
37
+ /**
38
+ * Track a child process for automatic cleanup
39
+ * @param {ChildProcess} process - Child process to track
40
+ * @param {Object} options - Tracking options
41
+ * @param {string} options.name - Process name for logging
42
+ * @param {string} options.tempDir - Temporary directory to cleanup
43
+ */
44
+ track(process, options = {}) {
45
+ if (!process || !process.pid) {
46
+ return;
47
+ }
48
+
49
+ this.processes.add({
50
+ process,
51
+ pid: process.pid,
52
+ name: options.name || 'unknown',
53
+ startTime: Date.now()
54
+ });
55
+
56
+ if (options.tempDir) {
57
+ this.tempDirs.add(options.tempDir);
58
+ }
59
+
60
+ // Auto-remove when process exits
61
+ process.on('exit', () => {
62
+ this._removeProcess(process.pid);
63
+ });
64
+
65
+ // Handle errors
66
+ process.on('error', (error) => {
67
+ console.error(`[ProcessManager] Process ${options.name || process.pid} error:`, error.message);
68
+ this._removeProcess(process.pid);
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Track a temporary directory for cleanup
74
+ * @param {string} dirPath - Directory path
75
+ */
76
+ trackTempDir(dirPath) {
77
+ this.tempDirs.add(dirPath);
78
+ }
79
+
80
+ /**
81
+ * Remove process from tracking
82
+ * @private
83
+ */
84
+ _removeProcess(pid) {
85
+ for (const tracked of this.processes) {
86
+ if (tracked.pid === pid) {
87
+ this.processes.delete(tracked);
88
+ break;
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Setup cleanup handlers for process exit
95
+ * @private
96
+ */
97
+ _setupCleanupHandlers() {
98
+ if (this.cleanupHandlersRegistered) {
99
+ return;
100
+ }
101
+
102
+ const cleanup = async (signal) => {
103
+ console.log(`\n[ProcessManager] Received ${signal}, cleaning up...`);
104
+ await this.cleanup();
105
+ process.exit(0);
106
+ };
107
+
108
+ // Handle various exit signals
109
+ process.on('SIGINT', () => cleanup('SIGINT'));
110
+ process.on('SIGTERM', () => cleanup('SIGTERM'));
111
+ process.on('SIGHUP', () => cleanup('SIGHUP'));
112
+
113
+ // Handle uncaught exceptions
114
+ process.on('uncaughtException', async (error) => {
115
+ console.error('[ProcessManager] Uncaught exception:', error);
116
+ await this.cleanup();
117
+ process.exit(1);
118
+ });
119
+
120
+ // Handle unhandled promise rejections
121
+ process.on('unhandledRejection', async (reason, promise) => {
122
+ console.error('[ProcessManager] Unhandled rejection:', reason);
123
+ await this.cleanup();
124
+ process.exit(1);
125
+ });
126
+
127
+ // Handle process exit
128
+ process.on('beforeExit', async () => {
129
+ await this.cleanup();
130
+ });
131
+
132
+ this.cleanupHandlersRegistered = true;
133
+ }
134
+
135
+ /**
136
+ * Cleanup all tracked processes and temporary directories
137
+ * @param {Object} options - Cleanup options
138
+ * @param {boolean} options.force - Force kill processes (SIGKILL)
139
+ * @param {boolean} options.silent - Suppress logging
140
+ */
141
+ async cleanup(options = {}) {
142
+ const { force = false, silent = false } = options;
143
+
144
+ if (!silent && this.processes.size > 0) {
145
+ console.log(`[ProcessManager] Cleaning up ${this.processes.size} tracked process(es)...`);
146
+ }
147
+
148
+ // Kill all tracked processes
149
+ const killPromises = [];
150
+ for (const tracked of this.processes) {
151
+ killPromises.push(this._killProcess(tracked, force, silent));
152
+ }
153
+ await Promise.allSettled(killPromises);
154
+
155
+ // Cleanup temporary directories
156
+ if (this.tempDirs.size > 0 && !silent) {
157
+ console.log(`[ProcessManager] Cleaning up ${this.tempDirs.size} temporary directory(ies)...`);
158
+ }
159
+
160
+ const cleanupPromises = [];
161
+ for (const dir of this.tempDirs) {
162
+ cleanupPromises.push(this._cleanupTempDir(dir, silent));
163
+ }
164
+ await Promise.allSettled(cleanupPromises);
165
+
166
+ // Cleanup orphaned Puppeteer processes
167
+ await this._cleanupOrphanedPuppeteer(silent);
168
+
169
+ this.processes.clear();
170
+ this.tempDirs.clear();
171
+
172
+ if (!silent) {
173
+ console.log('[ProcessManager] Cleanup complete');
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Kill a tracked process
179
+ * @private
180
+ */
181
+ async _killProcess(tracked, force, silent) {
182
+ const { process, pid, name } = tracked;
183
+
184
+ if (!this._isProcessRunning(pid)) {
185
+ this._removeProcess(pid);
186
+ return;
187
+ }
188
+
189
+ const signal = force ? 'SIGKILL' : 'SIGTERM';
190
+
191
+ if (!silent) {
192
+ console.log(`[ProcessManager] Killing ${name} (PID: ${pid}) with ${signal}...`);
193
+ }
194
+
195
+ try {
196
+ process.kill(signal);
197
+
198
+ // Wait up to 5 seconds for graceful termination
199
+ if (!force) {
200
+ await this._waitForProcessExit(pid, 5000);
201
+
202
+ // If still running, force kill
203
+ if (this._isProcessRunning(pid)) {
204
+ if (!silent) {
205
+ console.log(`[ProcessManager] Force killing ${name} (PID: ${pid})...`);
206
+ }
207
+ process.kill('SIGKILL');
208
+ }
209
+ }
210
+ } catch (error) {
211
+ if (error.code !== 'ESRCH') { // Ignore "process not found" errors
212
+ console.error(`[ProcessManager] Error killing ${name} (PID: ${pid}):`, error.message);
213
+ }
214
+ }
215
+
216
+ this._removeProcess(pid);
217
+ }
218
+
219
+ /**
220
+ * Check if process is running
221
+ * @private
222
+ */
223
+ _isProcessRunning(pid) {
224
+ try {
225
+ process.kill(pid, 0); // Signal 0 checks existence without killing
226
+ return true;
227
+ } catch (error) {
228
+ return false;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Wait for process to exit
234
+ * @private
235
+ */
236
+ async _waitForProcessExit(pid, timeout = 5000) {
237
+ const startTime = Date.now();
238
+ while (this._isProcessRunning(pid) && (Date.now() - startTime) < timeout) {
239
+ await new Promise(resolve => setTimeout(resolve, 100));
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Cleanup temporary directory
245
+ * @private
246
+ */
247
+ async _cleanupTempDir(dir, silent) {
248
+ try {
249
+ const exists = await fs.access(dir).then(() => true).catch(() => false);
250
+ if (exists) {
251
+ await fs.rm(dir, { recursive: true, force: true });
252
+ if (!silent) {
253
+ console.log(`[ProcessManager] Removed temp directory: ${dir}`);
254
+ }
255
+ }
256
+ } catch (error) {
257
+ console.error(`[ProcessManager] Error cleaning up ${dir}:`, error.message);
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Cleanup orphaned Puppeteer processes and temp directories
263
+ * @private
264
+ */
265
+ async _cleanupOrphanedPuppeteer(silent) {
266
+ try {
267
+ // Kill orphaned Chrome/Puppeteer processes
268
+ const { stdout } = await execAsync('pgrep -f "chrome.*puppeteer" || true');
269
+ if (stdout.trim()) {
270
+ const pids = stdout.trim().split('\n').filter(Boolean);
271
+ if (!silent && pids.length > 0) {
272
+ console.log(`[ProcessManager] Found ${pids.length} orphaned Puppeteer process(es), killing...`);
273
+ }
274
+ for (const pid of pids) {
275
+ try {
276
+ process.kill(parseInt(pid), 'SIGKILL');
277
+ } catch (error) {
278
+ // Ignore errors (process may have already exited)
279
+ }
280
+ }
281
+ }
282
+
283
+ // Cleanup orphaned Puppeteer temp directories
284
+ const tmpDir = os.tmpdir();
285
+ const puppeteerPattern = /^puppeteer_dev_profile-/;
286
+
287
+ const entries = await fs.readdir(tmpDir, { withFileTypes: true });
288
+ const puppeteerDirs = entries
289
+ .filter(entry => entry.isDirectory() && puppeteerPattern.test(entry.name))
290
+ .map(entry => path.join(tmpDir, entry.name));
291
+
292
+ if (puppeteerDirs.length > 0 && !silent) {
293
+ console.log(`[ProcessManager] Cleaning up ${puppeteerDirs.length} orphaned Puppeteer temp dir(s)...`);
294
+ }
295
+
296
+ for (const dir of puppeteerDirs) {
297
+ try {
298
+ await fs.rm(dir, { recursive: true, force: true });
299
+ } catch (error) {
300
+ // Ignore errors (may be in use or already deleted)
301
+ }
302
+ }
303
+ } catch (error) {
304
+ // Silently ignore errors in orphan cleanup
305
+ if (!silent) {
306
+ console.error('[ProcessManager] Error during orphan cleanup:', error.message);
307
+ }
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Get count of tracked processes
313
+ */
314
+ getProcessCount() {
315
+ return this.processes.size;
316
+ }
317
+
318
+ /**
319
+ * Get list of tracked processes
320
+ */
321
+ getProcesses() {
322
+ return Array.from(this.processes).map(({ pid, name, startTime }) => ({
323
+ pid,
324
+ name,
325
+ uptime: Date.now() - startTime
326
+ }));
327
+ }
328
+
329
+ /**
330
+ * Force cleanup all processes immediately
331
+ */
332
+ async forceCleanup() {
333
+ await this.cleanup({ force: true, silent: false });
334
+ }
335
+ }
336
+
337
+ // Export singleton instance
338
+ export const processManager = new ProcessManager();