@slicedearth/whoisleuth-cli 1.47.4 → 2.0.1

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 (236) hide show
  1. package/README.md +74 -177
  2. package/SECURITY.md +6 -3
  3. package/bin/whoisleuth.mjs +2 -1
  4. package/cli/archive-inspect.mjs +28 -10
  5. package/cli/arguments.mjs +204 -174
  6. package/cli/artifact-structure.mjs +837 -163
  7. package/cli/artifact-verify.mjs +90 -61
  8. package/cli/bounded-json.mjs +1 -1
  9. package/cli/bulk-checkpoint.mjs +33 -20
  10. package/cli/bulk.mjs +9 -2
  11. package/cli/case-pack.mjs +180 -576
  12. package/cli/ci-report.mjs +6 -1
  13. package/cli/collection-preflight.mjs +2 -2
  14. package/cli/command-catalogue.mjs +12 -3
  15. package/cli/command-reference.mjs +910 -92
  16. package/cli/completion.mjs +504 -226
  17. package/cli/config-profile.mjs +13 -3
  18. package/cli/discover.mjs +7 -1
  19. package/cli/discovery-observation-snapshot.mjs +24 -28
  20. package/cli/discovery-scan-command-runner.mjs +9 -9
  21. package/cli/discovery-scan.mjs +17 -3
  22. package/cli/discovery-snapshot.mjs +15 -7
  23. package/cli/domain-control-monitor.mjs +203 -30
  24. package/cli/domain-control-observations.mjs +15 -21
  25. package/cli/errors.mjs +8 -3
  26. package/cli/evidence-signing.mjs +10 -20
  27. package/cli/external-observation-mapping.mjs +3 -3
  28. package/cli/formatters/json.mjs +20 -32
  29. package/cli/formatters/terminal.mjs +583 -22
  30. package/cli/input.mjs +7 -2
  31. package/cli/interactive-launcher.mjs +226 -0
  32. package/cli/interchange-report.mjs +19 -19
  33. package/cli/investigation-manifest.mjs +1 -1
  34. package/cli/investigation-plan.mjs +167 -16
  35. package/cli/investigation-run.mjs +9 -1
  36. package/cli/lookalike-calibration.mjs +3 -1
  37. package/cli/lookup-brief.mjs +13 -8
  38. package/cli/lookup-browser.mjs +844 -0
  39. package/cli/lookup-command-runner.mjs +94 -31
  40. package/cli/lookup-diff.mjs +2 -2
  41. package/cli/lookup-plan.mjs +1 -0
  42. package/cli/mail-review.mjs +206 -94
  43. package/cli/manual.mjs +5 -5
  44. package/cli/offline-evidence-review.mjs +18 -7
  45. package/cli/open-asset-model-bridge.mjs +1 -1
  46. package/cli/output-file.mjs +70 -14
  47. package/cli/page-compare.mjs +11 -6
  48. package/cli/progress-events.mjs +2 -1
  49. package/cli/progress.mjs +3 -3
  50. package/cli/registry-cohort.mjs +9 -51
  51. package/cli/registry-doctor.mjs +67 -28
  52. package/cli/retained-artifact-diff.mjs +7 -8
  53. package/cli/risk-calibration.mjs +85 -38
  54. package/cli/runner.mjs +189 -75
  55. package/cli/saved-lookup.mjs +424 -57
  56. package/cli/sharing-review.mjs +6 -1
  57. package/cli/source-reliability.mjs +75 -36
  58. package/cli/terminal-presentation.mjs +43 -18
  59. package/docs/cli-reference.md +125 -1708
  60. package/docs/cli.md +86 -254
  61. package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -145
  62. package/frontend/src/lib/analysis/domain-control-records.js +3 -88
  63. package/lib/availability.mjs +58 -40
  64. package/lib/bounded-contract-normalizers.mjs +2 -4
  65. package/lib/bounded-file.mjs +78 -4
  66. package/lib/bounded-json.mjs +51 -13
  67. package/lib/browser-library-profile.mjs +1 -1
  68. package/lib/classify.mjs +56 -5
  69. package/lib/client-behavior-profile.mjs +1 -1
  70. package/lib/credential-surface-profile.mjs +1 -1
  71. package/lib/cryptographic-assurance.mjs +35 -17
  72. package/lib/ct-query.mjs +3 -3
  73. package/lib/ct-search.mjs +37 -21
  74. package/lib/distributed-operation-budget.mjs +4 -2
  75. package/lib/dns-delegation-health.mjs +9 -6
  76. package/lib/dns-intelligence.mjs +1 -1
  77. package/lib/dnssec-chain-validation.mjs +2 -3
  78. package/lib/dnssec-evidence-validation.mjs +13 -7
  79. package/lib/domain-assurance.mjs +12 -12
  80. package/lib/domain-change-packet.mjs +1 -1
  81. package/lib/domain-control-flight-recorder.mjs +442 -78
  82. package/lib/domain-control-manifest.mjs +314 -59
  83. package/lib/domain-posture-analysis.mjs +5 -2
  84. package/lib/encrypted-dns-contract.mjs +2 -3
  85. package/lib/evidence-export.mjs +518 -36
  86. package/lib/external-intelligence-risk.mjs +26 -10
  87. package/lib/feature-policy.mjs +1 -1
  88. package/lib/homepage-metadata-contract.mjs +290 -0
  89. package/lib/hostname.mjs +1 -1
  90. package/lib/html-signals.mjs +89 -5
  91. package/lib/http-intelligence.mjs +331 -2
  92. package/lib/interchange-fidelity-registry.mjs +28 -26
  93. package/lib/lookup-cache.mjs +6 -4
  94. package/lib/lookup.mjs +25 -2
  95. package/lib/observed-network-context.mjs +1 -1
  96. package/lib/page-fingerprints.mjs +2 -2
  97. package/lib/page-role-profile.mjs +1 -1
  98. package/lib/portable-generator.mjs +8 -0
  99. package/lib/project-metadata.mjs +1 -1
  100. package/lib/provider-policy-admission.mjs +3 -1
  101. package/lib/rdap-bootstrap.mjs +76 -16
  102. package/lib/rdap-client.mjs +20 -12
  103. package/lib/rdap-normalization.mjs +306 -75
  104. package/lib/rdap-transport.mjs +2 -2
  105. package/lib/rdap-validation.mjs +65 -14
  106. package/lib/rdap.mjs +2 -2
  107. package/lib/registrable-domain.mjs +36 -0
  108. package/lib/registry-insights.mjs +7 -0
  109. package/lib/response-policy.mjs +27 -4
  110. package/lib/risk-calibration-summary.mjs +35 -33
  111. package/lib/risk-scoring.mjs +4 -3
  112. package/lib/safe-fetch.mjs +62 -40
  113. package/lib/security-txt.mjs +1 -1
  114. package/lib/smtp-transport-review.mjs +8 -5
  115. package/lib/sslbl-intelligence.mjs +6 -8
  116. package/lib/static-html-analysis.mjs +301 -4
  117. package/lib/structured-data-identity.mjs +51 -19
  118. package/lib/threat-intelligence-fixtures.mjs +2 -4
  119. package/lib/threat-intelligence-manifests.mjs +2 -7
  120. package/lib/threat-intelligence-runtime.mjs +3 -5
  121. package/lib/threat-intelligence-types.mjs +3 -0
  122. package/lib/threatfox-intelligence.mjs +7 -3
  123. package/lib/tls-intelligence.mjs +4 -4
  124. package/lib/urlhaus-intelligence.mjs +4 -2
  125. package/lib/urlscan-intelligence.mjs +6 -5
  126. package/lib/website-security-posture.mjs +1 -1
  127. package/lib/website-technology.mjs +1 -1
  128. package/lib/whois-authority.mjs +43 -8
  129. package/lib/whois-chain.mjs +25 -3
  130. package/lib/whois-normalization.mjs +3 -1
  131. package/lib/whois-transport.mjs +12 -3
  132. package/lib/zone-intent-review.mjs +1 -1
  133. package/package.json +13 -5
  134. package/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
  135. package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
  136. package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
  137. package/packages/cases/case-migration-model.mjs +476 -0
  138. package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
  139. package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -21
  140. package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
  141. package/packages/cases/case-record-model.mjs +9 -0
  142. package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +72 -29
  143. package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
  144. package/packages/cases/case-response-model.mjs +1604 -0
  145. package/packages/cases/case-response-packet.mjs +1312 -0
  146. package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
  147. package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
  148. package/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
  149. package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
  150. package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
  151. package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -0
  152. package/packages/comparison/favicon-similarity.mjs +78 -0
  153. package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
  154. package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
  155. package/packages/contracts/analyst-review-state-contract.mjs +61 -0
  156. package/packages/contracts/analyst-review-state.mjs +50 -0
  157. package/packages/contracts/case-portability.mjs +1310 -0
  158. package/packages/contracts/cli-command-catalogue.mjs +2 -0
  159. package/packages/contracts/cli-lookup.mjs +394 -0
  160. package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
  161. package/packages/contracts/domain-control-manifest.mjs +1027 -0
  162. package/packages/contracts/domain-control-monitor.mjs +310 -0
  163. package/packages/contracts/domain-control-review.mjs +828 -0
  164. package/packages/contracts/domain-name.mjs +4 -0
  165. package/packages/contracts/external-observation-interchange.mjs +224 -0
  166. package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
  167. package/packages/contracts/investigation-portability.mjs +331 -0
  168. package/packages/contracts/offline-comparison.mjs +160 -0
  169. package/packages/contracts/risk-calibration.mjs +1288 -0
  170. package/packages/contracts/schema-compatibility.mjs +71 -0
  171. package/packages/contracts/schema-lifecycle.mjs +1702 -0
  172. package/packages/contracts/sslbl-snapshot.mjs +17 -0
  173. package/packages/contracts/workspace-portability.mjs +1347 -0
  174. package/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
  175. package/packages/evidence/decision-fact.mjs +735 -0
  176. package/packages/evidence/domain-control-runtime.mjs +547 -0
  177. package/packages/evidence/domain-name.mjs +41 -0
  178. package/packages/evidence/observation.mjs +384 -0
  179. package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +24 -22
  180. package/packages/interchange/retained-text.mjs +8 -0
  181. package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
  182. package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
  183. package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
  184. package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
  185. package/packages/investigation/bulk-review-policy.mjs +1 -0
  186. package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
  187. package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
  188. package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
  189. package/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
  190. package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
  191. package/packages/investigation/lookup-investigation-brief.mjs +172 -0
  192. package/packages/monitoring/analyst-review-state.mjs +680 -0
  193. package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +71 -48
  194. package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
  195. package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
  196. package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -5
  197. package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
  198. package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
  199. package/packages/workspace/hostile-input.mjs +172 -0
  200. package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +8 -45
  201. package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
  202. package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
  203. package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
  204. package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
  205. package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
  206. package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
  207. package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +13 -13
  208. package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
  209. package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +110 -89
  210. package/third-party-notices.txt +14 -14
  211. package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
  212. package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
  213. package/frontend/src/lib/analysis/case-migration-model.js +0 -305
  214. package/frontend/src/lib/analysis/case-record-model.js +0 -9
  215. package/frontend/src/lib/analysis/case-response-model.js +0 -728
  216. package/frontend/src/lib/analysis/case-response-packet.js +0 -583
  217. package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
  218. package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
  219. package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
  220. package/frontend/src/lib/analysis/lookup-response.js +0 -2
  221. package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
  222. package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
  223. package/frontend/src/lib/analysis/scoring.js +0 -55
  224. package/frontend/src/lib/analysis/utils.js +0 -255
  225. package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
  226. package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
  227. package/frontend/src/lib/analysis/visualization-matrix.js +0 -131
  228. package/frontend/src/lib/analysis/visualization-models.js +0 -338
  229. package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
  230. package/lib/bounded-json-response.mjs +0 -170
  231. package/lib/lookup-response-contract.mjs +0 -1034
  232. package/lib/observation.mjs +0 -115
  233. /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
  234. /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
  235. /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
  236. /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
