@slicedearth/whoisleuth-cli 1.36.1 → 1.43.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 (139) hide show
  1. package/README.md +32 -2
  2. package/bin/whoisleuth.mjs +52 -24
  3. package/cli/archive-inspect.mjs +37 -2
  4. package/cli/arguments.mjs +493 -157
  5. package/cli/artifact-verify.mjs +91 -15
  6. package/cli/bulk-checkpoint.mjs +7 -11
  7. package/cli/bulk-command-runner.mjs +19 -2
  8. package/cli/case-pack.mjs +136 -0
  9. package/cli/ci-report.mjs +73 -0
  10. package/cli/collection-preflight.mjs +59 -0
  11. package/cli/command-reference.mjs +395 -0
  12. package/cli/compare.mjs +1 -1
  13. package/cli/completion.mjs +49 -11
  14. package/cli/config-profile.mjs +180 -0
  15. package/cli/ct-event-intake.mjs +159 -0
  16. package/cli/discovery-scan-command-runner.mjs +15 -1
  17. package/cli/doctor.mjs +11 -5
  18. package/cli/domain-control-monitor.mjs +104 -0
  19. package/cli/domain-control-observations.mjs +235 -0
  20. package/cli/evidence-command-arguments.mjs +12 -1
  21. package/cli/evidence-command-runner.mjs +3 -2
  22. package/cli/evidence-signing.mjs +4 -4
  23. package/cli/external-observation-mapping.mjs +162 -0
  24. package/cli/fail-policy.mjs +64 -0
  25. package/cli/formatters/html.mjs +2 -2
  26. package/cli/formatters/json.mjs +13 -1
  27. package/cli/formatters/terminal.mjs +7 -3
  28. package/cli/investigation-manifest.mjs +113 -0
  29. package/cli/investigation-plan.mjs +2 -2
  30. package/cli/investigation-run.mjs +112 -0
  31. package/cli/local-mmdb-review.mjs +76 -0
  32. package/cli/lookalike-calibration.mjs +121 -0
  33. package/cli/lookup-brief.mjs +116 -0
  34. package/cli/lookup-command-runner.mjs +12 -2
  35. package/cli/lookup-diff.mjs +17 -19
  36. package/cli/lookup-plan.mjs +3 -3
  37. package/cli/lookup-reconcile.mjs +141 -0
  38. package/cli/lookup-timeline.mjs +1 -1
  39. package/cli/mail-review.mjs +1 -1
  40. package/cli/network-command-runner.mjs +4 -1
  41. package/cli/offline-evidence-review.mjs +125 -10
  42. package/cli/open-asset-model-bridge.mjs +169 -0
  43. package/cli/registry-cohort.mjs +121 -0
  44. package/cli/registry-doctor.mjs +220 -0
  45. package/cli/registry-fixture-scaffold.mjs +73 -0
  46. package/cli/risk-calibration.mjs +117 -9
  47. package/cli/runner.mjs +561 -280
  48. package/cli/saved-lookup.mjs +2 -2
  49. package/cli/sharing-review.mjs +202 -0
  50. package/cli/source-reliability.mjs +1 -1
  51. package/docs/cli-reference.md +1336 -0
  52. package/docs/cli.md +259 -940
  53. package/frontend/src/lib/analysis/acquisition-decision-packet.js +1 -1
  54. package/frontend/src/lib/analysis/acquisition-due-diligence.js +7 -7
  55. package/frontend/src/lib/analysis/brand-profile-model.js +46 -4
  56. package/frontend/src/lib/analysis/bulk-domain-comparison.js +12 -12
  57. package/frontend/src/lib/analysis/bulk-mail-exposure.js +1 -1
  58. package/frontend/src/lib/analysis/bulk-session-model.js +7 -2
  59. package/frontend/src/lib/analysis/case-evidence-model.js +26 -4
  60. package/frontend/src/lib/analysis/case-migration-model.js +6 -1
  61. package/frontend/src/lib/analysis/case-record-contracts.js +4 -2
  62. package/frontend/src/lib/analysis/case-record-core.js +5 -1
  63. package/frontend/src/lib/analysis/case-record-operations.js +6 -1
  64. package/frontend/src/lib/analysis/case-report.js +573 -0
  65. package/frontend/src/lib/analysis/case-response-model.js +30 -1
  66. package/frontend/src/lib/analysis/case-response-packet.js +7 -7
  67. package/frontend/src/lib/analysis/detection-rule-model.js +36 -0
  68. package/frontend/src/lib/analysis/evidence-coverage-ledger.js +328 -0
  69. package/frontend/src/lib/analysis/external-findings-import.js +320 -0
  70. package/frontend/src/lib/analysis/investigation-capsule.js +105 -0
  71. package/frontend/src/lib/analysis/lookup-asset-graph.js +806 -0
  72. package/frontend/src/lib/analysis/lookup-decision-support.js +487 -0
  73. package/frontend/src/lib/analysis/lookup-investigation-brief.js +153 -0
  74. package/frontend/src/lib/analysis/lookup-presentation.js +64 -0
  75. package/frontend/src/lib/analysis/lookup-response.js +2 -0
  76. package/frontend/src/lib/analysis/lookup-source-refresh.js +304 -0
  77. package/frontend/src/lib/analysis/lookup-summary-model.js +398 -0
  78. package/frontend/src/lib/analysis/page-similarity.js +1 -1
  79. package/frontend/src/lib/analysis/relationship-evidence.js +3 -3
  80. package/frontend/src/lib/analysis/relationship-observation-model.js +4 -4
  81. package/frontend/src/lib/analysis/scoring.js +9 -89
  82. package/frontend/src/lib/analysis/shortlist-model.js +8 -4
  83. package/frontend/src/lib/analysis/visualization-bounds.js +26 -0
  84. package/frontend/src/lib/analysis/visualization-force-graph.js +352 -0
  85. package/frontend/src/lib/analysis/visualization-matrix.js +88 -0
  86. package/frontend/src/lib/analysis/visualization-models.js +369 -0
  87. package/frontend/src/lib/analysis/visualization-time-series.js +144 -0
  88. package/frontend/src/lib/analysis/workspace-archive-crypto.js +1 -1
  89. package/frontend/src/lib/analysis/workspace-archive.js +4 -2
  90. package/lib/abusech-auth.mjs +1 -1
  91. package/lib/analyst-taxonomy.mjs +40 -0
  92. package/lib/availability.mjs +2 -2
  93. package/lib/bounded-contract-normalizers.mjs +33 -0
  94. package/lib/browser-library-profile.mjs +15 -2
  95. package/lib/certificate-extension-profile.mjs +71 -0
  96. package/lib/credential-surface-profile.mjs +1 -1
  97. package/lib/dns-convergence-review.mjs +183 -0
  98. package/lib/dns-delegation-health.mjs +196 -3
  99. package/lib/domain-assurance.mjs +368 -0
  100. package/lib/domain-change-packet.mjs +133 -0
  101. package/lib/domain-change-review.mjs +307 -0
  102. package/lib/domain-control-flight-recorder.mjs +268 -0
  103. package/lib/domain-control-manifest.mjs +50 -7
  104. package/lib/domain-portfolio-review.mjs +151 -0
  105. package/lib/domain-posture.mjs +9 -9
  106. package/lib/encrypted-dns-contract.mjs +1 -1
  107. package/lib/evidence-export.mjs +1 -1
  108. package/lib/evidence-report-markdown.mjs +1 -1
  109. package/lib/evidence-report.mjs +1 -1
  110. package/lib/generated/cisa-kev-catalog.mjs +1668 -0
  111. package/lib/html-signals.mjs +5 -11
  112. package/lib/ip-prefix.mjs +34 -1
  113. package/lib/local-geoip-evidence.mjs +2 -2
  114. package/lib/lookup-response-contract.mjs +346 -0
  115. package/lib/nameserver-preflight-review.mjs +159 -0
  116. package/lib/observed-network-context.mjs +1 -1
  117. package/lib/opportunity-scoring.mjs +107 -0
  118. package/lib/page-fingerprints.mjs +1 -1
  119. package/lib/page-language-signals.mjs +124 -0
  120. package/lib/rdap-capabilities.mjs +6 -6
  121. package/lib/rdap-search-workbench.mjs +96 -6
  122. package/lib/registry-access.mjs +1 -1
  123. package/lib/registry-capability-catalogue.mjs +4 -4
  124. package/lib/registry-insights.mjs +6 -6
  125. package/lib/risk-scoring.mjs +247 -83
  126. package/lib/scoring-evidence-quality.mjs +107 -0
  127. package/lib/security-txt.mjs +3 -3
  128. package/lib/static-html-analysis.mjs +29 -1
  129. package/lib/threat-intelligence-manifests.mjs +1 -1
  130. package/lib/threatfox-intelligence.mjs +1 -1
  131. package/lib/tls-intelligence.mjs +13 -3
  132. package/lib/trust-store-comparison.mjs +146 -0
  133. package/lib/urlhaus-intelligence.mjs +1 -1
  134. package/lib/website-security-posture.mjs +5 -5
  135. package/lib/website-technology.mjs +25 -11
  136. package/lib/whois-chain.mjs +1 -1
  137. package/lib/zone-intent-review.mjs +534 -0
  138. package/package.json +5 -1
  139. package/third-party-notices.txt +911 -19
