@slicedearth/whoisleuth-cli 1.47.4 → 2.0.1
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 +74 -177
- package/SECURITY.md +6 -3
- package/bin/whoisleuth.mjs +2 -1
- package/cli/archive-inspect.mjs +28 -10
- package/cli/arguments.mjs +204 -174
- package/cli/artifact-structure.mjs +837 -163
- package/cli/artifact-verify.mjs +90 -61
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +33 -20
- package/cli/bulk.mjs +9 -2
- package/cli/case-pack.mjs +180 -576
- package/cli/ci-report.mjs +6 -1
- package/cli/collection-preflight.mjs +2 -2
- package/cli/command-catalogue.mjs +12 -3
- package/cli/command-reference.mjs +910 -92
- package/cli/completion.mjs +504 -226
- package/cli/config-profile.mjs +13 -3
- package/cli/discover.mjs +7 -1
- package/cli/discovery-observation-snapshot.mjs +24 -28
- package/cli/discovery-scan-command-runner.mjs +9 -9
- package/cli/discovery-scan.mjs +17 -3
- package/cli/discovery-snapshot.mjs +15 -7
- package/cli/domain-control-monitor.mjs +203 -30
- package/cli/domain-control-observations.mjs +15 -21
- package/cli/errors.mjs +8 -3
- package/cli/evidence-signing.mjs +10 -20
- package/cli/external-observation-mapping.mjs +3 -3
- package/cli/formatters/json.mjs +20 -32
- package/cli/formatters/terminal.mjs +583 -22
- package/cli/input.mjs +7 -2
- package/cli/interactive-launcher.mjs +226 -0
- package/cli/interchange-report.mjs +19 -19
- package/cli/investigation-manifest.mjs +1 -1
- package/cli/investigation-plan.mjs +167 -16
- package/cli/investigation-run.mjs +9 -1
- package/cli/lookalike-calibration.mjs +3 -1
- package/cli/lookup-brief.mjs +13 -8
- package/cli/lookup-browser.mjs +844 -0
- package/cli/lookup-command-runner.mjs +94 -31
- package/cli/lookup-diff.mjs +2 -2
- package/cli/lookup-plan.mjs +1 -0
- package/cli/mail-review.mjs +206 -94
- package/cli/manual.mjs +5 -5
- package/cli/offline-evidence-review.mjs +18 -7
- package/cli/open-asset-model-bridge.mjs +1 -1
- package/cli/output-file.mjs +70 -14
- package/cli/page-compare.mjs +11 -6
- package/cli/progress-events.mjs +2 -1
- package/cli/progress.mjs +3 -3
- package/cli/registry-cohort.mjs +9 -51
- package/cli/registry-doctor.mjs +67 -28
- package/cli/retained-artifact-diff.mjs +7 -8
- package/cli/risk-calibration.mjs +85 -38
- package/cli/runner.mjs +189 -75
- package/cli/saved-lookup.mjs +424 -57
- package/cli/sharing-review.mjs +6 -1
- package/cli/source-reliability.mjs +75 -36
- package/cli/terminal-presentation.mjs +43 -18
- package/docs/cli-reference.md +125 -1708
- package/docs/cli.md +86 -254
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -145
- package/frontend/src/lib/analysis/domain-control-records.js +3 -88
- package/lib/availability.mjs +58 -40
- package/lib/bounded-contract-normalizers.mjs +2 -4
- package/lib/bounded-file.mjs +78 -4
- package/lib/bounded-json.mjs +51 -13
- package/lib/browser-library-profile.mjs +1 -1
- package/lib/classify.mjs +56 -5
- package/lib/client-behavior-profile.mjs +1 -1
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/cryptographic-assurance.mjs +35 -17
- package/lib/ct-query.mjs +3 -3
- package/lib/ct-search.mjs +37 -21
- package/lib/distributed-operation-budget.mjs +4 -2
- package/lib/dns-delegation-health.mjs +9 -6
- package/lib/dns-intelligence.mjs +1 -1
- package/lib/dnssec-chain-validation.mjs +2 -3
- package/lib/dnssec-evidence-validation.mjs +13 -7
- package/lib/domain-assurance.mjs +12 -12
- package/lib/domain-change-packet.mjs +1 -1
- package/lib/domain-control-flight-recorder.mjs +442 -78
- package/lib/domain-control-manifest.mjs +314 -59
- package/lib/domain-posture-analysis.mjs +5 -2
- package/lib/encrypted-dns-contract.mjs +2 -3
- package/lib/evidence-export.mjs +518 -36
- package/lib/external-intelligence-risk.mjs +26 -10
- package/lib/feature-policy.mjs +1 -1
- package/lib/homepage-metadata-contract.mjs +290 -0
- package/lib/hostname.mjs +1 -1
- package/lib/html-signals.mjs +89 -5
- package/lib/http-intelligence.mjs +331 -2
- package/lib/interchange-fidelity-registry.mjs +28 -26
- package/lib/lookup-cache.mjs +6 -4
- package/lib/lookup.mjs +25 -2
- package/lib/observed-network-context.mjs +1 -1
- package/lib/page-fingerprints.mjs +2 -2
- package/lib/page-role-profile.mjs +1 -1
- package/lib/portable-generator.mjs +8 -0
- package/lib/project-metadata.mjs +1 -1
- package/lib/provider-policy-admission.mjs +3 -1
- package/lib/rdap-bootstrap.mjs +76 -16
- package/lib/rdap-client.mjs +20 -12
- package/lib/rdap-normalization.mjs +306 -75
- package/lib/rdap-transport.mjs +2 -2
- package/lib/rdap-validation.mjs +65 -14
- package/lib/rdap.mjs +2 -2
- package/lib/registrable-domain.mjs +36 -0
- package/lib/registry-insights.mjs +7 -0
- package/lib/response-policy.mjs +27 -4
- package/lib/risk-calibration-summary.mjs +35 -33
- package/lib/risk-scoring.mjs +4 -3
- package/lib/safe-fetch.mjs +62 -40
- package/lib/security-txt.mjs +1 -1
- package/lib/smtp-transport-review.mjs +8 -5
- package/lib/sslbl-intelligence.mjs +6 -8
- package/lib/static-html-analysis.mjs +301 -4
- package/lib/structured-data-identity.mjs +51 -19
- package/lib/threat-intelligence-fixtures.mjs +2 -4
- package/lib/threat-intelligence-manifests.mjs +2 -7
- package/lib/threat-intelligence-runtime.mjs +3 -5
- package/lib/threat-intelligence-types.mjs +3 -0
- package/lib/threatfox-intelligence.mjs +7 -3
- package/lib/tls-intelligence.mjs +4 -4
- package/lib/urlhaus-intelligence.mjs +4 -2
- package/lib/urlscan-intelligence.mjs +6 -5
- package/lib/website-security-posture.mjs +1 -1
- package/lib/website-technology.mjs +1 -1
- package/lib/whois-authority.mjs +43 -8
- package/lib/whois-chain.mjs +25 -3
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-transport.mjs +12 -3
- package/lib/zone-intent-review.mjs +1 -1
- package/package.json +13 -5
- package/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
- package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
- package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
- package/packages/cases/case-migration-model.mjs +476 -0
- package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
- package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -21
- package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
- package/packages/cases/case-record-model.mjs +9 -0
- package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +72 -29
- package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
- package/packages/cases/case-response-model.mjs +1604 -0
- package/packages/cases/case-response-packet.mjs +1312 -0
- package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
- package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
- package/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
- package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
- package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
- package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -0
- package/packages/comparison/favicon-similarity.mjs +78 -0
- package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
- package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
- package/packages/contracts/analyst-review-state-contract.mjs +61 -0
- package/packages/contracts/analyst-review-state.mjs +50 -0
- package/packages/contracts/case-portability.mjs +1310 -0
- package/packages/contracts/cli-command-catalogue.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +394 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
- package/packages/contracts/domain-control-manifest.mjs +1027 -0
- package/packages/contracts/domain-control-monitor.mjs +310 -0
- package/packages/contracts/domain-control-review.mjs +828 -0
- package/packages/contracts/domain-name.mjs +4 -0
- package/packages/contracts/external-observation-interchange.mjs +224 -0
- package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
- package/packages/contracts/investigation-portability.mjs +331 -0
- package/packages/contracts/offline-comparison.mjs +160 -0
- package/packages/contracts/risk-calibration.mjs +1288 -0
- package/packages/contracts/schema-compatibility.mjs +71 -0
- package/packages/contracts/schema-lifecycle.mjs +1702 -0
- package/packages/contracts/sslbl-snapshot.mjs +17 -0
- package/packages/contracts/workspace-portability.mjs +1347 -0
- package/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
- package/packages/evidence/decision-fact.mjs +735 -0
- package/packages/evidence/domain-control-runtime.mjs +547 -0
- package/packages/evidence/domain-name.mjs +41 -0
- package/packages/evidence/observation.mjs +384 -0
- package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +24 -22
- package/packages/interchange/retained-text.mjs +8 -0
- package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
- package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
- package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
- package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
- package/packages/investigation/bulk-review-policy.mjs +1 -0
- package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
- package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
- package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
- package/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
- package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
- package/packages/investigation/lookup-investigation-brief.mjs +172 -0
- package/packages/monitoring/analyst-review-state.mjs +680 -0
- package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +71 -48
- package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
- package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
- package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -5
- package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
- package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
- package/packages/workspace/hostile-input.mjs +172 -0
- package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +8 -45
- package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
- package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
- package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
- package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
- package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
- package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
- package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +13 -13
- package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
- package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +110 -89
- package/third-party-notices.txt +14 -14
- package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
- package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
- package/frontend/src/lib/analysis/case-migration-model.js +0 -305
- package/frontend/src/lib/analysis/case-record-model.js +0 -9
- package/frontend/src/lib/analysis/case-response-model.js +0 -728
- package/frontend/src/lib/analysis/case-response-packet.js +0 -583
- package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
- package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
- package/frontend/src/lib/analysis/lookup-response.js +0 -2
- package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
- package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
- package/frontend/src/lib/analysis/scoring.js +0 -55
- package/frontend/src/lib/analysis/utils.js +0 -255
- package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
- package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
- package/frontend/src/lib/analysis/visualization-matrix.js +0 -131
- package/frontend/src/lib/analysis/visualization-models.js +0 -338
- package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
- package/lib/bounded-json-response.mjs +0 -170
- package/lib/lookup-response-contract.mjs +0 -1034
- package/lib/observation.mjs +0 -115
- /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
- /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
- /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
- /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
package/lib/rdap-bootstrap.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// IANA RDAP bootstrap retrieval, bounded stale caching, and authority
|
|
2
2
|
// selection for domain, IP, and ASN queries.
|
|
3
|
+
import net from 'node:net';
|
|
3
4
|
import { fetchRdapDetailedWithTimeout, } from "./rdap-transport.mjs";
|
|
4
5
|
const BOOTSTRAP_TTL_MS = 60 * 60 * 1000;
|
|
5
6
|
const BOOTSTRAP_STALE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -8,29 +9,71 @@ const BOOTSTRAP_FETCH_TIMEOUT_MS = 7000;
|
|
|
8
9
|
const MAX_RDAP_ENDPOINT_LENGTH = 2048;
|
|
9
10
|
const bootstrapCache = new Map();
|
|
10
11
|
const bootstrapInflight = new Map();
|
|
12
|
+
function admitRdapEndpoint(value, options = {}) {
|
|
13
|
+
if (typeof value !== 'string'
|
|
14
|
+
|| !value
|
|
15
|
+
|| value !== value.trim()
|
|
16
|
+
|| value.length > MAX_RDAP_ENDPOINT_LENGTH
|
|
17
|
+
|| /[\u0000-\u001f\u007f]/u.test(value))
|
|
18
|
+
return null;
|
|
19
|
+
try {
|
|
20
|
+
const parsed = new URL(value);
|
|
21
|
+
if (!['http:', 'https:'].includes(parsed.protocol)
|
|
22
|
+
|| !parsed.hostname
|
|
23
|
+
|| parsed.username
|
|
24
|
+
|| parsed.password
|
|
25
|
+
|| parsed.port
|
|
26
|
+
|| parsed.hash
|
|
27
|
+
|| (!options.allowQuery && parsed.search))
|
|
28
|
+
return null;
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
11
35
|
function ipv4ToLong(ip) {
|
|
12
36
|
return ip.split('.').reduce((accumulator, octet) => (accumulator << 8) + (parseInt(octet, 10) & 0xff), 0) >>> 0;
|
|
13
37
|
}
|
|
14
38
|
function ipInCidrV4(ip, cidr) {
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
39
|
+
const parts = cidr.split('/');
|
|
40
|
+
if (parts.length > 2)
|
|
41
|
+
return false;
|
|
42
|
+
const [range, bitsString] = parts;
|
|
43
|
+
if (!range || net.isIP(ip) !== 4 || net.isIP(range) !== 4)
|
|
44
|
+
return false;
|
|
45
|
+
const bits = bitsString === undefined || /^\d{1,2}$/u.test(bitsString)
|
|
46
|
+
? Number(bitsString ?? 32)
|
|
47
|
+
: -1;
|
|
48
|
+
if (!Number.isSafeInteger(bits) || bits < 0 || bits > 32)
|
|
17
49
|
return false;
|
|
18
|
-
const bits = bitsString !== undefined ? parseInt(bitsString, 10) : 32;
|
|
19
50
|
const mask = bits === 0 ? 0 : (0xffffffff << (32 - bits)) >>> 0;
|
|
20
51
|
return (ipv4ToLong(ip) & mask) === (ipv4ToLong(range) & mask);
|
|
21
52
|
}
|
|
22
53
|
function expandIpv6(ip) {
|
|
23
|
-
let
|
|
54
|
+
let normalized = ip;
|
|
55
|
+
if (normalized.includes('.')) {
|
|
56
|
+
const lastColon = normalized.lastIndexOf(':');
|
|
57
|
+
const embedded = normalized.slice(lastColon + 1);
|
|
58
|
+
if (lastColon < 0 || net.isIP(embedded) !== 4) {
|
|
59
|
+
throw new TypeError('Invalid IPv4-embedded IPv6 address.');
|
|
60
|
+
}
|
|
61
|
+
const value = ipv4ToLong(embedded);
|
|
62
|
+
normalized = `${normalized.slice(0, lastColon)}:${(value >>> 16).toString(16)}:${(value & 0xffff).toString(16)}`;
|
|
63
|
+
}
|
|
64
|
+
if (net.isIP(normalized) !== 6)
|
|
65
|
+
throw new TypeError('Invalid IPv6 address.');
|
|
66
|
+
let head = normalized;
|
|
24
67
|
let tail = '';
|
|
25
|
-
if (
|
|
26
|
-
const [headPart = '', tailPart = ''] =
|
|
68
|
+
if (normalized.includes('::')) {
|
|
69
|
+
const [headPart = '', tailPart = ''] = normalized.split('::');
|
|
27
70
|
head = headPart;
|
|
28
71
|
tail = tailPart;
|
|
29
72
|
}
|
|
30
73
|
const headParts = head ? head.split(':').filter(Boolean) : [];
|
|
31
74
|
const tailParts = tail ? tail.split(':').filter(Boolean) : [];
|
|
32
75
|
const missing = 8 - headParts.length - tailParts.length;
|
|
33
|
-
const parts =
|
|
76
|
+
const parts = normalized.includes('::')
|
|
34
77
|
? [...headParts, ...Array(Math.max(missing, 0)).fill('0'), ...tailParts]
|
|
35
78
|
: headParts;
|
|
36
79
|
while (parts.length < 8)
|
|
@@ -41,13 +84,25 @@ function ipv6ToBigInt(ip) {
|
|
|
41
84
|
return expandIpv6(ip).reduce((accumulator, part) => (accumulator << 16n) + BigInt(parseInt(part || '0', 16)), 0n);
|
|
42
85
|
}
|
|
43
86
|
function ipInCidrV6(ip, cidr) {
|
|
44
|
-
const
|
|
87
|
+
const parts = cidr.split('/');
|
|
88
|
+
if (parts.length > 2)
|
|
89
|
+
return false;
|
|
90
|
+
const [range, bitsString] = parts;
|
|
45
91
|
if (!range)
|
|
46
92
|
return false;
|
|
47
|
-
const bits = bitsString
|
|
93
|
+
const bits = bitsString === undefined || /^\d{1,3}$/u.test(bitsString)
|
|
94
|
+
? Number(bitsString ?? 128)
|
|
95
|
+
: -1;
|
|
96
|
+
if (!Number.isSafeInteger(bits) || bits < 0 || bits > 128)
|
|
97
|
+
return false;
|
|
48
98
|
const full = (1n << 128n) - 1n;
|
|
49
99
|
const mask = bits === 0 ? 0n : (full << BigInt(128 - bits)) & full;
|
|
50
|
-
|
|
100
|
+
try {
|
|
101
|
+
return (ipv6ToBigInt(ip) & mask) === (ipv6ToBigInt(range) & mask);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
51
106
|
}
|
|
52
107
|
function validBootstrap(data) {
|
|
53
108
|
const record = data && typeof data === 'object' && !Array.isArray(data)
|
|
@@ -74,7 +129,12 @@ async function fetchBootstrap(kind, options = {}) {
|
|
|
74
129
|
return inflight;
|
|
75
130
|
const request = (async () => {
|
|
76
131
|
try {
|
|
77
|
-
const
|
|
132
|
+
const requestedEndpoint = `https://data.iana.org/rdap/${kind}.json`;
|
|
133
|
+
const response = await fetchUpstream(requestedEndpoint, {}, BOOTSTRAP_FETCH_TIMEOUT_MS);
|
|
134
|
+
const finalEndpoint = admitRdapEndpoint(response.finalUrl ?? requestedEndpoint);
|
|
135
|
+
if (finalEndpoint !== requestedEndpoint) {
|
|
136
|
+
throw new Error(`IANA bootstrap redirected outside its fixed source endpoint for ${kind}`);
|
|
137
|
+
}
|
|
78
138
|
if (!response.ok) {
|
|
79
139
|
throw new Error(`IANA bootstrap fetch failed for ${kind} (${response.status})`);
|
|
80
140
|
}
|
|
@@ -112,10 +172,10 @@ function clearRdapBootstrapCache() {
|
|
|
112
172
|
function uniqueRdapBases(urls) {
|
|
113
173
|
const seen = new Set();
|
|
114
174
|
return (Array.isArray(urls) ? urls : [])
|
|
115
|
-
.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
175
|
+
.flatMap((url) => {
|
|
176
|
+
const admitted = admitRdapEndpoint(url);
|
|
177
|
+
return admitted ? [admitted] : [];
|
|
178
|
+
})
|
|
119
179
|
.sort((left, right) => Number(/^http:\/\//i.test(left)) - Number(/^http:\/\//i.test(right)))
|
|
120
180
|
.filter((url) => {
|
|
121
181
|
const key = url.replace(/\/$/, '').toLowerCase();
|
|
@@ -170,4 +230,4 @@ async function findRdapBases(type, value) {
|
|
|
170
230
|
}
|
|
171
231
|
return [];
|
|
172
232
|
}
|
|
173
|
-
export { BOOTSTRAP_STALE_TTL_MS, BOOTSTRAP_TTL_MS, MAX_RDAP_ENDPOINT_LENGTH, clearRdapBootstrapCache, fetchBootstrap, findRdapBases, ipv4ToLong, ipv6ToBigInt, uniqueRdapBases, };
|
|
233
|
+
export { BOOTSTRAP_STALE_TTL_MS, BOOTSTRAP_TTL_MS, MAX_RDAP_ENDPOINT_LENGTH, admitRdapEndpoint, clearRdapBootstrapCache, fetchBootstrap, findRdapBases, ipv4ToLong, ipv6ToBigInt, uniqueRdapBases, };
|
package/lib/rdap-client.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { cached } from "./lookup-cache.mjs";
|
|
2
2
|
import { findRdapBases, uniqueRdapBases, } from "./rdap-bootstrap.mjs";
|
|
3
3
|
import { rdapAttempt, rdapFailure } from "./rdap-attempts.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { validateRdapResponse } from "./rdap-validation.mjs";
|
|
4
|
+
import { fetchRdapDetailedWithTimeout, } from "./rdap-transport.mjs";
|
|
5
|
+
import { admitRdapObjectEndpoint, validateRdapResponse, } from "./rdap-validation.mjs";
|
|
6
6
|
const UPSTREAM_TIMEOUT_MS = 7_000;
|
|
7
7
|
const UPSTREAM_TOTAL_DEADLINE_MS = 12_000;
|
|
8
8
|
const MAX_RDAP_ENDPOINTS = 3;
|
|
@@ -24,7 +24,7 @@ function rdapPathFor(type, value) {
|
|
|
24
24
|
* Query the bounded bootstrap candidate set while retaining every attempted
|
|
25
25
|
* endpoint as separately attributed transport evidence.
|
|
26
26
|
*/
|
|
27
|
-
export async function fetchRdapFromBasesWithParser(type, value, bases, parseRdap, fetchUpstream =
|
|
27
|
+
export async function fetchRdapFromBasesWithParser(type, value, bases, parseRdap, fetchUpstream = fetchRdapDetailedWithTimeout) {
|
|
28
28
|
const candidates = uniqueRdapBases(bases).slice(0, MAX_RDAP_ENDPOINTS);
|
|
29
29
|
if (candidates.length === 0)
|
|
30
30
|
return null;
|
|
@@ -38,13 +38,21 @@ export async function fetchRdapFromBasesWithParser(type, value, bases, parseRdap
|
|
|
38
38
|
const url = `${base.replace(/\/$/, '')}/${rdapPathFor(type, value)}`;
|
|
39
39
|
try {
|
|
40
40
|
const upstream = await fetchUpstream(url, { headers: { Accept: 'application/rdap+json' } }, Math.min(UPSTREAM_TIMEOUT_MS, remaining));
|
|
41
|
+
const selectedEndpoint = admitRdapObjectEndpoint(type, value, upstream.finalUrl ?? url);
|
|
42
|
+
if (!selectedEndpoint) {
|
|
43
|
+
attempts.push(rdapAttempt(url, 'invalid_response', {
|
|
44
|
+
status: upstream.status,
|
|
45
|
+
detail: 'The endpoint returned final URL provenance for an invalid or different RDAP object.',
|
|
46
|
+
}));
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
41
49
|
if (upstream.status !== 404 && !upstream.ok) {
|
|
42
50
|
const outcome = upstream.status === 429
|
|
43
51
|
? 'rate_limited'
|
|
44
52
|
: upstream.status >= 500
|
|
45
53
|
? 'server_error'
|
|
46
54
|
: 'client_error';
|
|
47
|
-
attempts.push(rdapAttempt(
|
|
55
|
+
attempts.push(rdapAttempt(selectedEndpoint, outcome, {
|
|
48
56
|
status: upstream.status,
|
|
49
57
|
detail: `The endpoint returned HTTP ${upstream.status}.`,
|
|
50
58
|
}));
|
|
@@ -55,21 +63,21 @@ export async function fetchRdapFromBasesWithParser(type, value, bases, parseRdap
|
|
|
55
63
|
data = JSON.parse(upstream.text);
|
|
56
64
|
}
|
|
57
65
|
catch {
|
|
58
|
-
attempts.push(rdapAttempt(
|
|
66
|
+
attempts.push(rdapAttempt(selectedEndpoint, 'invalid_json', {
|
|
59
67
|
status: upstream.status,
|
|
60
68
|
detail: 'The endpoint returned invalid JSON.',
|
|
61
69
|
}));
|
|
62
70
|
continue;
|
|
63
71
|
}
|
|
64
72
|
if (upstream.status === 404) {
|
|
65
|
-
attempts.push(rdapAttempt(
|
|
73
|
+
attempts.push(rdapAttempt(selectedEndpoint, 'not_found', {
|
|
66
74
|
status: upstream.status,
|
|
67
75
|
detail: 'The authoritative endpoint reported no matching object.',
|
|
68
76
|
selected: true,
|
|
69
77
|
}));
|
|
70
78
|
return {
|
|
71
|
-
rdapServer:
|
|
72
|
-
transportSecurity: /^https:\/\//i.test(
|
|
79
|
+
rdapServer: selectedEndpoint,
|
|
80
|
+
transportSecurity: /^https:\/\//i.test(selectedEndpoint) ? 'https' : 'http',
|
|
73
81
|
upstreamStatus: upstream.status,
|
|
74
82
|
fetchedAt: new Date().toISOString(),
|
|
75
83
|
data,
|
|
@@ -80,20 +88,20 @@ export async function fetchRdapFromBasesWithParser(type, value, bases, parseRdap
|
|
|
80
88
|
const parsed = parseRdap(type, data);
|
|
81
89
|
const validation = validateRdapResponse(type, value, parsed);
|
|
82
90
|
if (!validation.valid) {
|
|
83
|
-
attempts.push(rdapAttempt(
|
|
91
|
+
attempts.push(rdapAttempt(selectedEndpoint, 'invalid_response', {
|
|
84
92
|
status: upstream.status,
|
|
85
93
|
detail: validation.detail,
|
|
86
94
|
}));
|
|
87
95
|
continue;
|
|
88
96
|
}
|
|
89
|
-
attempts.push(rdapAttempt(
|
|
97
|
+
attempts.push(rdapAttempt(selectedEndpoint, 'success', {
|
|
90
98
|
status: upstream.status,
|
|
91
99
|
detail: 'The endpoint returned the requested RDAP object.',
|
|
92
100
|
selected: true,
|
|
93
101
|
}));
|
|
94
102
|
return {
|
|
95
|
-
rdapServer:
|
|
96
|
-
transportSecurity: /^https:\/\//i.test(
|
|
103
|
+
rdapServer: selectedEndpoint,
|
|
104
|
+
transportSecurity: /^https:\/\//i.test(selectedEndpoint) ? 'https' : 'http',
|
|
97
105
|
upstreamStatus: upstream.status,
|
|
98
106
|
fetchedAt: new Date().toISOString(),
|
|
99
107
|
data,
|