@slicedearth/whoisleuth-cli 1.45.0 → 2.0.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 (268) hide show
  1. package/DISCLOSURE +7 -3
  2. package/NOTICE +5 -4
  3. package/README.md +74 -124
  4. package/SECURITY.md +6 -3
  5. package/bin/whoisleuth.mjs +7 -4
  6. package/cli/archive-inspect.mjs +28 -10
  7. package/cli/arguments.mjs +450 -173
  8. package/cli/artifact-structure.mjs +2507 -0
  9. package/cli/artifact-verify.mjs +286 -64
  10. package/cli/bounded-json.mjs +1 -0
  11. package/cli/bulk-checkpoint.mjs +35 -23
  12. package/cli/bulk-command-runner.mjs +2 -5
  13. package/cli/bulk.mjs +9 -2
  14. package/cli/case-pack.mjs +435 -46
  15. package/cli/ci-report.mjs +6 -1
  16. package/cli/collection-preflight.mjs +2 -2
  17. package/cli/command-catalogue.mjs +12 -3
  18. package/cli/command-reference.mjs +948 -106
  19. package/cli/completion.mjs +504 -218
  20. package/cli/config-profile.mjs +13 -3
  21. package/cli/ct-event-intake.mjs +32 -2
  22. package/cli/discover.mjs +7 -1
  23. package/cli/discovery-observation-snapshot.mjs +24 -28
  24. package/cli/discovery-scan-command-runner.mjs +13 -14
  25. package/cli/discovery-scan.mjs +17 -3
  26. package/cli/discovery-snapshot.mjs +15 -7
  27. package/cli/discovery-workflow.mjs +2 -3
  28. package/cli/doctor.mjs +2 -2
  29. package/cli/domain-control-monitor.mjs +215 -36
  30. package/cli/domain-control-observations.mjs +34 -17
  31. package/cli/errors.mjs +8 -3
  32. package/cli/evidence-command-runner.mjs +17 -11
  33. package/cli/evidence-signing.mjs +68 -50
  34. package/cli/export-evidence.mjs +11 -4
  35. package/cli/external-observation-mapping.mjs +3 -3
  36. package/cli/formatters/html.mjs +3 -3
  37. package/cli/formatters/json.mjs +20 -32
  38. package/cli/formatters/terminal.mjs +586 -22
  39. package/cli/input.mjs +62 -0
  40. package/cli/interactive-launcher.mjs +226 -0
  41. package/cli/interchange-report.mjs +226 -0
  42. package/cli/investigation-manifest.mjs +16 -17
  43. package/cli/investigation-plan.mjs +167 -16
  44. package/cli/investigation-run.mjs +80 -24
  45. package/cli/local-mmdb-review.mjs +45 -14
  46. package/cli/lookalike-calibration.mjs +3 -1
  47. package/cli/lookup-brief.mjs +13 -8
  48. package/cli/lookup-browser.mjs +844 -0
  49. package/cli/lookup-command-runner.mjs +96 -33
  50. package/cli/lookup-diff.mjs +11 -2
  51. package/cli/lookup-plan.mjs +1 -0
  52. package/cli/mail-review.mjs +206 -94
  53. package/cli/manual.mjs +5 -5
  54. package/cli/network-command-runner.mjs +78 -0
  55. package/cli/offline-evidence-review.mjs +45 -14
  56. package/cli/open-asset-model-bridge.mjs +1 -1
  57. package/cli/output-file.mjs +70 -14
  58. package/cli/page-compare.mjs +18 -10
  59. package/cli/progress-events.mjs +2 -1
  60. package/cli/progress.mjs +3 -3
  61. package/cli/registry-cohort.mjs +343 -61
  62. package/cli/registry-doctor.mjs +67 -28
  63. package/cli/registry-support.mjs +2 -1
  64. package/cli/retained-artifact-diff.mjs +375 -0
  65. package/cli/risk-calibration.mjs +89 -40
  66. package/cli/runner.mjs +322 -246
  67. package/cli/saved-lookup.mjs +439 -49
  68. package/cli/sharing-review.mjs +18 -7
  69. package/cli/source-reliability.mjs +75 -36
  70. package/cli/terminal-presentation.mjs +43 -18
  71. package/docs/cli-reference.md +125 -1334
  72. package/docs/cli.md +87 -236
  73. package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -0
  74. package/frontend/src/lib/analysis/domain-control-records.js +3 -0
  75. package/lib/application-version.mjs +3 -0
  76. package/lib/availability.mjs +72 -52
  77. package/lib/bounded-contract-normalizers.mjs +2 -4
  78. package/lib/bounded-file.mjs +91 -4
  79. package/lib/bounded-json.mjs +287 -0
  80. package/lib/browser-library-profile.mjs +1 -1
  81. package/lib/certificate-extension-profile.mjs +2 -1
  82. package/lib/classify.mjs +56 -5
  83. package/lib/client-behavior-profile.mjs +1 -1
  84. package/lib/credential-surface-profile.mjs +1 -1
  85. package/lib/cryptographic-assurance.mjs +173 -0
  86. package/lib/ct-query.mjs +3 -3
  87. package/lib/ct-search.mjs +120 -64
  88. package/lib/distributed-operation-budget.mjs +4 -2
  89. package/lib/dns-delegation-health.mjs +9 -6
  90. package/lib/dns-intelligence.mjs +4 -3
  91. package/lib/dns-resolver-selection.mjs +6 -4
  92. package/lib/dnssec-chain-validation.mjs +1052 -0
  93. package/lib/dnssec-evidence-validation.mjs +23 -10
  94. package/lib/domain-assurance.mjs +12 -12
  95. package/lib/domain-change-packet.mjs +7 -5
  96. package/lib/domain-control-flight-recorder.mjs +442 -78
  97. package/lib/domain-control-manifest.mjs +314 -171
  98. package/lib/domain-posture-analysis.mjs +5 -2
  99. package/lib/domain-posture.mjs +7 -4
  100. package/lib/encrypted-dns-contract.mjs +2 -3
  101. package/lib/evidence-export.mjs +1243 -88
  102. package/lib/evidence-report-markdown.mjs +3 -0
  103. package/lib/evidence-report.mjs +5 -0
  104. package/lib/external-intelligence-risk.mjs +26 -10
  105. package/lib/favicon.mjs +8 -5
  106. package/lib/feature-policy.mjs +1 -1
  107. package/lib/homepage-metadata-contract.mjs +290 -0
  108. package/lib/hostname.mjs +1 -1
  109. package/lib/html-signals.mjs +89 -5
  110. package/lib/http-evidence-bounds.mjs +3 -0
  111. package/lib/http-intelligence.mjs +332 -6
  112. package/lib/interchange-fidelity-registry.mjs +124 -0
  113. package/lib/local-geoip-evidence.mjs +2 -1
  114. package/lib/lookup-cache.mjs +6 -4
  115. package/lib/lookup-network-evidence-bounds.mjs +9 -0
  116. package/lib/lookup.mjs +27 -4
  117. package/lib/observed-network-context.mjs +1 -1
  118. package/lib/outbound-identity.mjs +2 -3
  119. package/lib/page-fingerprints.mjs +2 -2
  120. package/lib/page-role-profile.mjs +1 -1
  121. package/lib/perceptual-hash.mjs +12 -4
  122. package/lib/portable-generator.mjs +29 -0
  123. package/lib/provider-policy-admission.mjs +11 -4
  124. package/lib/rdap-bootstrap.mjs +76 -16
  125. package/lib/rdap-client.mjs +20 -12
  126. package/lib/rdap-normalization.mjs +324 -80
  127. package/lib/rdap-registrar.mjs +5 -1
  128. package/lib/rdap-transport.mjs +2 -2
  129. package/lib/rdap-validation.mjs +65 -14
  130. package/lib/rdap.mjs +2 -2
  131. package/lib/registrable-domain.mjs +36 -0
  132. package/lib/registry-capabilities.mjs +3 -0
  133. package/lib/registry-capability-catalogue.mjs +12 -2
  134. package/lib/registry-comparison.mjs +28 -11
  135. package/lib/registry-insights.mjs +7 -0
  136. package/lib/response-policy.mjs +27 -4
  137. package/lib/risk-calibration-summary.mjs +340 -0
  138. package/lib/risk-scoring.mjs +4 -3
  139. package/lib/rpki-evidence.mjs +9 -8
  140. package/lib/safe-fetch.mjs +121 -50
  141. package/lib/security-txt.mjs +27 -4
  142. package/lib/service-binding-dns.mjs +6 -1
  143. package/lib/smtp-transport-review.mjs +787 -0
  144. package/lib/sslbl-certificates.generated.mjs +136 -134
  145. package/lib/sslbl-intelligence.mjs +6 -8
  146. package/lib/static-html-analysis.mjs +301 -4
  147. package/lib/structured-data-identity.mjs +51 -19
  148. package/lib/threat-intelligence-fixtures.mjs +2 -4
  149. package/lib/threat-intelligence-manifests.mjs +2 -7
  150. package/lib/threat-intelligence-runtime.mjs +5 -15
  151. package/lib/threat-intelligence-types.mjs +21 -1
  152. package/lib/threatfox-intelligence.mjs +7 -3
  153. package/lib/tls-intelligence.mjs +15 -9
  154. package/lib/tlsa-evidence.mjs +37 -7
  155. package/lib/urlhaus-intelligence.mjs +4 -2
  156. package/lib/urlscan-intelligence.mjs +6 -5
  157. package/lib/website-security-posture.mjs +1 -1
  158. package/lib/website-technology.mjs +1 -1
  159. package/lib/whois-authority.mjs +43 -8
  160. package/lib/whois-chain.mjs +25 -3
  161. package/lib/whois-normalization.mjs +3 -1
  162. package/lib/whois-transport.mjs +12 -3
  163. package/lib/zone-intent-review.mjs +1 -1
  164. package/package.json +12 -4
  165. package/packages/cases/case-brand-profile-references.mjs +85 -0
  166. package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +43 -14
  167. package/packages/cases/case-investigation-branch-model.mjs +132 -0
  168. package/packages/cases/case-migration-model.mjs +476 -0
  169. package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
  170. package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -19
  171. package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
  172. package/packages/cases/case-record-model.mjs +9 -0
  173. package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +106 -33
  174. package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +143 -16
  175. package/packages/cases/case-response-model.mjs +1604 -0
  176. package/packages/cases/case-response-packet.mjs +1312 -0
  177. package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
  178. package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
  179. package/packages/cases/opaque-reference-id.mjs +9 -0
  180. package/packages/comparison/comparison-ledger-bulk.mjs +366 -0
  181. package/packages/comparison/comparison-ledger-contract.mjs +241 -0
  182. package/packages/comparison/comparison-ledger-json.mjs +80 -0
  183. package/packages/comparison/comparison-ledger-serialization.mjs +52 -0
  184. package/packages/comparison/favicon-similarity.mjs +78 -0
  185. package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
  186. package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
  187. package/packages/contracts/analyst-review-state-contract.mjs +61 -0
  188. package/packages/contracts/analyst-review-state.mjs +50 -0
  189. package/packages/contracts/case-portability.mjs +1310 -0
  190. package/packages/contracts/cli-command-catalogue.mjs +2 -0
  191. package/packages/contracts/cli-lookup.mjs +394 -0
  192. package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
  193. package/packages/contracts/domain-control-manifest.mjs +1027 -0
  194. package/packages/contracts/domain-control-monitor.mjs +310 -0
  195. package/packages/contracts/domain-control-review.mjs +828 -0
  196. package/packages/contracts/domain-name.mjs +4 -0
  197. package/packages/contracts/external-observation-interchange.mjs +224 -0
  198. package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
  199. package/packages/contracts/investigation-portability.mjs +331 -0
  200. package/packages/contracts/offline-comparison.mjs +160 -0
  201. package/packages/contracts/risk-calibration.mjs +1288 -0
  202. package/packages/contracts/schema-compatibility.mjs +71 -0
  203. package/packages/contracts/schema-lifecycle.mjs +1702 -0
  204. package/packages/contracts/sslbl-snapshot.mjs +17 -0
  205. package/packages/contracts/workspace-portability.mjs +1347 -0
  206. package/packages/evidence/artifact-integrity.mjs +65 -0
  207. package/packages/evidence/decision-fact.mjs +735 -0
  208. package/packages/evidence/domain-control-runtime.mjs +547 -0
  209. package/packages/evidence/domain-name.mjs +41 -0
  210. package/packages/evidence/observation.mjs +384 -0
  211. package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +86 -20
  212. package/packages/interchange/retained-text.mjs +8 -0
  213. package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +11 -8
  214. package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +28 -9
  215. package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +67 -18
  216. package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +10 -9
  217. package/packages/investigation/bulk-review-policy.mjs +1 -0
  218. package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +50 -18
  219. package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
  220. package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
  221. package/packages/investigation/lookup-claim-passport.mjs +232 -0
  222. package/packages/investigation/lookup-claim-readiness.mjs +287 -0
  223. package/packages/investigation/lookup-investigation-brief.mjs +172 -0
  224. package/packages/monitoring/analyst-review-state.mjs +680 -0
  225. package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +97 -50
  226. package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +15 -15
  227. package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +179 -39
  228. package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +12 -5
  229. package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
  230. package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
  231. package/packages/workspace/hostile-input.mjs +172 -0
  232. package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +51 -46
  233. package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +15 -15
  234. package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
  235. package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
  236. package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
  237. package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
  238. package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
  239. package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +18 -12
  240. package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
  241. package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +195 -84
  242. package/third-party-notices.txt +112 -5834
  243. package/frontend/src/lib/analysis/artifact-integrity.js +0 -21
  244. package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
  245. package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
  246. package/frontend/src/lib/analysis/case-migration-model.js +0 -253
  247. package/frontend/src/lib/analysis/case-record-model.js +0 -7
  248. package/frontend/src/lib/analysis/case-response-model.js +0 -669
  249. package/frontend/src/lib/analysis/case-response-packet.js +0 -573
  250. package/frontend/src/lib/analysis/lookup-decision-support.js +0 -487
  251. package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
  252. package/frontend/src/lib/analysis/lookup-presentation.js +0 -58
  253. package/frontend/src/lib/analysis/lookup-response.js +0 -2
  254. package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
  255. package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
  256. package/frontend/src/lib/analysis/scoring.js +0 -55
  257. package/frontend/src/lib/analysis/utils.js +0 -232
  258. package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
  259. package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
  260. package/frontend/src/lib/analysis/visualization-matrix.js +0 -88
  261. package/frontend/src/lib/analysis/visualization-models.js +0 -369
  262. package/frontend/src/lib/analysis/visualization-time-series.js +0 -144
  263. package/lib/bounded-json-response.mjs +0 -157
  264. package/lib/lookup-response-contract.mjs +0 -346
  265. package/lib/observation.mjs +0 -114
  266. /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
  267. /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
  268. /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