@@ -0,0 +1,1336 @@
1
+ # WHOISleuth CLI reference
2
+
3
+ This complete reference documents every supported command contract. For a
4
+ short installation and first-use path, start with the [CLI guide](cli.md).
5
+
6
+ The first-party command-line interface runs the same local classification and
7
+ lookup modules as the Express and serverless adapters. It does not call the
8
+ hosted WHOISleuth deployment.
9
+
10
+ `whoisleuth --help` displays the copyright, AGPL-3.0-only licence, and official
11
+ source location. It groups commands by investigation, discovery, saved-evidence,
12
+ integrity, and calibration workflows. `whoisleuth <command> --help` displays the
13
+ command's purpose, focused invocation, example, and collection boundary without
14
+ printing the full command list. `whoisleuth commands --json` exposes those same
15
+ contracts as a stable local catalogue for wrappers and terminal tooling.
16
+ Packaged copies include `LICENSE`, `NOTICE`, and
17
+ `TRADEMARKS.md` alongside the CLI documentation.
18
+
19
+ ## Installation
20
+
21
+ Public releases require Node.js 24 or later and install the `whoisleuth`
22
+ command:
23
+
24
+ ```bash
25
+ npm exec --yes --ignore-scripts --package=@slicedearth/whoisleuth-cli -- whoisleuth --help
26
+ npm install --global --ignore-scripts @slicedearth/whoisleuth-cli
27
+ whoisleuth doctor
28
+ ```
29
+
30
+ Update and verify an existing global installation with:
31
+
32
+ ```bash
33
+ npm install --global --ignore-scripts @slicedearth/whoisleuth-cli@latest
34
+ whoisleuth --version
35
+ ```
36
+
37
+ The scoped package and application share one semantic version. The package runs
38
+ locally and does not call the hosted WHOISleuth deployment. Networked commands
39
+ contact only the sources described by their collection profile; offline review,
40
+ comparison, verification, completion, and manual commands remain local.
41
+
42
+ ## Commands
43
+
44
+ ```bash
45
+ node bin/whoisleuth.mts lookup example.com
46
+ node bin/whoisleuth.mts lookup AS13335 --json
47
+ printf 'example.com\n' | node bin/whoisleuth.mts lookup --json
48
+ node bin/whoisleuth.mts lookup example.com --deep
49
+ node bin/whoisleuth.mts lookup example.com --deep --plan --json
50
+ node bin/whoisleuth.mts lookup example.com --deep --summary
51
+ node bin/whoisleuth.mts lookup example.com --deep --verbose
52
+ node bin/whoisleuth.mts lookup example.com --deep --markdown --output lookup.md
53
+ node bin/whoisleuth.mts lookup example.com --deep --json --strict-exit --events
54
+ node bin/whoisleuth.mts lookup example.test --deep --observer workstation-a --vantage office-egress --json > office.json
55
+ cat domains.txt | node bin/whoisleuth.mts bulk --jsonl
56
+ node bin/whoisleuth.mts bulk domains.txt --csv --registered-only
57
+ node bin/whoisleuth.mts bulk domains.txt --domains --inconclusive-only
58
+ node bin/whoisleuth.mts bulk domains.txt --queries --errors-only
59
+ node bin/whoisleuth.mts bulk domains.txt --concurrency 4
60
+ node bin/whoisleuth.mts bulk domains.txt --deep --checkpoint bulk-checkpoint.json
61
+ node bin/whoisleuth.mts bulk domains.txt --deep --checkpoint bulk-checkpoint.json --resume
62
+ node bin/whoisleuth.mts ct-search 'example brand' --json
63
+ node bin/whoisleuth.mts discover example.com --preset common --jsonl
64
+ node bin/whoisleuth.mts discover example.test --dictionary private-terms.txt --snapshot discovery-state.json --json
65
+ node bin/whoisleuth.mts discover-scan example.test --scan-limit 50 --checkpoint candidate-scan.json --json
66
+ node bin/whoisleuth.mts discover-scan example.test --deep --scan-limit 20 --resolver 192.0.2.53 --observation-snapshot observed-candidates.json --csv
67
+ node bin/whoisleuth.mts posture example.com --selectors selector1 --retired-selectors selector0 --mail-profile defensive-no-mail --json
68
+ node bin/whoisleuth.mts http example.com --json
69
+ node bin/whoisleuth.mts tls example.com --json
70
+ node bin/whoisleuth.mts registry-support example.uk --json
71
+ node bin/whoisleuth.mts registry-doctor lookup.json --json
72
+ node bin/whoisleuth.mts registry-cohort saved-lookups.jsonl --json
73
+ node bin/whoisleuth.mts registry-scaffold --profile example-profile --suffix test --scenario registered
74
+ node bin/whoisleuth.mts risk-calibrate calibration.json --json
75
+ node bin/whoisleuth.mts lookalike-calibrate reviewed-candidates.json --json
76
+ node bin/whoisleuth.mts verify-artifact workspace.json --json
77
+ node bin/whoisleuth.mts verify-artifact lookup.json --json
78
+ node bin/whoisleuth.mts verify-artifact workspace-encrypted.json --passphrase-file passphrase.txt --json
79
+ node bin/whoisleuth.mts source-report lookup.json --json
80
+ node bin/whoisleuth.mts inspect-archive workspace.json --json
81
+ node bin/whoisleuth.mts inspect-archive workspace.json --search example.invalid --json
82
+ node bin/whoisleuth.mts sign-artifact response-packet.json --private-key-file analyst-private.pem > response-packet.signed.json
83
+ node bin/whoisleuth.mts ct-intake certificate-events.json --json > external-findings.json
84
+ node bin/whoisleuth.mts map-observations observation-mapping.json --json > external-findings.json
85
+ node bin/whoisleuth.mts oam-export external-findings.json --json > asset-bridge.json
86
+ node bin/whoisleuth.mts manifest lookup.json comparison.json --workflow 'domain review' --json > investigation-manifest.json
87
+ node bin/whoisleuth.mts verify-signature response-packet.signed.json --public-key-file analyst-public.pem
88
+ node bin/whoisleuth.mts lookup example.com --deep --json > lookup.json
89
+ node bin/whoisleuth.mts compare lookup.json --json
90
+ node bin/whoisleuth.mts page-compare official.json candidate.json --json
91
+ node bin/whoisleuth.mts mail-review bulk.json --json
92
+ node bin/whoisleuth.mts review-evidence dnssec-evidence.json --json
93
+ node bin/whoisleuth.mts review-evidence trust-store-comparison.json --json
94
+ node bin/whoisleuth.mts domain-control domain-control-input.json --json
95
+ node bin/whoisleuth.mts assurance domain-assurance-input.json --json
96
+ node bin/whoisleuth.mts change-packet domain-change-packet-input.json --json
97
+ node bin/whoisleuth.mts sharing-review response-packet.json --marking amber --recipient-scope organization --purpose 'Reviewed incident handoff' --human-reviewed --personal-data-reviewed --redactions-confirmed --json
98
+ node bin/whoisleuth.mts workflow-plan domain-triage example.test --json
99
+ node bin/whoisleuth.mts workflow-run domain-triage example.test --approve-network --json --output run.json
100
+ node bin/whoisleuth.mts brief lookup.json
101
+ node bin/whoisleuth.mts case-pack cases.json --audience trusted --reviewed --json
102
+ node bin/whoisleuth.mts monitor-once control-manifest.json --previous prior-control.json --junit
103
+ node bin/whoisleuth.mts diff first-lookup.json second-lookup.json --json
104
+ node bin/whoisleuth.mts reconcile office.json mobile.json external.json --json
105
+ node bin/whoisleuth.mts timeline first-observation.json second-observation.json latest-observation.json --json
106
+ node bin/whoisleuth.mts export lookup.json > evidence.json
107
+ node bin/whoisleuth.mts export lookup.json --markdown > evidence.md
108
+ node bin/whoisleuth.mts export lookup.json --html > evidence.html
109
+ node bin/whoisleuth.mts doctor
110
+ node bin/whoisleuth.mts doctor --network --json
111
+ node bin/whoisleuth.mts commands --json
112
+ node bin/whoisleuth.mts completion zsh
113
+ node bin/whoisleuth.mts completion powershell
114
+ node bin/whoisleuth.mts manual | man -l -
115
+ ```
116
+
117
+ These examples run from a checked-out repository; replace
118
+ `node bin/whoisleuth.mts` with `whoisleuth` after installation. The root
119
+ application package remains private. A separate scoped CLI archive is assembled
120
+ from the exact executable dependency closure, installed in a temporary
121
+ directory, and smoke tested with:
122
+
123
+ ```bash
124
+ npm run cli:package:check
125
+ ```
126
+
127
+ The check includes only the executable, reachable TypeScript modules, CLI
128
+ guide, licence, notices, and trademark terms. It excludes application routes,
129
+ deployment adapters, tests, and development tools. Direct runtime dependencies
130
+ are pinned to the exact versions exercised by the reviewed repository lockfile.
131
+ The package and its dependencies require no lifecycle scripts, so the install
132
+ examples disable them explicitly.
133
+ Its generated manifest stays private. Release-candidate assembly is a separate
134
+ explicit operation and never publishes from a developer checkout. Use the
135
+ scoped package name; an unqualified `npx whoisleuth` is not this project.
136
+
137
+ Lookup defaults to the conservative fast profile. `--deep` must be requested
138
+ explicitly and can add bounded WHOIS, DNS, website, TLS, registrar RDAP, and
139
+ observed-network IP RDAP work for a domain. The browser Console's optional
140
+ security.txt and external intelligence selections are not CLI flags; the CLI
141
+ does not run those actions implicitly.
142
+
143
+ For a directly entered public IP address, `lookup --deep` can also run one
144
+ bounded reverse-DNS query. Terminal output shows the explicit source state and
145
+ up to five normalised PTR names; JSON retains up to eight. PTR names are
146
+ operator-published routing context, not proof of hosting control or ownership.
147
+ Fast lookups do not run this query.
148
+
149
+ `lookup --plan` performs classification and prints a versioned preflight without
150
+ starting collection. It lists the normalised target, selected fast or deep
151
+ mode, planned source families, conditional operations, and the data each family
152
+ may receive. The plan cannot predict live feature configuration, cache state,
153
+ redirects, referrals, source availability, or an exact request count. It can be
154
+ combined with `--json`, but not with report, detail, strict-exit, progress-event,
155
+ or quiet options.
156
+
157
+ Only one query is accepted by `lookup`. Multiple-input processing belongs to
158
+ the explicit `bulk` command rather than being silently inferred by `lookup`.
159
+ Standard input is capped at 4 KiB and must contain one non-empty line.
160
+
161
+ `registry-support` is an offline catalogue view. It accepts one domain, bare
162
+ suffix, or leading-dot suffix as an argument or on stdin and makes no network
163
+ request. Its output is described below.
164
+
165
+ `doctor` is offline by default. It checks the Node runtime, platform, terminal
166
+ capabilities, and availability of offline command families. `doctor --network`
167
+ adds independent bounded public-DNS, HTTPS/RDAP bootstrap, and WHOIS port 43
168
+ checks against fixed IANA diagnostic infrastructure. The three probes run
169
+ within the same per-probe ceiling so one stalled transport does not block the
170
+ others. The report retains only state, HTTP status, and a bounded explanation,
171
+ not resolved addresses or response content. A failed optional network check
172
+ returns the partial-failure exit code rather than claiming that all CLI
173
+ collection is unavailable.
174
+
175
+ `completion bash`, `completion zsh`, `completion fish`, and `completion
176
+ powershell` print static shell completion scripts to stdout. The command does
177
+ not edit a shell profile or make a network request. Review the generated script
178
+ before sourcing it or placing it in the relevant shell completion directory.
179
+ `manual` prints a generated roff manual page and likewise changes no local
180
+ configuration.
181
+
182
+ `commands` prints the installed command catalogue without running collection or
183
+ reading evidence. `commands --json` emits version 1 of the
184
+ `whoisleuth.cli.command-catalogue` schema, including each command's usage,
185
+ example, declared collection mode, collection scope, and boundary. This is the
186
+ supported discovery contract for local wrappers; it is not a permission grant
187
+ or a substitute for focused help.
188
+
189
+ Focused command help and the generated manual classify every operation as
190
+ offline or networked and state the relevant target, input, and concurrency
191
+ ceiling. These labels describe observable behaviour rather than granting
192
+ authorisation. The packaged `DISCLOSURE` defines the intended defensive scope;
193
+ operators remain responsible for law, provider terms, and authorisation.
194
+
195
+ ## Deployment boundary
196
+
197
+ The CLI runs locally. The serverless deployment publishes only `frontend/build`
198
+ and packages functions only from `netlify/functions`, so `bin/` and `cli/` are
199
+ not part of the hosted static site or function bundle. The application root and
200
+ ordinary package-check candidate remain private. Explicitly reviewed CLI
201
+ release candidates can be published under the scoped public package described
202
+ in the installation section.
203
+
204
+ Commands that query RDAP, WHOIS, DNS, HTTP, TLS, or Certificate Transparency do
205
+ so directly from the machine running the CLI. They do not use the hosted login,
206
+ hosted session, or deployment usage controls; upstream providers can see and
207
+ rate-limit the local machine's network address. Offline `discover`, `compare`,
208
+ `page-compare`, `mail-review`, `review-evidence`, `domain-control`, `assurance`, `change-packet`,
209
+ `sharing-review`, `workflow-plan`, `brief`, `case-pack`, `registry-cohort`,
210
+ `registry-scaffold`, `diff`, `reconcile`, `timeline`,
211
+ `risk-calibrate`, `lookalike-calibrate`, `registry-doctor`, `verify-artifact`, `source-report`,
212
+ `map-observations`, `oam-export`, `export`,
213
+ `commands`, `completion`, and `manual` operations make no network requests. Commands write
214
+ to stdout unless the analyst deliberately selects a local output file.
215
+
216
+ ## Output
217
+
218
+ Human-readable terminal output is the default. On an interactive terminal it
219
+ uses restrained semantic colour, groups Lookup evidence by purpose, wraps
220
+ prose to the detected terminal width, and shows transient progress on stderr
221
+ for slower collection. State remains explicit in text, so colour is never the
222
+ only distinction. Redirected terminal output, JSON, and JSONL contain no ANSI
223
+ sequences or progress text. `--no-color` and a non-empty conventional
224
+ `NO_COLOR` environment variable disable colour; `WHOISLEUTH_NO_PROGRESS=1`
225
+ disables the transient indicator. Command examples remain one copyable line
226
+ even when prose is wrapped for a narrow terminal.
227
+
228
+ Every command accepts `--output <file>` as a safer alternative to shell
229
+ redirection. WHOISleuth buffers bounded output, creates a private temporary file
230
+ beside the destination, syncs it, and publishes it atomically with mode `0600`.
231
+ An existing destination is refused. `--force` is valid only with `--output` and
232
+ allows an intentional atomic replacement. A failed or cancelled command does
233
+ not publish a partial output file. Output is capped at 32 MiB.
234
+
235
+ `--config <file> --profile <name>` reads a strict version-1 local configuration
236
+ document of at most 64 KiB. Without `--config`, the path is
237
+ `$XDG_CONFIG_HOME/whoisleuth/config.json`. A profile may contain no more than
238
+ 16 safe defaults drawn from `--no-color`, `--summary`, `--verbose`, `--fast`,
239
+ `--concurrency`, `--observer`, and `--vantage`. It cannot supply a target,
240
+ Deep mode, output path, network approval, failure policy, credential, or
241
+ arbitrary argument. Explicit command options override profile defaults from
242
+ the same group.
243
+
244
+ Lookup, Bulk, and `monitor-once` can emit bounded JUnit XML. Owned-domain
245
+ Posture can emit SARIF only when `--owned-domain` confirms the passive target
246
+ boundary. The reports contain categorical source or posture findings rather
247
+ than raw evidence. Supported network commands accept an explicit comma-
248
+ separated `--fail-on` policy from `source-failure`, `inconclusive`, `danger`,
249
+ and `material-drift`; a match returns the partial-result exit code after the
250
+ normal output is emitted.
251
+
252
+ `lookup --markdown` and `lookup --html` build the existing normalised evidence
253
+ report directly after one completed domain lookup. They do not start a second
254
+ collection. These report formats reject IP and ASN inputs before collection;
255
+ JSON and terminal output remain available for those query types.
256
+
257
+ Lookup terminal output supports three detail levels. The standard view keeps
258
+ the normal bounded evidence summary. `--summary` keeps the source states and
259
+ key findings while omitting endpoints and supporting detail. `--verbose` adds
260
+ the document generation time and the existing bounded per-source collection
261
+ timings. These switches are presentation-only and cannot be combined with
262
+ `--json`; they do not change collection or the result document.
263
+
264
+ `--json` writes one versioned document to standard output:
265
+
266
+ ```json
267
+ {
268
+ "schema": "whoisleuth.cli.lookup",
269
+ "version": 1,
270
+ "generatedAt": "2026-07-14T00:00:00.000Z",
271
+ "mode": "fast",
272
+ "query": "example.com",
273
+ "type": "domain"
274
+ }
275
+ ```
276
+
277
+ The complete document also carries the normalised `rdap`, `whois`,
278
+ `availability`, and `diagnostics` sections returned by the shared lookup
279
+ orchestrator. Machine output goes to stdout. Usage and lookup errors go to
280
+ stderr, so redirected JSON is not mixed with diagnostics.
281
+
282
+ Human-readable domain lookup output separately shows the registrar RDAP status
283
+ and endpoint whenever the shared lookup diagnostics represent that follow-up.
284
+ Deep mode may report success, unsupported, not found, or an explicit failure;
285
+ fast mode reports the existing skipped state. IP, ASN, and lookup responses
286
+ without registrar diagnostics remain unchanged. These source states are
287
+ provenance only and do not decide availability or imply safety.
288
+
289
+ When a deep domain response includes registry interpretation version 1, the
290
+ terminal summary also shows the lifecycle label, separately attributed RDAP and
291
+ WHOIS disclosure states, reconciliation state, and complete, partial, and
292
+ unavailable publication counts. It does not print published contact routes.
293
+ The JSON document retains the bounded interpretation and its limitations.
294
+
295
+ A deep domain lookup can also show the status, selected public address, and
296
+ registered network name from the bounded observed network context. It uses the
297
+ same address and IP RDAP source represented in the JSON document. This is
298
+ point-in-time edge or network-registration context, not proof of the origin
299
+ host, hosting control, ownership, intent, or maliciousness. Fast and compact
300
+ commands do not run the enrichment.
301
+
302
+ The deep terminal summary also reports the website activity state, page title,
303
+ DNS, HTTP, and TLS source states, bounded publisher-declared structured
304
+ identity entities, up to six bounded technology indicators, a browser-library
305
+ profile count, and the four passive security-posture counts.
306
+ These are concise projections of the same evidence already present in the
307
+ lookup response. The browser-library line counts apparent components and
308
+ catalogue advisory matches without retaining script references or raw
309
+ signatures. These summaries make no extra request, omit raw evidence
310
+ descriptions, and do not turn a technology or advisory match, or a missing
311
+ posture signal, into proof of exploitability, hosting control, ownership, or
312
+ maliciousness. Use `--json` when the full bounded evidence, limitations, and
313
+ source diagnostics are required.
314
+
315
+ `lookup --strict-exit` is an opt-in automation policy. It still emits the
316
+ ordinary complete output document, but returns exit code 4 when a requested
317
+ diagnostic source is partial, unavailable, rate-limited, timed out, or failed.
318
+ The default exit contract remains unchanged, so inconclusive source health does
319
+ not silently break existing scripts.
320
+
321
+ `lookup --events` and `bulk --events` write versioned
322
+ `whoisleuth.cli.progress` version 1 JSONL lifecycle events to stderr while the
323
+ ordinary final result stays on stdout. Events contain only the command,
324
+ sequence, time, source state or Bulk item index, categorical failure reasons,
325
+ and final exit status. They do not contain a target, query, endpoint, file
326
+ path, error detail, or evidence value. Human progress and error text are
327
+ suppressed while events are active. `--events` cannot be combined with
328
+ `--output`, because the final event must describe the same completed stdout
329
+ delivery observed by the caller.
330
+
331
+ Pressing Ctrl-C asks an in-flight Lookup or Bulk run to stop, suppresses any
332
+ partial final result, flushes an enabled Bulk checkpoint, and exits with code
333
+ 130. Already started network operations remain subject to their existing hard
334
+ timeouts while the shared in-process API unwinds; the executable terminates
335
+ after CLI cleanup. A second Ctrl-C removes any known unpublished temporary
336
+ output file on a best-effort basis before exiting immediately.
337
+
338
+ Bulk can persist compact progress with `--checkpoint <file>`. A new checkpoint
339
+ is private, bounded to 16 MiB, versioned, and tied to the exact ordered input
340
+ and scan mode by SHA-256. It stores only compact per-item results, never full
341
+ Lookup responses. Reusing the same path is refused unless `--resume` is also
342
+ specified. Resume revalidates the complete untrusted document and skips only
343
+ the exact completed rows; changed input, mode, schema, digest, or malformed
344
+ results fail closed.
345
+
346
+ If checkpoint persistence fails after collection, WHOISleuth still emits the
347
+ completed final result, reports the checkpoint limitation, and exits with code
348
+ 4. The last successfully published checkpoint remains available, but it may
349
+ not contain the final completed items.
350
+
351
+ `diff <left.json> <right.json>` compares two different saved domain Lookup
352
+ documents entirely offline. It reuses the bounded Bulk comparison model for
353
+ registration, DNS, page identity, mail, certificate, and relationship fields,
354
+ keeps missing and unavailable evidence distinct, and makes no ownership or
355
+ maliciousness inference. The output records both original observation times.
356
+
357
+ `lookup --observer <label> --vantage <label>` can add two bounded analyst labels
358
+ to a saved Lookup document. The labels identify the person or process that ran
359
+ the observation and the collection context chosen by that analyst; they are
360
+ not verified identities, network measurements, or proof of independent
361
+ collection. They do not change which sources are queried.
362
+
363
+ `reconcile <observation.json> <observation.json> [...]` accepts from 2 to 5
364
+ saved Lookup documents for the same domain and compares their bounded fields
365
+ offline. It keeps agreement, disagreement, and non-comparable evidence
366
+ separate, retains observation times and optional analyst labels, and copies no
367
+ filenames or raw registration payloads. Distinct complete label pairs are
368
+ reported as labelled collection contexts only; majority agreement is not
369
+ treated as truth or authority.
370
+
371
+ `timeline <observation.json> <observation.json> [...]` accepts from 2 to 20
372
+ saved Lookup documents for one domain, orders them by their validated
373
+ observation times, and compares each adjacent pair offline. The 32 MiB aggregate
374
+ input ceiling is enforced while files are read and again by the timeline
375
+ builder. Output retains normalised field states and observation times but no
376
+ input filenames or raw registry payloads. A difference can reflect a domain
377
+ change or changed collection conditions, so the command makes no current-state,
378
+ ownership, intent, safety, or maliciousness conclusion.
379
+
380
+ When diagnostics version 5, 6, 7, or 8 reports a documented registry collection
381
+ constraint, terminal output also shows the suffix, WHOIS and RDAP access
382
+ profiles, and the bounded limitation. This is static access-policy context: it
383
+ does not make another request, and restricted, unpublished, or unavailable
384
+ machine access is not evidence that a domain is unregistered or safe.
385
+
386
+ ## Exit codes
387
+
388
+ | Code | Meaning |
389
+ | ---: | --- |
390
+ | 0 | Command completed. Individual sources may still be partial or inconclusive; inspect diagnostics. |
391
+ | 2 | Invalid command, option, input, or stdin shape. |
392
+ | 3 | The requested lookup, collection, or comparison operation could not run. |
393
+ | 4 | A bounded operation completed partially, such as Bulk item failures or failed explicit Doctor network checks. |
394
+ | 70 | Unexpected CLI bootstrap failure. |
395
+ | 130 | The analyst cancelled the command. No partial final result was emitted. |
396
+
397
+ This release supports `lookup`, `bulk`, `ct-search`, `ct-intake`, `map-observations`, `oam-export`, `discover`, `discover-scan`, `posture`,
398
+ `http`, `tls`, `registry-support`, `registry-doctor`, `registry-cohort`, `registry-scaffold`, `risk-calibrate`,
399
+ `lookalike-calibrate`, `verify-artifact`,
400
+ `inspect-archive`, `manifest`, `sign-artifact`, `verify-signature`, `source-report`,
401
+ `compare`, `page-compare`, `mail-review`, `review-evidence`, `brief`, `case-pack`, `domain-control`,
402
+ `monitor-once`, `assurance`, `change-packet`, `sharing-review`, `workflow-plan`, `workflow-run`, `diff`, `reconcile`, `timeline`,
403
+ `export`, `doctor`, `commands`, `completion`, and `manual`. Additional command families
404
+ are added as separate bounded increments rather than exposing incomplete
405
+ aliases.
406
+
407
+ ## Registry capability coverage
408
+
409
+ `registry-support` projects the embedded, versioned registry compatibility
410
+ catalogue for one domain or suffix. It distinguishes an explicit
411
+ fixture-backed or access-documented suffix profile from the generic IANA
412
+ discovery profile and reports the RDAP and WHOIS discovery/access paths,
413
+ WHOIS query and parser profiles, fixture scenarios,
414
+ verification references, documentation references, and the catalogue's
415
+ limitation. The same document includes a dated, official-source aggregate of
416
+ generic, generic-restricted, sponsored, and infrastructure RDAP coverage. That
417
+ snapshot is separate from the suffix profile and from live reachability.
418
+
419
+ Terminal output is bounded and control-safe. `--json` emits version 3 of the
420
+ `whoisleuth.cli.registry-support` schema. Unknown but syntactically valid
421
+ suffixes retain the generic `discovery_only` profile; malformed input exits
422
+ with code 2. The command never probes a registry or tests current reachability.
423
+ Coverage is context only and cannot decide registration, availability,
424
+ ownership, safety, or maliciousness.
425
+
426
+ `registry-doctor [lookup.json]` compares one saved domain Lookup with the same
427
+ reviewed local capability profile. It reports whether RDAP and WHOIS collection
428
+ states align with allowed, permission-required, or unsupported access, counts
429
+ bounded normalised publication fields, and reports publication-quality context
430
+ for object identity, base conformance, redaction metadata, required self links,
431
+ and event consistency. A missing identifier or metadata field is a publication
432
+ omission, not a failed lookup. The command is offline, makes no retry, and
433
+ cannot establish current registry reachability.
434
+
435
+ `registry-cohort [lookups.json|lookups.jsonl]` aggregates from 1 to 500 saved
436
+ Lookups into suffix and capability-profile cohorts. It retains only target-free
437
+ counts for source alignment and publication-quality states. A cohort with
438
+ fewer than five samples is labelled insufficient rather than treated as a
439
+ quality result, and repeated observations from one environment are not assumed
440
+ representative.
441
+
442
+ `registry-scaffold --profile <id> --suffix <suffix> --scenario <state>` emits
443
+ one bounded, sanitised synthetic fixture scaffold from the installed registry
444
+ catalogue. The three supported states are `registered`, `not_found`, and
445
+ `inconclusive`. It does not fetch a live response, include a real target, or
446
+ alter the embedded catalogue.
447
+
448
+ The offline RDAP supplied-evidence review implements the request shape defined
449
+ by RFC 9536 for `resources`, `domains`, `nameservers`, and `entities`. It also
450
+ validates a supplied server's `reverse_search_properties_mapping` response
451
+ before preparing a request. Only registered IANA property paths and exact
452
+ supported resource/property pairs are accepted. Preparing the request neither
453
+ proves that a particular server supports it nor sends a reverse search; active
454
+ execution remains gated on a reviewed server policy, privacy boundary, and
455
+ deterministic fixtures.
456
+
457
+ The separate maintenance command `npm run rdap-extensions:drift` compares the
458
+ pinned official RDAP extension fixture with the reviewed local interpretation
459
+ catalogue entirely offline. `--live` performs one bounded manual fetch from the
460
+ fixed official CSV URL and reports added, removed, renamed, status-changed,
461
+ unrecognized, and local-only identifiers. `--json` emits
462
+ `whoisleuth.rdap-extension-drift-audit` version 1. Drift exits with status 1 and
463
+ requires specification and fixture review; the command never enables an
464
+ extension, changes authority or availability logic, or issues reverse search.
465
+
466
+ `npm run service-dependencies:audit` validates the passive service-dependency
467
+ catalogue entirely offline. It checks the catalogue digest, duplicate
468
+ identifiers and target suffixes, evidence classes, source and licence
469
+ treatment, source dates, and a fixed 180-day review age. `--json` emits
470
+ `whoisleuth.service-dependency-signature-audit` version 1. A stale or changed
471
+ signature requires manual provider and benign-fixture review; the command
472
+ never resolves a target, checks an account, or tests claimability.
473
+
474
+ ## Offline Risk calibration
475
+
476
+ `risk-calibrate` replays a versioned analyst-labelled fixture dataset through
477
+ the exact Risk model shared with the web application. It does not perform a
478
+ lookup, contact the hosted deployment, persist the dataset, alter model weights,
479
+ choose a threshold, or tune the model automatically. The report compares fixed
480
+ thresholds from 40 through 90 and identifies 70 as the current review band. It
481
+ also reports F1, balanced accuracy, Wilson 95% intervals, deliberately small
482
+ scan-depth and review-reason strata, and a current-versus-previous model replay.
483
+ Strata with fewer than 20 included labels are marked insufficient rather than
484
+ presented as reliable performance estimates.
485
+
486
+ The JSON report derives a bounded `interoperabilityTags` list from the
487
+ analyst-owned disposition and review reason. The mapping uses reviewed MISP
488
+ taxonomy terms only where the meaning is unambiguous. Tags do not change a
489
+ score, disposition, or case, and no MISP service is contacted.
490
+
491
+ Input is capped at 2 MiB and 500 records. Each record requires a unique bounded
492
+ ID, an ASCII DNS hostname, one existing analyst disposition, and a strict
493
+ whitelist of current scoring inputs. Unknown fields are discarded; arrays,
494
+ strings, ages, booleans, provider records, and external findings are revalidated
495
+ and bounded before scoring. The accepted dataset envelope is:
496
+
497
+ ```json
498
+ {
499
+ "schema": "whoisleuth.risk-calibration-dataset",
500
+ "version": 2,
501
+ "records": [
502
+ {
503
+ "id": "fixture-1",
504
+ "domain": "login.example.test",
505
+ "analystDisposition": "confirmed_abuse",
506
+ "reviewReasonCode": "confirmed_credential_abuse",
507
+ "evidence": {
508
+ "availability": "registered",
509
+ "mutationTypes": ["dictionary"],
510
+ "hasPasswordField": true
511
+ }
512
+ }
513
+ ]
514
+ }
515
+ ```
516
+
517
+ `confirmed_abuse` is treated as a positive calibration label; `false_positive`
518
+ and `expected` are negative labels. `unreviewed`, `suspicious`, and
519
+ `closed_no_action` remain contextual and are excluded from threshold quality
520
+ metrics, as are records for which Risk is not applicable. Terminal output caps
521
+ its record list at 100 while `--json` retains the complete bounded report,
522
+ factor breakdowns, score bands, confidence intervals, bounded strata, model
523
+ comparison counts, and per-threshold confusion metrics. Analyst
524
+ dispositions and heuristic scores are review context: neither proves
525
+ maliciousness or safety.
526
+
527
+ The authenticated Monitor workspace can deliberately create the accepted
528
+ dataset from explicitly selected cases. Selection is available only when a case
529
+ has a reviewed disposition and retained normalised evidence. That local export
530
+ contains the case ID, domain, disposition, optional reviewed reason code, and a
531
+ bounded whitelist of current scoring inputs. A matched review-language signal
532
+ is exported as a fixed indicator, not as captured page text. It excludes notes,
533
+ tags, assertions, actions, contacts, raw
534
+ source data, provider payloads, and stored Risk scores. Before download, a local
535
+ review step shows the selected, included, and excluded counts, the domains and
536
+ dispositions that will be written, and the excluded evidence classes. The CLI
537
+ still validates every exported record and never trains, tunes, or changes the
538
+ Risk model.
539
+
540
+ ## Lookalike review-yield calibration
541
+
542
+ `lookalike-calibrate [dataset.json]` summarises reviewed candidate outcomes by
543
+ mutation family without retaining domains, candidate identifiers, analyst
544
+ notes, or evidence values in its output. Input uses
545
+ `whoisleuth.lookalike-calibration-input` version 1, is capped at 2 MiB and 5,000
546
+ unique opaque records, and requires one reviewed disposition plus from 1 to 12
547
+ bounded mutation-family identifiers per record.
548
+
549
+ The report keeps exact disposition counts, review-lead rate, false-positive
550
+ rate, and a per-family sample state. Fewer than 20 reviewed observations remains
551
+ `insufficient`. The command is diagnostic only: it does not tune candidate
552
+ generation, filtering, ranking, or Risk, and a useful historical yield does not
553
+ establish that a current candidate is malicious or safe.
554
+
555
+ ## Offline artefact verification
556
+
557
+ `verify-artifact` validates a supported local artefact without printing its
558
+ evidence contents. Input is capped at 15 MiB. It currently recognises ordinary
559
+ and encrypted workspace archives, case-response packets, acquisition-decision
560
+ exports, domain-comparison exports, Bulk mail-exposure exports, and Bulk review
561
+ manifests. Reviewed CLI case packs are checked against their canonical digest,
562
+ bounded browser-importable case collection, audience, and review marker. The
563
+ command also verifies the internal digests of a complete investigation
564
+ capsule, including its evidence, brief, graph, and optional analyst-record
565
+ projections, and that capsule can be passed to `sign-artifact`. It also validates the bounded versioned structure of saved CLI
566
+ Lookup JSON. Because saved Lookup documents do not embed a checksum or
567
+ signature, that result is reported as `structure_valid` with content integrity
568
+ explicitly unchecked. Saved Lookup validation retains its stricter 8 MiB
569
+ document ceiling.
570
+
571
+ For an ordinary workspace archive, the command validates the versioned
572
+ structure, section byte counts, record counts, and per-section checksums. For a
573
+ supported signed review artefact, it validates the declared SHA-256 digest. An
574
+ encrypted archive without `--passphrase-file` can establish only that the
575
+ envelope is structurally valid; it reports `envelope_valid` and explicitly
576
+ leaves authenticated encryption and inner checksums unchecked.
577
+
578
+ Supplying a separate passphrase file, capped at 1 KiB, allows authenticated
579
+ decryption followed by the ordinary workspace checks. The passphrase is never
580
+ accepted as a command-line value, printed, or retained. Verification detects
581
+ changes against the artefact's declared contract; it does not authenticate the
582
+ analyst or establish that an observation was accurate or remains current.
583
+
584
+ ## Privacy-safe source reliability report
585
+
586
+ `source-report` summarises source states, durations, truncation, and rate-limit
587
+ counts from one version-1 CLI Lookup, Bulk, or Bulk-item document, a JSON array
588
+ of up to 100 such documents, or an array containing only previously generated
589
+ source-reliability reports. Lookup documents and reports cannot be mixed.
590
+ Merged reports preserve exact state and sample totals and add bounded ranges
591
+ for each input report's median and p95 duration, a bounded source timeline,
592
+ fast/deep/unknown cohorts, and the earliest and latest valid report generation
593
+ times. Exact duplicate reports are rejected so one saved report is not
594
+ accidentally counted twice. Per-source failure, partial, truncation, and
595
+ rate-limit rates remain separate. The command does not pretend that aggregate
596
+ timing distributions can reconstruct the original target-level samples. Input
597
+ is capped at 12 MiB and traversal, source, duration, and output counts are
598
+ bounded.
599
+
600
+ The report retains only fixed source identifiers and aggregate operational
601
+ counts. It does not output targets, queries, endpoints, source limitations, raw
602
+ evidence, or provider payloads. Observation-envelope duration and the
603
+ potentially overlapping Lookup timing are reported separately and never added
604
+ together. It is a local engineering diagnostic, not analytics, a source-quality
605
+ score, or evidence that a collector is currently healthy.
606
+
607
+ ## Workspace archive inspection
608
+
609
+ `inspect-archive` verifies an ordinary or encrypted workspace archive and then
610
+ reports its versioned sections, counts, byte sizes, and checksum states without
611
+ printing stored records. An encrypted archive requires
612
+ `--passphrase-file`; passphrases are never accepted on the command line.
613
+
614
+ An optional `--search` is exact and case-insensitive across a fixed allowlist
615
+ of target fields. Default matches disclose only the section, field name, and a
616
+ SHA-256 digest. `--reveal` must be selected explicitly to print the matched
617
+ value. Notes, contacts, raw evidence, arbitrary object fields, and partial text
618
+ search are excluded. Unicode domain searches are canonicalized to their
619
+ DNS-safe ASCII form before exact comparison. `--require-match` makes a
620
+ no-match result fail for automation and is valid only with `--search`.
621
+ Traversal, input size, matches, depth, arrays, and strings are bounded.
622
+
623
+ Version 2 of the inspection report adds a content digest over the ordered
624
+ section identifiers, schema versions, record counts, and normalised section
625
+ content. It deliberately excludes JSON formatting and each section envelope's
626
+ export time, so two separately exported archives with the same retained
627
+ workspace content have the same identity. `--expect-content-digest <sha256:digest>`
628
+ fails when that identity differs, which supports deliberate transfer checks
629
+ without printing records. Matching digests detect equivalent retained content;
630
+ they do not authenticate who created it or establish that its evidence is
631
+ accurate or current. The report separately identifies the source archive
632
+ version and the installed reader version.
633
+
634
+ ## Optional evidence-package signing
635
+
636
+ `sign-artifact` signs a locally verified case-response packet or supported
637
+ review manifest with an externally managed Ed25519 private key:
638
+
639
+ ```bash
640
+ node bin/whoisleuth.mts sign-artifact packet.json --private-key-file analyst-private.pem > packet.signed.json
641
+ node bin/whoisleuth.mts verify-signature packet.signed.json --public-key-file analyst-public.pem
642
+ ```
643
+
644
+ The signed package embeds the public key, key identifier, signature time, and
645
+ original artefact. Verification first checks the exact package field set and
646
+ signature, then the artefact's own checksums or manifest. A result is reported
647
+ as `signature_valid`; signer trust is separately reported as `trusted_key` or
648
+ `embedded_key_only`. Without a separately supplied trusted public key,
649
+ verification establishes only that the package is internally self-consistent.
650
+ WHOISleuth does not generate, store, recover, rotate, publish, or establish
651
+ trust in signing keys.
652
+
653
+ ## Reproducible investigation manifest
654
+
655
+ `manifest <artefact.json> [...] --workflow <label>` reads from 1 to 16 ordered
656
+ JSON artefacts and emits `whoisleuth.investigation-manifest` version 1. Each
657
+ entry records its sequence, bounded schema and version metadata, exact UTF-8
658
+ content digest, canonical JSON digest, and byte count. The manifest also records
659
+ the installed CLI version and can include an externally calculated
660
+ `--configuration-digest sha256:...` without copying configuration values.
661
+
662
+ Source paths, filenames, command-line arguments, and artefact contents are not
663
+ retained. Individual artefacts are capped at 15 MiB and the ordered set at
664
+ 32 MiB. The manifest carries its own canonical integrity digest, works with
665
+ `verify-artifact`, and can be passed to `sign-artifact`. Reproduction still
666
+ requires the exact digested artefacts, compatible source services, and any
667
+ separately held configuration; matching digests do not establish observation
668
+ accuracy or freshness.
669
+
670
+ ## Bulk lookup
671
+
672
+ `bulk` accepts a newline-delimited file or stdin. It preserves input order,
673
+ removes case-insensitive duplicates, and returns a result for every retained
674
+ query. Input is capped at 1 MiB. Fast mode is capped at 500 queries with four
675
+ workers by default. Deep mode is capped at 50 queries with two workers by
676
+ default. Explicit concurrency cannot exceed eight in fast mode or three in
677
+ deep mode.
678
+
679
+ Bulk uses the shared compact lookup response, so it does not retain raw RDAP
680
+ objects or WHOIS response bodies. `--json` returns one bounded collection;
681
+ `--jsonl` emits one self-contained versioned item per line. `--domains` emits
682
+ only successfully normalised domain names, while `--queries` preserves the
683
+ selected bounded input queries and is suitable for an exact retry queue.
684
+ Version 2 adds a
685
+ bounded `dnsSummary` projection for observed A, AAAA, NS, and MX records plus
686
+ null MX, SPF, and DMARC state. `--csv` writes fixed columns for automation,
687
+ including the DNS summaries and explicit outcome.
688
+
689
+ `--registered-only`, `--inconclusive-only`, and `--errors-only` are mutually
690
+ exclusive output filters. The first retains registered, for-sale, and expiring
691
+ authority-aware states. The second retains unknown authority states and failed
692
+ rows. The third retains only rows whose lookup operation failed, which is useful
693
+ for producing an exact retry queue without treating an inconclusive successful
694
+ lookup as an operational error. Filtering does not change collection and never
695
+ converts an unavailable or failed source into an unregistered result. Machine
696
+ documents record collected and emitted counts. A mixture of successful and
697
+ failed queries exits with code 4 while preserving every collected result before
698
+ output filtering. For example, `bulk domains.txt --queries --errors-only`
699
+ produces only the exact failed inputs while retaining the partial-failure exit.
700
+
701
+ ## Certificate Transparency search
702
+
703
+ `ct-search` accepts one keyword as an argument or on stdin and calls the same
704
+ bounded Certificate Transparency module as the web application. It contacts
705
+ the upstream public log search service directly from the local machine; it
706
+ does not call the hosted WHOISleuth deployment. Quote a multi-word keyword so
707
+ the shell passes it as one argument.
708
+
709
+ Terminal output summarises certificate rows, observed hostnames, canonical
710
+ registrable-domain matches, observation times, bounded certificate-issuance
711
+ groups, and completeness. An issuance group contains names observed together
712
+ in one public certificate record; a cross-domain group is a review lead, not
713
+ an attribution or ownership finding. Terminal output shows at most 100 matches
714
+ and five hostnames per match, with explicit omission notes. `--json` returns
715
+ the complete bounded structured result in the versioned
716
+ `whoisleuth.cli.ct-search` schema. The certificate-group cap is reported
717
+ separately from the registrable-domain result cap. CT observations do not prove
718
+ that a website is active or malicious.
719
+
720
+ ## Local certificate event intake
721
+
722
+ `ct-intake [events.json]` reads `whoisleuth.ct-event-batch` version 1 entirely
723
+ offline. Each source-qualified event supplies a log identifier, observation
724
+ time, certificate SHA-256 digest, one or more DNS names, completeness, and
725
+ optional issuer, expiry, and limitations. Unknown fields, malformed names,
726
+ unsupported completeness values, and oversized batches are rejected.
727
+
728
+ JSON output uses the existing `whoisleuth.external-findings` version 3 contract
729
+ and can be deliberately imported into the browser Console. Wildcard names are
730
+ normalised to their base DNS name, exact event/domain duplicates are removed,
731
+ and deterministic output is capped at 100 findings, 25 domains, and 20 findings
732
+ per domain to match the browser import boundary. Omission is stated in each
733
+ retained finding when the supplied batch exceeds those limits. The command
734
+ makes no request and treats every certificate event as a review lead, not proof
735
+ that the certificate was served, requested, or controlled by the named domain.
736
+
737
+ ## Declarative observation interchange
738
+
739
+ `map-observations [mapping.json]` reads
740
+ `whoisleuth.external-observation-mapping` version 1 and applies one strict,
741
+ declarative profile to at most 500 local records. The profile selects bounded
742
+ dotted field paths for the domain, summary, observation time, optional
743
+ reference, and optional completeness. It fixes one source, evidence class, and
744
+ finding category for the batch. Profiles cannot contain scripts, templates,
745
+ computed expressions, network endpoints, or executable callbacks. Prototype
746
+ paths and unknown fields are rejected.
747
+
748
+ The resulting JSON is the existing browser-compatible
749
+ `whoisleuth.external-findings` version 3 contract. It retains source identity,
750
+ observation time, completeness, and limitations, while stating that field
751
+ semantics were not independently verified. Deterministic output is capped at
752
+ 100 findings, 25 domains, and 20 findings per domain. The command makes no
753
+ request and never interprets an omitted field as a negative observation.
754
+
755
+ `oam-export [external-findings.json]` projects that strict findings document
756
+ into `whoisleuth.open-asset-model-bridge` version 1. It uses the documented
757
+ [OWASP Open Asset Model](https://github.com/owasp-amass/open-asset-model)
758
+ vocabulary for bounded `FQDN`, `IPAddress`, and `TLSCertificate` assets plus
759
+ `dns_record` and `san_dns_name` relations. Only typed A, AAAA, and SHA-256
760
+ certificate observations create infrastructure assets or relations. Other
761
+ findings remain represented by their FQDN asset.
762
+
763
+ This bridge wrapper is not a claim that every receiving implementation accepts
764
+ one universal native serialisation. Receivers must validate the projection
765
+ against their supported model version. WHOISleuth preserves source and
766
+ completeness as discovery metadata, does not invent a confidence score, and
767
+ does not treat an edge as proof of ownership, control, coordination, intent,
768
+ safety, or maliciousness.
769
+
770
+ ## Lookalike discovery
771
+
772
+ `discover` runs the same pure, bounded lookalike generator as the Discover
773
+ tool without making network requests. It accepts a brand label or a
774
+ domain with one suffix label. The default TLD set is `com,net,org`; replace it
775
+ with `--tlds com,net` when narrower coverage is wanted.
776
+
777
+ Generation presets are `common`, `impersonation`, and `all` (the default).
778
+ Keyboard-aware mutations support `qwerty` (the default), `azerty`, and
779
+ `qwertz`; `--keyboard all` uses their bounded combined neighbour set, including
780
+ adjacent number-row keys. Supply a UTF-8 text file with
781
+ `--dictionary terms.txt` to add up to 100 local terms when
782
+ using the `impersonation` or `all` preset. Dictionary input is capped at 4,096
783
+ bytes and individual terms at 32 characters. Machine output records accepted
784
+ and rejected term counts but never copies the private terms into its metadata.
785
+
786
+ Use `--families <ids>` instead of `--preset` to run an exact comma-separated
787
+ selection. Supported IDs are `character_addition`, `character_omission`,
788
+ `character_duplication`, `character_transposition`, `pluralization`,
789
+ `tld_embedding`, `www_prefix`, `hyphenation`, `separator_omission`,
790
+ `word_reordering`, `keyboard_substitution`, `keyboard_insertion`, `vowel_swap`,
791
+ `bitsquatting`, `ascii_homoglyph`, `unicode_homoglyph`,
792
+ `unicode_whole_label`, `unicode_homoglyph_depth_2`, `dictionary`,
793
+ `dictionary_token_replacement`, `tld_typo`, and `tld_substitution`. Machine
794
+ output records the normalised custom selection. A custom dictionary file
795
+ requires `dictionary` or `dictionary_token_replacement` in that selection.
796
+
797
+ `--domains` writes the unique candidate names only. `--snapshot <file>` keeps
798
+ one private, versioned local observation of the normalised seed, generation
799
+ configuration, custom-dictionary digest, and candidate set. The first run
800
+ creates the snapshot; later runs report added and removed candidates before
801
+ atomically replacing it. The snapshot never stores private dictionary terms.
802
+ Use the same command from a local scheduler such as cron or a system timer to
803
+ perform repeatable discovery runs. WHOISleuth does not install a scheduler,
804
+ run a background daemon, or infer that an added candidate is registered,
805
+ active, available, or malicious.
806
+
807
+ Dotted subdomain permutations are intentionally excluded because the lookup
808
+ pipeline validates a hostname's registrable parent. The CLI does not present
809
+ authoritative parent registration evidence as proof that a generated hostname
810
+ exists.
811
+
812
+ Terminal output is capped at 200 candidates with an explicit notice. Versioned
813
+ JSON and JSONL use `whoisleuth.cli.discover` schema version 2 and retain the
814
+ complete bounded candidate set and mutation provenance. Internationalised
815
+ candidates show the DNS-safe ASCII form and readable Unicode form together in
816
+ terminal output. The Impersonation and All presets can add up to six
817
+ deterministic whole-label Unicode candidates when every letter has a reviewed
818
+ same-script replacement. The command generates candidates only. It does not
819
+ claim that a domain is registered, active, or malicious.
820
+
821
+ `unicode_homoglyph_depth_2` is an explicit `--families` option and is never
822
+ enabled by a preset. It replaces exactly two ASCII letters with characters
823
+ from the same reviewed script, ranks curated mappings first, and returns at
824
+ most 256 candidates. JSON and terminal output report generated candidates,
825
+ cross-script or invalid combinations omitted by policy, and lower-ranked
826
+ label variants omitted by the family or overall generation budget.
827
+
828
+ ## Supervised candidate scan
829
+
830
+ `discover-scan` composes the same bounded generator with the compact Lookup
831
+ collector. Unlike `discover`, it is an explicitly networked command. It selects
832
+ the first deterministic candidates up to `--scan-limit`, processes fixed
833
+ chunks sequentially, and retains each registration and DNS source state. Fast
834
+ mode is the default and accepts at most 500 candidates. Explicit `--deep` mode
835
+ is capped at 50 candidates and concurrency 3 because it can add WHOIS, DNS,
836
+ HTTP, TLS, page, and technology work for every selected candidate.
837
+
838
+ Use `--chunk-size 1-100`, `--concurrency 1-8`, and the existing private
839
+ `--checkpoint <file> [--resume]` boundary to control a run. The checkpoint is
840
+ tied to the exact ordered candidate set and scan mode. A new generation order,
841
+ limit, or mode requires another checkpoint. `--resolver` accepts at most three
842
+ literal IPv4 or IPv6 recursive-resolver addresses and applies them only to DNS
843
+ observations in this local run. Resolver hostnames and automatic third-party
844
+ resolver selection are not supported. The selected resolvers can observe the
845
+ queried candidates and may apply their own logging and retention policies.
846
+
847
+ An optional newline-delimited `--allowlist` accepts at most 500 domains and 64
848
+ KiB. Matching candidates remain in evidence and are labelled `suppressed` in
849
+ the review queue; the full list and unmatched entries are not copied into
850
+ output, while each matching scanned candidate necessarily exposes that match.
851
+ Allowlisting never deletes a result or asserts that it is safe. Output filters
852
+ select registered, inconclusive, acquisition-review, or
853
+ suppressed rows without changing whole-run counts or source health. JSON,
854
+ JSONL, CSV, and domain-list forms retain mutation provenance and explicit
855
+ review lanes. The queue provides next manual actions, not automatic lookup,
856
+ acquisition, blocking, submission, or enforcement.
857
+
858
+ Deep compact results can group exact shared addresses, nameservers, and mail
859
+ servers observed across at least two candidates. These bounded relationships
860
+ are pivot leads only. Common hosting, delivery, registrar, and mail services
861
+ can produce the same relationship without common ownership or control.
862
+
863
+ `--observation-snapshot <file>` keeps a private versioned local projection of
864
+ registration state, confidence, and bounded A, AAAA, NS, MX, null-MX, SPF, and
865
+ DMARC summaries for the exact candidate set, mode, and resolver selection.
866
+ Version 2 records registration and DNS observation times and latest component
867
+ states separately; version 1 is read and normalised on the next write. A later
868
+ identical run reports material field changes only for components collected
869
+ with enough completeness to support the comparison. If registration or DNS is
870
+ partial or unavailable, the prior usable evidence for that component is
871
+ preserved and the attempt is reported as unavailable rather than converting
872
+ missing data into a removal. Raw registry
873
+ records, contacts, page content, and request details are excluded. Snapshot
874
+ differences are review prompts, not proof of ownership, control, intent,
875
+ safety, or maliciousness.
876
+
877
+ ## Domain posture audit
878
+
879
+ `posture` runs the standard-profile form of the owned-domain DNS and
880
+ email-security audit used by Brand Profiles. It queries registry status and
881
+ DNSSEC evidence, nameserver delegation, SPF, DMARC, MX, CAA, MTA-STS, TLS-RPT,
882
+ and BIMI directly from the local machine. Literal SPF include and redirect
883
+ branches are expanded within fixed depth, policy-query, DNS-term, void-answer,
884
+ cycle, and time bounds. External DMARC reporting authorisation and external
885
+ nameserver, mail, SPF, and reporting dependencies are separately reported.
886
+ Supply up to ten known active and retired DKIM selectors in total with
887
+ `--selectors selector1,selector2` and `--retired-selectors selector0`;
888
+ selectors cannot be reliably discovered from DNS, so no-selector output
889
+ reports DKIM as not checked. Use `--mail-profile standard`,
890
+ `--mail-profile defensive-no-mail`, or `--mail-profile parked` to make the
891
+ intended mail posture explicit rather than inferring it from DNS.
892
+
893
+ Terminal output shows each pass, review, action, or informational result and
894
+ caps displayed records at five per check with an explicit omission notice.
895
+ Versioned JSON retains the complete bounded report, including SPF traversal,
896
+ DMARC authorisation, and dependency provenance. Warnings and dangers are
897
+ findings rather than command failures; transient resolver or policy-fetch
898
+ failures remain informational and should be retried before changing DNS.
899
+
900
+ ## HTTP intelligence
901
+
902
+ `http` runs the same bounded safe homepage probe used by a deep lookup. It
903
+ tries HTTPS before HTTP, follows only validated public-network redirects, caps
904
+ each attempt at six seconds, and captures at most 300,000 response bytes for
905
+ hashing and metadata. A non-success HTTP response still proves that a web
906
+ service answered; failure of both schemes remains inconclusive rather than
907
+ evidence that no website exists.
908
+
909
+ The command reports normalised redirect, response, content, selected security-
910
+ header, body-hash, completeness, and attempt provenance. Captured homepage text
911
+ is never written to terminal or JSON output. Query strings are removed by the
912
+ shared HTTP evidence normalizer, and terminal values are additionally bounded
913
+ and control-safe.
914
+
915
+ ## TLS intelligence
916
+
917
+ `tls` runs the same bounded one-connection TLS collector used by deep lookups.
918
+ It resolves the hostname once through the public-address guard, validates every
919
+ answer, connects directly to the first validated address on port 443, and keeps
920
+ the original hostname for SNI and certificate identity checking. Resolution
921
+ and handshake work share a five-second deadline.
922
+
923
+ The report retains the negotiated protocol, ALPN and cipher, runtime trust and
924
+ hostname checks, certificate validity, bounded subject and issuer names, SANs,
925
+ SAN class counts, signature algorithm and OID, extended-key-usage purposes,
926
+ classified Authority Information Access presence counts, SHA-256 certificate
927
+ and public-key fingerprints, a bounded certificate chain, and neutral
928
+ findings. AIA responder and issuer locations are classified as HTTP, HTTPS, or
929
+ other and then discarded; they are not retained or followed. The collector
930
+ stores no certificate bytes, session material, or application data and does
931
+ not enumerate supported protocol or cipher suites.
932
+ A failed collection is inconclusive rather than proof that no TLS service
933
+ exists.
934
+
935
+ ## Registry-source comparison
936
+
937
+ `compare` reads one version-1 `whoisleuth.cli.lookup` domain document from a
938
+ file or stdin and emits a version-3 `whoisleuth.cli.compare` document. Its
939
+ primary comparison reconciles normalised registry RDAP and WHOIS fields. When
940
+ the saved deep lookup also represents the optional registrar RDAP follow-up,
941
+ an additional sibling comparison reconciles the portable registry and
942
+ registrar RDAP publication fields. The command does not repeat the lookup,
943
+ contact the hosted deployment, or treat a conflict as a command failure. Use a
944
+ deep lookup when multiple sources are wanted; a fast lookup deliberately
945
+ records WHOIS as skipped, and comparison preserves that state as unavailable
946
+ rather than misreporting RDAP-only publication.
947
+
948
+ Input is capped at 8 MiB and must retain the lookup schema, mode, source
949
+ diagnostics, and normalised parsed source sections. Per-value, list, and event
950
+ bounds are revalidated because saved JSON is treated as untrusted input. A
951
+ successful registrar source must include a normalised object and agree with
952
+ its diagnostic status. Raw RDAP JSON, registrar contacts and source-specific
953
+ handles, WHOIS response bodies, availability evidence, and unrelated lookup
954
+ fields are not copied into the comparison result.
955
+
956
+ When the saved Lookup contains valid version-5, version-6, or version-7, context-only
957
+ registry-access diagnostics, version 3 retains their bounded suffix, access profiles, and
958
+ limitation. Terminal output labels the same collection context explicitly.
959
+ It explains source reachability only and cannot decide registration,
960
+ availability, ownership, safety, or maliciousness. Older documents and saved
961
+ lookups without that context do not gain a registry-access field.
962
+
963
+ Terminal and versioned JSON output cover domain identity, registry object ID,
964
+ registrar, registrar IANA ID, lifecycle dates, DNSSEC, statuses, and name
965
+ servers. The shared comparison model normalises harmless case, punctuation,
966
+ ordering, and date-precision differences while distinguishing conflicts,
967
+ one-source publication, redaction, incomplete sources, and unavailable
968
+ sources. This is source reconciliation, not an availability, ownership, or
969
+ maliciousness decision.
970
+
971
+ ## Static page comparison
972
+
973
+ `page-compare <left.json> <right.json>` reads two different version-1 saved
974
+ Deep domain Lookup documents and compares the already-retained static page
975
+ identity, exact and perceptual favicon evidence, technology identifiers, TLS
976
+ issuer label, and TLS public-key fingerprint. DOM structure is compared first
977
+ by its exact bounded tag-sequence digest, then, when both captures provide it,
978
+ by a parse5-tokenized structural SimHash. It makes no request and requires
979
+ complete supported static page-identity observations on both sides.
980
+
981
+ Each page component remains independent. Exact, similar, overlapping,
982
+ different, unavailable, and partial evidence are not collapsed into a score.
983
+ Matching components are investigative relationships rather than proof of
984
+ copying, common ownership, control, intent, safety, or maliciousness. Static
985
+ comparison does not execute JavaScript; use the optional local rendered
986
+ capture package only when that additional active behaviour is authorised.
987
+
988
+ ## Passive mail exposure review
989
+
990
+ `mail-review [bulk.json|bulk.jsonl]` reads version-2 Bulk output locally and
991
+ summarises MX, null MX, SPF, DMARC, and mail-provider relationships. It keeps
992
+ authenticated mail, authentication gaps, incomplete authentication evidence,
993
+ no explicit MX, null MX, and incomplete DNS evidence as separate states.
994
+ Shared-provider relationships are based only on the registrable domain of an
995
+ observed MX hostname and do not establish shared ownership or control.
996
+
997
+ The command makes no DNS or SMTP request and retains no source path. It does
998
+ not test message acceptance, relay behaviour, mailbox existence, catch-all
999
+ behaviour, SMTP banners, or whether a mail server is rogue, safe, or malicious.
1000
+
1001
+ An analyst may add a bounded `tlsaEvidence` object to a version-2 Bulk item for
1002
+ offline DANE review. It must name the exact `_25._tcp.<mx-host>` service, and
1003
+ that host must occur in the same item's retained MX evidence. A certificate
1004
+ association match is complete only when `dnssecState` is `validated`. TLSA
1005
+ usages 0 and 1 additionally require `pkixValidationState: "validated"`.
1006
+ Usages 0 and 2 compare against the bounded `authorityMaterials` array; usages
1007
+ 1 and 3 compare against the supplied leaf certificate or SPKI. Mismatched
1008
+ service names, missing certificate roles, unvalidated prerequisites, malformed
1009
+ material, and truncation remain invalid, partial, or untrusted rather than
1010
+ becoming a DANE match.
1011
+
1012
+ ## Offline supplied-evidence review
1013
+
1014
+ `review-evidence [evidence.json]` accepts one version-1 JSON input and performs
1015
+ only deterministic local parsing, comparison, or request planning. Input is
1016
+ capped at 16 MiB. The command never retrieves DNS, opens SMTP, contacts an
1017
+ RDAP server, submits a resolver query, or looks up an address in a hosted
1018
+ GeoIP service.
1019
+
1020
+ Supported input schemas are:
1021
+
1022
+ - `whoisleuth.dnssec-evidence-input`: compares bounded DS and DNSKEY records,
1023
+ recalculates DNSKEY key tags and supported DS digests, and reviews supplied
1024
+ RRSIG validity windows at `observedAt`. `consistent` describes only the
1025
+ supplied DS/DNSKEY relationship. It does not authenticate a chain to a root
1026
+ trust anchor, validate RRSIG cryptography, or prove that a missing record is
1027
+ absent.
1028
+ - `whoisleuth.tlsa-evidence-input`: binds TLSA records to one explicit
1029
+ `_port._transport.hostname` service and compares supplied leaf or authority
1030
+ certificate/SPKI bytes. `matched` requires independently validated DNSSEC;
1031
+ PKIX-TA and PKIX-EE usages also require an independently validated PKIX path.
1032
+ The command does not retrieve the certificate or negotiate STARTTLS.
1033
+ - `whoisleuth.rdap-search-input`: normalises a supplied RDAP search-help
1034
+ response and prepares an exact supported reverse-search request without
1035
+ sending it. If the document also contains a previously obtained `response`,
1036
+ the command checks its bounded property mappings against the reviewed
1037
+ registration and reports omitted or unrecognised mappings without treating
1038
+ the response as authoritative or complete.
1039
+ - `whoisleuth.rpki-route-input`: compares a route prefix and origin ASN with a
1040
+ bounded analyst-supplied VRP set.
1041
+ - `whoisleuth.local-geoip-query`: queries an analyst-supplied bounded prefix
1042
+ database whose source, version, and licence metadata remain in the result.
1043
+ - `whoisleuth.local-mmdb-query`: with an explicit `--mmdb <database-file>`,
1044
+ queries one analyst-supplied MaxMind DB file locally. The query document must
1045
+ carry `address`, `sourceLabel`, `databaseVersion`, and `license`. WHOISleuth
1046
+ does not bundle, download, update, license, transmit, or retain the database.
1047
+ The nested result uses the same address, network, country, region, city, ASN,
1048
+ source, and limitation fields as the bounded JSON-prefix path. This mode is
1049
+ CLI-only because a hosted or browser deployment cannot open an analyst's
1050
+ local file; its JSON result remains portable to website-compatible evidence
1051
+ archives and reports.
1052
+ - `whoisleuth.encrypted-dns-plan-input`: validates an explicitly reviewed
1053
+ encrypted-DNS provider contract and prepares a bounded query plan. It does
1054
+ not execute the plan.
1055
+ - `whoisleuth.zone-intent.input`: parses either a deliberately supported,
1056
+ bounded BIND master-file subset or normalised DNS records and compares it
1057
+ with one separately attributed observation. TXT values are reduced to
1058
+ SHA-256 digests, unsupported directives and records remain rejected, and an
1059
+ incomplete observation never becomes a missing-record conclusion. The
1060
+ review does not contact a resolver or apply a DNS change.
1061
+ - `whoisleuth.domain-portfolio.input`: reviews up to 500 analyst-supplied
1062
+ domain inventory rows for exact registrar, DNS, mail, certificate and
1063
+ recovery concentration, renewal-review gaps and internal recovery-domain
1064
+ dependencies. Account labels and provider assignments remain analyst
1065
+ assertions, and the review makes no provider or account request.
1066
+ - `whoisleuth.domain-change.input`: compares bounded, separately labelled
1067
+ authority and resolver observations; reviews CDS, CDNSKEY and CSYNC
1068
+ publication consistency; inventories supplied SRV services and ACME
1069
+ dependencies; and records TLS key continuity, stapling, embedded timestamp,
1070
+ and HSTS preload context. TXT values are retained only as SHA-256 digests.
1071
+ The result is an offline review gate, not a DNSSEC validation, preload-list
1072
+ lookup, certificate-authority request, or permission to apply a change.
1073
+ - `whoisleuth.dns-convergence.input`: compares the latest supplied observation
1074
+ from at least two labelled resolvers or network vantages. It preserves
1075
+ converged, divergent, unexpected, incomplete, and insufficient record-set
1076
+ states, can compare against optional expected values, and projects a simple
1077
+ observation-time plus TTL cache horizon. It does not query DNS or decide
1078
+ which divergent value is authoritative, and supplied TTLs do not reveal
1079
+ actual cache age or resolver refresh policy.
1080
+ - `whoisleuth.nameserver-preflight.input`: reviews an intended nameserver set
1081
+ against separately attributed direct-service observations before a
1082
+ delegation change. It checks authoritative service, the served NS set, SOA
1083
+ evidence, public addresses and in-bailiwick glue readiness without querying
1084
+ DNS, opening a socket or changing registry configuration.
1085
+ - `whoisleuth.trust-store-comparison.input`: compares an already-observed,
1086
+ bounded certificate-chain SHA-256 fingerprint set with up to 16
1087
+ analyst-supplied trust-store snapshots. Each snapshot carries its own name,
1088
+ version, source, review time, completeness, and no more than 2,048 anchor
1089
+ fingerprints. Exact intersections, complete non-observations, and
1090
+ inconclusive inputs remain separate. A match does not build or validate a
1091
+ certificate path, and non-observation does not mean a client would reject
1092
+ the chain because served chains commonly omit their root.
1093
+
1094
+ The common output is `whoisleuth.cli.offline-evidence-review` version 1. It
1095
+ retains the nested result's explicit state and limitations. A locally
1096
+ consistent relationship is not converted into a claim about current
1097
+ publication, ownership, safety, or maliciousness.
1098
+
1099
+ `review-evidence --strict-exit` is an opt-in local automation boundary. It
1100
+ returns the partial-failure exit code when an input family exposes an explicit
1101
+ failed `gate`, or when a zone-intent comparison is incomplete or contains a
1102
+ difference, missing value or unexpected value. Other evidence families retain
1103
+ their ordinary exit behaviour; the flag does not invent a pass/fail policy for
1104
+ them. JSON is still emitted before the exit status is selected.
1105
+
1106
+ ## Domain control manifests
1107
+
1108
+ `domain-control [input.json]` builds or reviews a bounded desired-state
1109
+ manifest without making a request or changing configuration. A manifest input
1110
+ uses `whoisleuth.domain-control-manifest-input` version 1, includes an expiry,
1111
+ and may record expected nameservers, DS, MX, CAA, TLS issuer, TLS SPKI
1112
+ fingerprint, registrar-lock preference, and a renewal-review date for up to 100
1113
+ domains. Empty fields mean unconfigured; they do not require a record to be
1114
+ absent.
1115
+
1116
+ The emitted `whoisleuth.domain-control-manifest` version 1 is normalised and
1117
+ protected by a canonical SHA-256 digest. It can also be passed to
1118
+ `sign-artifact` for an optional local Ed25519 signature. Integrity and
1119
+ signature verification detect changes and authenticate possession of the
1120
+ selected key; neither establishes that the desired state is correct. Manifest
1121
+ input and verification reject unknown fields, and verification rejects
1122
+ non-canonical content rather than returning a shape that differs from the
1123
+ normalised contract covered by the digest.
1124
+
1125
+ A review input uses `whoisleuth.domain-control-review-input` version 1 and
1126
+ contains one manifest plus separately attributed observations. Only a complete
1127
+ `observed` field may produce `drift`. Partial, unavailable, unsupported, and
1128
+ missing observations remain inconclusive, and unrelated observations are
1129
+ counted but ignored. The review performs no DNS, RDAP, HTTP, TLS, SMTP, or
1130
+ registrar request.
1131
+
1132
+ ## Domain assurance review
1133
+
1134
+ `assurance [input.json]` reviews one bounded analyst-authored workflow entirely
1135
+ offline. Version 1 inputs remain supported. Version 2 adds optional
1136
+ `customChecks` to retirement inputs and remains the current normalised output
1137
+ version. The input uses `whoisleuth.domain-assurance.input` and one of three
1138
+ fixed kinds:
1139
+
1140
+ - `planned-change` records a bounded window, evidence-backed milestones,
1141
+ rollback criteria, and post-change checks;
1142
+ - `recovery-dependencies` reviews up to 100 domains across registrar, DNS,
1143
+ mail, certificate, and recovery providers and highlights exact provider
1144
+ concentration; and
1145
+ - `retirement` checks the fixed decommissioning controls without treating an
1146
+ unchecked item as complete. Version 2 can add up to 20 analyst-defined checks,
1147
+ each with a bounded identifier, label, expected boolean, and true, false, or
1148
+ null observed value.
1149
+
1150
+ Provider labels, readiness confirmations, evidence references, and expected
1151
+ states remain analyst assertions. The output preserves incomplete and
1152
+ needs-review states and gives every negative state an explicit review reason.
1153
+ The versioned input rejects unknown fields and contradictory evidence attached
1154
+ to an unfinished check. It stores no credentials, makes no request, and changes
1155
+ no registrar, DNS, mail, certificate, or recovery configuration.
1156
+
1157
+ ## Domain change assurance packet
1158
+
1159
+ `change-packet [input.json]` combines one pre-change domain review, one
1160
+ post-change domain review, and one planned-change assurance input for the same
1161
+ domain. The input uses `whoisleuth.domain-change-packet.input` version 1. The
1162
+ command revalidates each nested contract, summarises changed authoritative
1163
+ record sets, and emits `whoisleuth.domain-change-packet` version 1 with a
1164
+ canonical SHA-256 integrity digest.
1165
+
1166
+ The packet is ready only when both supplied observation reviews and the change
1167
+ plan pass their own bounded gates. Partial or unavailable evidence remains a
1168
+ review reason. Packet assembly performs no collection or configuration change,
1169
+ and digest validity does not authenticate the analyst. A reviewed packet can be
1170
+ passed to `sign-artifact` when the operator has an independently managed
1171
+ Ed25519 signing-key lifecycle.
1172
+
1173
+ ## Pre-sharing review
1174
+
1175
+ `sharing-review [artifact.json]` performs a redacted local preflight before an
1176
+ analyst deliberately shares an artefact. The analyst must select a TLP 2.0
1177
+ marking, a recipient scope, and a bounded purpose. Optional confirmations record
1178
+ human review, personal-data review, and redaction review. The command validates
1179
+ supported artefact integrity where possible, detects stricter embedded TLP
1180
+ markings, and reports only key-category counts plus bounded, control-free schema
1181
+ and integer version metadata. It emits no content values and copies no raw
1182
+ evidence.
1183
+
1184
+ An imported stricter marking cannot be downgraded by the requested marking, and
1185
+ recipient scope is checked against the effective marking. Evading this local
1186
+ preflight remains possible, so the result is a review aid rather than access
1187
+ control, legal advice, or recipient authorisation.
1188
+
1189
+ ## Fixed investigation plans
1190
+
1191
+ `workflow-plan <recipe> <domain|brand>` turns one of four fixed recipes into a
1192
+ versioned `whoisleuth.cli.investigation-plan` document. Supported recipes are
1193
+ `domain-triage`, `lookalike-review`, `owned-domain-review`, and
1194
+ `historical-comparison`. Each step names an existing CLI command, carries its
1195
+ arguments as a JSON array, declares whether it is offline or networked, and
1196
+ marks network-disclosure or analyst-selection approval gates.
1197
+
1198
+ The command is plan-only. It does not execute a step, construct a shell script,
1199
+ interpret a placeholder as a file path, read an artefact, make a request,
1200
+ change a case, or submit evidence. Analysts deliberately run selected commands
1201
+ after reviewing their collection boundaries. This provides repeatable
1202
+ domain-specific workflows without an arbitrary automation or plugin surface.
1203
+
1204
+ ## Local handoffs and one-shot monitoring
1205
+
1206
+ `brief [lookup.json]` reads one saved Lookup and emits version 2 of the bounded
1207
+ `whoisleuth.cli.lookup-brief` contract. It separates retained facts,
1208
+ contradictions, incomplete source states, and manual actions. Each action
1209
+ states its evidence basis and expected outcome; it does not run the action or
1210
+ create a case.
1211
+
1212
+ `case-pack [cases.json] --audience <internal|trusted|public> --reviewed` reads a
1213
+ browser case export containing at most 25 normalised cases, applies the selected
1214
+ redaction boundary, and emits a browser-importable case collection with a
1215
+ canonical SHA-256 digest and redaction manifest. It refuses an invalid,
1216
+ duplicate, larger, or generated package above the browser's 2 MiB import limit
1217
+ rather than silently omitting records or evidence. Trusted output removes notes,
1218
+ recipient values, and manual-pivot targets. Public output additionally removes
1219
+ actions and analyst assertions. The review flag records an explicit choice; it
1220
+ does not prove recipient authorisation or factual correctness.
1221
+
1222
+ `verify-artifact` recognises the complete case-pack envelope before browser
1223
+ import. It checks the canonical digest, supported case-export schema, bounded
1224
+ case count, report count, audience and explicit review marker without printing
1225
+ case contents. This detects a changed hand-off file but does not authenticate
1226
+ the analyst, recipient, source observations, or review decision.
1227
+
1228
+ `monitor-once [manifest.json]` performs one bounded Deep control review for at
1229
+ most 20 manifest domains with concurrency capped at three. An optional
1230
+ `--previous` checkpoint adds local first/last-observed comparison. It is not a
1231
+ daemon or scheduler, retains compact normalised observations rather than raw
1232
+ payloads, and keeps failed collection incomplete. JSON, JUnit, and explicit
1233
+ failure policies are supported.
1234
+
1235
+ `workflow-run <recipe> <subject>` executes only concrete steps from the four
1236
+ installed fixed recipes. Network steps require `--approve-network` for that
1237
+ invocation. Analyst-selection placeholders always pause. A bounded `--resume`
1238
+ state must match the exact recipe and subject, cannot add commands or shell
1239
+ syntax, and is not evidence that earlier results remain current.
1240
+
1241
+ ## Optional local rendered capture
1242
+
1243
+ `packages/web-capture` is a private repo-local Playwright package, not part of
1244
+ the distributable core CLI or hosted application. It requires an explicit
1245
+ authorisation flag and one new output directory:
1246
+
1247
+ ```bash
1248
+ npm run capture:local -- https://example.test --output-dir ./capture-example --authorize-rendered-capture
1249
+ npm run capture:compare -- ./official/manifest.json ./candidate/manifest.json --json
1250
+ ```
1251
+
1252
+ It writes a fixed 1024x768 PNG, screenshot SHA-256 and perceptual dHash, a DOM
1253
+ digest containing hashes and bounded element counts rather than markup or page
1254
+ text, and a version-2 `whoisleuth.web-capture-manifest` compatible with the
1255
+ Cases importer. Version-1 manifests remain importable.
1256
+
1257
+ Rendered capture executes page JavaScript. It caps HTTP(S) requests and request
1258
+ hostnames, blocks downloads, service workers, WebSockets, non-read methods,
1259
+ credentials, non-default ports, and private or reserved addresses, and retains
1260
+ no request path, query, headers, bodies, cookies, credentials, DOM markup, or
1261
+ page text. The browser connection is not pinned to the address checked before
1262
+ each hostname's first request, so DNS rebinding remains a residual risk. Use a
1263
+ disposable network-restricted environment for untrusted targets.
1264
+
1265
+ The separate offline comparison command accepts two selected version-2 local
1266
+ capture manifests. It first verifies each referenced screenshot and DOM digest
1267
+ against its declared size, SHA-256, screenshot dimensions, and perceptual hash;
1268
+ it also validates bounded source and limitation metadata and requires the DOM,
1269
+ manifest, and source collection times to agree. It then
1270
+ compares screenshot distance, exact rendered DOM and visible-text digests,
1271
+ bounded element counts, page identity, technologies, and request-domain sets.
1272
+ It does not recrawl either target, reveal the input paths, or collapse the
1273
+ independent components into a similarity or maliciousness score. A missing
1274
+ perceptual hash remains unavailable rather than becoming a visual difference.
1275
+
1276
+ ## Lookup evidence export
1277
+
1278
+ `export` converts one version-1 `whoisleuth.cli.lookup` domain document from a
1279
+ file or stdin into the same versioned `whoisleuth.lookup-evidence` JSON package
1280
+ produced by the web Lookup tool. It performs no lookup and writes only to
1281
+ stdout, so use ordinary shell redirection when a file is wanted. Pretty JSON is
1282
+ the default; `--compact` emits one compact JSON line for pipeline use, while
1283
+ `--markdown` produces a readable source-attributed summary and `--html`
1284
+ produces a self-contained printable report. Compact, Markdown, and HTML output
1285
+ are mutually exclusive.
1286
+
1287
+ The saved input is capped at 8 MiB and revalidated using the same schema,
1288
+ source-status, parsed-data, scalar, list, and event boundaries as `compare`.
1289
+ The export retains query context, source diagnostics, normalised registry data,
1290
+ raw registry RDAP JSON, the raw WHOIS referral chain, availability analysis,
1291
+ and the shared registry-source comparison. Version 23 can add the exact local
1292
+ SSLBL comparison already represented by a deep full Lookup. Version 21 adds the bounded registry
1293
+ lifecycle, disclosure, publication-quality, reconciliation, and abuse-routing
1294
+ interpretation derived from the already-collected sources. Registrar RDAP raw
1295
+ data, contacts, entities, links, notices, and source-specific handles remain
1296
+ excluded. Schema version 20 replaces selected security-policy values in
1297
+ retained HTTP evidence
1298
+ with presence-only markers and can include fixed response-policy findings from
1299
+ an already-represented deep lookup. Version 19 additionally retains the bounded credential-surface projection
1300
+ when the saved deep lookup represents it. Version 18 retains the bounded structured identity projection
1301
+ when the saved deep lookup represents it. Version 17 retains the normalised portable-field comparison, explicit
1302
+ source-health states, a strict bounded projection of observed network
1303
+ registration, an optional normalised security.txt disclosure source, HTTPS
1304
+ service-binding publications, and the passive browser-library profile when the
1305
+ saved deep lookup represents them. Raw IP RDAP payloads, security.txt response
1306
+ bodies, script references, matched script content, and contact entities remain
1307
+ excluded.
1308
+
1309
+ Markdown output summarises query context, assessment state, registry sources,
1310
+ source reconciliation, network observations, and collection diagnostics. It
1311
+ escapes all upstream text as untrusted content, bounds displayed values and
1312
+ lists, and deliberately omits raw RDAP JSON and full WHOIS response bodies.
1313
+ Use JSON when the complete evidence package or machine processing is required.
1314
+ The browser Console reuses this bounded Markdown renderer for domain results
1315
+ after first reducing the typed Lookup response to the known normalised fields
1316
+ needed by the report. It separately offers bounded IP and ASN summaries from
1317
+ their normalised RDAP and source-health fields; IP reports can also include
1318
+ bounded reverse-DNS context already collected by Lookup. The browser action
1319
+ does not change the CLI input or output contract.
1320
+ When a version-5, version-6, or version-7 lookup records a documented registry-access constraint, both
1321
+ Markdown and HTML include that context in collection diagnostics without
1322
+ changing its non-authoritative interpretation. Both formats also include the
1323
+ selected address and bounded network registration when present, together with
1324
+ the edge-versus-origin limitation.
1325
+
1326
+ HTML uses the same bounded summary model as Markdown. The generated document
1327
+ contains inline styling for screen and print, but no scripts, forms, active
1328
+ links, or external resources. A restrictive embedded Content Security Policy
1329
+ provides defence in depth when the local file is opened in a browser. All
1330
+ registry values are HTML-escaped and displayed as text.
1331
+
1332
+ This is a deliberately rich evidence package. Raw registry sources can contain
1333
+ publicly published contact data, and deep availability evidence can contain
1334
+ bounded website, DNS, mail, page-identity, and TLS observations. Review and
1335
+ secure the output before sharing it. The CLI does not add browser-only IDN
1336
+ profile analysis, so that optional evidence field is `null`.