package/README.md CHANGED
@@ -1,14 +1,13 @@
1
1
  # WHOISleuth CLI
2
2
 
3
- WHOISleuth's command-line interface runs the same bounded WHOIS, RDAP, DNS,
4
- HTTP, TLS, certificate-transparency, domain-posture, and lookalike analysis
5
- used by the self-hosted application. Network commands run directly from the
6
- operator's machine. Offline evidence commands do not contact the hosted site.
3
+ WHOISleuth's command-line interface runs local WHOIS, RDAP, DNS, HTTP, TLS,
4
+ certificate, posture and lookalike investigation commands. Networked commands
5
+ run from the operator's machine; offline commands read only selected local input.
6
+ It does not use the hosted WHOISleuth login or workspace.
7
7
 
8
8
  ## Install
9
9
 
10
- WHOISleuth requires Node.js 24 or later. Run the reviewed public package without
11
- a global installation:
10
+ WHOISleuth requires Node.js 24 or later. Run the public package directly:
12
11
 
13
12
  ```bash
14
13
  npm exec --yes --ignore-scripts --package=@slicedearth/whoisleuth-cli -- whoisleuth --help
@@ -21,189 +20,87 @@ npm install --global --ignore-scripts @slicedearth/whoisleuth-cli
21
20
  whoisleuth doctor
22
21
  ```
