@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.
Files changed (282) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +20 -15
  3. package/bin/whoisleuth.mjs +8 -1
  4. package/cli/archive-content-digest.mjs +14 -0
  5. package/cli/archive-inspect.mjs +20 -53
  6. package/cli/arguments.mjs +454 -1748
  7. package/cli/artifact-validation/case-response.mjs +66 -35
  8. package/cli/artifact-validation/investigation-capsule.mjs +52 -31
  9. package/cli/artifact-validation/lookup-evidence.mjs +12 -6
  10. package/cli/artifact-validation/signed-review.mjs +112 -72
  11. package/cli/artifact-validation/structure-primitives.mjs +1 -105
  12. package/cli/artifact-verify.mjs +90 -24
  13. package/cli/assurance-command-runner.mjs +39 -4
  14. package/cli/bagit-folder.mjs +57 -0
  15. package/cli/bagit-review.mjs +23 -0
  16. package/cli/bounded-json.mjs +1 -1
  17. package/cli/bulk-checkpoint.mjs +34 -3
  18. package/cli/bulk-command-runner.mjs +2 -0
  19. package/cli/bulk-output.mjs +29 -3
  20. package/cli/case-command.mjs +195 -0
  21. package/cli/case-pack.mjs +66 -111
  22. package/cli/command-argument-grammar.mjs +215 -0
  23. package/cli/command-reference.mjs +342 -285
  24. package/cli/completion.mjs +303 -80
  25. package/cli/config-profile.mjs +7 -5
  26. package/cli/ct-event-intake.mjs +12 -12
  27. package/cli/discover.mjs +4 -3
  28. package/cli/discovery-observation-snapshot.mjs +59 -18
  29. package/cli/discovery-scan-command-runner.mjs +21 -0
  30. package/cli/discovery-scan.mjs +13 -4
  31. package/cli/discovery-workflow.mjs +1 -1
  32. package/cli/domain-control-monitor.mjs +50 -20
  33. package/cli/domain-control-observations.mjs +149 -102
  34. package/cli/errors.mjs +33 -1
  35. package/cli/evidence-command-runner.mjs +16 -5
  36. package/cli/evidence-signing.mjs +2 -2
  37. package/cli/external-observation-mapping.mjs +7 -8
  38. package/cli/formatters/terminal-lookup.mjs +18 -46
  39. package/cli/formatters/terminal-shared.mjs +5 -3
  40. package/cli/formatters/terminal-technology.mjs +114 -0
  41. package/cli/inline-command-families.mjs +2 -0
  42. package/cli/input.mjs +116 -0
  43. package/cli/interactive-launcher.mjs +2 -160
  44. package/cli/interchange-report.mjs +18 -8
  45. package/cli/investigation-artifacts.mjs +176 -0
  46. package/cli/investigation-checkpoint.mjs +10 -0
  47. package/cli/investigation-folder.mjs +114 -0
  48. package/cli/investigation-manifest.mjs +1 -112
  49. package/cli/investigation-package-review.mjs +103 -0
  50. package/cli/investigation-plan.mjs +32 -19
  51. package/cli/investigation-run.mjs +188 -33
  52. package/cli/local-document-checkpoint.mjs +170 -0
  53. package/cli/lookalike-calibration.mjs +7 -2
  54. package/cli/lookup-brief.mjs +4 -2
  55. package/cli/lookup-browser-view.mjs +1 -1
  56. package/cli/lookup-browser.mjs +5 -12
  57. package/cli/lookup-command-runner.mjs +17 -6
  58. package/cli/lookup-diff.mjs +37 -3
  59. package/cli/lookup-plan.mjs +11 -5
  60. package/cli/lookup-reconcile.mjs +10 -8
  61. package/cli/mail-header-review.mjs +467 -0
  62. package/cli/mail-review.mjs +66 -14
  63. package/cli/manual.mjs +1 -0
  64. package/cli/network-command-runner.mjs +2 -0
  65. package/cli/output-file.mjs +31 -5
  66. package/cli/registry-doctor.mjs +3 -1
  67. package/cli/registry-support.mjs +1 -1
  68. package/cli/retained-artifact-diff.mjs +2 -2
  69. package/cli/review-command-runner.mjs +70 -0
  70. package/cli/risk-calibration.mjs +19 -17
  71. package/cli/runner.mjs +62 -7
  72. package/cli/sharing-review.mjs +21 -34
  73. package/cli/signer-trust.mjs +104 -0
  74. package/cli/source-reliability.mjs +74 -83
  75. package/cli/terminal-input.mjs +168 -0
  76. package/cli/workflow-command-runner.mjs +68 -13
  77. package/docs/cli-reference.md +77 -24
  78. package/docs/cli.md +300 -25
  79. package/lib/abort.mjs +4 -1
  80. package/lib/availability.mjs +161 -112
  81. package/lib/bounded-file.mjs +3 -1
  82. package/lib/bounded-html-document.mjs +125 -0
  83. package/lib/bounded-json.mjs +51 -8
  84. package/lib/browser-library-profile.mjs +42 -51
  85. package/lib/browser-library-worker.mjs +112 -0
  86. package/lib/client-behavior-profile.mjs +3 -3
  87. package/lib/ct-search.mjs +11 -4
  88. package/lib/dns-convergence-review.mjs +1 -0
  89. package/lib/dns-delegation-health.mjs +24 -14
  90. package/lib/dns-inheritance-review.mjs +217 -0
  91. package/lib/dns-intelligence.mjs +73 -26
  92. package/lib/dns-mx.mjs +1 -1
  93. package/lib/dnssec-chain-validation.mjs +6 -0
  94. package/lib/dnssec-evidence-validation.mjs +11 -3
  95. package/lib/domain-change-packet.mjs +49 -16
  96. package/lib/domain-change-review.mjs +14 -8
  97. package/lib/domain-control-flight-recorder.mjs +221 -117
  98. package/lib/domain-control-manifest.mjs +129 -69
  99. package/lib/domain-posture-analysis.mjs +20 -7
  100. package/lib/domain-posture-parsers.mjs +201 -8
  101. package/lib/domain-posture.mjs +147 -29
  102. package/lib/evidence-export-privacy.mjs +561 -0
  103. package/lib/evidence-export.mjs +11 -543
  104. package/lib/evidence-report.mjs +5 -3
  105. package/lib/favicon.mjs +117 -41
  106. package/lib/generated/cisa-kev-catalog.mjs +12 -4
  107. package/lib/generated/registrar-standing-catalogue.mjs +7 -7
  108. package/lib/generated/retire-browser-catalog.mjs +2335 -940
  109. package/lib/hostname.mjs +2 -38
  110. package/lib/html-signals.mjs +111 -108
  111. package/lib/http-evidence-bounds.mjs +2 -1
  112. package/lib/http-intelligence.mjs +39 -26
  113. package/lib/local-geoip-evidence.mjs +19 -3
  114. package/lib/lookup-cache.mjs +34 -11
  115. package/lib/lookup-child-profile-contract.mjs +24 -15
  116. package/lib/lookup-diagnostics.mjs +24 -1
  117. package/lib/lookup-progress.mjs +30 -13
  118. package/lib/lookup-response-contract.mjs +1048 -0
  119. package/lib/lookup-response.mjs +8 -10
  120. package/lib/lookup-source-progress.mjs +40 -14
  121. package/lib/lookup.mjs +48 -27
  122. package/lib/nameserver-preflight-review.mjs +1 -0
  123. package/lib/observed-network-context.mjs +43 -27
  124. package/lib/outbound-request-bounds.mjs +4 -1
  125. package/lib/page-fingerprints.mjs +69 -133
  126. package/lib/page-role-profile.mjs +12 -8
  127. package/lib/perceptual-hash.mjs +43 -14
  128. package/lib/rdap-bootstrap.mjs +72 -22
  129. package/lib/rdap-capabilities.mjs +1 -1
  130. package/lib/rdap-client.mjs +15 -7
  131. package/lib/rdap-normalization.mjs +33 -18
  132. package/lib/rdap-search-workbench.mjs +1 -1
  133. package/lib/rdap-transport.mjs +47 -20
  134. package/lib/rdap-types.mjs +2 -1
  135. package/lib/rdap.mjs +18 -3
  136. package/lib/registrable-domain.mjs +21 -1
  137. package/lib/registrar-standing-contract.mjs +1 -0
  138. package/lib/registry-capabilities.mjs +5 -1
  139. package/lib/registry-capability-catalogue.mjs +2 -1
  140. package/lib/registry-dates.mjs +2 -190
  141. package/lib/registry-insights.mjs +85 -34
  142. package/lib/response-policy.mjs +86 -48
  143. package/lib/risk-calibration-summary.mjs +18 -6
  144. package/lib/rpki-evidence.mjs +8 -5
  145. package/lib/safe-fetch.mjs +54 -47
  146. package/lib/security-txt.mjs +42 -7
  147. package/lib/service-binding-dns.mjs +8 -0
  148. package/lib/smtp-transport-review.mjs +27 -15
  149. package/lib/sslbl-certificates.generated.mjs +136 -135
  150. package/lib/static-html-analysis.mjs +178 -151
  151. package/lib/structured-data-identity.mjs +1 -0
  152. package/lib/technology-markup.mjs +100 -0
  153. package/lib/threat-intelligence-manifests.mjs +11 -139
  154. package/lib/threat-intelligence-runtime.mjs +24 -52
  155. package/lib/threat-intelligence-targets.mjs +6 -34
  156. package/lib/threat-intelligence-types.mjs +39 -0
  157. package/lib/typosquat-generator.mjs +19 -15
  158. package/lib/urlhaus-intelligence.mjs +8 -3
  159. package/lib/urlscan-intelligence.mjs +1 -1
  160. package/lib/website-security-posture.mjs +30 -6
  161. package/lib/website-technology.mjs +153 -45
  162. package/lib/whois-authority.mjs +14 -2
  163. package/lib/whois-chain.mjs +9 -2
  164. package/lib/whois-common-formats.mjs +5 -5
  165. package/lib/whois-contracts.mjs +2 -1
  166. package/lib/whois-dialects.mjs +2 -2
  167. package/lib/whois-parser-support.mjs +8 -1
  168. package/lib/whois-transport.mjs +23 -7
  169. package/lib/zone-intent-review.mjs +16 -6
  170. package/package.json +2 -1
  171. package/packages/cases/case-attachment-model.mjs +83 -0
  172. package/packages/cases/case-evidence-model.mjs +156 -62
  173. package/packages/cases/case-export-input.mjs +53 -0
  174. package/packages/cases/case-migration-model.mjs +127 -44
  175. package/packages/cases/case-model.mjs +1 -0
  176. package/packages/cases/case-recheck-model.mjs +91 -0
  177. package/packages/cases/case-record-contracts.mjs +5 -20
  178. package/packages/cases/case-record-core.mjs +12 -22
  179. package/packages/cases/case-record-decisions.mjs +72 -0
  180. package/packages/cases/case-record-model.mjs +1 -0
  181. package/packages/cases/case-record-operations.mjs +95 -31
  182. package/packages/cases/case-record-projection.mjs +221 -0
  183. package/packages/cases/case-report.mjs +70 -54
  184. package/packages/cases/case-response-actions.mjs +654 -0
  185. package/packages/cases/case-response-model.mjs +64 -1115
  186. package/packages/cases/case-response-outcomes.mjs +317 -0
  187. package/packages/cases/case-response-packet-render.mjs +153 -0
  188. package/packages/cases/case-response-packet-types.mjs +1 -0
  189. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  190. package/packages/cases/case-response-packet.mjs +74 -572
  191. package/packages/cases/case-response-records.mjs +94 -0
  192. package/packages/cases/case-response-review-inputs.mjs +371 -0
  193. package/packages/cases/case-response-values.mjs +85 -0
  194. package/packages/cases/case-review-package.mjs +45 -0
  195. package/packages/cases/case-selection.mjs +46 -0
  196. package/packages/cases/case-storage-model.mjs +31 -2
  197. package/packages/cases/response-route-freshness.mjs +16 -0
  198. package/packages/comparison/capture-context.mjs +61 -0
  199. package/packages/comparison/comparison-ledger-bulk.mjs +13 -11
  200. package/packages/comparison/page-similarity.mjs +5 -2
  201. package/packages/comparison/relationship-evidence.mjs +85 -8
  202. package/packages/comparison/relationship-provenance.mjs +93 -0
  203. package/packages/contracts/analyst-interchange.mjs +216 -0
  204. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  205. package/packages/contracts/analyst-review-state.mjs +5 -4
  206. package/packages/contracts/case-portability.mjs +366 -83
  207. package/packages/contracts/case-views-contract.mjs +12 -0
  208. package/packages/contracts/case-views.mjs +26 -0
  209. package/packages/contracts/cli-command-semantics.mjs +94 -0
  210. package/packages/contracts/cli-lookup.mjs +0 -1
  211. package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
  212. package/packages/contracts/domain-control-manifest.mjs +127 -73
  213. package/packages/contracts/domain-control-monitor.mjs +48 -45
  214. package/packages/contracts/domain-control-review.mjs +179 -121
  215. package/packages/contracts/domain-name.mjs +21 -0
  216. package/packages/contracts/external-observation-interchange.mjs +2 -1
  217. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  218. package/packages/contracts/http-url.mjs +2 -0
  219. package/packages/contracts/investigation-package-limits.mjs +24 -0
  220. package/packages/contracts/investigation-portability.mjs +56 -27
  221. package/packages/contracts/investigation-run.mjs +16 -0
  222. package/packages/contracts/ip-address.mjs +34 -0
  223. package/packages/contracts/lookup-evidence.mjs +35 -0
  224. package/packages/contracts/offline-comparison.mjs +1 -2
  225. package/packages/contracts/page-fingerprints.mjs +15 -0
  226. package/packages/contracts/risk-calibration.mjs +15 -11
  227. package/packages/contracts/schema-compatibility.mjs +1 -1
  228. package/packages/contracts/schema-lifecycle.mjs +83 -153
  229. package/packages/contracts/selected-file-limits.mjs +6 -0
  230. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  231. package/packages/contracts/web-capture.mjs +10 -0
  232. package/packages/contracts/workspace-portability.mjs +183 -211
  233. package/packages/evidence/artifact-integrity.mjs +8 -2
  234. package/packages/evidence/artifact-structure.mjs +105 -0
  235. package/packages/evidence/decision-fact.mjs +4 -1
  236. package/packages/evidence/domain-control-runtime.mjs +201 -33
  237. package/packages/evidence/domain-posture-context.mjs +79 -0
  238. package/packages/evidence/image-regions.mjs +56 -0
  239. package/packages/evidence/latest-observations.mjs +28 -0
  240. package/packages/evidence/lookup-target.mjs +104 -0
  241. package/packages/evidence/observation.mjs +16 -1
  242. package/packages/evidence/passphrase-encryption.mjs +44 -0
  243. package/packages/evidence/record-identity.mjs +72 -0
  244. package/packages/evidence/registry-dates.mjs +190 -0
  245. package/packages/evidence/retained-file.mjs +69 -0
  246. package/packages/evidence/review-age.mjs +14 -0
  247. package/packages/interchange/bagit.mjs +342 -0
  248. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  249. package/packages/interchange/crc32.mjs +17 -0
  250. package/packages/interchange/external-findings-import.mjs +92 -51
  251. package/packages/interchange/web-capture-import.mjs +382 -0
  252. package/packages/investigation/bulk-mail-exposure.mjs +2 -1
  253. package/packages/investigation/investigation-bagit.mjs +14 -0
  254. package/packages/investigation/investigation-capsule.mjs +33 -5
  255. package/packages/investigation/investigation-manifest.mjs +200 -0
  256. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  257. package/packages/investigation/investigation-package.mjs +200 -0
  258. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  259. package/packages/investigation/lookup-asset-graph.mjs +193 -165
  260. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  261. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  262. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  263. package/packages/monitoring/analyst-review-state.mjs +38 -90
  264. package/packages/workspace/brand-profile-model.mjs +276 -22
  265. package/packages/workspace/bulk-columns.mjs +17 -0
  266. package/packages/workspace/bulk-review-model.mjs +23 -11
  267. package/packages/workspace/bulk-session-model.mjs +100 -24
  268. package/packages/workspace/bulk-sort.mjs +1 -0
  269. package/packages/workspace/case-views.mjs +102 -0
  270. package/packages/workspace/detection-rule-model.mjs +70 -22
  271. package/packages/workspace/hostile-input.mjs +29 -8
  272. package/packages/workspace/investigation-template-model.mjs +19 -4
  273. package/packages/workspace/page-baseline.mjs +18 -15
  274. package/packages/workspace/relationship-observation-model.mjs +41 -23
  275. package/packages/workspace/watchlist-history.mjs +2 -2
  276. package/packages/workspace/watchlist-store.mjs +8 -2
  277. package/packages/workspace/website-snapshot-model.mjs +164 -26
  278. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  279. package/packages/workspace/workspace-archive.mjs +87 -21
  280. package/third-party-notices.txt +29 -2
  281. package/cli/evidence-command-arguments.mjs +0 -156
  282. 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, boundedCliErrorMessage } from "./errors.mjs";
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 query = args.query || await context.readSingleInput();
39
- if (!query)
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 (error) {
48
- throw new CliUsageError(boundedCliErrorMessage(error, 'Invalid query'));
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;
@@ -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 ?? 'different';
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 comparison = buildBulkDomainComparison(lookupComparisonInput(left), lookupComparisonInput(right), domainMode === 'same' ? right.generatedAt : null, {
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 (!comparison)
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 = [
@@ -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 resolvers and the target website infrastructure receive the hostname through bounded probes.',
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
- return Object.freeze([RDAP, WHOIS, DEEP_DOMAIN_EVIDENCE, REGISTRAR_RDAP, NETWORK_CONTEXT]);
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',
@@ -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 identities = observations.map((observation) => (observation.observerLabel && observation.vantageLabel
83
- ? `${observation.observerLabel}\u0000${observation.vantageLabel}`
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 different analyst-supplied observer and vantage label.',
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
- `Independence ${document.independence.state.replaceAll('_', ' ')}`,
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, };