package/DISCLOSURE CHANGED
@@ -8,9 +8,13 @@ can have both legitimate and potentially harmful uses.
8
8
  WHOISleuth is intended for authorised defensive investigation, abuse response,
9
9
  threat research, domain acquisition due diligence, asset review, and education.
10
10
  Network collection is initiated explicitly by the operator, uses bounded
11
- requests, and keeps source provenance and incomplete results visible. The tool
12
- does not exploit targets, bypass access controls, submit credentials, execute
13
- remote page scripts, or automate enforcement actions.
11
+ requests, and keeps source provenance and incomplete results visible. Hosted
12
+ and distributable collection does not exploit targets, bypass access controls,
13
+ submit credentials, execute remote page scripts, or automate enforcement
14
+ actions. The separate repo-local rendered-capture package is an explicit
15
+ authorised exception: it executes page JavaScript in a disposable,
16
+ network-bounded browser and writes only a local screenshot plus sanitised
17
+ digest and manifest artefacts for operator-controlled review and deletion.
14
18
 
15
19
  Operators are responsible for using WHOISleuth only on systems and data they
16
20
  are authorised to examine and in accordance with applicable laws, provider
package/NOTICE CHANGED
@@ -32,10 +32,11 @@ warning-lists are available under CC0 1.0 Universal or the BSD 2-Clause
32
32
  "Simplified" License. Source:
