@slicedearth/whoisleuth-cli 1.32.0
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/LICENSE +661 -0
- package/LICENSES/Retire.js-Apache-2.0.txt +199 -0
- package/NOTICE +76 -0
- package/README.md +84 -0
- package/TRADEMARKS.md +41 -0
- package/bin/whoisleuth.mjs +35 -0
- package/cli/archive-inspect.mjs +198 -0
- package/cli/arguments.mjs +1022 -0
- package/cli/artifact-verify.mjs +210 -0
- package/cli/bulk-checkpoint.mjs +218 -0
- package/cli/bulk-command-runner.mjs +109 -0
- package/cli/bulk-output.mjs +112 -0
- package/cli/bulk.mjs +133 -0
- package/cli/compare.mjs +238 -0
- package/cli/completion.mjs +175 -0
- package/cli/csv.mjs +1 -0
- package/cli/discover.mjs +50 -0
- package/cli/discovery-command-runner.mjs +36 -0
- package/cli/discovery-observation-snapshot.mjs +315 -0
- package/cli/discovery-scan-command-runner.mjs +141 -0
- package/cli/discovery-scan.mjs +325 -0
- package/cli/discovery-snapshot.mjs +133 -0
- package/cli/discovery-workflow.mjs +73 -0
- package/cli/doctor.mjs +137 -0
- package/cli/errors.mjs +21 -0
- package/cli/evidence-command-arguments.mjs +145 -0
- package/cli/evidence-command-runner.mjs +106 -0
- package/cli/evidence-signing.mjs +215 -0
- package/cli/exit-codes.mjs +9 -0
- package/cli/export-evidence.mjs +28 -0
- package/cli/formatters/evidence-report.mjs +3 -0
- package/cli/formatters/html.mjs +69 -0
- package/cli/formatters/json.mjs +135 -0
- package/cli/formatters/markdown.mjs +3 -0
- package/cli/formatters/terminal.mjs +658 -0
- package/cli/http.mjs +27 -0
- package/cli/lookup-command-runner.mjs +87 -0
- package/cli/lookup-diff.mjs +171 -0
- package/cli/mail-review.mjs +204 -0
- package/cli/manual.mjs +34 -0
- package/cli/network-command-runner.mjs +88 -0
- package/cli/output-file.mjs +97 -0
- package/cli/page-compare.mjs +123 -0
- package/cli/posture.mjs +20 -0
- package/cli/progress-events.mjs +53 -0
- package/cli/progress.mjs +56 -0
- package/cli/registry-access.mjs +3 -0
- package/cli/registry-support.mjs +120 -0
- package/cli/risk-calibration.mjs +294 -0
- package/cli/runner-types.mjs +1 -0
- package/cli/runner.mjs +726 -0
- package/cli/saved-lookup.mjs +87 -0
- package/cli/source-reliability.mjs +727 -0
- package/cli/strict-exit.mjs +55 -0
- package/cli/terminal-presentation.mjs +147 -0
- package/docs/cli.md +827 -0
- package/frontend/src/lib/analysis/acquisition-decision-packet.js +93 -0
- package/frontend/src/lib/analysis/acquisition-due-diligence.js +391 -0
- package/frontend/src/lib/analysis/artifact-integrity.js +21 -0
- package/frontend/src/lib/analysis/brand-profile-model.js +421 -0
- package/frontend/src/lib/analysis/bulk-domain-comparison.js +246 -0
- package/frontend/src/lib/analysis/bulk-mail-exposure.js +192 -0
- package/frontend/src/lib/analysis/bulk-retry-plan.js +86 -0
- package/frontend/src/lib/analysis/bulk-review-export.js +62 -0
- package/frontend/src/lib/analysis/bulk-review-model.js +266 -0
- package/frontend/src/lib/analysis/bulk-session-model.js +515 -0
- package/frontend/src/lib/analysis/bulk-sort.js +49 -0
- package/frontend/src/lib/analysis/campaign-model.js +265 -0
- package/frontend/src/lib/analysis/case-evidence-model.js +642 -0
- package/frontend/src/lib/analysis/case-migration-model.js +248 -0
- package/frontend/src/lib/analysis/case-model.js +5 -0
- package/frontend/src/lib/analysis/case-record-contracts.js +44 -0
- package/frontend/src/lib/analysis/case-record-core.js +232 -0
- package/frontend/src/lib/analysis/case-record-model.js +7 -0
- package/frontend/src/lib/analysis/case-record-operations.js +224 -0
- package/frontend/src/lib/analysis/case-response-model.js +640 -0
- package/frontend/src/lib/analysis/case-response-packet.js +573 -0
- package/frontend/src/lib/analysis/case-storage-model.js +93 -0
- package/frontend/src/lib/analysis/detection-rule-model.js +306 -0
- package/frontend/src/lib/analysis/dns-record-normalization.js +11 -0
- package/frontend/src/lib/analysis/http-summary.js +165 -0
- package/frontend/src/lib/analysis/investigation-guide.js +500 -0
- package/frontend/src/lib/analysis/investigation-template-model.js +144 -0
- package/frontend/src/lib/analysis/page-baseline.js +272 -0
- package/frontend/src/lib/analysis/page-similarity.js +179 -0
- package/frontend/src/lib/analysis/relationship-evidence.js +265 -0
- package/frontend/src/lib/analysis/relationship-observation-model.js +423 -0
- package/frontend/src/lib/analysis/scoring.js +135 -0
- package/frontend/src/lib/analysis/shortlist-model.js +209 -0
- package/frontend/src/lib/analysis/utils.js +265 -0
- package/frontend/src/lib/analysis/watchlist-history.js +643 -0
- package/frontend/src/lib/analysis/watchlist-store.js +127 -0
- package/frontend/src/lib/analysis/website-snapshot-model.js +302 -0
- package/frontend/src/lib/analysis/workspace-archive-crypto.js +282 -0
- package/frontend/src/lib/analysis/workspace-archive.js +472 -0
- package/lib/abort.mjs +26 -0
- package/lib/abusech-auth.mjs +19 -0
- package/lib/availability.mjs +716 -0
- package/lib/bounded-contract-normalizers.mjs +61 -0
- package/lib/browser-library-profile.mjs +310 -0
- package/lib/bulk-comparison-evidence.mjs +83 -0
- package/lib/classify.mjs +93 -0
- package/lib/client-behavior-profile.mjs +83 -0
- package/lib/credential-surface-profile.mjs +66 -0
- package/lib/csv.mjs +23 -0
- package/lib/ct-query.mjs +27 -0
- package/lib/ct-search.mjs +393 -0
- package/lib/distributed-operation-budget.mjs +461 -0
- package/lib/dns-delegation-health.mjs +344 -0
- package/lib/dns-intelligence.mjs +739 -0
- package/lib/dns-mx.mjs +24 -0
- package/lib/dns-resolver-selection.mjs +33 -0
- package/lib/domain-posture-analysis.mjs +254 -0
- package/lib/domain-posture-parsers.mjs +358 -0
- package/lib/domain-posture.mjs +745 -0
- package/lib/error-detail.mjs +10 -0
- package/lib/evidence-export.mjs +431 -0
- package/lib/evidence-report-markdown.mjs +71 -0
- package/lib/evidence-report.mjs +391 -0
- package/lib/external-intelligence-risk.mjs +108 -0
- package/lib/favicon.mjs +146 -0
- package/lib/feature-policy.mjs +70 -0
- package/lib/generated/retire-browser-catalog.mjs +8724 -0
- package/lib/generated/unicode-confusables-17.mjs +93 -0
- package/lib/hostname.mjs +38 -0
- package/lib/html-signals.mjs +680 -0
- package/lib/http-intelligence.mjs +232 -0
- package/lib/idn-confusable-policy.mjs +86 -0
- package/lib/idn-confusables.mjs +465 -0
- package/lib/lookup-cache.mjs +130 -0
- package/lib/lookup-progress.mjs +291 -0
- package/lib/lookup-source-progress.mjs +109 -0
- package/lib/lookup-threat-provider-inventory.mjs +13 -0
- package/lib/lookup.mjs +592 -0
- package/lib/observation.mjs +114 -0
- package/lib/observed-network-context.mjs +355 -0
- package/lib/operation-budget.mjs +489 -0
- package/lib/page-fingerprints.mjs +481 -0
- package/lib/page-role-profile.mjs +117 -0
- package/lib/perceptual-hash.mjs +451 -0
- package/lib/rdap-attempts.mjs +34 -0
- package/lib/rdap-bootstrap.mjs +173 -0
- package/lib/rdap-capabilities.mjs +292 -0
- package/lib/rdap-client.mjs +125 -0
- package/lib/rdap-normalization.mjs +691 -0
- package/lib/rdap-registrar.mjs +196 -0
- package/lib/rdap-transport.mjs +39 -0
- package/lib/rdap-types.mjs +1 -0
- package/lib/rdap-validation.mjs +94 -0
- package/lib/rdap.mjs +16 -0
- package/lib/registry-access.mjs +32 -0
- package/lib/registry-capabilities.mjs +117 -0
- package/lib/registry-capability-catalogue.mjs +1206 -0
- package/lib/registry-comparison.mjs +318 -0
- package/lib/registry-dates.mjs +190 -0
- package/lib/registry-insights.mjs +325 -0
- package/lib/response-policy.mjs +256 -0
- package/lib/risk-scoring.mjs +164 -0
- package/lib/safe-fetch.mjs +446 -0
- package/lib/security-txt.mjs +359 -0
- package/lib/service-binding-dns.mjs +608 -0
- package/lib/sslbl-certificates.generated.mjs +143 -0
- package/lib/sslbl-intelligence.mjs +241 -0
- package/lib/static-html-analysis.mjs +340 -0
- package/lib/structured-data-identity.mjs +323 -0
- package/lib/threat-intelligence-contract.mjs +5 -0
- package/lib/threat-intelligence-definition-registry.mjs +34 -0
- package/lib/threat-intelligence-fixtures.mjs +82 -0
- package/lib/threat-intelligence-manifests.mjs +512 -0
- package/lib/threat-intelligence-runtime.mjs +662 -0
- package/lib/threat-intelligence-targets.mjs +168 -0
- package/lib/threat-intelligence-types.mjs +3 -0
- package/lib/threatfox-intelligence.mjs +307 -0
- package/lib/tls-intelligence.mjs +814 -0
- package/lib/typosquat-generator.mjs +884 -0
- package/lib/urlhaus-intelligence.mjs +361 -0
- package/lib/urlscan-intelligence.mjs +349 -0
- package/lib/website-security-posture.mjs +325 -0
- package/lib/website-technology.mjs +437 -0
- package/lib/whois-authority.mjs +98 -0
- package/lib/whois-chain.mjs +261 -0
- package/lib/whois-contacts.mjs +163 -0
- package/lib/whois-contracts.mjs +3 -0
- package/lib/whois-normalization.mjs +27 -0
- package/lib/whois-transport.mjs +100 -0
- package/lib/whois-values.mjs +92 -0
- package/lib/whois.mjs +1187 -0
- package/package.json +55 -0
- package/third-party-notices.txt +5571 -0
package/lib/lookup.mjs
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
// Unified single-query orchestration shared by Express and Netlify.
|
|
2
|
+
//
|
|
3
|
+
// The browser used to call the RDAP, WHOIS, and availability functions
|
|
4
|
+
// separately. On serverless deployments those functions do not share an
|
|
5
|
+
// in-memory cache, so availability repeated registry work that RDAP/WHOIS had
|
|
6
|
+
// just performed. This module fetches each registration source once and feeds
|
|
7
|
+
// those exact results into availability classification.
|
|
8
|
+
import { fetchRdapRecord, fetchRegistrarRdapRecord } from "./rdap.mjs";
|
|
9
|
+
import { buildWhoisChain, parseWhoisChain } from "./whois.mjs";
|
|
10
|
+
import { OPERATION_BUDGET_ERROR_CODE } from "./operation-budget.mjs";
|
|
11
|
+
import { checkDomainAvailability } from "./availability.mjs";
|
|
12
|
+
import { abortable } from "./abort.mjs";
|
|
13
|
+
import { collectDnsIntelligence, collectReverseDnsIntelligence, failedReverseDnsIntelligence, skippedReverseDnsIntelligence, } from "./dns-intelligence.mjs";
|
|
14
|
+
import { createSelectedDnsResolvers } from "./dns-resolver-selection.mjs";
|
|
15
|
+
import { collectObservedNetworkContext } from "./observed-network-context.mjs";
|
|
16
|
+
import { collectSecurityTxt, securityTxtUnavailable } from "./security-txt.mjs";
|
|
17
|
+
import { registryAccessDiagnosticFor } from "./registry-capabilities.mjs";
|
|
18
|
+
import { FEATURE_DISABLED_ERROR_CODE, featureDecision, networkFeaturePolicy } from "./feature-policy.mjs";
|
|
19
|
+
import { lookupUrlscanDomain } from "./urlscan-intelligence.mjs";
|
|
20
|
+
import { lookupUrlhausDomain } from "./urlhaus-intelligence.mjs";
|
|
21
|
+
import { lookupThreatfoxDomain } from "./threatfox-intelligence.mjs";
|
|
22
|
+
import { THREATFOX_PROVIDER, URLHAUS_PROVIDER, URLSCAN_PROVIDER, } from "./lookup-threat-provider-inventory.mjs";
|
|
23
|
+
import { createThreatIntelligenceResult } from "./threat-intelligence-contract.mjs";
|
|
24
|
+
import { buildRegistryInsights } from "./registry-insights.mjs";
|
|
25
|
+
import { inspectSslblCertificate } from "./sslbl-intelligence.mjs";
|
|
26
|
+
import { normalizeLookupSourceSettlement, plannedLookupProgressSources, } from "./lookup-source-progress.mjs";
|
|
27
|
+
import { buildBulkComparisonEvidence } from "./bulk-comparison-evidence.mjs";
|
|
28
|
+
const LOOKUP_DIAGNOSTICS_VERSION = 8;
|
|
29
|
+
const LOOKUP_LEGACY_DIAGNOSTICS_VERSION = 7;
|
|
30
|
+
const LOOKUP_TIMING_VERSION = 1;
|
|
31
|
+
const MAX_LOOKUP_TIMING_MS = 120_000;
|
|
32
|
+
const LOOKUP_TIMING_SOURCE_ORDER = Object.freeze([
|
|
33
|
+
'rdap',
|
|
34
|
+
'whois',
|
|
35
|
+
'domain_evidence',
|
|
36
|
+
'reverse_dns',
|
|
37
|
+
'registrar_rdap',
|
|
38
|
+
'network_context',
|
|
39
|
+
'security_txt',
|
|
40
|
+
'external_intelligence',
|
|
41
|
+
'malware_host_intelligence',
|
|
42
|
+
'malware_ioc_intelligence',
|
|
43
|
+
]);
|
|
44
|
+
const LOOKUP_ERROR_CODES = Object.freeze({
|
|
45
|
+
AUTH_REQUIRED: 'AUTH_REQUIRED',
|
|
46
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
47
|
+
CONCURRENCY_LIMITED: OPERATION_BUDGET_ERROR_CODE,
|
|
48
|
+
FEATURE_DISABLED: FEATURE_DISABLED_ERROR_CODE,
|
|
49
|
+
MISSING_QUERY: 'MISSING_QUERY',
|
|
50
|
+
INVALID_QUERY: 'INVALID_QUERY',
|
|
51
|
+
LOOKUP_FAILED: 'LOOKUP_FAILED',
|
|
52
|
+
RDAP_UPSTREAM_FAILED: 'RDAP_UPSTREAM_FAILED',
|
|
53
|
+
RDAP_UNSUPPORTED: 'RDAP_UNSUPPORTED',
|
|
54
|
+
WHOIS_UPSTREAM_FAILED: 'WHOIS_UPSTREAM_FAILED',
|
|
55
|
+
AVAILABILITY_CHECK_FAILED: 'AVAILABILITY_CHECK_FAILED',
|
|
56
|
+
});
|
|
57
|
+
function errorMessage(err, fallback) {
|
|
58
|
+
const message = err && typeof err === 'object' && 'message' in err
|
|
59
|
+
? err.message
|
|
60
|
+
: null;
|
|
61
|
+
return String(message || fallback);
|
|
62
|
+
}
|
|
63
|
+
function boundedSourceDetail(err, fallback) {
|
|
64
|
+
return errorMessage(err, fallback)
|
|
65
|
+
.replace(/[\u0000-\u001f\u007f]+/g, ' ')
|
|
66
|
+
.replace(/\s+/g, ' ')
|
|
67
|
+
.trim()
|
|
68
|
+
.slice(0, 240) || fallback;
|
|
69
|
+
}
|
|
70
|
+
function boundedTimingMs(value) {
|
|
71
|
+
if (!Number.isFinite(value))
|
|
72
|
+
return 0;
|
|
73
|
+
return Math.min(MAX_LOOKUP_TIMING_MS, Math.max(0, Math.round(value)));
|
|
74
|
+
}
|
|
75
|
+
function createLookupTimingTracker(enabled, now) {
|
|
76
|
+
const lookupStartedAt = now();
|
|
77
|
+
const entries = new Map();
|
|
78
|
+
function measure(source, operation) {
|
|
79
|
+
const sourceStartedAt = now();
|
|
80
|
+
return Promise.resolve()
|
|
81
|
+
.then(operation)
|
|
82
|
+
.then((value) => {
|
|
83
|
+
const finishedAt = now();
|
|
84
|
+
entries.set(source, {
|
|
85
|
+
source,
|
|
86
|
+
outcome: 'fulfilled',
|
|
87
|
+
durationMs: boundedTimingMs(finishedAt - sourceStartedAt),
|
|
88
|
+
completedAfterMs: boundedTimingMs(finishedAt - lookupStartedAt),
|
|
89
|
+
});
|
|
90
|
+
return value;
|
|
91
|
+
}, (error) => {
|
|
92
|
+
const finishedAt = now();
|
|
93
|
+
entries.set(source, {
|
|
94
|
+
source,
|
|
95
|
+
outcome: 'rejected',
|
|
96
|
+
durationMs: boundedTimingMs(finishedAt - sourceStartedAt),
|
|
97
|
+
completedAfterMs: boundedTimingMs(finishedAt - lookupStartedAt),
|
|
98
|
+
});
|
|
99
|
+
throw error;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
measure(source, operation) {
|
|
104
|
+
return enabled ? measure(source, operation) : Promise.resolve().then(operation);
|
|
105
|
+
},
|
|
106
|
+
snapshot() {
|
|
107
|
+
if (!enabled)
|
|
108
|
+
return null;
|
|
109
|
+
const totalMs = boundedTimingMs(now() - lookupStartedAt);
|
|
110
|
+
return {
|
|
111
|
+
version: LOOKUP_TIMING_VERSION,
|
|
112
|
+
totalMs,
|
|
113
|
+
sources: LOOKUP_TIMING_SOURCE_ORDER
|
|
114
|
+
.map((source) => entries.get(source))
|
|
115
|
+
.filter((entry) => Boolean(entry)),
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function runUnifiedLookup(classified, options = {}) {
|
|
121
|
+
const fetchRdap = options.fetchRdapRecord || fetchRdapRecord;
|
|
122
|
+
const fetchRegistrarRdap = options.fetchRegistrarRdapRecord || fetchRegistrarRdapRecord;
|
|
123
|
+
const fetchWhois = options.buildWhoisChain || buildWhoisChain;
|
|
124
|
+
const checkAvailability = options.checkDomainAvailability || checkDomainAvailability;
|
|
125
|
+
const selectedDnsResolvers = options.dnsResolverServers?.length
|
|
126
|
+
? createSelectedDnsResolvers(options.dnsResolverServers)
|
|
127
|
+
: null;
|
|
128
|
+
const collectReverseDns = options.collectReverseDnsIntelligence || collectReverseDnsIntelligence;
|
|
129
|
+
const collectNetworkContext = options.collectObservedNetworkContext || collectObservedNetworkContext;
|
|
130
|
+
const collectDisclosureContacts = options.collectSecurityTxt || collectSecurityTxt;
|
|
131
|
+
const fetchUrlscanIntelligence = options.lookupUrlscanDomain || lookupUrlscanDomain;
|
|
132
|
+
const fetchUrlhausIntelligence = options.lookupUrlhausDomain || lookupUrlhausDomain;
|
|
133
|
+
const fetchThreatfoxIntelligence = options.lookupThreatfoxDomain || lookupThreatfoxDomain;
|
|
134
|
+
const inspectCertificateWarningData = options.inspectSslblCertificate || inspectSslblCertificate;
|
|
135
|
+
const fast = options.fast === true;
|
|
136
|
+
const compact = options.compact === true;
|
|
137
|
+
const externalIntelligence = options.externalIntelligence === true;
|
|
138
|
+
const malwareHostIntelligence = options.malwareHostIntelligence === true;
|
|
139
|
+
const malwareIocIntelligence = options.malwareIocIntelligence === true;
|
|
140
|
+
const securityTxtRequested = options.securityTxt === true;
|
|
141
|
+
const featurePolicy = options.featurePolicy || networkFeaturePolicy();
|
|
142
|
+
const timing = createLookupTimingTracker(!fast && !compact, options.now || Date.now);
|
|
143
|
+
const measure = (source, operation) => (timing.measure(source, () => abortable(operation, options.signal)));
|
|
144
|
+
const rdapEnabled = featureDecision('rdap', featurePolicy).enabled;
|
|
145
|
+
const whoisEnabled = featureDecision('whois', featurePolicy).enabled;
|
|
146
|
+
const availabilityEnabled = featureDecision('availability', featurePolicy).enabled;
|
|
147
|
+
const websiteProbeEnabled = featureDecision('website_probe', featurePolicy).enabled;
|
|
148
|
+
const dnsIntelligenceEnabled = featureDecision('dns_intelligence', featurePolicy).enabled;
|
|
149
|
+
const skipWhois = fast || !whoisEnabled;
|
|
150
|
+
const rdapPromise = rdapEnabled
|
|
151
|
+
? measure('rdap', () => fetchRdap(classified.type, classified.value))
|
|
152
|
+
: Promise.resolve(null);
|
|
153
|
+
const whoisPromise = skipWhois
|
|
154
|
+
? Promise.resolve(null)
|
|
155
|
+
: measure('whois', () => fetchWhois(classified.value));
|
|
156
|
+
// Registrar RDAP is a separately attributed deep-lookup enrichment. It may
|
|
157
|
+
// overlap the WHOIS chain, but it never joins the promises used to decide
|
|
158
|
+
// availability and can add up to its own bounded timeout to a deep lookup.
|
|
159
|
+
const registrarRdapPromise = classified.type === 'domain' && rdapEnabled && !fast && !compact
|
|
160
|
+
? rdapPromise.then((record) => record && record.upstreamStatus === 200 && record.parsed
|
|
161
|
+
? measure('registrar_rdap', () => fetchRegistrarRdap(classified.value, record))
|
|
162
|
+
: null)
|
|
163
|
+
: null;
|
|
164
|
+
const availabilityPromise = classified.type === 'domain' && availabilityEnabled
|
|
165
|
+
? measure('domain_evidence', () => checkAvailability(classified.value, {
|
|
166
|
+
fast,
|
|
167
|
+
includeExtendedDnsContext: !compact,
|
|
168
|
+
includeInheritedCaa: !fast && !compact,
|
|
169
|
+
includeCredentialSurfaceProfile: !fast && !compact,
|
|
170
|
+
includeStructuredDataIdentity: !fast && !compact,
|
|
171
|
+
includeTechnologyProfile: !fast,
|
|
172
|
+
includeSecurityPosture: !compact,
|
|
173
|
+
featurePolicy,
|
|
174
|
+
rdapRecordPromise: rdapPromise,
|
|
175
|
+
whoisChainPromise: whoisPromise,
|
|
176
|
+
...(selectedDnsResolvers ? {
|
|
177
|
+
dnsResolvers: selectedDnsResolvers,
|
|
178
|
+
resolveNs: selectedDnsResolvers.resolveNs,
|
|
179
|
+
collectDnsIntelligence,
|
|
180
|
+
} : {}),
|
|
181
|
+
}))
|
|
182
|
+
: null;
|
|
183
|
+
// Reverse DNS is separately attributed operator context for deep public-IP
|
|
184
|
+
// lookups. It is never used by RDAP, WHOIS, availability, Risk, fast,
|
|
185
|
+
// compact, Bulk, or monitoring contracts.
|
|
186
|
+
const reverseDnsEligible = (classified.type === 'ipv4' || classified.type === 'ipv6')
|
|
187
|
+
&& !fast
|
|
188
|
+
&& !compact;
|
|
189
|
+
const reverseDnsPromise = reverseDnsEligible && dnsIntelligenceEnabled
|
|
190
|
+
? measure('reverse_dns', () => collectReverseDns(classified.value))
|
|
191
|
+
: null;
|
|
192
|
+
// Network registration is an additive deep-only source. It starts only
|
|
193
|
+
// after availability has produced the existing TLS/DNS observations and
|
|
194
|
+
// never joins the evidence used to decide domain availability. The shared
|
|
195
|
+
// RDAP helper supplies its existing three-minute cache and safe transport.
|
|
196
|
+
const networkContextPromise = classified.type === 'domain'
|
|
197
|
+
&& rdapEnabled
|
|
198
|
+
&& availabilityEnabled
|
|
199
|
+
&& !fast
|
|
200
|
+
&& !compact
|
|
201
|
+
&& availabilityPromise
|
|
202
|
+
? availabilityPromise.then((availability) => measure('network_context', () => collectNetworkContext(availability, { fetchRdapRecord: fetchRdap })), () => measure('network_context', () => collectNetworkContext({}, { fetchRdapRecord: fetchRdap })))
|
|
203
|
+
: null;
|
|
204
|
+
// security.txt is an explicit, additive deep-lookup action. It uses the
|
|
205
|
+
// exact queried hostname because RFC 9116 scopes a file to that hostname;
|
|
206
|
+
// the result never joins registration, availability, or Risk inputs.
|
|
207
|
+
const securityTxtPromise = securityTxtRequested
|
|
208
|
+
&& classified.type === 'domain'
|
|
209
|
+
&& websiteProbeEnabled
|
|
210
|
+
&& !fast
|
|
211
|
+
&& !compact
|
|
212
|
+
? measure('security_txt', () => collectDisclosureContacts(classified.inputHostname))
|
|
213
|
+
: null;
|
|
214
|
+
const urlscanIntelligencePromise = externalIntelligence
|
|
215
|
+
&& classified.type === 'domain'
|
|
216
|
+
&& !fast
|
|
217
|
+
&& !compact
|
|
218
|
+
? measure('external_intelligence', () => fetchUrlscanIntelligence(classified.registrableDomain || classified.value))
|
|
219
|
+
: null;
|
|
220
|
+
const urlhausIntelligencePromise = malwareHostIntelligence
|
|
221
|
+
&& classified.type === 'domain'
|
|
222
|
+
&& !fast
|
|
223
|
+
&& !compact
|
|
224
|
+
? measure('malware_host_intelligence', () => fetchUrlhausIntelligence(classified.registrableDomain || classified.value))
|
|
225
|
+
: null;
|
|
226
|
+
const threatfoxIntelligencePromise = malwareIocIntelligence
|
|
227
|
+
&& classified.type === 'domain'
|
|
228
|
+
&& !fast
|
|
229
|
+
&& !compact
|
|
230
|
+
? measure('malware_ioc_intelligence', () => fetchThreatfoxIntelligence(classified.registrableDomain || classified.value))
|
|
231
|
+
: null;
|
|
232
|
+
// Optional incremental presentation observes the same promises used by the
|
|
233
|
+
// ordinary Lookup result. It starts no additional collection and cannot
|
|
234
|
+
// alter, reject, or persist a source result. The callback receives only a
|
|
235
|
+
// bounded source-health summary; raw payloads remain inside the final
|
|
236
|
+
// response path.
|
|
237
|
+
if (!fast && !compact && options.onSourceSettled) {
|
|
238
|
+
const sourcePromises = new Map([
|
|
239
|
+
['rdap', rdapPromise],
|
|
240
|
+
['whois', whoisPromise],
|
|
241
|
+
['domain_evidence', availabilityPromise],
|
|
242
|
+
['reverse_dns', reverseDnsPromise],
|
|
243
|
+
['registrar_rdap', registrarRdapPromise],
|
|
244
|
+
['network_context', networkContextPromise],
|
|
245
|
+
['security_txt', securityTxtPromise],
|
|
246
|
+
['external_intelligence', urlscanIntelligencePromise],
|
|
247
|
+
['malware_host_intelligence', urlhausIntelligencePromise],
|
|
248
|
+
['malware_ioc_intelligence', threatfoxIntelligencePromise],
|
|
249
|
+
]);
|
|
250
|
+
const notify = (source, outcome, value) => {
|
|
251
|
+
try {
|
|
252
|
+
options.onSourceSettled?.(normalizeLookupSourceSettlement(source, outcome, value));
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// Presentation callbacks must never change evidence collection.
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
for (const source of plannedLookupProgressSources(classified, {
|
|
259
|
+
externalIntelligence,
|
|
260
|
+
malwareHostIntelligence,
|
|
261
|
+
malwareIocIntelligence,
|
|
262
|
+
securityTxt: securityTxtRequested,
|
|
263
|
+
})) {
|
|
264
|
+
const sourcePromise = sourcePromises.get(source) ?? null;
|
|
265
|
+
void Promise.resolve(sourcePromise).then((value) => notify(source, 'fulfilled', value), (error) => notify(source, 'rejected', error));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const [rdapResult, whoisResult, availabilityResult, reverseDnsResult, registrarRdapResult, networkContextResult, securityTxtResult, urlscanIntelligenceResult, urlhausIntelligenceResult, threatfoxIntelligenceResult] = await Promise.allSettled([
|
|
269
|
+
rdapPromise,
|
|
270
|
+
whoisPromise,
|
|
271
|
+
availabilityPromise,
|
|
272
|
+
reverseDnsPromise,
|
|
273
|
+
registrarRdapPromise,
|
|
274
|
+
networkContextPromise,
|
|
275
|
+
securityTxtPromise,
|
|
276
|
+
urlscanIntelligencePromise,
|
|
277
|
+
urlhausIntelligencePromise,
|
|
278
|
+
threatfoxIntelligencePromise,
|
|
279
|
+
]);
|
|
280
|
+
options.signal?.throwIfAborted();
|
|
281
|
+
const rdapRecord = rdapResult.status === 'fulfilled' ? rdapResult.value : null;
|
|
282
|
+
const whoisChain = whoisResult.status === 'fulfilled' ? whoisResult.value : null;
|
|
283
|
+
const rdapAttempts = rdapRecord && Array.isArray(rdapRecord.attempts)
|
|
284
|
+
? rdapRecord.attempts
|
|
285
|
+
: rdapResult.status === 'rejected' && Array.isArray(rdapResult.reason?.attempts)
|
|
286
|
+
? rdapResult.reason.attempts
|
|
287
|
+
: [];
|
|
288
|
+
let registrarRdap = null;
|
|
289
|
+
const registryRdapUsable = rdapRecord && rdapRecord.upstreamStatus === 200 && rdapRecord.parsed;
|
|
290
|
+
if (classified.type === 'domain' && rdapEnabled && !compact && registryRdapUsable) {
|
|
291
|
+
if (fast) {
|
|
292
|
+
registrarRdap = {
|
|
293
|
+
status: 'skipped',
|
|
294
|
+
detail: 'Registrar RDAP is omitted in fast RDAP-only mode.',
|
|
295
|
+
endpoint: null,
|
|
296
|
+
transportSecurity: null,
|
|
297
|
+
upstreamStatus: null,
|
|
298
|
+
fetchedAt: null,
|
|
299
|
+
attempt: null,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
else if (registrarRdapResult.status === 'fulfilled') {
|
|
303
|
+
registrarRdap = registrarRdapResult.value;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
registrarRdap = registrarRdapResult.reason?.registrarRdap || {
|
|
307
|
+
status: 'error',
|
|
308
|
+
detail: boundedSourceDetail(registrarRdapResult.reason, 'Registrar RDAP lookup failed'),
|
|
309
|
+
endpoint: null,
|
|
310
|
+
transportSecurity: null,
|
|
311
|
+
upstreamStatus: null,
|
|
312
|
+
fetchedAt: null,
|
|
313
|
+
attempt: null,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const rdap = !rdapEnabled
|
|
318
|
+
? { skipped: true, detail: 'RDAP is disabled by deployment policy.' }
|
|
319
|
+
: rdapRecord
|
|
320
|
+
? { ...rdapRecord, ...(registrarRdap ? { registrarRdap } : {}) }
|
|
321
|
+
: {
|
|
322
|
+
error: rdapResult.status === 'rejected'
|
|
323
|
+
? errorMessage(rdapResult.reason, 'RDAP lookup failed')
|
|
324
|
+
: 'No RDAP registry found for this query via IANA bootstrap',
|
|
325
|
+
attempts: rdapAttempts,
|
|
326
|
+
};
|
|
327
|
+
let whois;
|
|
328
|
+
if (!whoisEnabled) {
|
|
329
|
+
whois = { skipped: true, detail: 'WHOIS is disabled by deployment policy.' };
|
|
330
|
+
}
|
|
331
|
+
else if (skipWhois) {
|
|
332
|
+
whois = { skipped: true, detail: 'WHOIS is omitted in fast RDAP-only mode.' };
|
|
333
|
+
}
|
|
334
|
+
else if (Array.isArray(whoisChain)) {
|
|
335
|
+
whois = { chain: whoisChain, parsed: parseWhoisChain(whoisChain) };
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
whois = {
|
|
339
|
+
error: whoisResult.status === 'rejected'
|
|
340
|
+
? errorMessage(whoisResult.reason, 'WHOIS lookup failed')
|
|
341
|
+
: 'WHOIS returned no referral chain',
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
let availability = { applicable: false, type: classified.type };
|
|
345
|
+
if (classified.type === 'domain') {
|
|
346
|
+
if (!availabilityEnabled) {
|
|
347
|
+
availability = {
|
|
348
|
+
applicable: true,
|
|
349
|
+
domain: classified.value,
|
|
350
|
+
state: 'unknown',
|
|
351
|
+
confidence: 'low',
|
|
352
|
+
disabled: true,
|
|
353
|
+
detail: 'Availability analysis is disabled by deployment policy.',
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
else if (availabilityResult.status === 'fulfilled') {
|
|
357
|
+
const result = availabilityResult.value;
|
|
358
|
+
availability = {
|
|
359
|
+
applicable: true,
|
|
360
|
+
domain: classified.value,
|
|
361
|
+
inputHostname: classified.inputHostname,
|
|
362
|
+
registrableDomain: classified.registrableDomain,
|
|
363
|
+
isSubdomain: classified.isSubdomain,
|
|
364
|
+
...result,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
availability = {
|
|
369
|
+
applicable: true,
|
|
370
|
+
domain: classified.value,
|
|
371
|
+
state: 'unknown',
|
|
372
|
+
confidence: 'low',
|
|
373
|
+
detail: errorMessage(availabilityResult.reason, 'Availability lookup failed'),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const rdapStatus = !rdapEnabled
|
|
378
|
+
? 'disabled'
|
|
379
|
+
: rdapResult.status === 'rejected'
|
|
380
|
+
? 'error'
|
|
381
|
+
: !rdapRecord
|
|
382
|
+
? 'unsupported'
|
|
383
|
+
: rdapRecord.upstreamStatus === 404 ? 'not_found' : 'success';
|
|
384
|
+
const whoisStatus = !whoisEnabled
|
|
385
|
+
? 'disabled'
|
|
386
|
+
: skipWhois
|
|
387
|
+
? 'skipped'
|
|
388
|
+
: whoisResult.status === 'rejected' || !Array.isArray(whoisChain)
|
|
389
|
+
? 'error'
|
|
390
|
+
: whoisChain.length === 1
|
|
391
|
+
? 'unsupported'
|
|
392
|
+
: whois.parsed && whois.parsed.chainStatus === 'complete' ? 'complete' : 'partial';
|
|
393
|
+
const availabilityStatus = classified.type !== 'domain'
|
|
394
|
+
? 'not_applicable'
|
|
395
|
+
: !availabilityEnabled ? 'disabled'
|
|
396
|
+
: availabilityResult.status === 'rejected' ? 'error' : 'complete';
|
|
397
|
+
const reverseDns = reverseDnsEligible
|
|
398
|
+
? !dnsIntelligenceEnabled
|
|
399
|
+
? skippedReverseDnsIntelligence()
|
|
400
|
+
: reverseDnsResult.status === 'fulfilled' && reverseDnsResult.value
|
|
401
|
+
? reverseDnsResult.value
|
|
402
|
+
: failedReverseDnsIntelligence(reverseDnsResult.status === 'rejected'
|
|
403
|
+
? reverseDnsResult.reason
|
|
404
|
+
: 'Reverse DNS lookup returned no result')
|
|
405
|
+
: null;
|
|
406
|
+
// This is static access-policy context only. It performs no network work and
|
|
407
|
+
// is deliberately excluded from compact Bulk responses and from every
|
|
408
|
+
// availability input.
|
|
409
|
+
const registryAccess = classified.type === 'domain' && !compact
|
|
410
|
+
? registryAccessDiagnosticFor(classified.value)
|
|
411
|
+
: null;
|
|
412
|
+
const networkContext = networkContextPromise && networkContextResult.status === 'fulfilled'
|
|
413
|
+
? networkContextResult.value
|
|
414
|
+
: null;
|
|
415
|
+
const securityTxt = securityTxtPromise
|
|
416
|
+
? securityTxtResult.status === 'fulfilled'
|
|
417
|
+
? securityTxtResult.value
|
|
418
|
+
: securityTxtUnavailable(classified.inputHostname, securityTxtResult.reason)
|
|
419
|
+
: null;
|
|
420
|
+
// SSLBL comparison is an exact, local, deep-only enrichment over the leaf
|
|
421
|
+
// certificate already observed by the bounded TLS collector. It performs no
|
|
422
|
+
// request, never contributes to availability, and is omitted from compact
|
|
423
|
+
// Bulk and monitoring responses.
|
|
424
|
+
const sslbl = classified.type === 'domain' && !fast && !compact
|
|
425
|
+
? inspectCertificateWarningData(availability && typeof availability.tls === 'object' ? availability.tls : null, {
|
|
426
|
+
...(options.sslblSnapshot === undefined ? {} : { snapshot: options.sslblSnapshot }),
|
|
427
|
+
...(options.sslblNow === undefined ? {} : { now: options.sslblNow }),
|
|
428
|
+
})
|
|
429
|
+
: null;
|
|
430
|
+
const networkContextRdap = networkContext && typeof networkContext.rdap === 'object' && networkContext.rdap
|
|
431
|
+
? networkContext.rdap
|
|
432
|
+
: null;
|
|
433
|
+
const networkContextEndpoint = networkContext && typeof networkContext.endpoint === 'object' && networkContext.endpoint
|
|
434
|
+
? networkContext.endpoint
|
|
435
|
+
: null;
|
|
436
|
+
const lookupTiming = timing.snapshot();
|
|
437
|
+
const diagnostics = {
|
|
438
|
+
version: lookupTiming ? LOOKUP_DIAGNOSTICS_VERSION : LOOKUP_LEGACY_DIAGNOSTICS_VERSION,
|
|
439
|
+
...(lookupTiming ? { timing: lookupTiming } : {}),
|
|
440
|
+
...(registryAccess ? { registryAccess } : {}),
|
|
441
|
+
rdap: {
|
|
442
|
+
status: rdapStatus,
|
|
443
|
+
errorCode: rdapStatus === 'disabled'
|
|
444
|
+
? LOOKUP_ERROR_CODES.FEATURE_DISABLED
|
|
445
|
+
: rdapStatus === 'error'
|
|
446
|
+
? LOOKUP_ERROR_CODES.RDAP_UPSTREAM_FAILED
|
|
447
|
+
: rdapStatus === 'unsupported' ? LOOKUP_ERROR_CODES.RDAP_UNSUPPORTED : null,
|
|
448
|
+
endpoint: rdapRecord ? rdapRecord.rdapServer || null : null,
|
|
449
|
+
transportSecurity: rdapRecord ? rdapRecord.transportSecurity || null : null,
|
|
450
|
+
httpStatus: rdapRecord ? rdapRecord.upstreamStatus ?? null : null,
|
|
451
|
+
fetchedAt: rdapRecord ? rdapRecord.fetchedAt || null : null,
|
|
452
|
+
attempts: rdapAttempts,
|
|
453
|
+
...(registrarRdap ? {
|
|
454
|
+
registrar: {
|
|
455
|
+
status: registrarRdap.status,
|
|
456
|
+
endpoint: registrarRdap.endpoint || null,
|
|
457
|
+
transportSecurity: registrarRdap.transportSecurity || null,
|
|
458
|
+
httpStatus: registrarRdap.upstreamStatus ?? null,
|
|
459
|
+
fetchedAt: registrarRdap.fetchedAt || null,
|
|
460
|
+
attempt: registrarRdap.attempt || null,
|
|
461
|
+
},
|
|
462
|
+
} : {}),
|
|
463
|
+
},
|
|
464
|
+
whois: {
|
|
465
|
+
status: whoisStatus,
|
|
466
|
+
errorCode: whoisStatus === 'disabled'
|
|
467
|
+
? LOOKUP_ERROR_CODES.FEATURE_DISABLED
|
|
468
|
+
: whoisStatus === 'error' ? LOOKUP_ERROR_CODES.WHOIS_UPSTREAM_FAILED : null,
|
|
469
|
+
queriedAt: Array.isArray(whoisChain) && whoisChain[0] ? whoisChain[0].queriedAt || null : null,
|
|
470
|
+
authoritativeHop: whois.parsed ? whois.parsed.authoritativeHop || null : null,
|
|
471
|
+
failedHop: whois.parsed ? whois.parsed.failedHop || null : null,
|
|
472
|
+
conflictingHop: whois.parsed ? whois.parsed.conflictingHop || null : null,
|
|
473
|
+
},
|
|
474
|
+
availability: {
|
|
475
|
+
status: availabilityStatus,
|
|
476
|
+
errorCode: availabilityStatus === 'disabled'
|
|
477
|
+
? LOOKUP_ERROR_CODES.FEATURE_DISABLED
|
|
478
|
+
: availabilityStatus === 'error' ? LOOKUP_ERROR_CODES.AVAILABILITY_CHECK_FAILED : null,
|
|
479
|
+
resultState: availability.applicable === true ? availability.state || 'unknown' : null,
|
|
480
|
+
},
|
|
481
|
+
...(reverseDns ? {
|
|
482
|
+
reverseDns: {
|
|
483
|
+
status: reverseDns.status,
|
|
484
|
+
observedAt: reverseDns.observedAt,
|
|
485
|
+
complete: reverseDns.complete,
|
|
486
|
+
truncated: reverseDns.truncated,
|
|
487
|
+
},
|
|
488
|
+
} : {}),
|
|
489
|
+
...(networkContext ? {
|
|
490
|
+
network: {
|
|
491
|
+
status: networkContext.status,
|
|
492
|
+
address: networkContextEndpoint?.address || null,
|
|
493
|
+
family: networkContextEndpoint?.family || null,
|
|
494
|
+
addressSource: networkContextEndpoint?.selectedFrom || null,
|
|
495
|
+
endpoint: networkContextRdap?.endpoint || null,
|
|
496
|
+
transportSecurity: networkContextRdap?.transportSecurity || null,
|
|
497
|
+
httpStatus: networkContextRdap?.httpStatus ?? null,
|
|
498
|
+
fetchedAt: networkContextRdap?.fetchedAt || null,
|
|
499
|
+
attempts: networkContextRdap?.attempts || [],
|
|
500
|
+
},
|
|
501
|
+
} : {}),
|
|
502
|
+
...(securityTxt ? {
|
|
503
|
+
securityTxt: {
|
|
504
|
+
status: securityTxt.status,
|
|
505
|
+
state: securityTxt.state,
|
|
506
|
+
endpoint: securityTxt.finalUrl,
|
|
507
|
+
httpStatus: securityTxt.httpStatus,
|
|
508
|
+
observedAt: securityTxt.observedAt,
|
|
509
|
+
complete: securityTxt.complete,
|
|
510
|
+
truncated: securityTxt.truncated,
|
|
511
|
+
},
|
|
512
|
+
} : {}),
|
|
513
|
+
...(sslbl ? {
|
|
514
|
+
sslbl: {
|
|
515
|
+
status: sslbl.status,
|
|
516
|
+
verdict: sslbl.verdict,
|
|
517
|
+
observedAt: sslbl.observedAt,
|
|
518
|
+
complete: sslbl.complete,
|
|
519
|
+
snapshotUpdatedAt: sslbl.snapshot.sourceUpdatedAt,
|
|
520
|
+
},
|
|
521
|
+
} : {}),
|
|
522
|
+
};
|
|
523
|
+
// Bulk triage only consumes the derived availability evidence and source
|
|
524
|
+
// diagnostics. Omitting raw RDAP JSON and multi-hop WHOIS bodies from that
|
|
525
|
+
// opt-in response prevents large scans from downloading and retaining the
|
|
526
|
+
// same registry payloads the backend already used to build `availability`.
|
|
527
|
+
if (compact) {
|
|
528
|
+
const bulkComparison = !fast
|
|
529
|
+
? buildBulkComparisonEvidence(availability)
|
|
530
|
+
: null;
|
|
531
|
+
const { credentialSurfaceProfile: _credentialSurfaceProfile, structuredDataIdentity: _structuredDataIdentity, technologyProfile: _technologyProfile, pageRoleProfile: _pageRoleProfile, clientBehaviorProfile: _clientBehaviorProfile, securityPosture: _securityPosture, ...compactAvailability } = availability;
|
|
532
|
+
return {
|
|
533
|
+
availability: {
|
|
534
|
+
...compactAvailability,
|
|
535
|
+
...(bulkComparison ? { bulkComparison } : {}),
|
|
536
|
+
},
|
|
537
|
+
diagnostics,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
const targetDomain = classified.registrableDomain || classified.value;
|
|
541
|
+
const threatIntelligenceProviders = [];
|
|
542
|
+
if (urlscanIntelligencePromise) {
|
|
543
|
+
threatIntelligenceProviders.push(urlscanIntelligenceResult.status === 'fulfilled' && urlscanIntelligenceResult.value
|
|
544
|
+
? urlscanIntelligenceResult.value
|
|
545
|
+
: createThreatIntelligenceResult(URLSCAN_PROVIDER, { type: 'domain', value: targetDomain }, { state: 'error', detail: 'Archived provider intelligence could not be completed.' }));
|
|
546
|
+
}
|
|
547
|
+
if (urlhausIntelligencePromise) {
|
|
548
|
+
threatIntelligenceProviders.push(urlhausIntelligenceResult.status === 'fulfilled' && urlhausIntelligenceResult.value
|
|
549
|
+
? urlhausIntelligenceResult.value
|
|
550
|
+
: createThreatIntelligenceResult(URLHAUS_PROVIDER, { type: 'domain', value: targetDomain }, { state: 'error', detail: 'Malware-host intelligence could not be completed.' }));
|
|
551
|
+
}
|
|
552
|
+
if (threatfoxIntelligencePromise) {
|
|
553
|
+
threatIntelligenceProviders.push(threatfoxIntelligenceResult.status === 'fulfilled' && threatfoxIntelligenceResult.value
|
|
554
|
+
? threatfoxIntelligenceResult.value
|
|
555
|
+
: createThreatIntelligenceResult(THREATFOX_PROVIDER, { type: 'domain', value: targetDomain }, { state: 'error', detail: 'Malware-IOC intelligence could not be completed.' }));
|
|
556
|
+
}
|
|
557
|
+
const threatIntelligence = threatIntelligenceProviders.length
|
|
558
|
+
? { version: 1, providers: threatIntelligenceProviders }
|
|
559
|
+
: null;
|
|
560
|
+
const registrarRdapParsed = registrarRdap
|
|
561
|
+
&& typeof registrarRdap.parsed === 'object'
|
|
562
|
+
&& registrarRdap.parsed
|
|
563
|
+
&& !Array.isArray(registrarRdap.parsed)
|
|
564
|
+
? registrarRdap.parsed
|
|
565
|
+
: null;
|
|
566
|
+
const registryInsights = classified.type === 'domain' && !fast && !compact
|
|
567
|
+
? buildRegistryInsights({
|
|
568
|
+
rdapParsed: rdapRecord?.parsed,
|
|
569
|
+
rdapStatus,
|
|
570
|
+
rdapFetchedAt: rdapRecord?.fetchedAt,
|
|
571
|
+
whoisParsed: whois.parsed,
|
|
572
|
+
whoisStatus,
|
|
573
|
+
whoisQueriedAt: Array.isArray(whoisChain) && whoisChain[0] ? whoisChain[0].queriedAt : null,
|
|
574
|
+
registrarRdapParsed,
|
|
575
|
+
registrarRdapStatus: registrarRdap?.status,
|
|
576
|
+
registrarRdapFetchedAt: registrarRdap?.fetchedAt,
|
|
577
|
+
})
|
|
578
|
+
: null;
|
|
579
|
+
return {
|
|
580
|
+
rdap,
|
|
581
|
+
whois,
|
|
582
|
+
availability,
|
|
583
|
+
diagnostics,
|
|
584
|
+
...(registryInsights ? { registryInsights } : {}),
|
|
585
|
+
...(reverseDns ? { reverseDns } : {}),
|
|
586
|
+
...(networkContext ? { networkContext } : {}),
|
|
587
|
+
...(securityTxt ? { securityTxt } : {}),
|
|
588
|
+
...(sslbl ? { sslbl } : {}),
|
|
589
|
+
...(threatIntelligence ? { threatIntelligence } : {}),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
export { runUnifiedLookup, LOOKUP_DIAGNOSTICS_VERSION, LOOKUP_LEGACY_DIAGNOSTICS_VERSION, LOOKUP_TIMING_VERSION, MAX_LOOKUP_TIMING_MS, LOOKUP_ERROR_CODES, };
|