23
22
 
24
- Update an existing global installation and confirm the installed release with:
23
+ The package has no dependency lifecycle scripts. Its version follows the
24
+ application version.
25
+
26
+ ## Quick start
27
+
28
+ Inspect the installed command set and plan a Lookup:
25
29
 
26
30
  ```bash
27
- npm install --global --ignore-scripts @slicedearth/whoisleuth-cli@latest
28
- whoisleuth --version
31
+ whoisleuth --help
32
+ whoisleuth commands --common
33
+ whoisleuth lookup example.test --plan --json
29
34
  ```
30
35
 
31
- The package version follows the application version. Each release is assembled
32
- from the executable's bounded dependency closure and retains the project
33
- licence, notices, trademark terms, source location, and provenance metadata.
34
- The generated package manifest pins each direct runtime dependency to the exact
35
- version exercised by the reviewed repository lockfile.
36
+ Run common commands:
37
+
38
+ ```bash
39
+ whoisleuth example.test
40
+ whoisleuth lookup example.test --deep --summary
41
+ whoisleuth bulk domains.txt --csv
42
+ whoisleuth discover example.test --preset common --jsonl
43
+ whoisleuth compare lookup.json --json
44
+ whoisleuth verify-artifact lookup.json --json --strict-exit
45
+ ```
46
+
47
+ One strict domain, IP address or ASN can occupy command position as Lookup
48
+ shorthand. URL-like or ambiguous input requires the explicit `lookup` command;
49
+ only `bulk` accepts multiple targets.
50
+
51
+ ## Command groups
52
+
53
+ | Group | Common commands |
54
+ | --- | --- |
55
+ | Investigate | `lookup`, `bulk`, `discover`, `ct-search`, `posture`, `http`, `tls`, `compare`, `brief` |
56
+ | Respond | `case-pack`, `change-packet`, `sharing-review`, `export` |
57
+ | Assure | `dnssec-validate`, `mail-transport`, `domain-control`, `assurance`, `workflow-plan`, `diff`, `inspect-archive`, `verify-artifact` |
58
+ | Utilities | `doctor`, `commands`, `completion`, `manual`, `registry-scaffold` |
59
+
60
+ Use the installed registry for the complete inventory:
61
+
62
+ ```bash
63
+ whoisleuth commands --group investigate
64
+ whoisleuth commands --group respond --mode offline
65
+ whoisleuth commands --json
66
+ whoisleuth manual | man -l -
67
+ ```
68
+
69
+ `registry-scaffold` has a separate fixture contract: its `--profile` selects a
70
+ fixed fixture profile and it rejects shared `--config` profiles. It creates
71
+ sanitised local fixture material.
72
+
73
+ ## Collection and output
74
+
75
+ Fast Lookup is registration-first. Deep Lookup explicitly adds the applicable
76
+ WHOIS, DNS, website, TLS, registrar-RDAP and observed-network sources. Compact
77
+ Deep Bulk retains only the fields needed for comparison. Authorised active
78
+ commands require their dedicated command and per-run acknowledgement.
79
+
80
+ Focused `whoisleuth <command> --help` states the exact target, request, input and
81
+ output limits. `lookup --plan` lists planned sources before collection.
82
+
83
+ Terminal text is the default. Commands expose JSON, JSONL, CSV, Markdown, HTML
84
+ or domain-only output only where declared. `--output` writes a private local
85
+ file atomically and refuses an existing path unless `--force` is selected.
86
+ `--strict-exit` and `--fail-on` expose selected evidence states to automation.
36
87
 