33
33
  https://github.com/MISP/misp-warninglists
34
34
 
35
- The exact locked production-dependency inventory and the licence documents
36
- distributed by those packages are retained in
37
- frontend/static/third-party-notices.txt. The same file is served by the static
38
- website at /third-party-notices.txt and is checked for lockfile drift in CI.
35
+ The exact locked production-dependency inventories and licence documents are
36
+ distributed as third-party-notices.txt. The website inventory is served at
37
+ /third-party-notices.txt; the installed CLI contains its own notice file for
38
+ only the CLI runtime dependency closure. Both are generated from the reviewed
39
+ lockfile, and the website inventory is checked for lockfile drift in CI.
39
40
 
40
41
  The generated Unicode confusable projection is derived from Unicode UTS #39
41
42
  data file confusables.txt, Unicode version 17.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,136 +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 domain-control domain-control-input.json --json
63
- node bin/whoisleuth.mts assurance domain-assurance-input.json --json
64
- 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
65
- node bin/whoisleuth.mts workflow-plan domain-triage example.test --json
66
- node bin/whoisleuth.mts diff first-lookup.json second-lookup.json --json
67
- node bin/whoisleuth.mts reconcile office.json mobile.json external.json --json
68
- node bin/whoisleuth.mts timeline first-observation.json second-observation.json latest-observation.json --json
69
- node bin/whoisleuth.mts registry-support example.test --json
70
- node bin/whoisleuth.mts registry-doctor lookup.json --json
71
- node bin/whoisleuth.mts registry-cohort saved-lookups.jsonl --json
72
- node bin/whoisleuth.mts brief lookup.json
73
- node bin/whoisleuth.mts case-pack cases.json --audience trusted --reviewed --json
74
- node bin/whoisleuth.mts monitor-once control-manifest.json --json
75
- node bin/whoisleuth.mts workflow-run domain-triage example.test --approve-network --json --output run.json
76
- node bin/whoisleuth.mts lookalike-calibrate reviewed-candidates.json --json
77
- node bin/whoisleuth.mts doctor
78
- node bin/whoisleuth.mts commands --json
79
- node bin/whoisleuth.mts completion zsh
80
- node bin/whoisleuth.mts completion powershell
81
- node bin/whoisleuth.mts manual | man -l -
98
+ node bin/whoisleuth.mts lookup example.test --plan --json
99
+ npm run cli:package:check
82
100
  ```
83
101
 
84
- Fast lookup is the default. Deep collection must be requested explicitly and
85
- can disclose the target to additional authoritative or first-party network
86
- sources. Missing, partial, rate-limited, and unsupported sources remain
87
- explicit and are never interpreted as evidence of safety or absence.
88
- `lookup --plan` classifies a target and explains the selected source families
89
- and disclosures without making a network request. `commands --json` exposes the
90
- installed command contracts for local wrappers without running collection.
91
- Focused command help and the generated manual label every operation as offline
92
- or networked and state its target, input, and concurrency boundaries. The
93
- packaged [dual-use disclosure](https://github.com/slicedearth/whoisleuth/blob/main/DISCLOSURE)
94
- defines the supported defensive use of those capabilities.
95
-
96
- Interactive output uses restrained semantic colour, width-aware wrapping, and
97
- stderr-only progress for slower collection. Redirected and machine-readable
98
- output stays free of ANSI and progress text. Lookup also provides `--summary`
99
- and `--verbose` terminal views without changing what is collected. `doctor` is
100
- offline unless `--network` is explicitly supplied, and completion scripts are
101
- printed without modifying shell configuration. Commands can use atomic private
102
- `--output` files, Lookup can emit strict automation exits and target-free
103
- versioned progress events, and Bulk can resume an exact validated compact
104
- checkpoint. Checkpoint schema 2 preserves each current row's observation time;
105
- legacy schema-1 rows remain readable with unknown observation time rather than
106
- being restamped on resume. Bulk machine output schema 3 distinguishes current
107
- and resumed rows. A checkpoint write failure preserves completed output and returns
108
- the partial-result exit code. Ctrl-C suppresses partial final output and
109
- returns exit code 130. SIGTERM uses the same graceful cleanup path and returns
110
- 143; a second interrupt performs best-effort temporary-file
111
- cleanup before exiting immediately.
112
- Strict local profiles can supply only safe presentation, Fast-mode,
113
- concurrency, and observer defaults; command-line options override them.
114
- Supported automation commands can emit JUnit or SARIF and apply explicit
115
- failure policies without changing their collection result.
116
-
117
- The offline `timeline` command orders 2 to 20 saved Lookup documents for one
118
- domain and compares each adjacent observation. It retains observation times and
119
- normalised field states but no input filenames or raw registry payloads.
120
- Observed differences remain separate from missing, unavailable, and changed
121
- collection conditions.
122
-
123
- Optional bounded observer and vantage labels let analysts distinguish saved
124
- collection contexts without changing collection. `reconcile` compares 2 to 5
125
- same-domain observations while keeping agreement, disagreement, and
126
- non-comparable evidence separate; the labels are context, not proof of
127
- independence. `registry-doctor`, `assurance`, `sharing-review`, and
128
- `lookalike-calibrate` add offline compatibility, change/recovery, sharing, and
129
- review-yield workflows without contacting the hosted application.
130
- `registry-cohort` aggregates target-free publication-quality counts from saved
131
- Lookups, while `registry-scaffold` produces a sanitised synthetic contributor
132
- fixture. `brief` and `case-pack` create bounded local handoffs, `monitor-once`
133
- runs one explicit control review rather than a daemon, and `workflow-run`
134
- executes only installed fixed-recipe steps with per-run network approval and
135
- analyst-selection pauses.
136
-
137
- Bulk also supports fixed-column CSV, domain-only or exact-query output,
138
- registered, inconclusive, and hard-failure output filters, and bounded A, AAAA,
139
- NS, and MX summaries. Discover
140
- supports local custom dictionaries and private snapshot comparison suitable
141
- for an operator-managed scheduler. The separate networked `discover-scan`
142
- command can collect a deterministic bounded subset in chunks, retain mutation
143
- provenance, apply a non-destructive analyst allowlist, group exact shared DNS
144
- observations, and compare a private component-aware material-change snapshot.
145
- The full allowlist is not emitted, but matching scanned candidates are labelled
146
- as suppressed so the review decision remains explainable. Saved Deep
147
- lookups can be compared across static page, parse5-tokenized DOM structure,
148
- favicon, technology, and TLS evidence, while saved Bulk output can be reviewed
149
- for passive MX, null MX, SPF, DMARC, and shared-provider context.
150
-
151
- See `docs/cli.md` in the package for installation and first use, and
152
- `docs/cli-reference.md` for the complete command, privacy, output, and
153
- evidence-contract reference. Report suspected vulnerabilities through the
154
- 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`.
155
105
 
