@slicedearth/whoisleuth-cli 2.2.0 → 2.4.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/NOTICE +6 -0
- package/README.md +20 -15
- package/bin/whoisleuth.mjs +8 -1
- package/cli/archive-content-digest.mjs +14 -0
- package/cli/archive-inspect.mjs +20 -53
- package/cli/arguments.mjs +454 -1748
- package/cli/artifact-validation/case-response.mjs +66 -35
- package/cli/artifact-validation/investigation-capsule.mjs +52 -31
- package/cli/artifact-validation/lookup-evidence.mjs +12 -6
- package/cli/artifact-validation/signed-review.mjs +112 -72
- package/cli/artifact-validation/structure-primitives.mjs +1 -105
- package/cli/artifact-verify.mjs +90 -24
- package/cli/assurance-command-runner.mjs +39 -4
- package/cli/bagit-folder.mjs +57 -0
- package/cli/bagit-review.mjs +23 -0
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +34 -3
- package/cli/bulk-command-runner.mjs +2 -0
- package/cli/bulk-output.mjs +29 -3
- package/cli/case-command.mjs +195 -0
- package/cli/case-pack.mjs +66 -111
- package/cli/command-argument-grammar.mjs +215 -0
- package/cli/command-reference.mjs +342 -285
- package/cli/completion.mjs +303 -80
- package/cli/config-profile.mjs +7 -5
- package/cli/ct-event-intake.mjs +12 -12
- package/cli/discover.mjs +4 -3
- package/cli/discovery-observation-snapshot.mjs +59 -18
- package/cli/discovery-scan-command-runner.mjs +21 -0
- package/cli/discovery-scan.mjs +13 -4
- package/cli/discovery-workflow.mjs +1 -1
- package/cli/domain-control-monitor.mjs +50 -20
- package/cli/domain-control-observations.mjs +149 -102
- package/cli/errors.mjs +33 -1
- package/cli/evidence-command-runner.mjs +16 -5
- package/cli/evidence-signing.mjs +2 -2
- package/cli/external-observation-mapping.mjs +7 -8
- package/cli/formatters/terminal-lookup.mjs +18 -46
- package/cli/formatters/terminal-shared.mjs +5 -3
- package/cli/formatters/terminal-technology.mjs +114 -0
- package/cli/inline-command-families.mjs +2 -0
- package/cli/input.mjs +116 -0
- package/cli/interactive-launcher.mjs +2 -160
- package/cli/interchange-report.mjs +18 -8
- package/cli/investigation-artifacts.mjs +176 -0
- package/cli/investigation-checkpoint.mjs +10 -0
- package/cli/investigation-folder.mjs +114 -0
- package/cli/investigation-manifest.mjs +1 -112
- package/cli/investigation-package-review.mjs +103 -0
- package/cli/investigation-plan.mjs +32 -19
- package/cli/investigation-run.mjs +188 -33
- package/cli/local-document-checkpoint.mjs +170 -0
- package/cli/lookalike-calibration.mjs +7 -2
- package/cli/lookup-brief.mjs +4 -2
- package/cli/lookup-browser-view.mjs +1 -1
- package/cli/lookup-browser.mjs +5 -12
- package/cli/lookup-command-runner.mjs +17 -6
- package/cli/lookup-diff.mjs +37 -3
- package/cli/lookup-plan.mjs +11 -5
- package/cli/lookup-reconcile.mjs +10 -8
- package/cli/mail-header-review.mjs +467 -0
- package/cli/mail-review.mjs +66 -14
- package/cli/manual.mjs +1 -0
- package/cli/network-command-runner.mjs +2 -0
- package/cli/output-file.mjs +31 -5
- package/cli/registry-doctor.mjs +3 -1
- package/cli/registry-support.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +2 -2
- package/cli/review-command-runner.mjs +70 -0
- package/cli/risk-calibration.mjs +19 -17
- package/cli/runner.mjs +62 -7
- package/cli/sharing-review.mjs +21 -34
- package/cli/signer-trust.mjs +104 -0
- package/cli/source-reliability.mjs +74 -83
- package/cli/terminal-input.mjs +168 -0
- package/cli/workflow-command-runner.mjs +68 -13
- package/docs/cli-reference.md +77 -24
- package/docs/cli.md +300 -25
- package/lib/abort.mjs +4 -1
- package/lib/availability.mjs +161 -112
- package/lib/bounded-file.mjs +3 -1
- package/lib/bounded-html-document.mjs +125 -0
- package/lib/bounded-json.mjs +51 -8
- package/lib/browser-library-profile.mjs +42 -51
- package/lib/browser-library-worker.mjs +112 -0
- package/lib/client-behavior-profile.mjs +3 -3
- package/lib/ct-search.mjs +11 -4
- package/lib/dns-convergence-review.mjs +1 -0
- package/lib/dns-delegation-health.mjs +24 -14
- package/lib/dns-inheritance-review.mjs +217 -0
- package/lib/dns-intelligence.mjs +73 -26
- package/lib/dns-mx.mjs +1 -1
- package/lib/dnssec-chain-validation.mjs +6 -0
- package/lib/dnssec-evidence-validation.mjs +11 -3
- package/lib/domain-change-packet.mjs +49 -16
- package/lib/domain-change-review.mjs +14 -8
- package/lib/domain-control-flight-recorder.mjs +221 -117
- package/lib/domain-control-manifest.mjs +129 -69
- package/lib/domain-posture-analysis.mjs +20 -7
- package/lib/domain-posture-parsers.mjs +201 -8
- package/lib/domain-posture.mjs +147 -29
- package/lib/evidence-export-privacy.mjs +561 -0
- package/lib/evidence-export.mjs +11 -543
- package/lib/evidence-report.mjs +5 -3
- package/lib/favicon.mjs +117 -41
- package/lib/generated/cisa-kev-catalog.mjs +12 -4
- package/lib/generated/registrar-standing-catalogue.mjs +7 -7
- package/lib/generated/retire-browser-catalog.mjs +2335 -940
- package/lib/hostname.mjs +2 -38
- package/lib/html-signals.mjs +111 -108
- package/lib/http-evidence-bounds.mjs +2 -1
- package/lib/http-intelligence.mjs +39 -26
- package/lib/local-geoip-evidence.mjs +19 -3
- package/lib/lookup-cache.mjs +34 -11
- package/lib/lookup-child-profile-contract.mjs +24 -15
- package/lib/lookup-diagnostics.mjs +24 -1
- package/lib/lookup-progress.mjs +30 -13
- package/lib/lookup-response-contract.mjs +1048 -0
- package/lib/lookup-response.mjs +8 -10
- package/lib/lookup-source-progress.mjs +40 -14
- package/lib/lookup.mjs +48 -27
- package/lib/nameserver-preflight-review.mjs +1 -0
- package/lib/observed-network-context.mjs +43 -27
- package/lib/outbound-request-bounds.mjs +4 -1
- package/lib/page-fingerprints.mjs +69 -133
- package/lib/page-role-profile.mjs +12 -8
- package/lib/perceptual-hash.mjs +43 -14
- package/lib/rdap-bootstrap.mjs +72 -22
- package/lib/rdap-capabilities.mjs +1 -1
- package/lib/rdap-client.mjs +15 -7
- package/lib/rdap-normalization.mjs +33 -18
- package/lib/rdap-search-workbench.mjs +1 -1
- package/lib/rdap-transport.mjs +47 -20
- package/lib/rdap-types.mjs +2 -1
- package/lib/rdap.mjs +18 -3
- package/lib/registrable-domain.mjs +21 -1
- package/lib/registrar-standing-contract.mjs +1 -0
- package/lib/registry-capabilities.mjs +5 -1
- package/lib/registry-capability-catalogue.mjs +2 -1
- package/lib/registry-dates.mjs +2 -190
- package/lib/registry-insights.mjs +85 -34
- package/lib/response-policy.mjs +86 -48
- package/lib/risk-calibration-summary.mjs +18 -6
- package/lib/rpki-evidence.mjs +8 -5
- package/lib/safe-fetch.mjs +54 -47
- package/lib/security-txt.mjs +42 -7
- package/lib/service-binding-dns.mjs +8 -0
- package/lib/smtp-transport-review.mjs +27 -15
- package/lib/sslbl-certificates.generated.mjs +136 -135
- package/lib/static-html-analysis.mjs +178 -151
- package/lib/structured-data-identity.mjs +1 -0
- package/lib/technology-markup.mjs +100 -0
- package/lib/threat-intelligence-manifests.mjs +11 -139
- package/lib/threat-intelligence-runtime.mjs +24 -52
- package/lib/threat-intelligence-targets.mjs +6 -34
- package/lib/threat-intelligence-types.mjs +39 -0
- package/lib/typosquat-generator.mjs +19 -15
- package/lib/urlhaus-intelligence.mjs +8 -3
- package/lib/urlscan-intelligence.mjs +1 -1
- package/lib/website-security-posture.mjs +30 -6
- package/lib/website-technology.mjs +153 -45
- package/lib/whois-authority.mjs +14 -2
- package/lib/whois-chain.mjs +9 -2
- package/lib/whois-common-formats.mjs +5 -5
- package/lib/whois-contracts.mjs +2 -1
- package/lib/whois-dialects.mjs +2 -2
- package/lib/whois-parser-support.mjs +8 -1
- package/lib/whois-transport.mjs +23 -7
- package/lib/zone-intent-review.mjs +16 -6
- package/package.json +2 -1
- package/packages/cases/case-attachment-model.mjs +83 -0
- package/packages/cases/case-evidence-model.mjs +156 -62
- package/packages/cases/case-export-input.mjs +53 -0
- package/packages/cases/case-migration-model.mjs +127 -44
- package/packages/cases/case-model.mjs +1 -0
- package/packages/cases/case-recheck-model.mjs +91 -0
- package/packages/cases/case-record-contracts.mjs +5 -20
- package/packages/cases/case-record-core.mjs +12 -22
- package/packages/cases/case-record-decisions.mjs +72 -0
- package/packages/cases/case-record-model.mjs +1 -0
- package/packages/cases/case-record-operations.mjs +95 -31
- package/packages/cases/case-record-projection.mjs +221 -0
- package/packages/cases/case-report.mjs +70 -54
- package/packages/cases/case-response-actions.mjs +654 -0
- package/packages/cases/case-response-model.mjs +64 -1115
- package/packages/cases/case-response-outcomes.mjs +317 -0
- package/packages/cases/case-response-packet-render.mjs +153 -0
- package/packages/cases/case-response-packet-types.mjs +1 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
- package/packages/cases/case-response-packet.mjs +74 -572
- package/packages/cases/case-response-records.mjs +94 -0
- package/packages/cases/case-response-review-inputs.mjs +371 -0
- package/packages/cases/case-response-values.mjs +85 -0
- package/packages/cases/case-review-package.mjs +45 -0
- package/packages/cases/case-selection.mjs +46 -0
- package/packages/cases/case-storage-model.mjs +31 -2
- package/packages/cases/response-route-freshness.mjs +16 -0
- package/packages/comparison/capture-context.mjs +61 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +13 -11
- package/packages/comparison/page-similarity.mjs +5 -2
- package/packages/comparison/relationship-evidence.mjs +85 -8
- package/packages/comparison/relationship-provenance.mjs +93 -0
- package/packages/contracts/analyst-interchange.mjs +216 -0
- package/packages/contracts/analyst-review-state-contract.mjs +11 -0
- package/packages/contracts/analyst-review-state.mjs +5 -4
- package/packages/contracts/case-portability.mjs +366 -83
- package/packages/contracts/case-views-contract.mjs +12 -0
- package/packages/contracts/case-views.mjs +26 -0
- package/packages/contracts/cli-command-semantics.mjs +94 -0
- package/packages/contracts/cli-lookup.mjs +0 -1
- package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
- package/packages/contracts/domain-control-manifest.mjs +127 -73
- package/packages/contracts/domain-control-monitor.mjs +48 -45
- package/packages/contracts/domain-control-review.mjs +179 -121
- package/packages/contracts/domain-name.mjs +21 -0
- package/packages/contracts/external-observation-interchange.mjs +2 -1
- package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
- package/packages/contracts/http-url.mjs +2 -0
- package/packages/contracts/investigation-package-limits.mjs +24 -0
- package/packages/contracts/investigation-portability.mjs +56 -27
- package/packages/contracts/investigation-run.mjs +16 -0
- package/packages/contracts/ip-address.mjs +34 -0
- package/packages/contracts/lookup-evidence.mjs +35 -0
- package/packages/contracts/offline-comparison.mjs +1 -2
- package/packages/contracts/page-fingerprints.mjs +15 -0
- package/packages/contracts/risk-calibration.mjs +15 -11
- package/packages/contracts/schema-compatibility.mjs +1 -1
- package/packages/contracts/schema-lifecycle.mjs +83 -153
- package/packages/contracts/selected-file-limits.mjs +6 -0
- package/packages/contracts/vulnerability-identifiers.mjs +5 -0
- package/packages/contracts/web-capture.mjs +10 -0
- package/packages/contracts/workspace-portability.mjs +183 -211
- package/packages/evidence/artifact-integrity.mjs +8 -2
- package/packages/evidence/artifact-structure.mjs +105 -0
- package/packages/evidence/decision-fact.mjs +4 -1
- package/packages/evidence/domain-control-runtime.mjs +201 -33
- package/packages/evidence/domain-posture-context.mjs +79 -0
- package/packages/evidence/image-regions.mjs +56 -0
- package/packages/evidence/latest-observations.mjs +28 -0
- package/packages/evidence/lookup-target.mjs +104 -0
- package/packages/evidence/observation.mjs +16 -1
- package/packages/evidence/passphrase-encryption.mjs +44 -0
- package/packages/evidence/record-identity.mjs +72 -0
- package/packages/evidence/registry-dates.mjs +190 -0
- package/packages/evidence/retained-file.mjs +69 -0
- package/packages/evidence/review-age.mjs +14 -0
- package/packages/interchange/bagit.mjs +342 -0
- package/packages/interchange/bounded-zip-extraction.mjs +278 -0
- package/packages/interchange/crc32.mjs +17 -0
- package/packages/interchange/external-findings-import.mjs +92 -51
- package/packages/interchange/web-capture-import.mjs +382 -0
- package/packages/investigation/bulk-mail-exposure.mjs +2 -1
- package/packages/investigation/investigation-bagit.mjs +14 -0
- package/packages/investigation/investigation-capsule.mjs +33 -5
- package/packages/investigation/investigation-manifest.mjs +200 -0
- package/packages/investigation/investigation-package-crypto.mjs +98 -0
- package/packages/investigation/investigation-package.mjs +200 -0
- package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
- package/packages/investigation/lookup-asset-graph.mjs +193 -165
- package/packages/investigation/lookup-claim-readiness.mjs +18 -0
- package/packages/investigation/lookup-investigation-brief.mjs +3 -3
- package/packages/investigation/lookup-task-guidance.mjs +58 -0
- package/packages/monitoring/analyst-review-state.mjs +38 -90
- package/packages/workspace/brand-profile-model.mjs +276 -22
- package/packages/workspace/bulk-columns.mjs +17 -0
- package/packages/workspace/bulk-review-model.mjs +23 -11
- package/packages/workspace/bulk-session-model.mjs +100 -24
- package/packages/workspace/bulk-sort.mjs +1 -0
- package/packages/workspace/case-views.mjs +102 -0
- package/packages/workspace/detection-rule-model.mjs +70 -22
- package/packages/workspace/hostile-input.mjs +29 -8
- package/packages/workspace/investigation-template-model.mjs +19 -4
- package/packages/workspace/page-baseline.mjs +18 -15
- package/packages/workspace/relationship-observation-model.mjs +41 -23
- package/packages/workspace/watchlist-history.mjs +2 -2
- package/packages/workspace/watchlist-store.mjs +8 -2
- package/packages/workspace/website-snapshot-model.mjs +164 -26
- package/packages/workspace/workspace-archive-crypto.mjs +41 -61
- package/packages/workspace/workspace-archive.mjs +87 -21
- package/third-party-notices.txt +29 -2
- package/cli/evidence-command-arguments.mjs +0 -156
- package/packages/investigation/bulk-review-export.mjs +0 -63
|
@@ -2,7 +2,7 @@ import { abortable } from "../lib/abort.mjs";
|
|
|
2
2
|
import { classifyQuery } from "../lib/classify.mjs";
|
|
3
3
|
import { runUnifiedLookup } from "../lib/lookup.mjs";
|
|
4
4
|
import { plannedLookupProgressSources } from "../lib/lookup-source-progress.mjs";
|
|
5
|
-
import { CliUsageError
|
|
5
|
+
import { CliUsageError } from "./errors.mjs";
|
|
6
6
|
import EXIT_CODES from "./exit-codes.mjs";
|
|
7
7
|
import { buildCliEvidenceExport } from "./export-evidence.mjs";
|
|
8
8
|
import { formatLookupEvidenceHtml } from "./formatters/html.mjs";
|
|
@@ -17,6 +17,7 @@ import { evaluateCliFailPolicies, formatFailPolicyNotice } from "./fail-policy.m
|
|
|
17
17
|
import { formatCliJunit } from "./ci-report.mjs";
|
|
18
18
|
import { browseLookupOperation, canBrowseLookup } from "./lookup-browser.mjs";
|
|
19
19
|
import { writePrivateFile } from "./output-file.mjs";
|
|
20
|
+
import { prepareLookupCollectionTarget, prepareSelectedLookupUrl } from "../packages/evidence/lookup-target.mjs";
|
|
20
21
|
async function runLookupCommand(args, dependencies, context) {
|
|
21
22
|
const browserInput = dependencies.stdin || process.stdin;
|
|
22
23
|
const browserEnvironment = dependencies.environment || process.env;
|
|
@@ -35,23 +36,31 @@ async function runLookupCommand(args, dependencies, context) {
|
|
|
35
36
|
});
|
|
36
37
|
context.setEventProgress(eventProgress);
|
|
37
38
|
eventProgress.emit({ event: 'started' });
|
|
38
|
-
const
|
|
39
|
-
if (!
|
|
39
|
+
const input = args.query || await context.readSingleInput();
|
|
40
|
+
if (!input)
|
|
40
41
|
throw new CliUsageError('lookup requires one domain, IP address, or ASN as an argument or on stdin.');
|
|
41
42
|
const classify = dependencies.classifyQuery || classifyQuery;
|
|
42
43
|
const executeLookup = dependencies.runUnifiedLookup || runUnifiedLookup;
|
|
43
44
|
let classified;
|
|
45
|
+
let query;
|
|
46
|
+
let selectedUrl;
|
|
44
47
|
try {
|
|
48
|
+
query = prepareLookupCollectionTarget(input);
|
|
45
49
|
classified = classify(query);
|
|
50
|
+
if (args.exactUrl) {
|
|
51
|
+
if (!args.deep || classified.type !== 'domain')
|
|
52
|
+
throw new TypeError();
|
|
53
|
+
selectedUrl = prepareSelectedLookupUrl(input, classified.inputHostname);
|
|
54
|
+
}
|
|
46
55
|
}
|
|
47
|
-
catch
|
|
48
|
-
throw new CliUsageError(
|
|
56
|
+
catch {
|
|
57
|
+
throw new CliUsageError(args.exactUrl ? '--exact-url requires --deep and an HTTP(S) domain URL without credentials or a custom port.' : 'Invalid domain, IP address, ASN or credential-free HTTP(S) URL.');
|
|
49
58
|
}
|
|
50
59
|
if ((args.output === 'markdown' || args.output === 'html') && classified.type !== 'domain') {
|
|
51
60
|
throw new CliUsageError('Markdown and HTML reports support domain lookups only.');
|
|
52
61
|
}
|
|
53
62
|
if (args.plan) {
|
|
54
|
-
const plan = buildCliLookupPlan(query, classified, args.deep);
|
|
63
|
+
const plan = buildCliLookupPlan(query, classified, args.deep, Boolean(selectedUrl));
|
|
55
64
|
context.writeStdout(args.output === 'json'
|
|
56
65
|
? formatJsonDocument(plan)
|
|
57
66
|
: context.terminal(formatCliLookupPlan(plan), args.color));
|
|
@@ -83,6 +92,7 @@ async function runLookupCommand(args, dependencies, context) {
|
|
|
83
92
|
? {
|
|
84
93
|
fast: false,
|
|
85
94
|
compact: false,
|
|
95
|
+
...(selectedUrl ? { selectedUrl } : {}),
|
|
86
96
|
signal,
|
|
87
97
|
onSourceSettled: (settlement) => {
|
|
88
98
|
onSourceSettled(settlement);
|
|
@@ -119,6 +129,7 @@ async function runLookupCommand(args, dependencies, context) {
|
|
|
119
129
|
? {
|
|
120
130
|
fast: false,
|
|
121
131
|
compact: false,
|
|
132
|
+
...(selectedUrl ? { selectedUrl } : {}),
|
|
122
133
|
...(dependencies.signal ? { signal: dependencies.signal } : {}),
|
|
123
134
|
onSourceSettled: (settlement) => {
|
|
124
135
|
settledSources += 1;
|
package/cli/lookup-diff.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { buildBulkDomainComparison, } from "../packages/investigation/bulk-domai
|
|
|
4
4
|
import { relationshipObservation } from "../packages/comparison/relationship-evidence.mjs";
|
|
5
5
|
import { CliUsageError } from "./errors.mjs";
|
|
6
6
|
import { parseSavedLookupDocument } from "./saved-lookup.mjs";
|
|
7
|
+
import { lookupObservationHostname } from "../packages/evidence/lookup-target.mjs";
|
|
7
8
|
export const CLI_LOOKUP_DIFF_SCHEMA = 'whoisleuth.cli.lookup-diff';
|
|
8
9
|
export const CLI_LOOKUP_DIFF_VERSION = 1;
|
|
9
10
|
function text(value, maximum = 300) {
|
|
@@ -129,19 +130,22 @@ function lookupComparisonInput(document) {
|
|
|
129
130
|
function buildCliLookupDiff(leftText, rightText, generatedAt = new Date().toISOString(), options = {}) {
|
|
130
131
|
const left = parseSavedLookupDocument(leftText, { label: 'Left lookup input' });
|
|
131
132
|
const right = parseSavedLookupDocument(rightText, { label: 'Right lookup input' });
|
|
132
|
-
const domainMode = options.domainMode
|
|
133
|
+
const domainMode = options.domainMode === 'auto'
|
|
134
|
+
? left.registrableDomain === right.registrableDomain ? 'same' : 'different'
|
|
135
|
+
: options.domainMode ?? 'different';
|
|
133
136
|
if (domainMode === 'different' && left.registrableDomain === right.registrableDomain) {
|
|
134
137
|
throw new CliUsageError('Lookup diff requires documents for two different domains.');
|
|
135
138
|
}
|
|
136
139
|
if (domainMode === 'same' && left.registrableDomain !== right.registrableDomain) {
|
|
137
140
|
throw new CliUsageError('Lookup history requires observations for the same domain.');
|
|
138
141
|
}
|
|
139
|
-
const
|
|
142
|
+
const measuredComparison = buildBulkDomainComparison(lookupComparisonInput(left), lookupComparisonInput(right), domainMode === 'same' ? right.generatedAt : null, {
|
|
140
143
|
allowSameDomain: domainMode === 'same',
|
|
141
144
|
...(domainMode === 'same' ? { now: Date.parse(right.generatedAt) } : {}),
|
|
142
145
|
});
|
|
143
|
-
if (!
|
|
146
|
+
if (!measuredComparison)
|
|
144
147
|
throw new CliUsageError('Lookup documents could not be compared.');
|
|
148
|
+
const comparison = qualifyLookupComparison(measuredComparison, left, right, domainMode === 'same');
|
|
145
149
|
return {
|
|
146
150
|
schema: CLI_LOOKUP_DIFF_SCHEMA,
|
|
147
151
|
version: CLI_LOOKUP_DIFF_VERSION,
|
|
@@ -160,6 +164,36 @@ function buildCliLookupDiff(leftText, rightText, generatedAt = new Date().toISOS
|
|
|
160
164
|
],
|
|
161
165
|
};
|
|
162
166
|
}
|
|
167
|
+
function qualifyLookupComparison(comparison, left, right, temporal) {
|
|
168
|
+
const leftScope = recordOrEmpty(left.availability);
|
|
169
|
+
const rightScope = recordOrEmpty(right.availability);
|
|
170
|
+
const hostnameChanged = temporal && lookupObservationHostname(leftScope) !== lookupObservationHostname(rightScope);
|
|
171
|
+
const selectedPage = leftScope.webObservationMode === 'selected_url' || rightScope.webObservationMode === 'selected_url';
|
|
172
|
+
if (!hostnameChanged && !selectedPage)
|
|
173
|
+
return comparison;
|
|
174
|
+
const limitation = 'The retained collection hostname differs or a selected URL has no retained complete path/query identity. Values remain visible, but those fields cannot establish change at the same target.';
|
|
175
|
+
const rows = comparison.rows.map((row) => {
|
|
176
|
+
const web = ['web', 'technology', 'identity'].includes(row.category);
|
|
177
|
+
const incomparable = hostnameChanged && !['registration', 'lifecycle', 'source'].includes(row.category)
|
|
178
|
+
|| temporal && selectedPage && web;
|
|
179
|
+
return {
|
|
180
|
+
...row,
|
|
181
|
+
...(selectedPage && web ? { method: 'Bounded website observation; one or both captures concern a selected URL rather than the homepage' } : {}),
|
|
182
|
+
...(incomparable ? {
|
|
183
|
+
state: 'not_recorded',
|
|
184
|
+
source: `${row.source}; comparable target identity not recorded`,
|
|
185
|
+
leftSourceState: 'not_recorded',
|
|
186
|
+
rightSourceState: 'not_recorded',
|
|
187
|
+
limitations: [...row.limitations, limitation],
|
|
188
|
+
} : {}),
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
...comparison, rows,
|
|
193
|
+
counts: Object.fromEntries(Object.keys(comparison.counts).map((state) => [state, rows.filter((row) => row.state === state).length])),
|
|
194
|
+
limitations: [...comparison.limitations, limitation],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
163
197
|
function formatCliLookupDiff(document) {
|
|
164
198
|
const rows = document.comparison.rows.filter((row) => row.state !== 'equal');
|
|
165
199
|
const output = [
|
package/cli/lookup-plan.mjs
CHANGED
|
@@ -21,7 +21,7 @@ const FAST_DOMAIN_EVIDENCE = Object.freeze({
|
|
|
21
21
|
const DEEP_DOMAIN_EVIDENCE = Object.freeze({
|
|
22
22
|
source: 'domain_evidence',
|
|
23
23
|
purpose: 'Collect bounded DNS, HTTP, TLS, page-identity, technology, and security-posture evidence.',
|
|
24
|
-
disclosure: 'DNS
|
|
24
|
+
disclosure: 'DNS, TLS and website probes use the submitted hostname. Registration-delegation queries use the registrable domain.',
|
|
25
25
|
conditional: false,
|
|
26
26
|
});
|
|
27
27
|
const REGISTRAR_RDAP = Object.freeze({
|
|
@@ -42,19 +42,23 @@ const REVERSE_DNS = Object.freeze({
|
|
|
42
42
|
disclosure: 'A DNS resolver receives the reverse lookup for the normalised IP address.',
|
|
43
43
|
conditional: false,
|
|
44
44
|
});
|
|
45
|
-
function plannedSources(classified, deep) {
|
|
45
|
+
function plannedSources(classified, deep, selectedUrl) {
|
|
46
46
|
if (!deep) {
|
|
47
47
|
return Object.freeze(classified.type === 'domain' ? [RDAP, FAST_DOMAIN_EVIDENCE] : [RDAP]);
|
|
48
48
|
}
|
|
49
49
|
if (classified.type === 'domain') {
|
|
50
|
-
|
|
50
|
+
const domainEvidence = selectedUrl ? Object.freeze({
|
|
51
|
+
...DEEP_DOMAIN_EVIDENCE,
|
|
52
|
+
disclosure: 'The selected URL path and query are sent to the website and followed redirects. The fragment is not sent. DNS and TLS use its hostname; registration uses the registrable domain. Saved HTTP provenance omits queries, but page-derived text may contain sensitive information.',
|
|
53
|
+
}) : DEEP_DOMAIN_EVIDENCE;
|
|
54
|
+
return Object.freeze([RDAP, WHOIS, domainEvidence, REGISTRAR_RDAP, NETWORK_CONTEXT]);
|
|
51
55
|
}
|
|
52
56
|
if (classified.type === 'ipv4' || classified.type === 'ipv6') {
|
|
53
57
|
return Object.freeze([RDAP, WHOIS, REVERSE_DNS]);
|
|
54
58
|
}
|
|
55
59
|
return Object.freeze([RDAP, WHOIS]);
|
|
56
60
|
}
|
|
57
|
-
function buildCliLookupPlan(query, classified, deep) {
|
|
61
|
+
function buildCliLookupPlan(query, classified, deep, selectedUrl = false) {
|
|
58
62
|
return Object.freeze({
|
|
59
63
|
schema: CLI_LOOKUP_PLAN_SCHEMA,
|
|
60
64
|
version: CLI_LOOKUP_PLAN_VERSION,
|
|
@@ -69,7 +73,7 @@ function buildCliLookupPlan(query, classified, deep) {
|
|
|
69
73
|
planning: Object.freeze({
|
|
70
74
|
networkRequestsMade: false,
|
|
71
75
|
collectionRequiresNetwork: true,
|
|
72
|
-
sources: plannedSources(classified, deep),
|
|
76
|
+
sources: plannedSources(classified, deep, selectedUrl),
|
|
73
77
|
}),
|
|
74
78
|
limitations: Object.freeze([
|
|
75
79
|
'This is a local preflight. It does not test source availability, feature configuration, cache state, redirects, referrals, or the exact number of requests a completed lookup may require.',
|
|
@@ -81,6 +85,8 @@ function formatCliLookupPlan(plan) {
|
|
|
81
85
|
const lines = [
|
|
82
86
|
'WHOISleuth lookup preflight',
|
|
83
87
|
`Target: ${plan.target.normalized}`,
|
|
88
|
+
...(plan.target.inputHostname && plan.target.inputHostname !== plan.target.normalized
|
|
89
|
+
? [`Submitted hostname: ${plan.target.inputHostname}`] : []),
|
|
84
90
|
`Type: ${plan.target.type}`,
|
|
85
91
|
`Mode: ${plan.mode}`,
|
|
86
92
|
'Network requests made: no',
|
package/cli/lookup-reconcile.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CliUsageError } from "./errors.mjs";
|
|
2
2
|
import { buildCliLookupDiff } from "./lookup-diff.mjs";
|
|
3
3
|
import { parseSavedLookupDocument } from "./saved-lookup.mjs";
|
|
4
|
+
import { compareObservationContexts } from "../packages/comparison/capture-context.mjs";
|
|
4
5
|
const CLI_LOOKUP_RECONCILIATION_SCHEMA = 'whoisleuth.cli.lookup-reconciliation';
|
|
5
6
|
const CLI_LOOKUP_RECONCILIATION_VERSION = 1;
|
|
6
7
|
const MAX_LOOKUP_RECONCILIATION_INPUT_BYTES = 32 * 1024 * 1024;
|
|
@@ -79,15 +80,11 @@ function buildCliLookupReconciliation(inputs, generatedAt = new Date().toISOStri
|
|
|
79
80
|
};
|
|
80
81
|
});
|
|
81
82
|
const observations = documents.map(observationContext);
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
: null));
|
|
85
|
-
const labelsComplete = identities.every((identity) => identity !== null);
|
|
86
|
-
const labelsDistinct = labelsComplete && new Set(identities).size === identities.length;
|
|
87
|
-
const independence = labelsDistinct
|
|
83
|
+
const context = compareObservationContexts(observations.map(observation => ({ ...observation, observedAt: observation.generatedAt })));
|
|
84
|
+
const independence = context.labels === 'distinct'
|
|
88
85
|
? {
|
|
89
86
|
state: 'verified_distinct_labels',
|
|
90
|
-
reason: 'Every observation carries a
|
|
87
|
+
reason: 'Every observation carries a distinct analyst-supplied observer/vantage label pair. Actual collection independence is not verified.',
|
|
91
88
|
}
|
|
92
89
|
: {
|
|
93
90
|
state: 'unverified',
|
|
@@ -117,15 +114,19 @@ function buildCliLookupReconciliation(inputs, generatedAt = new Date().toISOStri
|
|
|
117
114
|
};
|
|
118
115
|
}
|
|
119
116
|
function formatCliLookupReconciliation(document) {
|
|
117
|
+
const context = compareObservationContexts(document.observations.map(observation => ({ ...observation, observedAt: observation.generatedAt })));
|
|
120
118
|
const output = [
|
|
121
119
|
'Lookup observation reconciliation',
|
|
122
120
|
`Domain ${document.domain}`,
|
|
123
121
|
`Observations ${document.summary.observationCount}`,
|
|
124
|
-
`
|
|
122
|
+
`Declared labels ${context.labels}; collection independence not verified`,
|
|
123
|
+
`Capture time span ${context.time.spanMilliseconds === null ? 'unknown' : `${context.time.spanMilliseconds / 1000} seconds`}`,
|
|
125
124
|
`Agreement ${document.summary.agreement}`,
|
|
126
125
|
`Disagreement ${document.summary.disagreement}`,
|
|
127
126
|
`Non-comparable ${document.summary.nonComparable}`,
|
|
128
127
|
'',
|
|
128
|
+
...document.observations.map((observation, index) => `Observation ${index + 1}: ${observation.generatedAt} · ${observation.mode} · observer ${observation.observerLabel ?? 'not declared'} · vantage ${observation.vantageLabel ?? 'not declared'}`),
|
|
129
|
+
'',
|
|
129
130
|
];
|
|
130
131
|
for (const field of document.fields.filter((candidate) => candidate.state !== 'agreement')) {
|
|
131
132
|
output.push(`${field.label} [${field.state.replaceAll('_', ' ')}]`);
|
|
@@ -136,6 +137,7 @@ function formatCliLookupReconciliation(document) {
|
|
|
136
137
|
output.push('', 'Limitations:');
|
|
137
138
|
for (const limitation of document.limitations)
|
|
138
139
|
output.push(` - ${limitation}`);
|
|
140
|
+
output.push(' - These selected observations do not establish worldwide availability, removal or takedown.');
|
|
139
141
|
return `${output.join('\n')}\n`;
|
|
140
142
|
}
|
|
141
143
|
export { CLI_LOOKUP_RECONCILIATION_SCHEMA, CLI_LOOKUP_RECONCILIATION_VERSION, MAX_LOOKUP_RECONCILIATION_INPUT_BYTES, buildCliLookupReconciliation, formatCliLookupReconciliation, };
|