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,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
|
+
}
|