156
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) {
@@ -28,12 +29,14 @@ else {
28
29
  import("../cli/runner.mjs"),
29
30
  ]);
30
31
  let resolvedArgv;
32
+ let profileResolutionFailed = false;
31
33
  try {
32
34
  resolvedArgv = await configModule.resolveCliProfileArguments(argv);
33
35
  }
34
36
  catch (error) {
35
37
  process.stderr.write(`Usage error: ${errorsModule.boundedCliErrorMessage(error, 'Invalid CLI profile')}\n`);
36
38
  process.exitCode = EXIT_CODES.USAGE;
39
+ profileResolutionFailed = true;
37
40
  resolvedArgv = [];
38
41
  }
39
42
  const cancellation = new AbortController();
@@ -54,9 +57,9 @@ else {
54
57
  const onSigterm = () => interrupt('SIGTERM');
55
58
  process.on('SIGINT', onSigint);
56
59
  process.on('SIGTERM', onSigterm);
57
- const execution = resolvedArgv.length
58
- ? runnerModule.runCli(resolvedArgv, { signal: cancellation.signal })
59
- : Promise.resolve(process.exitCode || EXIT_CODES.USAGE);
60
+ const execution = profileResolutionFailed
61
+ ? Promise.resolve(process.exitCode || EXIT_CODES.USAGE)
62
+ : runnerModule.runCli(resolvedArgv, { signal: cancellation.signal });
60
63
  execution.then((code) => {
61
64
  if (code === 130) {
62
65
  process.removeListener('SIGINT', onSigint);
@@ -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
  }