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,148 @@
1
+ /**
2
+ * CommandRunner
3
+ *
4
+ * Executes CLI commands with:
5
+ * - Availability checking
6
+ * - Timeout handling
7
+ * - Output buffering
8
+ * - Error handling
9
+ * - Automatic process cleanup via ProcessManager
10
+ */
11
+
12
+ import { spawn } from 'child_process';
13
+ import { processManager } from './process-manager.js';
14
+
15
+ export class CommandRunner {
16
+ constructor() {
17
+ this.availabilityCache = new Map();
18
+ }
19
+
20
+ /**
21
+ * Check if a command is available in PATH
22
+ */
23
+ async isAvailable(command) {
24
+ if (this.availabilityCache.has(command)) {
25
+ return this.availabilityCache.get(command);
26
+ }
27
+
28
+ // Don't track 'which' processes (they're just checks, not actual work)
29
+ const result = await this.run('which', [command], {
30
+ timeout: 1000,
31
+ trackProcess: false
32
+ });
33
+ const available = result.ok && result.stdout.trim().length > 0;
34
+
35
+ this.availabilityCache.set(command, available);
36
+ return available;
37
+ }
38
+
39
+ /**
40
+ * Run a command with timeout and buffering
41
+ */
42
+ async run(command, args = [], options = {}) {
43
+ const timeout = options.timeout || 30000;
44
+ const maxBuffer = options.maxBuffer || 1024 * 1024; // 1MB default
45
+ const trackProcess = options.trackProcess !== false; // Default: true
46
+
47
+ return new Promise((resolve) => {
48
+ const proc = spawn(command, args, {
49
+ shell: false,
50
+ stdio: ['ignore', 'pipe', 'pipe']
51
+ });
52
+
53
+ // Track process for automatic cleanup (unless disabled for 'which' checks)
54
+ if (trackProcess && proc.pid) {
55
+ const commandName = `${command} ${args.slice(0, 2).join(' ')}`.substring(0, 50);
56
+ processManager.track(proc, { name: commandName });
57
+ }
58
+
59
+ let stdout = '';
60
+ let stderr = '';
61
+ let killed = false;
62
+ let timeoutId;
63
+
64
+ const cleanup = () => {
65
+ if (timeoutId) clearTimeout(timeoutId);
66
+ if (!proc.killed) proc.kill();
67
+ };
68
+
69
+ // Set timeout
70
+ if (timeout > 0) {
71
+ timeoutId = setTimeout(() => {
72
+ killed = true;
73
+ cleanup();
74
+ resolve({
75
+ ok: false,
76
+ stdout: '',
77
+ stderr: 'Command timed out',
78
+ error: { code: 'TIMEOUT', message: 'Command timed out' }
79
+ });
80
+ }, timeout);
81
+ }
82
+
83
+ // Capture stdout
84
+ proc.stdout.on('data', (chunk) => {
85
+ stdout += chunk.toString();
86
+ if (stdout.length > maxBuffer) {
87
+ killed = true;
88
+ cleanup();
89
+ resolve({
90
+ ok: false,
91
+ stdout: stdout.substring(0, maxBuffer),
92
+ stderr: 'Output exceeded maxBuffer',
93
+ error: { code: 'MAXBUFFER', message: 'Output exceeded maxBuffer' }
94
+ });
95
+ }
96
+ });
97
+
98
+ // Capture stderr
99
+ proc.stderr.on('data', (chunk) => {
100
+ stderr += chunk.toString();
101
+ if (stderr.length > maxBuffer) {
102
+ killed = true;
103
+ cleanup();
104
+ resolve({
105
+ ok: false,
106
+ stdout: '',
107
+ stderr: stderr.substring(0, maxBuffer),
108
+ error: { code: 'MAXBUFFER', message: 'Stderr exceeded maxBuffer' }
109
+ });
110
+ }
111
+ });
112
+
113
+ // Handle errors
114
+ proc.on('error', (error) => {
115
+ if (!killed) {
116
+ cleanup();
117
+ resolve({
118
+ ok: false,
119
+ stdout: '',
120
+ stderr: error.message,
121
+ error: { code: error.code || 'ERROR', message: error.message }
122
+ });
123
+ }
124
+ });
125
+
126
+ // Handle exit
127
+ proc.on('close', (code) => {
128
+ if (!killed) {
129
+ cleanup();
130
+ resolve({
131
+ ok: code === 0,
132
+ stdout,
133
+ stderr,
134
+ exitCode: code,
135
+ error: code !== 0 ? { code: 'EXITCODE', message: `Command exited with code ${code}` } : null
136
+ });
137
+ }
138
+ });
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Clear availability cache
144
+ */
145
+ clearCache() {
146
+ this.availabilityCache.clear();
147
+ }
148
+ }
@@ -0,0 +1,372 @@
1
+ /**
2
+ * DiffDetector
3
+ *
4
+ * Change detection between scan runs:
5
+ * - Compares fingerprints
6
+ * - Identifies infrastructure changes
7
+ * - Tracks attack surface evolution
8
+ * - Detects security posture changes
9
+ */
10
+
11
+ export class DiffDetector {
12
+ /**
13
+ * Detect changes between two reports
14
+ */
15
+ static detect(previousReport, currentReport) {
16
+ if (!previousReport || !currentReport) {
17
+ return null;
18
+ }
19
+
20
+ const diff = {
21
+ timestamp: new Date().toISOString(),
22
+ previousScan: previousReport.timestamp,
23
+ currentScan: currentReport.timestamp,
24
+ changes: {},
25
+ summary: {
26
+ totalChanges: 0,
27
+ severity: 'low'
28
+ }
29
+ };
30
+
31
+ // Detect changes in each stage
32
+ diff.changes.dns = this._detectDnsChanges(previousReport.results?.dns, currentReport.results?.dns);
33
+ diff.changes.certificate = this._detectCertificateChanges(
34
+ previousReport.results?.certificate,
35
+ currentReport.results?.certificate
36
+ );
37
+ diff.changes.ports = this._detectPortChanges(previousReport.results?.ports, currentReport.results?.ports);
38
+ diff.changes.subdomains = this._detectSubdomainChanges(
39
+ previousReport.results?.subdomains,
40
+ currentReport.results?.subdomains
41
+ );
42
+ diff.changes.paths = this._detectPathChanges(
43
+ previousReport.results?.webDiscovery,
44
+ currentReport.results?.webDiscovery
45
+ );
46
+ diff.changes.technologies = this._detectTechnologyChanges(
47
+ previousReport.results?.fingerprint,
48
+ currentReport.results?.fingerprint
49
+ );
50
+ diff.changes.security = this._detectSecurityChanges(
51
+ previousReport.results?.tlsAudit,
52
+ currentReport.results?.tlsAudit,
53
+ previousReport.results?.vulnerability,
54
+ currentReport.results?.vulnerability
55
+ );
56
+
57
+ // Calculate summary
58
+ diff.summary = this._calculateSummary(diff.changes);
59
+
60
+ return diff;
61
+ }
62
+
63
+ /**
64
+ * Detect DNS changes (IPs, nameservers, mail servers)
65
+ */
66
+ static _detectDnsChanges(oldDns, newDns) {
67
+ if (!oldDns || !newDns) return null;
68
+
69
+ const changes = {};
70
+
71
+ // IPv4 changes
72
+ const oldIPv4 = new Set(oldDns.records?.A || []);
73
+ const newIPv4 = new Set(newDns.records?.A || []);
74
+ const addedIPv4 = [...newIPv4].filter(ip => !oldIPv4.has(ip));
75
+ const removedIPv4 = [...oldIPv4].filter(ip => !newIPv4.has(ip));
76
+ if (addedIPv4.length > 0 || removedIPv4.length > 0) {
77
+ changes.ipv4 = { added: addedIPv4, removed: removedIPv4 };
78
+ }
79
+
80
+ // IPv6 changes
81
+ const oldIPv6 = new Set(oldDns.records?.AAAA || []);
82
+ const newIPv6 = new Set(newDns.records?.AAAA || []);
83
+ const addedIPv6 = [...newIPv6].filter(ip => !oldIPv6.has(ip));
84
+ const removedIPv6 = [...oldIPv6].filter(ip => !newIPv6.has(ip));
85
+ if (addedIPv6.length > 0 || removedIPv6.length > 0) {
86
+ changes.ipv6 = { added: addedIPv6, removed: removedIPv6 };
87
+ }
88
+
89
+ // Nameserver changes
90
+ const oldNS = new Set(oldDns.records?.NS || []);
91
+ const newNS = new Set(newDns.records?.NS || []);
92
+ const addedNS = [...newNS].filter(ns => !oldNS.has(ns));
93
+ const removedNS = [...oldNS].filter(ns => !newNS.has(ns));
94
+ if (addedNS.length > 0 || removedNS.length > 0) {
95
+ changes.nameservers = { added: addedNS, removed: removedNS };
96
+ }
97
+
98
+ // Mail server changes
99
+ const oldMX = new Set((oldDns.records?.MX || []).map(mx => mx.exchange));
100
+ const newMX = new Set((newDns.records?.MX || []).map(mx => mx.exchange));
101
+ const addedMX = [...newMX].filter(mx => !oldMX.has(mx));
102
+ const removedMX = [...oldMX].filter(mx => !newMX.has(mx));
103
+ if (addedMX.length > 0 || removedMX.length > 0) {
104
+ changes.mailServers = { added: addedMX, removed: removedMX };
105
+ }
106
+
107
+ return Object.keys(changes).length > 0 ? changes : null;
108
+ }
109
+
110
+ /**
111
+ * Detect certificate changes
112
+ */
113
+ static _detectCertificateChanges(oldCert, newCert) {
114
+ if (!oldCert || !newCert) return null;
115
+
116
+ const changes = {};
117
+
118
+ // Fingerprint change (certificate rotation)
119
+ if (oldCert.fingerprint !== newCert.fingerprint) {
120
+ changes.rotated = true;
121
+ changes.old = {
122
+ issuer: oldCert.issuer,
123
+ validFrom: oldCert.validFrom,
124
+ validTo: oldCert.validTo,
125
+ fingerprint: oldCert.fingerprint
126
+ };
127
+ changes.new = {
128
+ issuer: newCert.issuer,
129
+ validFrom: newCert.validFrom,
130
+ validTo: newCert.validTo,
131
+ fingerprint: newCert.fingerprint
132
+ };
133
+ }
134
+
135
+ // SAN changes
136
+ const oldSANs = new Set(oldCert.subjectAltName || []);
137
+ const newSANs = new Set(newCert.subjectAltName || []);
138
+ const addedSANs = [...newSANs].filter(san => !oldSANs.has(san));
139
+ const removedSANs = [...oldSANs].filter(san => !newSANs.has(san));
140
+ if (addedSANs.length > 0 || removedSANs.length > 0) {
141
+ changes.sans = { added: addedSANs, removed: removedSANs };
142
+ }
143
+
144
+ return Object.keys(changes).length > 0 ? changes : null;
145
+ }
146
+
147
+ /**
148
+ * Detect port changes
149
+ */
150
+ static _detectPortChanges(oldPorts, newPorts) {
151
+ if (!oldPorts || !newPorts) return null;
152
+
153
+ const oldPortSet = new Set((oldPorts.openPorts || []).map(p => p.port));
154
+ const newPortSet = new Set((newPorts.openPorts || []).map(p => p.port));
155
+
156
+ const added = [...newPortSet].filter(p => !oldPortSet.has(p));
157
+ const removed = [...oldPortSet].filter(p => !newPortSet.has(p));
158
+
159
+ if (added.length === 0 && removed.length === 0) {
160
+ return null;
161
+ }
162
+
163
+ return {
164
+ added: added.map(port => {
165
+ const portInfo = (newPorts.openPorts || []).find(p => p.port === port);
166
+ return portInfo || { port };
167
+ }),
168
+ removed: removed.map(port => {
169
+ const portInfo = (oldPorts.openPorts || []).find(p => p.port === port);
170
+ return portInfo || { port };
171
+ }),
172
+ total: {
173
+ added: added.length,
174
+ removed: removed.length
175
+ }
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Detect subdomain changes
181
+ */
182
+ static _detectSubdomainChanges(oldSubs, newSubs) {
183
+ if (!oldSubs || !newSubs) return null;
184
+
185
+ const oldSet = new Set(oldSubs.list || []);
186
+ const newSet = new Set(newSubs.list || []);
187
+
188
+ const added = [...newSet].filter(s => !oldSet.has(s));
189
+ const removed = [...oldSet].filter(s => !newSet.has(s));
190
+
191
+ if (added.length === 0 && removed.length === 0) {
192
+ return null;
193
+ }
194
+
195
+ return {
196
+ added,
197
+ removed,
198
+ total: {
199
+ added: added.length,
200
+ removed: removed.length,
201
+ previous: oldSubs.total || 0,
202
+ current: newSubs.total || 0
203
+ }
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Detect discovered path changes
209
+ */
210
+ static _detectPathChanges(oldWeb, newWeb) {
211
+ if (!oldWeb || !newWeb) return null;
212
+
213
+ // Extract paths from tools
214
+ const extractPaths = (webData) => {
215
+ const paths = new Set();
216
+ Object.values(webData.tools || {}).forEach(tool => {
217
+ if (tool.status === 'ok' && tool.paths) {
218
+ tool.paths.forEach(path => paths.add(path));
219
+ }
220
+ });
221
+ return paths;
222
+ };
223
+
224
+ const oldPaths = extractPaths(oldWeb);
225
+ const newPaths = extractPaths(newWeb);
226
+
227
+ const added = [...newPaths].filter(p => !oldPaths.has(p));
228
+ const removed = [...oldPaths].filter(p => !newPaths.has(p));
229
+
230
+ if (added.length === 0 && removed.length === 0) {
231
+ return null;
232
+ }
233
+
234
+ return {
235
+ added,
236
+ removed,
237
+ total: {
238
+ added: added.length,
239
+ removed: removed.length,
240
+ previous: oldPaths.size,
241
+ current: newPaths.size
242
+ }
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Detect technology changes
248
+ */
249
+ static _detectTechnologyChanges(oldFP, newFP) {
250
+ if (!oldFP || !newFP) return null;
251
+
252
+ const oldTech = new Set(oldFP.technologies || []);
253
+ const newTech = new Set(newFP.technologies || []);
254
+
255
+ const added = [...newTech].filter(t => !oldTech.has(t));
256
+ const removed = [...oldTech].filter(t => !newTech.has(t));
257
+
258
+ if (added.length === 0 && removed.length === 0) {
259
+ return null;
260
+ }
261
+
262
+ return {
263
+ added,
264
+ removed,
265
+ total: {
266
+ added: added.length,
267
+ removed: removed.length
268
+ }
269
+ };
270
+ }
271
+
272
+ /**
273
+ * Detect security changes (TLS, vulnerabilities)
274
+ */
275
+ static _detectSecurityChanges(oldTLS, newTLS, oldVuln, newVuln) {
276
+ const changes = {};
277
+
278
+ // TLS changes
279
+ if (oldTLS && newTLS) {
280
+ const oldGrade = oldTLS.grade || 'unknown';
281
+ const newGrade = newTLS.grade || 'unknown';
282
+ if (oldGrade !== newGrade) {
283
+ changes.tlsGrade = { old: oldGrade, new: newGrade };
284
+ }
285
+ }
286
+
287
+ // Vulnerability changes
288
+ if (oldVuln && newVuln) {
289
+ const oldVulnCount = this._countVulnerabilities(oldVuln);
290
+ const newVulnCount = this._countVulnerabilities(newVuln);
291
+ if (oldVulnCount !== newVulnCount) {
292
+ changes.vulnerabilities = {
293
+ old: oldVulnCount,
294
+ new: newVulnCount,
295
+ delta: newVulnCount - oldVulnCount
296
+ };
297
+ }
298
+ }
299
+
300
+ return Object.keys(changes).length > 0 ? changes : null;
301
+ }
302
+
303
+ /**
304
+ * Count vulnerabilities from scan results
305
+ */
306
+ static _countVulnerabilities(vulnData) {
307
+ let count = 0;
308
+ Object.values(vulnData.tools || {}).forEach(tool => {
309
+ if (tool.status === 'ok' && tool.vulnerabilities) {
310
+ count += tool.vulnerabilities.length;
311
+ }
312
+ });
313
+ return count;
314
+ }
315
+
316
+ /**
317
+ * Calculate summary of changes
318
+ */
319
+ static _calculateSummary(changes) {
320
+ let totalChanges = 0;
321
+ let severity = 'low';
322
+
323
+ // Count changes
324
+ if (changes.dns) {
325
+ totalChanges += (changes.dns.ipv4?.added?.length || 0) + (changes.dns.ipv4?.removed?.length || 0);
326
+ totalChanges += (changes.dns.ipv6?.added?.length || 0) + (changes.dns.ipv6?.removed?.length || 0);
327
+ totalChanges += (changes.dns.nameservers?.added?.length || 0) + (changes.dns.nameservers?.removed?.length || 0);
328
+ totalChanges += (changes.dns.mailServers?.added?.length || 0) + (changes.dns.mailServers?.removed?.length || 0);
329
+ }
330
+
331
+ if (changes.certificate?.rotated) {
332
+ totalChanges += 1;
333
+ severity = 'medium'; // Certificate rotation is notable
334
+ }
335
+
336
+ if (changes.ports) {
337
+ const portChanges = (changes.ports.added?.length || 0) + (changes.ports.removed?.length || 0);
338
+ totalChanges += portChanges;
339
+ if (changes.ports.added?.length > 0) {
340
+ severity = 'high'; // New open ports are critical
341
+ }
342
+ }
343
+
344
+ if (changes.subdomains) {
345
+ totalChanges += (changes.subdomains.added?.length || 0) + (changes.subdomains.removed?.length || 0);
346
+ if (changes.subdomains.added?.length > 10) {
347
+ severity = 'medium'; // Many new subdomains
348
+ }
349
+ }
350
+
351
+ if (changes.paths) {
352
+ totalChanges += (changes.paths.added?.length || 0) + (changes.paths.removed?.length || 0);
353
+ }
354
+
355
+ if (changes.technologies) {
356
+ totalChanges += (changes.technologies.added?.length || 0) + (changes.technologies.removed?.length || 0);
357
+ }
358
+
359
+ if (changes.security?.vulnerabilities?.delta > 0) {
360
+ severity = 'critical'; // New vulnerabilities are critical
361
+ totalChanges += changes.security.vulnerabilities.delta;
362
+ }
363
+
364
+ return {
365
+ totalChanges,
366
+ severity,
367
+ hasInfrastructureChanges: !!(changes.dns || changes.certificate),
368
+ hasAttackSurfaceChanges: !!(changes.ports || changes.subdomains || changes.paths),
369
+ hasSecurityChanges: !!changes.security
370
+ };
371
+ }
372
+ }