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.
- package/README.md +139 -43
- package/dist/s3db.cjs +72425 -38970
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72177 -38764
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +94 -49
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +180 -41
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/api/utils/template-engine.js +77 -3
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/README.md +126 -126
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- 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();
|