37
- The package has no install, postinstall, or other lifecycle scripts. Network
38
- access begins only when an analyst runs a networked command. Offline commands
39
- may read deliberately selected local evidence or configuration files, but do
40
- not upload them or invoke a hosted WHOISleuth deployment. The installation
41
- examples disable lifecycle scripts for the package and its dependency tree;
42
- WHOISleuth does not require them.
88
+ Exit codes are 0 for completion, 2 for invalid input, 3 for collection or
89
+ comparison failure, 4 for a partial result or unmet selected evidence policy,
90
+ 70 for internal bootstrap failure, 130 for cancellation and 143 for SIGTERM.
43
91
 
44
92
  ## Repository checkout
45
93
 
46
- Contributors can run the same source command without installing the package:
94
+ Contributors can run the source command without installing the package:
47
95
 
48
96
  ```bash
49
97
  node bin/whoisleuth.mts --help
50
- node bin/whoisleuth.mts lookup example.test
51
- node bin/whoisleuth.mts lookup example.test --deep --summary
52
- node bin/whoisleuth.mts lookup example.test --deep --plan --json
53
- node bin/whoisleuth.mts lookup example.test --deep --observer workstation-a --vantage office-egress --json
54
- node bin/whoisleuth.mts lookup example.test --deep --markdown --output lookup.md
55
- node bin/whoisleuth.mts bulk domains.txt --deep --checkpoint bulk-checkpoint.json
56
- node bin/whoisleuth.mts bulk domains.txt --csv --registered-only
57
- node bin/whoisleuth.mts bulk domains.txt --queries --errors-only
58
- node bin/whoisleuth.mts discover example.test --dictionary private-terms.txt --snapshot discovery-state.json --json
59
- node bin/whoisleuth.mts discover-scan example.test --scan-limit 50 --checkpoint candidate-scan.json --json
60
- node bin/whoisleuth.mts page-compare official.json candidate.json --json
61
- node bin/whoisleuth.mts mail-review bulk.json --json
62
- node bin/whoisleuth.mts dnssec-validate example.test --resolver "$PUBLIC_RESOLVER_IP" --trust-anchor anchor.json --owned-or-authorized --json
63
- node bin/whoisleuth.mts mail-transport selected-mx.json --resolver "$PUBLIC_RESOLVER_IP" --trust-anchor anchor.json --owned-or-authorized --active-probe --json
64
- node bin/whoisleuth.mts domain-control domain-control-input.json --json
65
- node bin/whoisleuth.mts assurance domain-assurance-input.json --json
66
- node bin/whoisleuth.mts sharing-review response-packet.json --marking amber --recipient-scope organization --purpose 'Reviewed handoff' --human-reviewed --personal-data-reviewed --redactions-confirmed --json
67
- node bin/whoisleuth.mts workflow-plan domain-triage example.test --json
68
- node bin/whoisleuth.mts diff first-lookup.json second-lookup.json --json
69
- node bin/whoisleuth.mts reconcile office.json mobile.json external.json --json
70
- node bin/whoisleuth.mts timeline first-observation.json second-observation.json latest-observation.json --json
71
- node bin/whoisleuth.mts registry-support example.test --json
72
- node bin/whoisleuth.mts registry-doctor lookup.json --json
73
- node bin/whoisleuth.mts registry-cohort saved-lookups.jsonl --json
74
- node bin/whoisleuth.mts brief lookup.json
75
- node bin/whoisleuth.mts case-pack cases.json --audience trusted --reviewed --json
76
- node bin/whoisleuth.mts monitor-once control-manifest.json --json
77
- node bin/whoisleuth.mts workflow-run domain-triage example.test --approve-network --json --output run.json
78
- node bin/whoisleuth.mts lookalike-calibrate reviewed-candidates.json --json
79
- node bin/whoisleuth.mts doctor
80
- node bin/whoisleuth.mts commands --json
81
- node bin/whoisleuth.mts completion zsh
82
- node bin/whoisleuth.mts completion powershell
83
- node bin/whoisleuth.mts manual | man -l -
98
+ node bin/whoisleuth.mts lookup example.test --plan --json
99
+ npm run cli:package:check
84
100
  ```
