@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.
Files changed (189) hide show
  1. package/LICENSE +661 -0
  2. package/LICENSES/Retire.js-Apache-2.0.txt +199 -0
  3. package/NOTICE +76 -0
  4. package/README.md +84 -0
  5. package/TRADEMARKS.md +41 -0
  6. package/bin/whoisleuth.mjs +35 -0
  7. package/cli/archive-inspect.mjs +198 -0
  8. package/cli/arguments.mjs +1022 -0
  9. package/cli/artifact-verify.mjs +210 -0
  10. package/cli/bulk-checkpoint.mjs +218 -0
  11. package/cli/bulk-command-runner.mjs +109 -0
  12. package/cli/bulk-output.mjs +112 -0
  13. package/cli/bulk.mjs +133 -0
  14. package/cli/compare.mjs +238 -0
  15. package/cli/completion.mjs +175 -0
  16. package/cli/csv.mjs +1 -0
  17. package/cli/discover.mjs +50 -0
  18. package/cli/discovery-command-runner.mjs +36 -0
  19. package/cli/discovery-observation-snapshot.mjs +315 -0
  20. package/cli/discovery-scan-command-runner.mjs +141 -0
  21. package/cli/discovery-scan.mjs +325 -0
  22. package/cli/discovery-snapshot.mjs +133 -0
  23. package/cli/discovery-workflow.mjs +73 -0
  24. package/cli/doctor.mjs +137 -0
  25. package/cli/errors.mjs +21 -0
  26. package/cli/evidence-command-arguments.mjs +145 -0
  27. package/cli/evidence-command-runner.mjs +106 -0
  28. package/cli/evidence-signing.mjs +215 -0
  29. package/cli/exit-codes.mjs +9 -0
  30. package/cli/export-evidence.mjs +28 -0
  31. package/cli/formatters/evidence-report.mjs +3 -0
  32. package/cli/formatters/html.mjs +69 -0
  33. package/cli/formatters/json.mjs +135 -0
  34. package/cli/formatters/markdown.mjs +3 -0
  35. package/cli/formatters/terminal.mjs +658 -0
  36. package/cli/http.mjs +27 -0
  37. package/cli/lookup-command-runner.mjs +87 -0
  38. package/cli/lookup-diff.mjs +171 -0
  39. package/cli/mail-review.mjs +204 -0
  40. package/cli/manual.mjs +34 -0
  41. package/cli/network-command-runner.mjs +88 -0
  42. package/cli/output-file.mjs +97 -0
  43. package/cli/page-compare.mjs +123 -0
  44. package/cli/posture.mjs +20 -0
  45. package/cli/progress-events.mjs +53 -0
  46. package/cli/progress.mjs +56 -0
  47. package/cli/registry-access.mjs +3 -0
  48. package/cli/registry-support.mjs +120 -0
  49. package/cli/risk-calibration.mjs +294 -0
  50. package/cli/runner-types.mjs +1 -0
  51. package/cli/runner.mjs +726 -0
  52. package/cli/saved-lookup.mjs +87 -0
  53. package/cli/source-reliability.mjs +727 -0
  54. package/cli/strict-exit.mjs +55 -0
  55. package/cli/terminal-presentation.mjs +147 -0
  56. package/docs/cli.md +827 -0
  57. package/frontend/src/lib/analysis/acquisition-decision-packet.js +93 -0
  58. package/frontend/src/lib/analysis/acquisition-due-diligence.js +391 -0
  59. package/frontend/src/lib/analysis/artifact-integrity.js +21 -0
  60. package/frontend/src/lib/analysis/brand-profile-model.js +421 -0
  61. package/frontend/src/lib/analysis/bulk-domain-comparison.js +246 -0
  62. package/frontend/src/lib/analysis/bulk-mail-exposure.js +192 -0
  63. package/frontend/src/lib/analysis/bulk-retry-plan.js +86 -0
  64. package/frontend/src/lib/analysis/bulk-review-export.js +62 -0
  65. package/frontend/src/lib/analysis/bulk-review-model.js +266 -0
  66. package/frontend/src/lib/analysis/bulk-session-model.js +515 -0
  67. package/frontend/src/lib/analysis/bulk-sort.js +49 -0
  68. package/frontend/src/lib/analysis/campaign-model.js +265 -0
  69. package/frontend/src/lib/analysis/case-evidence-model.js +642 -0
  70. package/frontend/src/lib/analysis/case-migration-model.js +248 -0
  71. package/frontend/src/lib/analysis/case-model.js +5 -0
  72. package/frontend/src/lib/analysis/case-record-contracts.js +44 -0
  73. package/frontend/src/lib/analysis/case-record-core.js +232 -0
  74. package/frontend/src/lib/analysis/case-record-model.js +7 -0
  75. package/frontend/src/lib/analysis/case-record-operations.js +224 -0
  76. package/frontend/src/lib/analysis/case-response-model.js +640 -0
  77. package/frontend/src/lib/analysis/case-response-packet.js +573 -0
  78. package/frontend/src/lib/analysis/case-storage-model.js +93 -0
  79. package/frontend/src/lib/analysis/detection-rule-model.js +306 -0
  80. package/frontend/src/lib/analysis/dns-record-normalization.js +11 -0
  81. package/frontend/src/lib/analysis/http-summary.js +165 -0
  82. package/frontend/src/lib/analysis/investigation-guide.js +500 -0
  83. package/frontend/src/lib/analysis/investigation-template-model.js +144 -0
  84. package/frontend/src/lib/analysis/page-baseline.js +272 -0
  85. package/frontend/src/lib/analysis/page-similarity.js +179 -0
  86. package/frontend/src/lib/analysis/relationship-evidence.js +265 -0
  87. package/frontend/src/lib/analysis/relationship-observation-model.js +423 -0
  88. package/frontend/src/lib/analysis/scoring.js +135 -0
  89. package/frontend/src/lib/analysis/shortlist-model.js +209 -0
  90. package/frontend/src/lib/analysis/utils.js +265 -0
  91. package/frontend/src/lib/analysis/watchlist-history.js +643 -0
  92. package/frontend/src/lib/analysis/watchlist-store.js +127 -0
  93. package/frontend/src/lib/analysis/website-snapshot-model.js +302 -0
  94. package/frontend/src/lib/analysis/workspace-archive-crypto.js +282 -0
  95. package/frontend/src/lib/analysis/workspace-archive.js +472 -0
  96. package/lib/abort.mjs +26 -0
  97. package/lib/abusech-auth.mjs +19 -0
  98. package/lib/availability.mjs +716 -0
  99. package/lib/bounded-contract-normalizers.mjs +61 -0
  100. package/lib/browser-library-profile.mjs +310 -0
  101. package/lib/bulk-comparison-evidence.mjs +83 -0
  102. package/lib/classify.mjs +93 -0
  103. package/lib/client-behavior-profile.mjs +83 -0
  104. package/lib/credential-surface-profile.mjs +66 -0
  105. package/lib/csv.mjs +23 -0
  106. package/lib/ct-query.mjs +27 -0
  107. package/lib/ct-search.mjs +393 -0
  108. package/lib/distributed-operation-budget.mjs +461 -0
  109. package/lib/dns-delegation-health.mjs +344 -0
  110. package/lib/dns-intelligence.mjs +739 -0
  111. package/lib/dns-mx.mjs +24 -0
  112. package/lib/dns-resolver-selection.mjs +33 -0
  113. package/lib/domain-posture-analysis.mjs +254 -0
  114. package/lib/domain-posture-parsers.mjs +358 -0
  115. package/lib/domain-posture.mjs +745 -0
  116. package/lib/error-detail.mjs +10 -0
  117. package/lib/evidence-export.mjs +431 -0
  118. package/lib/evidence-report-markdown.mjs +71 -0
  119. package/lib/evidence-report.mjs +391 -0
  120. package/lib/external-intelligence-risk.mjs +108 -0
  121. package/lib/favicon.mjs +146 -0
  122. package/lib/feature-policy.mjs +70 -0
  123. package/lib/generated/retire-browser-catalog.mjs +8724 -0
  124. package/lib/generated/unicode-confusables-17.mjs +93 -0
  125. package/lib/hostname.mjs +38 -0
  126. package/lib/html-signals.mjs +680 -0
  127. package/lib/http-intelligence.mjs +232 -0
  128. package/lib/idn-confusable-policy.mjs +86 -0
  129. package/lib/idn-confusables.mjs +465 -0
  130. package/lib/lookup-cache.mjs +130 -0
  131. package/lib/lookup-progress.mjs +291 -0
  132. package/lib/lookup-source-progress.mjs +109 -0
  133. package/lib/lookup-threat-provider-inventory.mjs +13 -0
  134. package/lib/lookup.mjs +592 -0
  135. package/lib/observation.mjs +114 -0
  136. package/lib/observed-network-context.mjs +355 -0
  137. package/lib/operation-budget.mjs +489 -0
  138. package/lib/page-fingerprints.mjs +481 -0
  139. package/lib/page-role-profile.mjs +117 -0
  140. package/lib/perceptual-hash.mjs +451 -0
  141. package/lib/rdap-attempts.mjs +34 -0
  142. package/lib/rdap-bootstrap.mjs +173 -0
  143. package/lib/rdap-capabilities.mjs +292 -0
  144. package/lib/rdap-client.mjs +125 -0
  145. package/lib/rdap-normalization.mjs +691 -0
  146. package/lib/rdap-registrar.mjs +196 -0
  147. package/lib/rdap-transport.mjs +39 -0
  148. package/lib/rdap-types.mjs +1 -0
  149. package/lib/rdap-validation.mjs +94 -0
  150. package/lib/rdap.mjs +16 -0
  151. package/lib/registry-access.mjs +32 -0
  152. package/lib/registry-capabilities.mjs +117 -0
  153. package/lib/registry-capability-catalogue.mjs +1206 -0
  154. package/lib/registry-comparison.mjs +318 -0
  155. package/lib/registry-dates.mjs +190 -0
  156. package/lib/registry-insights.mjs +325 -0
  157. package/lib/response-policy.mjs +256 -0
  158. package/lib/risk-scoring.mjs +164 -0
  159. package/lib/safe-fetch.mjs +446 -0
  160. package/lib/security-txt.mjs +359 -0
  161. package/lib/service-binding-dns.mjs +608 -0
  162. package/lib/sslbl-certificates.generated.mjs +143 -0
  163. package/lib/sslbl-intelligence.mjs +241 -0
  164. package/lib/static-html-analysis.mjs +340 -0
  165. package/lib/structured-data-identity.mjs +323 -0
  166. package/lib/threat-intelligence-contract.mjs +5 -0
  167. package/lib/threat-intelligence-definition-registry.mjs +34 -0
  168. package/lib/threat-intelligence-fixtures.mjs +82 -0
  169. package/lib/threat-intelligence-manifests.mjs +512 -0
  170. package/lib/threat-intelligence-runtime.mjs +662 -0
  171. package/lib/threat-intelligence-targets.mjs +168 -0
  172. package/lib/threat-intelligence-types.mjs +3 -0
  173. package/lib/threatfox-intelligence.mjs +307 -0
  174. package/lib/tls-intelligence.mjs +814 -0
  175. package/lib/typosquat-generator.mjs +884 -0
  176. package/lib/urlhaus-intelligence.mjs +361 -0
  177. package/lib/urlscan-intelligence.mjs +349 -0
  178. package/lib/website-security-posture.mjs +325 -0
  179. package/lib/website-technology.mjs +437 -0
  180. package/lib/whois-authority.mjs +98 -0
  181. package/lib/whois-chain.mjs +261 -0
  182. package/lib/whois-contacts.mjs +163 -0
  183. package/lib/whois-contracts.mjs +3 -0
  184. package/lib/whois-normalization.mjs +27 -0
  185. package/lib/whois-transport.mjs +100 -0
  186. package/lib/whois-values.mjs +92 -0
  187. package/lib/whois.mjs +1187 -0
  188. package/package.json +55 -0
  189. package/third-party-notices.txt +5571 -0