85
101
 
86
- Fast lookup is the default. Deep collection must be requested explicitly and
87
- can disclose the target to additional authoritative or first-party network
88
- sources. Missing, partial, rate-limited, and unsupported sources remain
89
- explicit and are never interpreted as evidence of safety or absence.
90
- `lookup --plan` classifies a target and explains the selected source families
91
- and disclosures without making a network request. `commands --json` exposes the
92
- installed command contracts for local wrappers without running collection.
93
- The isolated `dnssec-validate` and `mail-transport` actions are not part of
94
- Lookup, Bulk, monitoring, or recipes. Each run requires explicit
95
- owned-or-authorised acknowledgement; mail transport also requires a separate
96
- active-probe acknowledgement and never sends a message, authenticates, tests
97
- relay, or enumerates recipients or mailboxes. Its output distinguishes selected,
98
- public-revalidated, and connected addresses and explicitly reports that A, AAAA,
99
- and CNAME authentication was not evaluated after a candidate is retained, or
100
- that address authentication is unavailable when no candidate exists; only
101
- confirmed connections can form address relationship leads. If a DANE-TA TLSA
102
- usage 2 association is published, active collection retains only the observed
103
- leaf certificate and leaves that comparison partial without certificate-path
104
- construction and trust-anchor path validation. SMTP relay PKIX-TA usage 0 and
105
- PKIX-EE usage 1 records are retained as unsupported and cannot complete SMTP
106
- DANE assurance; a separate usage 3 match remains eligible. Saved
107
- Lookup JSON is byte-, nesting-, entry-, and per-container-bounded before parsing and portable export.
108
- Focused command help and the generated manual label every operation as offline
109
- or networked and state its target, input, and concurrency boundaries. The
110
- packaged [dual-use disclosure](https://github.com/slicedearth/whoisleuth/blob/main/DISCLOSURE)
111
- defines the supported defensive use of those capabilities.
112
-
113
- Interactive output uses restrained semantic colour, width-aware wrapping, and
114
- stderr-only progress for slower collection. Redirected and machine-readable
115
- output stays free of ANSI and progress text. Lookup also provides `--summary`
116
- and `--verbose` terminal views without changing what is collected. `doctor` is
117
- offline unless `--network` is explicitly supplied, and completion scripts are
118
- printed without modifying shell configuration. Commands can use atomic private
119
- `--output` files, Lookup can emit strict automation exits and target-free
120
- versioned progress events, and Bulk can resume an exact validated compact
121
- checkpoint. Checkpoint schema 2 preserves each current row's observation time;
122
- legacy schema-1 rows remain readable with unknown observation time rather than
123
- being restamped on resume. Bulk machine output schema 3 distinguishes current
124
- and resumed rows. A checkpoint write failure preserves completed output and returns
125
- the partial-result exit code. Ctrl-C suppresses partial final output and
126
- returns exit code 130. SIGTERM uses the same graceful cleanup path and returns
127
- 143; a second interrupt performs best-effort temporary-file
128
- cleanup before exiting immediately.
129
- Strict local profiles can supply only safe presentation, Fast-mode,
130
- concurrency, and observer defaults; command-line options override them.
131
- Supported automation commands can emit JUnit or SARIF and apply explicit
132
- failure policies without changing their collection result.
133
-
134
- The offline `timeline` command orders 2 to 20 saved Lookup documents for one
135
- domain and compares each adjacent observation. It retains observation times and
136
- normalised field states but no input filenames or raw registry payloads.
137
- Observed differences remain separate from missing, unavailable, and changed
138
- collection conditions.
139
-
140
- Optional bounded observer and vantage labels let analysts distinguish saved
141
- collection contexts without changing collection. `reconcile` compares 2 to 5
142
- same-domain observations while keeping agreement, disagreement, and
143
- non-comparable evidence separate; the labels are context, not proof of
144
- independence. `registry-doctor`, `assurance`, `sharing-review`, and
145
- `lookalike-calibrate` add offline compatibility, change/recovery, sharing, and
146
- review-yield workflows without contacting the hosted application.
147
- `registry-cohort` emits target-free publication-quality timelines from either
148
- saved Lookups or one unmixed family of retained version-1/version-2 cohort
149
- reports; it never sums overlapping retained samples into independent
150
- consistency. `registry-scaffold` produces a sanitised synthetic contributor
151
- fixture. `diff` preserves saved-Lookup output and also emits a bounded
152
- comparison ledger for explicitly paired Bulk sessions or domain portfolios.
153
- `verify-artifact --manifest ... --manifest-entry ...` separates exact retained
154
- byte identity from canonical-only or mismatched identity. `brief` and
155
- `case-pack` create bounded local handoffs, `monitor-once` runs one explicit
156
- control review rather than a daemon, and `workflow-run` executes only installed
157
- fixed-recipe steps with per-run network approval and analyst-selection pauses.
158
- The current schema-26 JSON produced by `export` is accepted directly by
159
- `verify-artifact` and reported as `structure_valid`. It has no embedded checksum
160
- or signature, so exact retained bytes require a verified investigation manifest
161
- entry. The export can contain a bounded privacy-projected registry RDAP
162
- publication, normalised WHOIS values, and bounded contacts and should be
163
- reviewed before sharing. Request and response headers, cookies, session and
164
- credential fields, and credential-bearing or query-bearing URLs are excluded.
165
- Schema 25 remains readable through its historical wrapper contract: retained
166
- diagnostics are authoritative, unavailable wrapper data is suppressed during
167
- replay, and other contradictions fail closed. Verification and browser replay share a 5 MiB,
168
- 20,000-entry, 24-level portable boundary.
169
-
170
- Lookup can also export a version-1 `whoisleuth.lookup-claim-passport` for one
171
- selected readiness statement. `verify-artifact` checks its strict bounded
172
- structure and sorted-json-v2 digest; `interchange-report` describes the exact
173
- source-state fields it preserves and the raw payload, contact, page-value,
174
- request-path, credential, browser-store, and signer-authentication fields it
175
- excludes.
176
-
177
- Current `case-pack` output keeps its version-1 envelope. Trusted and internal
178
- audiences preserve exact analyst-selected Case-to-Brand Profile identifiers;
179
- public output clears them from cases and embedded reports and discloses the
180
- bounded `brandProfileReferencesOmitted` count. Verification also enforces the
181
- actual audience projection after digest validation, rejects re-signed
182
- sensitive-field leaks at expected or unexpected nested paths, and binds each
183
- report to its canonical top-level Case and exact manifest counts. Current
184
- schema-12 input must survive bounded normalisation exactly, provide safe unique
185
- Case identities and a valid unique at-most-eight identifier list, and use
186
- report v8. Schema 11 uses report v7; valid collections through schema 10 remain
187
- compatible with supported reports through v6 and without later branch,
188
- reference, or omission-count fields.
189
-
190
- Bulk also supports fixed-column CSV, domain-only or exact-query output,
191
- registered, inconclusive, and hard-failure output filters, and bounded A, AAAA,
192
- NS, and MX summaries. Discover
193
- supports local custom dictionaries and private snapshot comparison suitable
194
- for an operator-managed scheduler. The separate networked `discover-scan`
195
- command can collect a deterministic bounded subset in chunks, retain mutation
196
- provenance, apply a non-destructive analyst allowlist, group exact shared DNS
197
- observations, and compare a private component-aware material-change snapshot.
198
- The full allowlist is not emitted, but matching scanned candidates are labelled
199
- as suppressed so the review decision remains explainable. Saved Deep
200
- lookups can be compared across static page, parse5-tokenized DOM structure,
201
- favicon, technology, and TLS evidence, while saved Bulk output can be reviewed
202
- for passive MX, null MX, SPF, DMARC, and shared-provider context.
203
-
204
- See `docs/cli.md` in the package for installation and first use, and
205
- `docs/cli-reference.md` for the complete command, privacy, output, and
206
- evidence-contract reference. Report suspected vulnerabilities through the
207
- private channel described in `SECURITY.md`.
102
+ The package includes `docs/cli.md` for usage and `docs/cli-reference.md` for
103
+ compatibility, privacy and evidence contracts. Report suspected vulnerabilities
104
+ through the private channel in `SECURITY.md`.
208
105
 
209
106
  Copyright 2026 slicedearth. Licensed under AGPL-3.0-only.
package/SECURITY.md CHANGED
@@ -26,9 +26,12 @@ weakness can use the public issue tracker.
26
26
 
27
27
  Reports are assessed against WHOISleuth's documented trust boundaries,
28
28
  including its local-first workspace, bounded network collection, shared
29
- deployment password, and optional hosted monitoring. A source outage, partial
30
- registry response, or heuristic disagreement is not by itself a security
31
- vulnerability, but unsafe handling of those states may be.
29
+ deployment password, and optional hosted monitoring. The accepted
30
+ [product boundary](https://github.com/slicedearth/whoisleuth/blob/main/docs/product-boundary.md) and
31
+ [threat model](https://github.com/slicedearth/whoisleuth/blob/main/docs/threat-model.md) describe those boundaries, principal abuse
32
+ cases, controls, residual risks, and deliberate non-goals. A source outage,
33
+ partial registry response, or heuristic disagreement is not by itself a
34
+ security vulnerability, but unsafe handling of those states may be.
32
35
 
33
36
  Confirmed reports will be handled through a private advisory until a fix and
34
37
  coordinated disclosure are ready. No bounty or guaranteed response time is
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import EXIT_CODES from "../cli/exit-codes.mjs";
4
+ import { metaActionDefinition } from "../cli/command-reference.mjs";
4
5
  for (const stream of [process.stdout, process.stderr]) {
5
6
  stream.on('error', (error) => {
6
7
  if (error.code === 'EPIPE')
@@ -9,7 +10,7 @@ for (const stream of [process.stdout, process.stderr]) {
9
10
  });
10
11
  }
11
12
  const argv = process.argv.slice(2);
12
- if (argv.length === 1 && (argv[0] === '--version' || argv[0] === '-V')) {
13
+ if (argv.length === 1 && metaActionDefinition('version').aliases.includes(argv[0] ?? '')) {
13
14
  const require = createRequire(import.meta.url);
14
15
  const metadata = require('../package.json');
15
16
  if (typeof metadata.version !== 'string' || !metadata.version) {
@@ -1,10 +1,12 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { createHash } from 'node:crypto';
3
3
  import { domainToASCII } from 'node:url';
4
- import { decryptWorkspaceArchive, isEncryptedWorkspaceArchive, } from "../frontend/src/lib/analysis/workspace-archive-crypto.js";
5
- import { WORKSPACE_ARCHIVE_SCHEMA, readWorkspaceArchive, } from "../frontend/src/lib/analysis/workspace-archive.js";
6
- import { canonicalArtifactJson } from "../frontend/src/lib/analysis/artifact-integrity.js";
4
+ import { scanBoundedJson } from "../lib/bounded-json.mjs";
5
+ import { decryptWorkspaceArchive, isEncryptedWorkspaceArchive, } from "../packages/workspace/workspace-archive-crypto.mjs";
6
+ import { WORKSPACE_ARCHIVE_SCHEMA, readWorkspaceArchive, } from "../packages/workspace/workspace-archive.mjs";
7
+ import { canonicalArtifactJson } from "../packages/evidence/artifact-integrity.mjs";
7
8
  import { MAX_OFFLINE_ARTIFACT_BYTES, verifyOfflineArtifact, } from "./artifact-verify.mjs";
9
+ import { safeTerminalValue } from "./formatters/terminal.mjs";
8
10
  export const ARCHIVE_INSPECTION_SCHEMA = 'whoisleuth.workspace-archive-inspection';
9
11
  export const ARCHIVE_INSPECTION_VERSION = 2;
10
12
  export const MAX_ARCHIVE_SEARCH_LENGTH = 253;
@@ -35,10 +37,11 @@ function parseJson(raw) {
35
37
  }
36
38
  let parsed;
37
39
  try {
40
+ scanBoundedJson(raw);
38
41
  parsed = JSON.parse(raw);
39
42
  }
40
43
  catch {
41
- throw new TypeError('Archive input must be valid JSON.');
44
+ throw new TypeError('Archive input must be valid bounded JSON without duplicate keys.');
42
45
  }
43
46
  const value = record(parsed);
44
47
  if (!value)
@@ -96,8 +99,12 @@ function searchSection(section, value, search, reveal, results, state) {
96
99
  const stack = [{ value, depth: 0 }];
97
100
  while (stack.length && results.length < MAX_ARCHIVE_SEARCH_MATCHES) {
98
101
  const current = stack.pop();
99
- if (!current || current.depth > 16)
102
+ if (!current)
100
103
  continue;
104
+ if (current.depth > 16) {
105
+ state.truncated = true;
106
+ continue;
107
+ }
101
108
  if (state.visited >= MAX_ARCHIVE_SEARCH_NODES) {
102
109
  state.truncated = true;
103
110
  return;
@@ -105,7 +112,10 @@ function searchSection(section, value, search, reveal, results, state) {
105
112
  state.visited += 1;
106
113
  const object = record(current.value);
107
114
  if (object) {
108
- for (const [field, child] of Object.entries(object).slice(0, 300)) {
115
+ const entries = Object.entries(object);
116
+ if (entries.length > 300)
117
+ state.truncated = true;
118
+ for (const [field, child] of entries.slice(0, 300)) {
109
119
  if (SEARCHABLE_FIELDS.has(field) && typeof child === 'string') {
110
120
  const normalized = canonicalSearchValue(child);
111
121
  if (normalized === search) {
@@ -116,7 +126,7 @@ function searchSection(section, value, search, reveal, results, state) {
116
126
  ...(reveal ? { value: child.slice(0, MAX_ARCHIVE_SEARCH_LENGTH) } : {}),
117
127
  }));
118
128
  if (results.length >= MAX_ARCHIVE_SEARCH_MATCHES) {
119
- state.truncated = stack.length > 0;
129
+ state.truncated = true;
120
130
  return;
121
131
  }
122
132
  }
@@ -126,6 +136,8 @@ function searchSection(section, value, search, reveal, results, state) {
126
136
  }
127
137
  }
128
138
  else if (Array.isArray(current.value)) {
139
+ if (current.value.length > 5_000)
140
+ state.truncated = true;
129
141
  for (const child of current.value.slice(0, 5_000)) {
130
142
  if (child && typeof child === 'object')
131
143
  stack.push({ value: child, depth: current.depth + 1 });
@@ -165,6 +177,9 @@ export async function inspectWorkspaceArchive(raw, options = {}) {
165
177
  }
166
178
  }
167
179
  if (options.requireMatch === true && search && results.length === 0) {
180
+ if (searchState.truncated) {
181
+ throw new TypeError('Archive search reached a configured bound before an exact canonical match could be established.');
182
+ }
168
183
  throw new TypeError('Archive search found no exact canonical match.');
169
184
  }
170
185
  return Object.freeze({
@@ -207,6 +222,9 @@ export async function inspectWorkspaceArchive(raw, options = {}) {
207
222
  ...(reveal
208
223
  ? ['Search values were revealed only because the operator supplied --reveal; handle the output as sensitive evidence.']
209
224
  : ['Search results contain field names and digests only. A digest does not authenticate or establish the accuracy of a retained value.']),
225
+ ...(searchState.truncated
226
+ ? ['Archive search reached a configured traversal or result boundary; zero retained matches is inconclusive.']
227
+ : []),
210
228
  ]),
211
229
  });
212
230
  }
@@ -219,15 +237,15 @@ export function formatArchiveInspection(report) {
219
237
  `Content digest: ${report.summary.contentDigestSha256}`,
220
238
  ];
221
239
  for (const section of report.sections) {
222
- lines.push(`${section.label}: ${section.recordCount} records · ${section.status} · ${section.bytes} bytes`);
240
+ lines.push(`${safeTerminalValue(section.label)}: ${section.recordCount} records · ${safeTerminalValue(section.status)} · ${section.bytes} bytes`);
223
241
  }
224
242
  if (report.search.requested) {
225
243
  lines.push(`Search matches: ${report.search.matchCount}${report.search.truncated ? ' (truncated)' : ''}`);
226
244
  for (const match of report.search.results) {
227
- lines.push(`${match.section}.${match.field}: ${match.value ?? `sha256:${match.valueDigestSha256}`}`);
245
+ lines.push(`${safeTerminalValue(match.section)}.${safeTerminalValue(match.field)}: ${safeTerminalValue(match.value ?? `sha256:${match.valueDigestSha256}`)}`);
228
246
  }
229
247
  }
230
248
  for (const limitation of report.limitations)
231
- lines.push(`Limitation: ${limitation}`);
249
+ lines.push(`Limitation: ${safeTerminalValue(limitation)}`);
232
250
  return `${lines.join('\n')}\n`;
233
251
  }