package/docs/cli.md ADDED
@@ -0,0 +1,827 @@
1
+ # WHOISleuth CLI
2
+
3
+ The first-party command-line interface runs the same local classification and
4
+ lookup modules as the Express and serverless adapters. It does not call the
5
+ hosted WHOISleuth deployment.
6
+
7
+ `whoisleuth --help` displays the copyright, AGPL-3.0-only licence, and official
8
+ source location. It groups commands by investigation, discovery, saved-evidence,
9
+ integrity, and calibration workflows. `whoisleuth <command> --help` displays the
10
+ command's purpose, focused invocation, example, and collection boundary without
11
+ printing the full command list. Packaged copies include `LICENSE`, `NOTICE`, and
12
+ `TRADEMARKS.md` alongside the CLI documentation.
13
+
14
+ ## Installation
15
+
16
+ Public releases require Node.js 24 or later and install the `whoisleuth`
17
+ command:
18
+
19
+ ```bash
20
+ npm exec --yes --package=@slicedearth/whoisleuth-cli -- whoisleuth --help
21
+ npm install --global @slicedearth/whoisleuth-cli
22
+ whoisleuth doctor
23
+ ```
24
+
25
+ The scoped package and application share one semantic version. The package runs
26
+ locally and does not call the hosted WHOISleuth deployment. Networked commands
27
+ contact only the sources described by their collection profile; offline review,
28
+ comparison, verification, completion, and manual commands remain local.
29
+
30
+ ## Commands
31
+
32
+ ```bash
33
+ node bin/whoisleuth.mts lookup example.com
34
+ node bin/whoisleuth.mts lookup AS13335 --json
35
+ printf 'example.com\n' | node bin/whoisleuth.mts lookup --json
36
+ node bin/whoisleuth.mts lookup example.com --deep
37
+ node bin/whoisleuth.mts lookup example.com --deep --summary
38
+ node bin/whoisleuth.mts lookup example.com --deep --verbose
39
+ node bin/whoisleuth.mts lookup example.com --deep --markdown --output lookup.md
40
+ node bin/whoisleuth.mts lookup example.com --deep --json --strict-exit --events
41
+ cat domains.txt | node bin/whoisleuth.mts bulk --jsonl
42
+ node bin/whoisleuth.mts bulk domains.txt --csv --registered-only
43
+ node bin/whoisleuth.mts bulk domains.txt --domains --inconclusive-only
44
+ node bin/whoisleuth.mts bulk domains.txt --concurrency 4
45
+ node bin/whoisleuth.mts bulk domains.txt --deep --checkpoint bulk-checkpoint.json
46
+ node bin/whoisleuth.mts bulk domains.txt --deep --checkpoint bulk-checkpoint.json --resume
47
+ node bin/whoisleuth.mts ct-search 'example brand' --json
48
+ node bin/whoisleuth.mts discover example.com --preset common --jsonl
49
+ node bin/whoisleuth.mts discover example.test --dictionary private-terms.txt --snapshot discovery-state.json --json
50
+ node bin/whoisleuth.mts discover-scan example.test --scan-limit 50 --checkpoint candidate-scan.json --json
51
+ node bin/whoisleuth.mts discover-scan example.test --deep --scan-limit 20 --resolver 192.0.2.53 --observation-snapshot observed-candidates.json --csv
52
+ node bin/whoisleuth.mts posture example.com --selectors selector1 --retired-selectors selector0 --mail-profile defensive-no-mail --json
53
+ node bin/whoisleuth.mts http example.com --json
54
+ node bin/whoisleuth.mts tls example.com --json
55
+ node bin/whoisleuth.mts registry-support example.uk --json
56
+ node bin/whoisleuth.mts risk-calibrate calibration.json --json
57
+ node bin/whoisleuth.mts verify-artifact workspace.json --json
58
+ node bin/whoisleuth.mts verify-artifact workspace-encrypted.json --passphrase-file passphrase.txt --json
59
+ node bin/whoisleuth.mts source-report lookup.json --json
60
+ node bin/whoisleuth.mts inspect-archive workspace.json --json
61
+ node bin/whoisleuth.mts inspect-archive workspace.json --search example.invalid --json
62
+ node bin/whoisleuth.mts sign-artifact response-packet.json --private-key-file analyst-private.pem > response-packet.signed.json
63
+ node bin/whoisleuth.mts verify-signature response-packet.signed.json --public-key-file analyst-public.pem
64
+ node bin/whoisleuth.mts lookup example.com --deep --json > lookup.json
65
+ node bin/whoisleuth.mts compare lookup.json --json
66
+ node bin/whoisleuth.mts page-compare official.json candidate.json --json
67
+ node bin/whoisleuth.mts mail-review bulk.json --json
68
+ node bin/whoisleuth.mts diff first-lookup.json second-lookup.json --json
69
+ node bin/whoisleuth.mts export lookup.json > evidence.json
70
+ node bin/whoisleuth.mts export lookup.json --markdown > evidence.md
71
+ node bin/whoisleuth.mts export lookup.json --html > evidence.html
72
+ node bin/whoisleuth.mts doctor
73
+ node bin/whoisleuth.mts doctor --network --json
74
+ node bin/whoisleuth.mts completion zsh
75
+ node bin/whoisleuth.mts manual | man -l -
76
+ ```
77
+
78
+ These examples run from a checked-out repository; replace
79
+ `node bin/whoisleuth.mts` with `whoisleuth` after installation. The root
80
+ application package remains private. A separate scoped CLI archive is assembled
81
+ from the exact executable dependency closure, installed in a temporary
82
+ directory, and smoke tested with:
83
+
84
+ ```bash
85
+ npm run cli:package:check
86
+ ```
87
+
88
+ The check includes only the executable, reachable TypeScript modules, CLI
89
+ guide, licence, notices, and trademark terms. It excludes application routes,
90
+ deployment adapters, tests, and development tools. Its generated manifest stays
91
+ private. Release-candidate assembly is a separate explicit operation and never
92
+ publishes from a developer checkout. Use the scoped package name; an unqualified
93
+ `npx whoisleuth` is not this project.
94
+
95
+ Lookup defaults to the conservative fast profile. `--deep` must be requested
96
+ explicitly and can add bounded WHOIS, DNS, website, TLS, registrar RDAP, and
97
+ observed-network IP RDAP work for a domain. The browser Console's optional
98
+ security.txt and external intelligence selections are not CLI flags; the CLI
99
+ does not run those actions implicitly.
100
+
101
+ For a directly entered public IP address, `lookup --deep` can also run one
102
+ bounded reverse-DNS query. Terminal output shows the explicit source state and
103
+ up to five normalized PTR names; JSON retains up to eight. PTR names are
104
+ operator-published routing context, not proof of hosting control or ownership.
105
+ Fast lookups do not run this query.
106
+
107
+ Only one query is accepted by `lookup`. Multiple-input processing belongs to
108
+ the explicit `bulk` command rather than being silently inferred by `lookup`.
109
+ Standard input is capped at 4 KiB and must contain one non-empty line.
110
+
111
+ `registry-support` is an offline catalogue view. It accepts one domain, bare
112
+ suffix, or leading-dot suffix as an argument or on stdin and makes no network
113
+ request. Its output is described below.
114
+
115
+ `doctor` is offline by default. It checks the Node runtime, platform, terminal
116
+ capabilities, and availability of offline command families. `doctor --network`
117
+ adds bounded public-DNS and WHOIS port 43 checks against fixed diagnostic
118
+ infrastructure. It retains only state and a bounded explanation, not resolved
119
+ addresses or response content. A failed optional network check returns the
120
+ partial-failure exit code rather than claiming that all CLI collection is
121
+ unavailable.
122
+
123
+ `completion bash`, `completion zsh`, and `completion fish` print static shell
124
+ completion scripts to stdout. The command does not edit a shell profile or make
125
+ a network request. Review the generated script before sourcing it or placing it
126
+ in the relevant shell completion directory. `manual` prints a generated roff
127
+ manual page and likewise changes no local configuration.
128
+
129
+ ## Deployment boundary
130
+
131
+ The CLI is a local repository tool. The serverless deployment publishes only
132
+ `frontend/build` and packages functions only from `netlify/functions`, so
133
+ `bin/` and `cli/` are not part of the hosted static site or function bundle.
134
+ Both the application root and the assembled CLI package remain private and are
135
+ not published to the public npm registry.
136
+
137
+ Commands that query RDAP, WHOIS, DNS, HTTP, TLS, or Certificate Transparency do
138
+ so directly from the machine running the CLI. They do not use the hosted login,
139
+ hosted session, or deployment usage controls; upstream providers can see and
140
+ rate-limit the local machine's network address. Offline `discover`, `compare`,
141
+ `page-compare`, `mail-review`, `diff`, `risk-calibrate`, `verify-artifact`, `source-report`, `export`,
142
+ `completion`, and `manual` operations make no network requests. Commands write
143
+ to stdout unless the analyst deliberately selects a local output file.
144
+
145
+ ## Output
146
+
147
+ Human-readable terminal output is the default. On an interactive terminal it
148
+ uses restrained semantic colour, groups Lookup evidence by purpose, wraps
149
+ prose to the detected terminal width, and shows transient progress on stderr
150
+ for slower collection. State remains explicit in text, so colour is never the
151
+ only distinction. Redirected terminal output, JSON, and JSONL contain no ANSI
152
+ sequences or progress text. `--no-color` and a non-empty conventional
153
+ `NO_COLOR` environment variable disable colour; `WHOISLEUTH_NO_PROGRESS=1`
154
+ disables the transient indicator. Command examples remain one copyable line
155
+ even when prose is wrapped for a narrow terminal.
156
+
157
+ Every command accepts `--output <file>` as a safer alternative to shell
158
+ redirection. WHOISleuth buffers bounded output, creates a private temporary file
159
+ beside the destination, syncs it, and publishes it atomically with mode `0600`.
160
+ An existing destination is refused. `--force` is valid only with `--output` and
161
+ allows an intentional atomic replacement. A failed or cancelled command does
162
+ not publish a partial output file. Output is capped at 32 MiB.
163
+
164
+ `lookup --markdown` and `lookup --html` build the existing normalized evidence
165
+ report directly after one completed domain lookup. They do not start a second
166
+ collection. These report formats reject IP and ASN inputs before collection;
167
+ JSON and terminal output remain available for those query types.
168
+
169
+ Lookup terminal output supports three detail levels. The standard view keeps
170
+ the normal bounded evidence summary. `--summary` keeps the source states and
171
+ key findings while omitting endpoints and supporting detail. `--verbose` adds
172
+ the document generation time and the existing bounded per-source collection
173
+ timings. These switches are presentation-only and cannot be combined with
174
+ `--json`; they do not change collection or the result document.
175
+
176
+ `--json` writes one versioned document to standard output:
177
+
178
+ ```json
179
+ {
180
+ "schema": "whoisleuth.cli.lookup",
181
+ "version": 1,
182
+ "generatedAt": "2026-07-14T00:00:00.000Z",
183
+ "mode": "fast",
184
+ "query": "example.com",
185
+ "type": "domain"
186
+ }
187
+ ```
188
+
189
+ The complete document also carries the normalized `rdap`, `whois`,
190
+ `availability`, and `diagnostics` sections returned by the shared lookup
191
+ orchestrator. Machine output goes to stdout. Usage and lookup errors go to
192
+ stderr, so redirected JSON is not mixed with diagnostics.
193
+
194
+ Human-readable domain lookup output separately shows the registrar RDAP status
195
+ and endpoint whenever the shared lookup diagnostics represent that follow-up.
196
+ Deep mode may report success, unsupported, not found, or an explicit failure;
197
+ fast mode reports the existing skipped state. IP, ASN, and lookup responses
198
+ without registrar diagnostics remain unchanged. These source states are
199
+ provenance only and do not decide availability or imply safety.
200
+
201
+ When a deep domain response includes registry interpretation version 1, the
202
+ terminal summary also shows the lifecycle label, separately attributed RDAP and
203
+ WHOIS disclosure states, reconciliation state, and complete, partial, and
204
+ unavailable publication counts. It does not print published contact routes.
205
+ The JSON document retains the bounded interpretation and its limitations.
206
+
207
+ A deep domain lookup can also show the status, selected public address, and
208
+ registered network name from the bounded observed network context. It uses the
209
+ same address and IP RDAP source represented in the JSON document. This is
210
+ point-in-time edge or network-registration context, not proof of the origin
211
+ host, hosting control, ownership, intent, or maliciousness. Fast and compact
212
+ commands do not run the enrichment.
213
+
214
+ The deep terminal summary also reports the website activity state, page title,
215
+ DNS, HTTP, and TLS source states, bounded publisher-declared structured
216
+ identity entities, up to six bounded technology indicators, a browser-library
217
+ profile count, and the four passive security-posture counts.
218
+ These are concise projections of the same evidence already present in the
219
+ lookup response. The browser-library line counts apparent components and
220
+ catalogue advisory matches without retaining script references or raw
221
+ signatures. These summaries make no extra request, omit raw evidence
222
+ descriptions, and do not turn a technology or advisory match, or a missing
223
+ posture signal, into proof of exploitability, hosting control, ownership, or
224
+ maliciousness. Use `--json` when the full bounded evidence, limitations, and
225
+ source diagnostics are required.
226
+
227
+ `lookup --strict-exit` is an opt-in automation policy. It still emits the
228
+ ordinary complete output document, but returns exit code 4 when a requested
229
+ diagnostic source is partial, unavailable, rate-limited, timed out, or failed.
230
+ The default exit contract remains unchanged, so inconclusive source health does
231
+ not silently break existing scripts.
232
+
233
+ `lookup --events` and `bulk --events` write versioned
234
+ `whoisleuth.cli.progress` version 1 JSONL lifecycle events to stderr while the
235
+ ordinary final result stays on stdout. Events contain only the command,
236
+ sequence, time, source state or Bulk item index, categorical failure reasons,
237
+ and final exit status. They do not contain a target, query, endpoint, file
238
+ path, error detail, or evidence value. Human progress and error text are
239
+ suppressed while events are active. `--events` cannot be combined with
240
+ `--output`, because the final event must describe the same completed stdout
241
+ delivery observed by the caller.
242
+
243
+ Pressing Ctrl-C asks an in-flight Lookup or Bulk run to stop, suppresses any
244
+ partial final result, flushes an enabled Bulk checkpoint, and exits with code
245
+ 130. Already started network operations remain subject to their existing hard
246
+ timeouts while the shared in-process API unwinds; the executable terminates
247
+ after CLI cleanup. A second Ctrl-C removes any known unpublished temporary
248
+ output file on a best-effort basis before exiting immediately.
249
+
250
+ Bulk can persist compact progress with `--checkpoint <file>`. A new checkpoint
251
+ is private, bounded to 16 MiB, versioned, and tied to the exact ordered input
252
+ and scan mode by SHA-256. It stores only compact per-item results, never full
253
+ Lookup responses. Reusing the same path is refused unless `--resume` is also
254
+ specified. Resume revalidates the complete untrusted document and skips only
255
+ the exact completed rows; changed input, mode, schema, digest, or malformed
256
+ results fail closed.
257
+
258
+ If checkpoint persistence fails after collection, WHOISleuth still emits the
259
+ completed final result, reports the checkpoint limitation, and exits with code
260
+ 4. The last successfully published checkpoint remains available, but it may
261
+ not contain the final completed items.
262
+
263
+ `diff <left.json> <right.json>` compares two different saved domain Lookup
264
+ documents entirely offline. It reuses the bounded Bulk comparison model for
265
+ registration, DNS, page identity, mail, certificate, and relationship fields,
266
+ keeps missing and unavailable evidence distinct, and makes no ownership or
267
+ maliciousness inference. The output records both original observation times.
268
+
269
+ When diagnostics version 5, 6, 7, or 8 reports a documented registry collection
270
+ constraint, terminal output also shows the suffix, WHOIS and RDAP access
271
+ profiles, and the bounded limitation. This is static access-policy context: it
272
+ does not make another request, and restricted, unpublished, or unavailable
273
+ machine access is not evidence that a domain is unregistered or safe.
274
+
275
+ ## Exit codes
276
+
277
+ | Code | Meaning |
278
+ | ---: | --- |
279
+ | 0 | Command completed. Individual sources may still be partial or inconclusive; inspect diagnostics. |
280
+ | 2 | Invalid command, option, input, or stdin shape. |
281
+ | 3 | The requested lookup, collection, or comparison operation could not run. |
282
+ | 4 | A bounded operation completed partially, such as Bulk item failures or failed explicit Doctor network checks. |
283
+ | 70 | Unexpected CLI bootstrap failure. |
284
+ | 130 | The analyst cancelled the command. No partial final result was emitted. |
285
+
286
+ This release supports `lookup`, `bulk`, `ct-search`, `discover`, `discover-scan`, `posture`,
287
+ `http`, `tls`, `registry-support`, `risk-calibrate`, `verify-artifact`,
288
+ `inspect-archive`, `sign-artifact`, `verify-signature`, `source-report`,
289
+ `compare`, `page-compare`, `mail-review`, `diff`, `export`, `doctor`, `completion`, and `manual`. Additional command families
290
+ are added as separate bounded increments rather than exposing incomplete
291
+ aliases.
292
+
293
+ ## Registry capability coverage
294
+
295
+ `registry-support` projects the embedded, versioned registry compatibility
296
+ catalogue for one domain or suffix. It distinguishes an explicit
297
+ fixture-backed or access-documented suffix profile from the generic IANA
298
+ discovery profile and reports the RDAP and WHOIS discovery/access paths,
299
+ WHOIS query and parser profiles, any bounded fallback, fixture scenarios,
300
+ verification references, documentation references, and the catalogue's
301
+ limitation. The same document includes a dated, official-source aggregate of
302
+ generic, generic-restricted, sponsored, and infrastructure RDAP coverage. That
303
+ snapshot is separate from the suffix profile and from live reachability.
304
+
305
+ Terminal output is bounded and control-safe. `--json` emits version 2 of the
306
+ `whoisleuth.cli.registry-support` schema. Unknown but syntactically valid
307
+ suffixes retain the generic `discovery_only` profile; malformed input exits
308
+ with code 2. The command never probes a registry or tests current reachability.
309
+ Coverage is context only and cannot decide registration, availability,
310
+ ownership, safety, or maliciousness.
311
+
312
+ The separate maintenance command `npm run rdap-extensions:drift` compares the
313
+ pinned official RDAP extension fixture with the reviewed local interpretation
314
+ catalogue entirely offline. `--live` performs one bounded manual fetch from the
315
+ fixed official CSV URL and reports added, removed, renamed, status-changed,
316
+ unrecognized, and local-only identifiers. `--json` emits
317
+ `whoisleuth.rdap-extension-drift-audit` version 1. Drift exits with status 1 and
318
+ requires specification and fixture review; the command never enables an
319
+ extension, changes authority or availability logic, or issues reverse search.
320
+
321
+ `npm run service-dependencies:audit` validates the passive service-dependency
322
+ catalogue entirely offline. It checks the catalogue digest, duplicate
323
+ identifiers and target suffixes, evidence classes, source and licence
324
+ treatment, source dates, and a fixed 180-day review age. `--json` emits
325
+ `whoisleuth.service-dependency-signature-audit` version 1. A stale or changed
326
+ signature requires manual provider and benign-fixture review; the command
327
+ never resolves a target, checks an account, or tests claimability.
328
+
329
+ ## Offline Risk calibration
330
+
331
+ `risk-calibrate` replays a versioned analyst-labelled fixture dataset through
332
+ the exact Risk model shared with the web application. It does not perform a
333
+ lookup, contact the hosted deployment, persist the dataset, alter model weights,
334
+ choose a threshold, or tune the model automatically. The report compares fixed
335
+ thresholds from 40 through 90 and identifies 70 as the current review band.
336
+
337
+ Input is capped at 2 MiB and 500 records. Each record requires a unique bounded
338
+ ID, an ASCII DNS hostname, one existing analyst disposition, and a strict
339
+ whitelist of current scoring inputs. Unknown fields are discarded; arrays,
340
+ strings, ages, booleans, provider records, and external findings are revalidated
341
+ and bounded before scoring. The accepted dataset envelope is:
342
+
343
+ ```json
344
+ {
345
+ "schema": "whoisleuth.risk-calibration-dataset",
346
+ "version": 1,
347
+ "records": [
348
+ {
349
+ "id": "fixture-1",
350
+ "domain": "login.example.test",
351
+ "analystDisposition": "confirmed_abuse",
352
+ "evidence": {
353
+ "availability": "registered",
354
+ "mutationTypes": ["dictionary"],
355
+ "hasPasswordField": true
356
+ }
357
+ }
358
+ ]
359
+ }
360
+ ```
361
+
362
+ `confirmed_abuse` is treated as a positive calibration label; `false_positive`
363
+ and `expected` are negative labels. `unreviewed`, `suspicious`, and
364
+ `closed_no_action` remain contextual and are excluded from threshold quality
365
+ metrics, as are records for which Risk is not applicable. Terminal output caps
366
+ its record list at 100 while `--json` retains the complete bounded report,
367
+ factor breakdowns, score bands, and per-threshold confusion metrics. Analyst
368
+ dispositions and heuristic scores are review context: neither proves
369
+ maliciousness or safety.
370
+
371
+ The authenticated Monitor workspace can deliberately create the accepted
372
+ dataset from explicitly selected cases. Selection is available only when a case
373
+ has a reviewed disposition and retained normalized evidence. That local export
374
+ contains the case ID, domain, disposition, and a bounded whitelist of current
375
+ scoring inputs. It excludes notes, tags, assertions, actions, contacts, raw
376
+ source data, provider payloads, and stored Risk scores. Before download, a local
377
+ review step shows the selected, included, and excluded counts, the domains and
378
+ dispositions that will be written, and the excluded evidence classes. The CLI
379
+ still validates every exported record and never trains, tunes, or changes the
380
+ Risk model.
381
+
382
+ ## Offline artifact verification
383
+
384
+ `verify-artifact` validates a supported local artifact without printing its
385
+ evidence contents. Input is capped at 15 MiB. It currently recognises ordinary
386
+ and encrypted workspace archives, case-response packets, acquisition-decision
387
+ exports, domain-comparison exports, Bulk mail-exposure exports, and Bulk review
388
+ manifests.
389
+
390
+ For an ordinary workspace archive, the command validates the versioned
391
+ structure, section byte counts, record counts, and per-section checksums. For a
392
+ supported signed review artifact, it validates the declared SHA-256 digest. An
393
+ encrypted archive without `--passphrase-file` can establish only that the
394
+ envelope is structurally valid; it reports `envelope_valid` and explicitly
395
+ leaves authenticated encryption and inner checksums unchecked.
396
+
397
+ Supplying a separate passphrase file, capped at 1 KiB, allows authenticated
398
+ decryption followed by the ordinary workspace checks. The passphrase is never
399
+ accepted as a command-line value, printed, or retained. Verification detects
400
+ changes against the artifact's declared contract; it does not authenticate the
401
+ analyst or establish that an observation was accurate or remains current.
402
+
403
+ ## Privacy-safe source reliability report
404
+
405
+ `source-report` summarises source states, durations, truncation, and rate-limit
406
+ counts from one version-1 CLI Lookup, Bulk, or Bulk-item document, a JSON array
407
+ of up to 100 such documents, or an array containing only previously generated
408
+ source-reliability reports. Lookup documents and reports cannot be mixed.
409
+ Merged reports preserve exact state and sample totals and add bounded ranges
410
+ for each input report's median and p95 duration, a bounded source timeline,
411
+ fast/deep/unknown cohorts, and the earliest and latest valid report generation
412
+ times. Exact duplicate reports are rejected so one saved report is not
413
+ accidentally counted twice. Per-source failure, partial, truncation, and
414
+ rate-limit rates remain separate. The command does not pretend that aggregate
415
+ timing distributions can reconstruct the original target-level samples. Input
416
+ is capped at 12 MiB and traversal, source, duration, and output counts are
417
+ bounded.
418
+
419
+ The report retains only fixed source identifiers and aggregate operational
420
+ counts. It does not output targets, queries, endpoints, source limitations, raw
421
+ evidence, or provider payloads. Observation-envelope duration and the
422
+ potentially overlapping Lookup timing are reported separately and never added
423
+ together. It is a local engineering diagnostic, not analytics, a source-quality
424
+ score, or evidence that a collector is currently healthy.
425
+
426
+ ## Workspace archive inspection
427
+
428
+ `inspect-archive` verifies an ordinary or encrypted workspace archive and then
429
+ reports its versioned sections, counts, byte sizes, and checksum states without
430
+ printing stored records. An encrypted archive requires
431
+ `--passphrase-file`; passphrases are never accepted on the command line.
432
+
433
+ An optional `--search` is exact and case-insensitive across a fixed allowlist
434
+ of target fields. Default matches disclose only the section, field name, and a
435
+ SHA-256 digest. `--reveal` must be selected explicitly to print the matched
436
+ value. Notes, contacts, raw evidence, arbitrary object fields, and partial text
437
+ search are excluded. Unicode domain searches are canonicalized to their
438
+ DNS-safe ASCII form before exact comparison. `--require-match` makes a
439
+ no-match result fail for automation and is valid only with `--search`.
440
+ Traversal, input size, matches, depth, arrays, and strings are bounded.
441
+
442
+ ## Optional evidence-package signing
443
+
444
+ `sign-artifact` signs a locally verified case-response packet or supported
445
+ review manifest with an externally managed Ed25519 private key:
446
+
447
+ ```bash
448
+ node bin/whoisleuth.mts sign-artifact packet.json --private-key-file analyst-private.pem > packet.signed.json
449
+ node bin/whoisleuth.mts verify-signature packet.signed.json --public-key-file analyst-public.pem
450
+ ```
451
+
452
+ The signed package embeds the public key, key identifier, signature time, and
453
+ original artifact. Verification first checks the exact package field set and
454
+ signature, then the artifact's own checksums or manifest. A result is reported
455
+ as `signature_valid`; signer trust is separately reported as `trusted_key` or
456
+ `embedded_key_only`. Without a separately supplied trusted public key,
457
+ verification establishes only that the package is internally self-consistent.
458
+ WHOISleuth does not generate, store, recover, rotate, publish, or establish
459
+ trust in signing keys.
460
+
461
+ ## Bulk lookup
462
+
463
+ `bulk` accepts a newline-delimited file or stdin. It preserves input order,
464
+ removes case-insensitive duplicates, and returns a result for every retained
465
+ query. Input is capped at 1 MiB. Fast mode is capped at 500 queries with four
466
+ workers by default. Deep mode is capped at 50 queries with two workers by
467
+ default. Explicit concurrency cannot exceed eight in fast mode or three in
468
+ deep mode.
469
+
470
+ Bulk uses the shared compact lookup response, so it does not retain raw RDAP
471
+ objects or WHOIS response bodies. `--json` returns one bounded collection;
472
+ `--jsonl` emits one self-contained versioned item per line. Version 2 adds a
473
+ bounded `dnsSummary` projection for observed A, AAAA, NS, and MX records plus
474
+ null MX, SPF, and DMARC state. `--csv` writes fixed columns for automation,
475
+ including the DNS summaries and explicit outcome; `--domains` writes only the
476
+ normalized retained domain names for a subsequent command.
477
+
478
+ `--registered-only` and `--inconclusive-only` are mutually exclusive output
479
+ filters. The first retains registered, for-sale, and expiring authority-aware
480
+ states. The second retains unknown authority states and failed rows. Filtering
481
+ does not change collection and never converts an unavailable or failed source
482
+ into an unregistered result. Machine documents record collected and emitted
483
+ counts. A mixture of successful and failed queries exits with code 4 while
484
+ preserving every collected result before output filtering.
485
+
486
+ ## Certificate Transparency search
487
+
488
+ `ct-search` accepts one keyword as an argument or on stdin and calls the same
489
+ bounded Certificate Transparency module as the web application. It contacts
490
+ the upstream public log search service directly from the local machine; it
491
+ does not call the hosted WHOISleuth deployment. Quote a multi-word keyword so
492
+ the shell passes it as one argument.
493
+
494
+ Terminal output summarizes certificate rows, observed hostnames, canonical
495
+ registrable-domain matches, observation times, and completeness. It shows at
496
+ most 100 matches and five hostnames per match, with explicit omission notes.
497
+ `--json` returns the complete bounded structured result in the versioned
498
+ `whoisleuth.cli.ct-search` schema. CT observations do not prove that a website
499
+ is active or malicious.
500
+
501
+ ## Lookalike discovery
502
+
503
+ `discover` runs the same pure, bounded lookalike generator as the Discover
504
+ tool without making network requests. It accepts a brand label or a
505
+ domain with one suffix label. The default TLD set is `com,net,org`; replace it
506
+ with `--tlds com,net` when narrower coverage is wanted.
507
+
508
+ Generation presets are `common`, `impersonation`, and `all` (the default).
509
+ Keyboard-aware mutations support `qwerty` (the default), `azerty`, and
510
+ `qwertz`; `--keyboard all` uses their bounded combined neighbour set, including
511
+ adjacent number-row keys. Supply a UTF-8 text file with
512
+ `--dictionary terms.txt` to add up to 100 local terms when
513
+ using the `impersonation` or `all` preset. Dictionary input is capped at 4,096
514
+ bytes and individual terms at 32 characters. Machine output records accepted
515
+ and rejected term counts but never copies the private terms into its metadata.
516
+
517
+ Use `--families <ids>` instead of `--preset` to run an exact comma-separated
518
+ selection. Supported IDs are `character_addition`, `character_omission`,
519
+ `character_duplication`, `character_transposition`, `pluralization`,
520
+ `tld_embedding`, `www_prefix`, `hyphenation`, `separator_omission`,
521
+ `word_reordering`, `keyboard_substitution`, `keyboard_insertion`, `vowel_swap`,
522
+ `bitsquatting`, `ascii_homoglyph`, `unicode_homoglyph`,
523
+ `unicode_whole_label`, `unicode_homoglyph_depth_2`, `dictionary`,
524
+ `dictionary_token_replacement`, `tld_typo`, and `tld_substitution`. Machine
525
+ output records the normalized custom selection. A custom dictionary file
526
+ requires `dictionary` or `dictionary_token_replacement` in that selection.
527
+
528
+ `--domains` writes the unique candidate names only. `--snapshot <file>` keeps
529
+ one private, versioned local observation of the normalized seed, generation
530
+ configuration, custom-dictionary digest, and candidate set. The first run
531
+ creates the snapshot; later runs report added and removed candidates before
532
+ atomically replacing it. The snapshot never stores private dictionary terms.
533
+ Use the same command from a local scheduler such as cron or a system timer to
534
+ perform repeatable discovery runs. WHOISleuth does not install a scheduler,
535
+ run a background daemon, or infer that an added candidate is registered,
536
+ active, available, or malicious.
537
+
538
+ Dotted subdomain permutations are intentionally excluded because the lookup
539
+ pipeline validates a hostname's registrable parent. The CLI does not present
540
+ authoritative parent registration evidence as proof that a generated hostname
541
+ exists.
542
+
543
+ Terminal output is capped at 200 candidates with an explicit notice. Versioned
544
+ JSON and JSONL use `whoisleuth.cli.discover` schema version 2 and retain the
545
+ complete bounded candidate set and mutation provenance. Internationalised
546
+ candidates show the DNS-safe ASCII form and readable Unicode form together in
547
+ terminal output. The Impersonation and All presets can add up to six
548
+ deterministic whole-label Unicode candidates when every letter has a reviewed
549
+ same-script replacement. The command generates candidates only. It does not
550
+ claim that a domain is registered, active, or malicious.
551
+
552
+ `unicode_homoglyph_depth_2` is an explicit `--families` option and is never
553
+ enabled by a preset. It replaces exactly two ASCII letters with characters
554
+ from the same reviewed script, ranks curated mappings first, and returns at
555
+ most 256 candidates. JSON and terminal output report generated candidates,
556
+ cross-script or invalid combinations omitted by policy, and lower-ranked
557
+ label variants omitted by the family or overall generation budget.
558
+
559
+ ## Supervised candidate scan
560
+
561
+ `discover-scan` composes the same bounded generator with the compact Lookup
562
+ collector. Unlike `discover`, it is an explicitly networked command. It selects
563
+ the first deterministic candidates up to `--scan-limit`, processes fixed
564
+ chunks sequentially, and retains each registration and DNS source state. Fast
565
+ mode is the default and accepts at most 500 candidates. Explicit `--deep` mode
566
+ is capped at 50 candidates and concurrency 3 because it can add WHOIS, DNS,
567
+ HTTP, TLS, page, and technology work for every selected candidate.
568
+
569
+ Use `--chunk-size 1-100`, `--concurrency 1-8`, and the existing private
570
+ `--checkpoint <file> [--resume]` boundary to control a run. The checkpoint is
571
+ tied to the exact ordered candidate set and scan mode. A new generation order,
572
+ limit, or mode requires another checkpoint. `--resolver` accepts at most three
573
+ literal IPv4 or IPv6 recursive-resolver addresses and applies them only to DNS
574
+ observations in this local run. Resolver hostnames and automatic third-party
575
+ resolver selection are not supported. The selected resolvers can observe the
576
+ queried candidates and may apply their own logging and retention policies.
577
+
578
+ An optional newline-delimited `--allowlist` accepts at most 500 domains and 64
579
+ KiB. Matching candidates remain in evidence and are labelled `suppressed` in
580
+ the review queue; the full list and unmatched entries are not copied into
581
+ output, while each matching scanned candidate necessarily exposes that match.
582
+ Allowlisting never deletes a result or asserts that it is safe. Output filters
583
+ select registered, inconclusive, acquisition-review, or
584
+ suppressed rows without changing whole-run counts or source health. JSON,
585
+ JSONL, CSV, and domain-list forms retain mutation provenance and explicit
586
+ review lanes. The queue provides next manual actions, not automatic lookup,
587
+ acquisition, blocking, submission, or enforcement.
588
+
589
+ Deep compact results can group exact shared addresses, nameservers, and mail
590
+ servers observed across at least two candidates. These bounded relationships
591
+ are pivot leads only. Common hosting, delivery, registrar, and mail services
592
+ can produce the same relationship without common ownership or control.
593
+
594
+ `--observation-snapshot <file>` keeps a private versioned local projection of
595
+ registration state, confidence, and bounded A, AAAA, NS, MX, null-MX, SPF, and
596
+ DMARC summaries for the exact candidate set, mode, and resolver selection.
597
+ Version 2 records registration and DNS observation times and latest component
598
+ states separately; version 1 is read and normalized on the next write. A later
599
+ identical run reports material field changes only for components collected
600
+ with enough completeness to support the comparison. If registration or DNS is
601
+ partial or unavailable, the prior usable evidence for that component is
602
+ preserved and the attempt is reported as unavailable rather than converting
603
+ missing data into a removal. Raw registry
604
+ records, contacts, page content, and request details are excluded. Snapshot
605
+ differences are review prompts, not proof of ownership, control, intent,
606
+ safety, or maliciousness.
607
+
608
+ ## Domain posture audit
609
+
610
+ `posture` runs the standard-profile form of the owned-domain DNS and
611
+ email-security audit used by Brand Profiles. It queries registry status and
612
+ DNSSEC evidence, nameserver delegation, SPF, DMARC, MX, CAA, MTA-STS, TLS-RPT,
613
+ and BIMI directly from the local machine. Literal SPF include and redirect
614
+ branches are expanded within fixed depth, policy-query, DNS-term, void-answer,
615
+ cycle, and time bounds. External DMARC reporting authorization and external
616
+ nameserver, mail, SPF, and reporting dependencies are separately reported.
617
+ Supply up to ten known active and retired DKIM selectors in total with
618
+ `--selectors selector1,selector2` and `--retired-selectors selector0`;
619
+ selectors cannot be reliably discovered from DNS, so no-selector output
620
+ reports DKIM as not checked. Use `--mail-profile standard`,
621
+ `--mail-profile defensive-no-mail`, or `--mail-profile parked` to make the
622
+ intended mail posture explicit rather than inferring it from DNS.
623
+
624
+ Terminal output shows each pass, review, action, or informational result and
625
+ caps displayed records at five per check with an explicit omission notice.
626
+ Versioned JSON retains the complete bounded report, including SPF traversal,
627
+ DMARC authorization, and dependency provenance. Warnings and dangers are
628
+ findings rather than command failures; transient resolver or policy-fetch
629
+ failures remain informational and should be retried before changing DNS.
630
+
631
+ ## HTTP intelligence
632
+
633
+ `http` runs the same bounded safe homepage probe used by a deep lookup. It
634
+ tries HTTPS before HTTP, follows only validated public-network redirects, caps
635
+ each attempt at six seconds, and captures at most 300,000 response bytes for
636
+ hashing and metadata. A non-success HTTP response still proves that a web
637
+ service answered; failure of both schemes remains inconclusive rather than
638
+ evidence that no website exists.
639
+
640
+ The command reports normalized redirect, response, content, selected security-
641
+ header, body-hash, completeness, and attempt provenance. Captured homepage text
642
+ is never written to terminal or JSON output. Query strings are removed by the
643
+ shared HTTP evidence normalizer, and terminal values are additionally bounded
644
+ and control-safe.
645
+
646
+ ## TLS intelligence
647
+
648
+ `tls` runs the same bounded one-connection TLS collector used by deep lookups.
649
+ It resolves the hostname once through the public-address guard, validates every
650
+ answer, connects directly to the first validated address on port 443, and keeps
651
+ the original hostname for SNI and certificate identity checking. Resolution
652
+ and handshake work share a five-second deadline.
653
+
654
+ The report retains the negotiated protocol, ALPN and cipher, runtime trust and
655
+ hostname checks, certificate validity, bounded subject and issuer names, SANs,
656
+ SAN class counts, signature algorithm and OID, extended-key-usage purposes,
657
+ classified Authority Information Access presence counts, SHA-256 certificate
658
+ and public-key fingerprints, a bounded certificate chain, and neutral
659
+ findings. AIA responder and issuer locations are classified as HTTP, HTTPS, or
660
+ other and then discarded; they are not retained or followed. The collector
661
+ stores no certificate bytes, session material, or application data and does
662
+ not enumerate supported protocol or cipher suites.
663
+ A failed collection is inconclusive rather than proof that no TLS service
664
+ exists.
665
+
666
+ ## Registry-source comparison
667
+
668
+ `compare` reads one version-1 `whoisleuth.cli.lookup` domain document from a
669
+ file or stdin and emits a version-3 `whoisleuth.cli.compare` document. Its
670
+ primary comparison reconciles normalized registry RDAP and WHOIS fields. When
671
+ the saved deep lookup also represents the optional registrar RDAP follow-up,
672
+ an additional sibling comparison reconciles the portable registry and
673
+ registrar RDAP publication fields. The command does not repeat the lookup,
674
+ contact the hosted deployment, or treat a conflict as a command failure. Use a
675
+ deep lookup when multiple sources are wanted; a fast lookup deliberately
676
+ records WHOIS as skipped, and comparison preserves that state as unavailable
677
+ rather than misreporting RDAP-only publication.
678
+
679
+ Input is capped at 8 MiB and must retain the lookup schema, mode, source
680
+ diagnostics, and normalized parsed source sections. Per-value, list, and event
681
+ bounds are revalidated because saved JSON is treated as untrusted input. A
682
+ successful registrar source must include a normalized object and agree with
683
+ its diagnostic status. Raw RDAP JSON, registrar contacts and source-specific
684
+ handles, WHOIS response bodies, availability evidence, and unrelated lookup
685
+ fields are not copied into the comparison result.
686
+
687
+ When the saved Lookup contains valid version-5, version-6, or version-7, context-only
688
+ registry-access diagnostics, version 3 retains their bounded suffix, access profiles, and
689
+ limitation. Terminal output labels the same collection context explicitly.
690
+ It explains source reachability only and cannot decide registration,
691
+ availability, ownership, safety, or maliciousness. Older documents and saved
692
+ lookups without that context do not gain a registry-access field.
693
+
694
+ Terminal and versioned JSON output cover domain identity, registry object ID,
695
+ registrar, registrar IANA ID, lifecycle dates, DNSSEC, statuses, and name
696
+ servers. The shared comparison model normalizes harmless case, punctuation,
697
+ ordering, and date-precision differences while distinguishing conflicts,
698
+ one-source publication, redaction, incomplete sources, and unavailable
699
+ sources. This is source reconciliation, not an availability, ownership, or
700
+ maliciousness decision.
701
+
702
+ ## Static page comparison
703
+
704
+ `page-compare <left.json> <right.json>` reads two different version-1 saved
705
+ Deep domain Lookup documents and compares the already-retained static page
706
+ identity, exact and perceptual favicon evidence, technology identifiers, TLS
707
+ issuer label, and TLS public-key fingerprint. DOM structure is compared first
708
+ by its exact bounded tag-sequence digest, then, when both captures provide it,
709
+ by a parse5-tokenized structural SimHash. It makes no request and requires
710
+ complete supported static page-identity observations on both sides.
711
+
712
+ Each page component remains independent. Exact, similar, overlapping,
713
+ different, unavailable, and partial evidence are not collapsed into a score.
714
+ Matching components are investigative relationships rather than proof of
715
+ copying, common ownership, control, intent, safety, or maliciousness. Static
716
+ comparison does not execute JavaScript; use the optional local rendered
717
+ capture package only when that additional active behavior is authorised.
718
+
719
+ ## Passive mail exposure review
720
+
721
+ `mail-review [bulk.json|bulk.jsonl]` reads version-2 Bulk output locally and
722
+ summarizes MX, null MX, SPF, DMARC, and mail-provider relationships. It keeps
723
+ authenticated mail, authentication gaps, incomplete authentication evidence,
724
+ no explicit MX, null MX, and incomplete DNS evidence as separate states.
725
+ Shared-provider relationships are based only on the registrable domain of an
726
+ observed MX hostname and do not establish shared ownership or control.
727
+
728
+ The command makes no DNS or SMTP request and retains no source path. It does
729
+ not test message acceptance, relay behavior, mailbox existence, catch-all
730
+ behavior, SMTP banners, or whether a mail server is rogue, safe, or malicious.
731
+
732
+ ## Optional local rendered capture
733
+
734
+ `packages/web-capture` is a private repo-local Playwright package, not part of
735
+ the distributable core CLI or hosted application. It requires an explicit
736
+ authorization flag and one new output directory:
737
+
738
+ ```bash
739
+ npm run capture:local -- https://example.test --output-dir ./capture-example --authorize-rendered-capture
740
+ npm run capture:compare -- ./official/manifest.json ./candidate/manifest.json --json
741
+ ```
742
+
743
+ It writes a fixed 1024x768 PNG, screenshot SHA-256 and perceptual dHash, a DOM
744
+ digest containing hashes and bounded element counts rather than markup or page
745
+ text, and a version-2 `whoisleuth.web-capture-manifest` compatible with the
746
+ Cases importer. Version-1 manifests remain importable.
747
+
748
+ Rendered capture executes page JavaScript. It caps HTTP(S) requests and request
749
+ hostnames, blocks downloads, service workers, WebSockets, non-read methods,
750
+ credentials, non-default ports, and private or reserved addresses, and retains
751
+ no request path, query, headers, bodies, cookies, credentials, DOM markup, or
752
+ page text. The browser connection is not pinned to the address checked before
753
+ each hostname's first request, so DNS rebinding remains a residual risk. Use a
754
+ disposable network-restricted environment for untrusted targets.
755
+
756
+ The separate offline comparison command accepts two selected version-2 local
757
+ capture manifests. It first verifies each referenced screenshot and DOM digest
758
+ against its declared size, SHA-256, screenshot dimensions, and perceptual hash;
759
+ it also validates bounded source and limitation metadata and requires the DOM,
760
+ manifest, and source collection times to agree. It then
761
+ compares screenshot distance, exact rendered DOM and visible-text digests,
762
+ bounded element counts, page identity, technologies, and request-domain sets.
763
+ It does not recrawl either target, reveal the input paths, or collapse the
764
+ independent components into a similarity or maliciousness score. A missing
765
+ perceptual hash remains unavailable rather than becoming a visual difference.
766
+
767
+ ## Lookup evidence export
768
+
769
+ `export` converts one version-1 `whoisleuth.cli.lookup` domain document from a
770
+ file or stdin into the same versioned `whoisleuth.lookup-evidence` JSON package
771
+ produced by the web Lookup tool. It performs no lookup and writes only to
772
+ stdout, so use ordinary shell redirection when a file is wanted. Pretty JSON is
773
+ the default; `--compact` emits one compact JSON line for pipeline use, while
774
+ `--markdown` produces a readable source-attributed summary and `--html`
775
+ produces a self-contained printable report. Compact, Markdown, and HTML output
776
+ are mutually exclusive.
777
+
778
+ The saved input is capped at 8 MiB and revalidated using the same schema,
779
+ source-status, parsed-data, scalar, list, and event boundaries as `compare`.
780
+ The export retains query context, source diagnostics, normalized registry data,
781
+ raw registry RDAP JSON, the raw WHOIS referral chain, availability analysis,
782
+ and the shared registry-source comparison. Version 23 can add the exact local
783
+ SSLBL comparison already represented by a deep full Lookup. Version 21 adds the bounded registry
784
+ lifecycle, disclosure, publication-quality, reconciliation, and abuse-routing
785
+ interpretation derived from the already-collected sources. Registrar RDAP raw
786
+ data, contacts, entities, links, notices, and source-specific handles remain
787
+ excluded. Schema version 20 replaces selected security-policy values in
788
+ retained HTTP evidence
789
+ with presence-only markers and can include fixed response-policy findings from
790
+ an already-represented deep lookup. Version 19 additionally retains the bounded credential-surface projection
791
+ when the saved deep lookup represents it. Version 18 retains the bounded structured identity projection
792
+ when the saved deep lookup represents it. Version 17 retains the normalized portable-field comparison, explicit
793
+ source-health states, a strict bounded projection of observed network
794
+ registration, an optional normalized security.txt disclosure source, HTTPS
795
+ service-binding publications, and the passive browser-library profile when the
796
+ saved deep lookup represents them. Raw IP RDAP payloads, security.txt response
797
+ bodies, script references, matched script content, and contact entities remain
798
+ excluded.
799
+
800
+ Markdown output summarizes query context, assessment state, registry sources,
801
+ source reconciliation, network observations, and collection diagnostics. It
802
+ escapes all upstream text as untrusted content, bounds displayed values and
803
+ lists, and deliberately omits raw RDAP JSON and full WHOIS response bodies.
804
+ Use JSON when the complete evidence package or machine processing is required.
805
+ The browser Console reuses this bounded Markdown renderer for domain results
806
+ after first reducing the typed Lookup response to the known normalized fields
807
+ needed by the report. It separately offers bounded IP and ASN summaries from
808
+ their normalized RDAP and source-health fields; IP reports can also include
809
+ bounded reverse-DNS context already collected by Lookup. The browser action
810
+ does not change the CLI input or output contract.
811
+ When a version-5, version-6, or version-7 lookup records a documented registry-access constraint, both
812
+ Markdown and HTML include that context in collection diagnostics without
813
+ changing its non-authoritative interpretation. Both formats also include the
814
+ selected address and bounded network registration when present, together with
815
+ the edge-versus-origin limitation.
816
+
817
+ HTML uses the same bounded summary model as Markdown. The generated document
818
+ contains inline styling for screen and print, but no scripts, forms, active
819
+ links, or external resources. A restrictive embedded Content Security Policy
820
+ provides defense in depth when the local file is opened in a browser. All
821
+ registry values are HTML-escaped and displayed as text.
822
+
823
+ This is a deliberately rich evidence package. Raw registry sources can contain
824
+ publicly published contact data, and deep availability evidence can contain
825
+ bounded website, DNS, mail, page-identity, and TLS observations. Review and
826
+ secure the output before sharing it. The CLI does not add browser-only IDN
827
+ profile analysis, so that optional evidence field is `null`.