@slicedearth/whoisleuth-cli 1.47.4 → 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 (235) 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/provider-policy-admission.mjs +3 -1
  100. package/lib/rdap-bootstrap.mjs +76 -16
  101. package/lib/rdap-client.mjs +20 -12
  102. package/lib/rdap-normalization.mjs +306 -75
  103. package/lib/rdap-transport.mjs +2 -2
  104. package/lib/rdap-validation.mjs +65 -14
  105. package/lib/rdap.mjs +2 -2
  106. package/lib/registrable-domain.mjs +36 -0
  107. package/lib/registry-insights.mjs +7 -0
  108. package/lib/response-policy.mjs +27 -4
  109. package/lib/risk-calibration-summary.mjs +35 -33
  110. package/lib/risk-scoring.mjs +4 -3
  111. package/lib/safe-fetch.mjs +62 -40
  112. package/lib/security-txt.mjs +1 -1
  113. package/lib/smtp-transport-review.mjs +8 -5
  114. package/lib/sslbl-intelligence.mjs +6 -8
  115. package/lib/static-html-analysis.mjs +301 -4
  116. package/lib/structured-data-identity.mjs +51 -19
  117. package/lib/threat-intelligence-fixtures.mjs +2 -4
  118. package/lib/threat-intelligence-manifests.mjs +2 -7
  119. package/lib/threat-intelligence-runtime.mjs +3 -5
  120. package/lib/threat-intelligence-types.mjs +3 -0
  121. package/lib/threatfox-intelligence.mjs +7 -3
  122. package/lib/tls-intelligence.mjs +4 -4
  123. package/lib/urlhaus-intelligence.mjs +4 -2
  124. package/lib/urlscan-intelligence.mjs +6 -5
  125. package/lib/website-security-posture.mjs +1 -1
  126. package/lib/website-technology.mjs +1 -1
  127. package/lib/whois-authority.mjs +43 -8
  128. package/lib/whois-chain.mjs +25 -3
  129. package/lib/whois-normalization.mjs +3 -1
  130. package/lib/whois-transport.mjs +12 -3
  131. package/lib/zone-intent-review.mjs +1 -1
  132. package/package.json +12 -4
  133. package/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
  134. package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
  135. package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
  136. package/packages/cases/case-migration-model.mjs +476 -0
  137. package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
  138. package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -21
  139. package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
  140. package/packages/cases/case-record-model.mjs +9 -0
  141. package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +72 -29
  142. package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
  143. package/packages/cases/case-response-model.mjs +1604 -0
  144. package/packages/cases/case-response-packet.mjs +1312 -0
  145. package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
  146. package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
  147. package/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
  148. package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
  149. package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
  150. package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -0
  151. package/packages/comparison/favicon-similarity.mjs +78 -0
  152. package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
  153. package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
  154. package/packages/contracts/analyst-review-state-contract.mjs +61 -0
  155. package/packages/contracts/analyst-review-state.mjs +50 -0
  156. package/packages/contracts/case-portability.mjs +1310 -0
  157. package/packages/contracts/cli-command-catalogue.mjs +2 -0
  158. package/packages/contracts/cli-lookup.mjs +394 -0
  159. package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
  160. package/packages/contracts/domain-control-manifest.mjs +1027 -0
  161. package/packages/contracts/domain-control-monitor.mjs +310 -0
  162. package/packages/contracts/domain-control-review.mjs +828 -0
  163. package/packages/contracts/domain-name.mjs +4 -0
  164. package/packages/contracts/external-observation-interchange.mjs +224 -0
  165. package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
  166. package/packages/contracts/investigation-portability.mjs +331 -0
  167. package/packages/contracts/offline-comparison.mjs +160 -0
  168. package/packages/contracts/risk-calibration.mjs +1288 -0
  169. package/packages/contracts/schema-compatibility.mjs +71 -0
  170. package/packages/contracts/schema-lifecycle.mjs +1702 -0
  171. package/packages/contracts/sslbl-snapshot.mjs +17 -0
  172. package/packages/contracts/workspace-portability.mjs +1347 -0
  173. package/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
  174. package/packages/evidence/decision-fact.mjs +735 -0
  175. package/packages/evidence/domain-control-runtime.mjs +547 -0
  176. package/packages/evidence/domain-name.mjs +41 -0
  177. package/packages/evidence/observation.mjs +384 -0
  178. package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +24 -22
  179. package/packages/interchange/retained-text.mjs +8 -0
  180. package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
  181. package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
  182. package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
  183. package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
  184. package/packages/investigation/bulk-review-policy.mjs +1 -0
  185. package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
  186. package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
  187. package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
  188. package/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
  189. package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
  190. package/packages/investigation/lookup-investigation-brief.mjs +172 -0
  191. package/packages/monitoring/analyst-review-state.mjs +680 -0
  192. package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +70 -47
  193. package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
  194. package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
  195. package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -5
  196. package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
  197. package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
  198. package/packages/workspace/hostile-input.mjs +172 -0
  199. package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +8 -45
  200. package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
  201. package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
  202. package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
  203. package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
  204. package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
  205. package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
  206. package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +13 -13
  207. package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
  208. package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +110 -89
  209. package/third-party-notices.txt +14 -14
  210. package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
  211. package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
  212. package/frontend/src/lib/analysis/case-migration-model.js +0 -305
  213. package/frontend/src/lib/analysis/case-record-model.js +0 -9
  214. package/frontend/src/lib/analysis/case-response-model.js +0 -728
  215. package/frontend/src/lib/analysis/case-response-packet.js +0 -583
  216. package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
  217. package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
  218. package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
  219. package/frontend/src/lib/analysis/lookup-response.js +0 -2
  220. package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
  221. package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
  222. package/frontend/src/lib/analysis/scoring.js +0 -55
  223. package/frontend/src/lib/analysis/utils.js +0 -255
  224. package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
  225. package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
  226. package/frontend/src/lib/analysis/visualization-matrix.js +0 -131
  227. package/frontend/src/lib/analysis/visualization-models.js +0 -338
  228. package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
  229. package/lib/bounded-json-response.mjs +0 -170
  230. package/lib/lookup-response-contract.mjs +0 -1034
  231. package/lib/observation.mjs +0 -115
  232. /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
  233. /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
  234. /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
  235. /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
package/docs/cli.md CHANGED
@@ -1,16 +1,18 @@
1
1
  # WHOISleuth CLI guide
2
2
 
3
- The first-party CLI runs locally and uses the same bounded classification,
4
- collection, and analysis modules as the web application. It does not call the
5
- hosted WHOISleuth deployment. Use this guide for installation, first commands,
6
- collection boundaries, output, and automation behaviour. The
7
- [complete CLI reference](cli-reference.md) documents every command family and
8
- artefact contract.
3
+ The first-party CLI runs on the operator's machine and does not call the hosted
4
+ WHOISleuth deployment. Use this guide for installation, common commands,
5
+ collection boundaries and output. Installed `whoisleuth --help`, focused
6
+ `--help` and `whoisleuth commands` output are the authority for that installed
7
+ version. The [CLI reference](cli-reference.md) covers durable command and
8
+ artefact contracts.
9
+
10
+ The generated [privacy and data-flow catalogue](https://github.com/slicedearth/whoisleuth/blob/main/docs/privacy-data-flow-catalogue.md)
11
+ lists the network, recipient, retention and export boundary for every command.
9
12
 
10
13
  ## Installation
11
14
 
12
- Public releases require Node.js 24 or later and install the `whoisleuth`
13
- command:
15
+ Public releases require Node.js 24 or later:
14
16
 
15
17
  ```bash
16
18
  npm exec --yes --ignore-scripts --package=@slicedearth/whoisleuth-cli -- whoisleuth --help
@@ -26,296 +28,126 @@ whoisleuth --version
26
28
  whoisleuth doctor
27
29
  ```
28
30
 
29
- The scoped package and application share one semantic version. The package
30
- includes `LICENSE`, `NOTICE`, `TRADEMARKS.md`, this guide, and the complete
31
- reference. It requires no lifecycle scripts, so the examples disable them.
32
- An unqualified `npx whoisleuth` is not this project.
33
-
34
- From a repository checkout, replace `whoisleuth` with
35
- `node bin/whoisleuth.mts`. Maintainers can assemble, install, and smoke-test the
36
- exact compiled package closure without publishing it:
37
-
38
- ```bash
39
- npm run cli:package:check
40
- ```
31
+ The package and application share one semantic version. The package requires no
32
+ dependency lifecycle scripts. From a repository checkout, replace `whoisleuth`
33
+ with `node bin/whoisleuth.mts`; maintainers can verify the exact package closure
34
+ with `npm run cli:package:check`.
41
35
 
42
36
  ## First commands
43
37
 
44
- Inspect the installed capabilities before collection:
38
+ Inspect the installed command set and plan one Lookup before collecting:
45
39
 
46
40
  ```bash
47
41
  whoisleuth --help
48
- whoisleuth commands
42
+ whoisleuth commands --common
49
43
  whoisleuth doctor
50
- whoisleuth lookup example.com --plan --json
44
+ whoisleuth lookup example.test --plan --json
51
45
  ```
52
46
 
53
- Run a fast lookup or deliberately request the deeper profile:
47
+ Run Fast or Deep Lookup:
54
48
 
55
49
  ```bash
56
- whoisleuth lookup example.com
57
- whoisleuth lookup example.com --deep
58
- whoisleuth lookup example.com --deep --summary
59
- whoisleuth lookup example.com --deep --markdown --output lookup.md
50
+ whoisleuth example.test
51
+ whoisleuth lookup example.test --deep
52
+ whoisleuth lookup example.test --deep --summary
53
+ whoisleuth lookup example.test --deep --browse
54
+ whoisleuth lookup example.test --deep --markdown --output lookup.md
60
55
  ```
61
56
 
62
- Process an explicit bounded list, generate lookalike candidates, or inspect a
63
- saved result without sending it anywhere:
57
+ Process selected local input:
64
58
 
65
59
  ```bash
66
60
  whoisleuth bulk domains.txt --csv
67
- whoisleuth discover example.com --preset common --jsonl
61
+ whoisleuth discover example.test --preset common --jsonl
68
62
  whoisleuth verify-artifact lookup.json --json --strict-exit
69
- whoisleuth verify-artifact report.json --manifest manifest.json --manifest-entry artifact-2 --json --strict-exit
70
- whoisleuth interchange-report workspace.json --json
71
63
  whoisleuth compare lookup.json --json
72
- whoisleuth diff earlier-bulk.json later-bulk.json --left-session session-1 --right-session session-2 --json
73
64
  whoisleuth brief lookup.json
74
- whoisleuth registry-doctor lookup.json --json
75
- whoisleuth registry-cohort retained-cohorts.jsonl --json
76
65
  ```
77
66
 
78
- Only one query is accepted by `lookup`; multi-input processing belongs to
79
- `bulk`. Standard input to `lookup` is capped at 4 KiB and must contain one
80
- non-empty line.
67
+ One strict domain, IP address or ASN can occupy command position as Lookup
68
+ shorthand. URL-like or ambiguous input requires the explicit `lookup` command.
69
+ Only `bulk` accepts multiple targets.
81
70
 
82
- ## Collection profiles
71
+ ## Command groups
83
72
 
84
- Commands are classified as either networked or offline in focused help, the
85
- generated manual, and `commands --json`.
73
+ The installed registry groups commands under the same analyst vocabulary as the
74
+ application:
86
75
 
87
- | Profile | Boundary |
76
+ | Group | Common commands |
88
77
  | --- | --- |
89
- | Fast Lookup | Conservative default for domain triage. It does not run WHOIS or deep website and TLS collection. |
90
- | Deep Lookup | Explicitly requested bounded RDAP, WHOIS, DNS, website, TLS, registrar-RDAP, and observed-network work where applicable. |
91
- | Compact deep Bulk | Uses the shared Lookup orchestration but omits full-only response fields and collectors. |
92
- | Offline review | Reads only the named bounded artefact or analyst-authored input and makes no network request. |
93
- | Isolated authorised action | Runs only when its dedicated command and per-run acknowledgement flags are supplied; it is never added to Lookup, Bulk, monitoring, or recipes. |
94
-
95
- Networked commands run from the local machine and contact the sources declared
96
- by their profile. They do not use the hosted login or hosted usage controls;
97
- upstream services can see and rate-limit the local network address. The CLI
98
- does not implicitly enable the browser Console's optional external intelligence
99
- actions.
100
-
101
- `lookup --plan` performs classification and emits a versioned preflight without
102
- starting collection. It lists planned source families and disclosure targets,
103
- but cannot predict cache state, live configuration, redirects, referrals,
104
- source availability, or exact request count.
105
-
106
- `doctor` is offline by default. `doctor --network` adds three independent,
107
- bounded checks against fixed diagnostic infrastructure and retains only state,
108
- HTTP status, and a bounded explanation.
109
-
110
- ## Command map
111
-
112
- | Goal | Commands |
113
- | --- | --- |
114
- | Investigate a target | `lookup`, `http`, `tls`, `dnssec-validate`, `mail-transport`, `posture`, `registry-support` |
115
- | Review many targets | `bulk`, `discover`, `discover-scan`, `ct-search`, `ct-intake` |
116
- | Compare saved evidence | `compare`, `page-compare`, `diff`, `reconcile`, `timeline`, `mail-review` |
117
- | Review supplied evidence | `brief`, `review-evidence`, `registry-doctor`, `registry-cohort`, `source-report`, `sharing-review`, `map-observations` |
118
- | Plan and assure changes | `domain-control`, `monitor-once`, `assurance`, `change-packet`, `workflow-plan`, `workflow-run` |
119
- | Verify and package evidence | `case-pack`, `manifest`, `oam-export`, `verify-artifact`, `interchange-report`, `inspect-archive`, `sign-artifact`, `verify-signature`, `export` |
120
- | Calibrate offline | `risk-calibrate`, `lookalike-calibrate` |
121
- | Operate the CLI | `doctor`, `registry-scaffold`, `commands`, `completion`, `manual` |
78
+ | Investigate | `lookup`, `bulk`, `discover`, `ct-search`, `posture`, `http`, `tls`, `compare`, `brief` |
79
+ | Respond | `case-pack`, `change-packet`, `sharing-review`, `export` |
80
+ | Assure | `dnssec-validate`, `mail-transport`, `domain-control`, `assurance`, `workflow-plan`, `diff`, `inspect-archive`, `verify-artifact` |
81
+ | Utilities | `doctor`, `commands`, `completion`, `manual`, `registry-scaffold` |
122
82
 
123
- Use focused help for the exact arguments and collection ceiling:
83
+ Filter the canonical index without running the selected commands:
124
84
 
125
85
  ```bash
126
- whoisleuth lookup --help
127
- whoisleuth sharing-review --help
86
+ whoisleuth commands --group investigate
87
+ whoisleuth commands --group respond --mode offline
128
88
  whoisleuth commands --json
129
89
  whoisleuth manual | man -l -
130
90
  ```
131
91
 
132
- ## Output and automation
133
-
134
- Human-readable terminal output is the default. Interactive terminals use
135
- restrained semantic colour and transient progress on stderr. Redirected output,
136
- JSON, and JSONL contain no ANSI sequences or progress text. State remains
137
- explicit in text, so colour is never the only distinction.
138
-
139
- - `--json` and `--jsonl` provide bounded machine-readable documents.
140
- - `--summary` and `--verbose` change human detail without changing collection.
141
- - `--no-color` or `NO_COLOR` disables colour.
142
- - `WHOISLEUTH_NO_PROGRESS=1` disables the transient progress indicator.
143
- - `--events` keeps the final document on stdout and writes versioned JSONL
144
- lifecycle events to stderr.
145
- - `--strict-exit` treats failures in explicitly requested source families as a
146
- partial failure while keeping skipped or unsupported sources neutral.
147
- - `--fail-on` applies explicit `source-failure`, `inconclusive`, `danger`, or
148
- `material-drift` automation policy where the selected command supports it.
149
- - `--junit` emits bounded CI test cases for Lookup, Bulk, and one-shot control
150
- reviews; owned-domain Posture can emit `--sarif` only with
151
- `--owned-domain`.
152
-
153
- `--config <file> --profile <name>` applies a strict version-1 local profile.
154
- The default path is `$XDG_CONFIG_HOME/whoisleuth/config.json`. Profiles can set
155
- only presentation mode, Fast collection, bounded concurrency, and observer or
156
- vantage labels. They cannot add targets, enable Deep collection, select output
157
- paths, approve network work, or set failure policy. Explicit command options
158
- override defaults from the same option group.
159
-
160
- Every command accepts `--output <file>`. WHOISleuth buffers bounded output,
161
- creates a private temporary file beside the destination, syncs it, and publishes
162
- it atomically with mode `0600`. Existing destinations are refused unless
163
- `--force` is deliberately combined with `--output`. Failed or cancelled
164
- commands do not publish partial files. Output is capped at 32 MiB.
165
-
166
- ## Exit codes
167
-
168
- | Code | Meaning |
169
- | ---: | --- |
170
- | 0 | Command completed. Individual sources can still be partial or inconclusive; inspect diagnostics. |
171
- | 2 | Invalid command, option, input, or stdin shape. |
172
- | 3 | The requested lookup, collection, or comparison could not run. |
173
- | 4 | A bounded operation completed partially. |
174
- | 70 | Unexpected CLI bootstrap failure. |
175
- | 130 | The analyst cancelled the command; no partial final result was emitted. |
176
- | 143 | The process received SIGTERM; no partial final result was emitted. |
177
-
178
- ## Detailed command reference
179
-
180
- The headings below preserve the established guide anchors while moving their
181
- full contracts into the packaged reference.
182
-
183
- ### Registry capability coverage
184
-
185
- [Read the complete registry capability reference.](cli-reference.md#registry-capability-coverage)
186
-
187
- ### Offline Risk calibration
188
-
189
- [Read the calibration dataset and replay contract.](cli-reference.md#offline-risk-calibration)
190
-
191
- ### Lookalike review-yield calibration
192
-
193
- [Read the reviewed candidate calibration contract.](cli-reference.md#lookalike-review-yield-calibration)
194
-
195
- ### Offline artefact verification
196
-
197
- [Read the archive, saved Lookup, Lookup-evidence, and signed-artefact verification contract.](cli-reference.md#offline-artefact-verification)
198
-
199
- ### Privacy-safe source reliability report
200
-
201
- [Read the target-free source diagnostic contract.](cli-reference.md#privacy-safe-source-reliability-report)
202
-
203
- ### Workspace archive inspection
204
-
205
- [Read the redacted archive summary, stable content-digest, transfer-check, and
206
- search contract.](cli-reference.md#workspace-archive-inspection)
207
-
208
- ### Optional evidence-package signing
209
-
210
- [Read the local signing and verification contract.](cli-reference.md#optional-evidence-package-signing)
211
-
212
- ### Reproducible investigation manifest
213
-
214
- [Read the ordered, path-free artefact digest contract.](cli-reference.md#reproducible-investigation-manifest)
215
-
216
- ### Bulk lookup
217
-
218
- [Read Bulk bounds, filters, formats, checkpoints, and cancellation behaviour.](cli-reference.md#bulk-lookup)
219
-
220
- ### Certificate Transparency search
221
-
222
- [Read the CT query and source-limit contract.](cli-reference.md#certificate-transparency-search)
92
+ `registry-scaffold` has a separate fixture bootstrap contract: its `--profile`
93
+ selects one fixed fixture profile and it rejects shared `--config` profiles.
94
+ It creates sanitised local fixtures and does not contact a registry.
223
95
 
224
- ### Local certificate event intake
96
+ ## Collection boundaries
225
97
 
226
- [Read the offline, source-qualified event import contract.](cli-reference.md#local-certificate-event-intake)
227
-
228
- ### Declarative observation interchange
229
-
230
- [Read the bounded field-mapping and Open Asset Model bridge contracts.](cli-reference.md#declarative-observation-interchange)
231
-
232
- ### Lookalike discovery
233
-
234
- [Read candidate generation, dictionaries, snapshots, and output behaviour.](cli-reference.md#lookalike-discovery)
235
-
236
- ### Supervised candidate scan
237
-
238
- [Read the bounded candidate collection workflow.](cli-reference.md#supervised-candidate-scan)
239
-
240
- ### Domain posture audit
241
-
242
- [Read the DNS, mail, and certificate posture contract.](cli-reference.md#domain-posture-audit)
243
-
244
- ### HTTP intelligence
245
-
246
- [Read the bounded HTTP collection contract.](cli-reference.md#http-intelligence)
247
-
248
- ### TLS intelligence
249
-
250
- [Read the bounded TLS collection contract.](cli-reference.md#tls-intelligence)
251
-
252
- ### Isolated cryptographic and mail transport review
253
-
254
- [Read the explicitly authorised DNSSEC, TLSA/DANE, STARTTLS, and SMTP transport contract.](cli-reference.md#isolated-cryptographic-and-mail-transport-review)
255
- The mail report keeps address selection, public revalidation, confirmed
256
- connection, and cryptographic address authentication as separate provenance;
257
- address authentication is unavailable when no address candidate was retained.
258
- If a DANE-TA TLSA usage 2 association is published, active collection retains
259
- only the observed leaf certificate and leaves that comparison partial without
260
- certificate-path construction and trust-anchor path validation. SMTP relay
261
- PKIX-TA usage 0 and PKIX-EE usage 1 records are retained as unsupported and
262
- cannot complete SMTP DANE assurance; a separate usage 3 match remains eligible.
263
-
264
- ### Registry-source comparison
265
-
266
- [Read the normalised RDAP and WHOIS comparison contract.](cli-reference.md#registry-source-comparison)
267
-
268
- ### Static page comparison
269
-
270
- [Read saved page-identity comparison behaviour.](cli-reference.md#static-page-comparison)
271
-
272
- ### Passive mail exposure review
273
-
274
- [Read the saved-evidence mail review contract.](cli-reference.md#passive-mail-exposure-review)
275
-
276
- ### Offline supplied-evidence review
277
-
278
- [Read the separate DNSSEC, route-origin, DANE/TLSA, zone-intent, portfolio, domain-change, and supplied-observation review contract.](cli-reference.md#offline-supplied-evidence-review)
279
-
280
- ### Domain control manifests
281
-
282
- [Read desired-state manifest and drift-review behaviour.](cli-reference.md#domain-control-manifests)
283
-
284
- ### Domain assurance review
285
-
286
- [Read planned-change, recovery-dependency, and retirement contracts.](cli-reference.md#domain-assurance-review)
287
-
288
- ### Domain change assurance packet
289
-
290
- [Read the bounded pre-change, post-change, planning, and integrity contract.](cli-reference.md#domain-change-assurance-packet)
291
-
292
- ### Pre-sharing review
98
+ | Mode | Behaviour |
99
+ | --- | --- |
100
+ | Fast Lookup | Registration-first triage without WHOIS or deeper website and TLS collection. |
101
+ | Deep Lookup | Explicit RDAP, WHOIS, DNS, website, TLS, registrar-RDAP and observed-network collection where applicable. |
102
+ | Compact Deep Bulk | Comparison fields from shared Lookup orchestration, omitting full-only collectors and response fields. |
103
+ | Offline | Reads only the named local artefact, stdin or built-in catalogue. |
104
+ | Authorised active | Requires its dedicated command and per-run owned-or-authorised acknowledgement. |
293
105
 
294
- [Read the local marking, scope, redaction, and integrity preflight.](cli-reference.md#pre-sharing-review)
106
+ Networked commands run from the local machine and contact the sources named in
107
+ their focused help. They do not use the hosted login or hosted usage controls.
108
+ `lookup --plan` lists planned source families and disclosure targets before
109
+ collection. `doctor` is offline unless `--network` is selected.
295
110
 
296
- ### Fixed investigation plans
111
+ ## Output and automation
297
112
 
298
- [Read the plan-only recipe contract and approval gates.](cli-reference.md#fixed-investigation-plans)
113
+ Terminal text is the default. Commands expose JSON, JSONL, CSV, Markdown, HTML
114
+ or domain-only output only where declared by the installed registry. Redirected
115
+ and machine output contains no ANSI or transient progress text.
299
116
 
300
- ### Local handoffs and one-shot monitoring
117
+ - `--summary` and `--verbose` change presentation, not collection.
118
+ - `lookup --browse` provides an interactive terminal view; press `?` for keys.
119
+ - `--events` writes versioned lifecycle events to stderr.
120
+ - `--strict-exit` and `--fail-on` expose selected evidence states to automation.
121
+ - `--output` writes a private local file atomically and refuses an existing path
122
+ unless `--force` is also selected.
301
123
 
302
- [Read brief, case-package, fixed-recipe execution, and one-shot control-review contracts.](cli-reference.md#local-handoffs-and-one-shot-monitoring)
124
+ Local configuration profiles can set presentation, Fast collection, bounded
125
+ concurrency and observer labels. They cannot add targets, enable Deep
126
+ collection, choose output paths or approve network work. Explicit command
127
+ options override profile defaults.
303
128
 
304
- ### Optional local rendered capture
129
+ ## Exit codes
305
130
 
306
- [Read the separate Playwright capture package boundary.](cli-reference.md#optional-local-rendered-capture)
131
+ | Code | Meaning |
132
+ | ---: | --- |
133
+ | 0 | The command completed. |
134
+ | 2 | Invalid command, option or input. |
135
+ | 3 | Collection, lookup or comparison failed. |
136
+ | 4 | The result was partial or a selected evidence policy was not met. |
137
+ | 70 | Internal CLI bootstrap failure. |
138
+ | 130 | The analyst cancelled the command. |
139
+ | 143 | The process received SIGTERM. |
307
140
 
308
- ### Lookup evidence export
141
+ ## Command details
309
142
 
310
- [Read normalised JSON, Markdown, and HTML export behaviour.](cli-reference.md#lookup-evidence-export)
311
- Saved Lookup JSON is byte-, nesting-, entry-, and per-container-bounded before parsing, and the
312
- completed evidence package must satisfy the stricter portable-tree contract.
143
+ Run `whoisleuth <command> --help` for exact arguments, input ceilings, network
144
+ effects and output formats. The packaged [CLI reference](cli-reference.md) gives
145
+ the longer-lived compatibility and evidence contracts, while the generated
146
+ [online command reference](https://whoisleuth.com/cli#commands) provides a searchable index.
313
147
 
314
148
  ## Safety and limitations
315
149
 
316
150
  WHOISleuth is a defensive investigation tool, not an authorisation mechanism.
317
- Operators remain responsible for applicable law, registry and provider terms,
318
- and permission to assess a target. Missing, unsupported, partial, or failed
319
- evidence never establishes absence, availability, safety, ownership, or intent.
320
- Offline analyst assertions remain separately attributed and are not converted
321
- into observed facts.
151
+ Operators remain responsible for permission, applicable law and provider terms.
152
+ Incomplete evidence remains explicitly qualified, and analyst assertions remain
153
+ separate from observed facts.
@@ -1,145 +1,3 @@
1
- import { canonicalArtifactJsonFor, resolveArtifactCanonicalization, SORTED_JSON_V1, SORTED_JSON_V2, } from "./artifact-integrity.js";
2
- import { normalizeDomain } from "./case-record-core.js";
3
- import { canonicalDomainControlRecordList } from "./domain-control-records.js";
4
- export const DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA = 'whoisleuth.domain-control-manifest-input';
5
- export const DOMAIN_CONTROL_PASSPORT_SCHEMA = 'whoisleuth.domain-control-manifest';
6
- export const DOMAIN_CONTROL_PASSPORT_VERSION = 1;
7
- export const DOMAIN_CONTROL_MANIFEST_VERSION = 2;
8
- export const MAX_DOMAIN_CONTROL_PASSPORT_ENTRIES = 100;
9
- export const DOMAIN_CONTROL_PASSPORT_LIMITATIONS = Object.freeze([
10
- 'This analyst-authored manifest records intended domain-control state. It does not collect evidence or change registrar, DNS, mail, or certificate configuration.',
11
- 'Empty desired fields are unconfigured rather than claims that a record should be absent.',
12
- ]);
13
- const INPUT_KEYS = new Set(['schema', 'version', 'expiresAt', 'entries']);
14
- const MANIFEST_KEYS = new Set(['schema', 'version', 'generatedAt', 'expiresAt', 'entries', 'limitations', 'integrity']);
15
- const ENTRY_KEYS = new Set(['domain', 'nameservers', 'ds', 'mx', 'caa', 'tlsIssuer', 'tlsSpkiSha256', 'registrarLock', 'renewalReviewAt', 'note']);
16
- const INTEGRITY_KEYS = new Set(['algorithm', 'canonicalization', 'digestSha256']);
17
- const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
18
- function record(value) {
19
- return value && typeof value === 'object' && !Array.isArray(value)
20
- ? value
21
- : null;
22
- }
23
- function exactKeys(value, allowed, label) {
24
- const unknown = Object.keys(value).find((key) => !allowed.has(key));
25
- if (unknown)
26
- throw new TypeError(`${label} contains unknown field: ${unknown}.`);
27
- }
28
- function text(value, maximum = 300) {
29
- if (typeof value !== 'string' || value.length > maximum * 4 || CONTROL_RE.test(value))
30
- return null;
31
- return value.replace(/\s+/gu, ' ').trim().slice(0, maximum) || null;
32
- }
33
- function timestamp(value) {
34
- const candidate = text(value, 64);
35
- if (!candidate)
36
- return null;
37
- const parsed = Date.parse(candidate);
38
- return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
39
- }
40
- function hostnames(value) {
41
- if (!Array.isArray(value))
42
- return [];
43
- return [...new Set(value.slice(0, 128).flatMap((item) => {
44
- const normalized = normalizeDomain(item);
45
- return normalized ? [normalized] : [];
46
- }))].sort().slice(0, 32);
47
- }
48
- function digest(value) {
49
- const candidate = text(value, 64)?.toLowerCase() ?? '';
50
- return /^[a-f0-9]{64}$/u.test(candidate) ? candidate : null;
51
- }
52
- function normalizeEntry(value) {
53
- const source = record(value);
54
- if (!source)
55
- return null;
56
- exactKeys(source, ENTRY_KEYS, 'Domain control manifest entry');
57
- const domain = normalizeDomain(source.domain);
58
- if (!domain)
59
- return null;
60
- return Object.freeze({
61
- domain,
62
- nameservers: Object.freeze(hostnames(source.nameservers)),
63
- ds: Object.freeze(canonicalDomainControlRecordList(source.ds, 'ds')),
64
- mx: Object.freeze(canonicalDomainControlRecordList(source.mx, 'mx')),
65
- caa: Object.freeze(canonicalDomainControlRecordList(source.caa, 'caa')),
66
- tlsIssuer: text(source.tlsIssuer, 300)?.toLowerCase() ?? null,
67
- tlsSpkiSha256: digest(source.tlsSpkiSha256),
68
- registrarLock: source.registrarLock === 'required' || source.registrarLock === 'not_required'
69
- ? source.registrarLock
70
- : null,
71
- renewalReviewAt: timestamp(source.renewalReviewAt),
72
- note: text(source.note, 500),
73
- });
74
- }
75
- export function buildUnsignedDomainControlPassport(input, generatedAtValue) {
76
- const source = record(input);
77
- if (!source || source.schema !== DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA || source.version !== DOMAIN_CONTROL_PASSPORT_VERSION) {
78
- throw new TypeError(`Domain control manifest input must use ${DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA} version ${DOMAIN_CONTROL_PASSPORT_VERSION}.`);
79
- }
80
- exactKeys(source, INPUT_KEYS, 'Domain control manifest input');
81
- const generatedAt = timestamp(generatedAtValue);
82
- const expiresAt = timestamp(source.expiresAt);
83
- if (!generatedAt || !expiresAt || Date.parse(expiresAt) <= Date.parse(generatedAt)) {
84
- throw new TypeError('Domain control manifest expiry must be a valid time after generation.');
85
- }
86
- if (!Array.isArray(source.entries) || source.entries.length < 1 || source.entries.length > MAX_DOMAIN_CONTROL_PASSPORT_ENTRIES) {
87
- throw new TypeError(`Domain control manifest input must contain between 1 and ${MAX_DOMAIN_CONTROL_PASSPORT_ENTRIES} entries.`);
88
- }
89
- const entries = source.entries.map(normalizeEntry);
90
- if (entries.some((entry) => entry === null))
91
- throw new TypeError('Domain control manifest contains an invalid entry.');
92
- const normalizedEntries = entries;
93
- if (new Set(normalizedEntries.map((entry) => entry.domain)).size !== normalizedEntries.length) {
94
- throw new TypeError('Domain control manifest entries must use unique domains.');
95
- }
96
- return Object.freeze({
97
- schema: DOMAIN_CONTROL_PASSPORT_SCHEMA,
98
- version: DOMAIN_CONTROL_MANIFEST_VERSION,
99
- generatedAt,
100
- expiresAt,
101
- entries: Object.freeze([...normalizedEntries].sort((left, right) => left.domain.localeCompare(right.domain))),
102
- limitations: DOMAIN_CONTROL_PASSPORT_LIMITATIONS,
103
- });
104
- }
105
- export function normalizeDomainControlPassportDocument(value) {
106
- const source = record(value);
107
- const integrity = record(source?.integrity);
108
- if (!source
109
- || source.schema !== DOMAIN_CONTROL_PASSPORT_SCHEMA
110
- || !integrity
111
- || integrity.algorithm !== 'SHA-256'
112
- || typeof integrity.digestSha256 !== 'string'
113
- || !/^sha256:[a-f0-9]{64}$/u.test(integrity.digestSha256)) {
114
- throw new TypeError('Domain control manifest has an unsupported or malformed structure.');
115
- }
116
- exactKeys(source, MANIFEST_KEYS, 'Domain control manifest');
117
- exactKeys(integrity, INTEGRITY_KEYS, 'Domain control manifest integrity');
118
- const canonicalization = resolveArtifactCanonicalization(source.version, integrity.canonicalization, [
119
- { version: DOMAIN_CONTROL_PASSPORT_VERSION, canonicalization: SORTED_JSON_V1, explicit: true },
120
- { version: DOMAIN_CONTROL_MANIFEST_VERSION, canonicalization: SORTED_JSON_V2, explicit: true },
121
- ], 'Domain control manifest');
122
- if (!Array.isArray(source.limitations)
123
- || source.limitations.length !== DOMAIN_CONTROL_PASSPORT_LIMITATIONS.length
124
- || source.limitations.some((item, index) => item !== DOMAIN_CONTROL_PASSPORT_LIMITATIONS[index])) {
125
- throw new TypeError('Domain control manifest has an unsupported or malformed structure.');
126
- }
127
- const baseUnsigned = buildUnsignedDomainControlPassport({
128
- schema: DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA,
129
- version: DOMAIN_CONTROL_PASSPORT_VERSION,
130
- expiresAt: source.expiresAt,
131
- entries: source.entries,
132
- }, source.generatedAt);
133
- const unsigned = Object.freeze({ ...baseUnsigned, version: source.version });
134
- const { integrity: _integrity, ...suppliedUnsigned } = source;
135
- const canonicalUnsigned = canonicalArtifactJsonFor(unsigned, canonicalization);
136
- if (canonicalArtifactJsonFor(suppliedUnsigned, canonicalization) !== canonicalUnsigned) {
137
- throw new TypeError('Domain control manifest must use its canonical normalised content.');
138
- }
139
- return {
140
- manifest: source,
141
- unsigned,
142
- canonicalUnsigned,
143
- canonicalization,
144
- };
145
- }
1
+ // Historical browser-safe import facade. The pure domain-control manifest
2
+ // contract is shared by browser, CLI, and Node adapters.
3
+ export { DOMAIN_CONTROL_MANIFEST_VERSION, DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA, DOMAIN_CONTROL_PASSPORT_LIMITATIONS, DOMAIN_CONTROL_PASSPORT_SCHEMA, DOMAIN_CONTROL_PASSPORT_VERSION, MAX_DOMAIN_CONTROL_MANIFEST_BYTES, MAX_DOMAIN_CONTROL_PASSPORT_BYTES, MAX_DOMAIN_CONTROL_PASSPORT_ENTRIES, assertDomainControlPassportByteBudget, buildUnsignedDomainControlPassport, domainControlPassportSerialisedBytes, normalizeDomainControlPassportDocument, } from "../../../../packages/evidence/domain-control-runtime.mjs";
@@ -1,88 +1,3 @@
1
- import { normalizeDomain } from "./case-record-core.js";
2
- export const MAX_CANONICAL_DOMAIN_CONTROL_RECORDS = 32;
3
- function record(value) {
4
- return value && typeof value === 'object' && !Array.isArray(value)
5
- ? value
6
- : {};
7
- }
8
- function text(value, maximum = 500) {
9
- if (typeof value !== 'string' || value.length > maximum * 4 || /[\u0000-\u001f\u007f]/u.test(value))
10
- return '';
11
- return value.replace(/\s+/gu, ' ').trim().slice(0, maximum).trim();
12
- }
13
- function integer(value, maximum) {
14
- const candidate = typeof value === 'number' ? value : typeof value === 'string' && /^\d+$/u.test(value.trim()) ? Number(value) : NaN;
15
- return Number.isSafeInteger(candidate) && candidate >= 0 && candidate <= maximum ? candidate : null;
16
- }
17
- function exchange(value) {
18
- const candidate = text(value, 253).toLowerCase();
19
- if (candidate === '.')
20
- return '.';
21
- return normalizeDomain(candidate) ?? '';
22
- }
23
- export function canonicalMxRecord(value) {
24
- const item = record(value);
25
- if (Object.keys(item).length) {
26
- const host = exchange(item.exchange ?? item.host ?? item.value);
27
- const priority = integer(item.priority ?? item.preference, 65_535);
28
- return host && priority !== null ? `${priority} ${host}` : '';
29
- }
30
- const candidate = text(value, 500).toLowerCase();
31
- const match = /^(\d{1,5})\s+(.+)$/u.exec(candidate);
32
- if (match) {
33
- const priority = integer(match[1], 65_535);
34
- const host = exchange(match[2]);
35
- return priority !== null && host ? `${priority} ${host}` : '';
36
- }
37
- return exchange(candidate);
38
- }
39
- function caaValue(value) {
40
- return text(value, 500).replace(/^(?:"([\s\S]*)"|'([\s\S]*)')$/u, '$1$2').trim().toLowerCase();
41
- }
42
- export function canonicalCaaRecord(value) {
43
- const item = record(value);
44
- if (Object.keys(item).length) {
45
- const flags = integer(item.critical ?? item.flags ?? 0, 255);
46
- const tag = text(item.tag, 32).toLowerCase();
47
- const payload = caaValue(item.value);
48
- return flags !== null && /^(?:issue|issuewild|iodef)$/u.test(tag) && payload
49
- ? `${flags} ${tag} ${payload}`
50
- : '';
51
- }
52
- const candidate = text(value, 600);
53
- const match = /^(\d{1,3})\s+(issue|issuewild|iodef)\s+(.+)$/iu.exec(candidate);
54
- if (!match)
55
- return '';
56
- const flags = integer(match[1], 255);
57
- const tag = match[2]?.toLowerCase() ?? '';
58
- const payload = caaValue(match[3]);
59
- return flags !== null && payload ? `${flags} ${tag} ${payload}` : '';
60
- }
61
- export function canonicalDsRecord(value) {
62
- const item = record(value);
63
- if (Object.keys(item).length) {
64
- const keyTag = integer(item.keyTag ?? item.key_tag, 65_535);
65
- const algorithm = integer(item.algorithm, 255);
66
- const digestType = integer(item.digestType ?? item.digest_type, 255);
67
- const digest = text(item.digest, 1024).replace(/\s+/gu, '').toLowerCase();
68
- return keyTag !== null && algorithm !== null && digestType !== null && /^[a-f0-9]{2,1024}$/u.test(digest)
69
- ? `${keyTag} ${algorithm} ${digestType} ${digest}`
70
- : '';
71
- }
72
- const candidate = text(value, 1200);
73
- const match = /^(\d{1,5})\s+(\d{1,3})\s+(\d{1,3})\s+([a-f0-9\s]+)$/iu.exec(candidate);
74
- if (!match)
75
- return '';
76
- return canonicalDsRecord({ keyTag: match[1], algorithm: match[2], digestType: match[3], digest: match[4] });
77
- }
78
- export function canonicalDomainControlRecordList(value, kind) {
79
- if (!Array.isArray(value))
80
- return [];
81
- const normalizer = kind === 'mx' ? canonicalMxRecord : kind === 'caa' ? canonicalCaaRecord : canonicalDsRecord;
82
- return [...new Set(value
83
- .slice(0, MAX_CANONICAL_DOMAIN_CONTROL_RECORDS * 4)
84
- .map(normalizer)
85
- .filter(Boolean))]
86
- .sort()
87
- .slice(0, MAX_CANONICAL_DOMAIN_CONTROL_RECORDS);
88
- }
1
+ // Historical browser-safe import facade. Domain-control record normalisation
2
+ // is owned by the pure evidence package and shared unchanged across runtimes.
3
+ export { MAX_CANONICAL_DOMAIN_CONTROL_RECORDS, canonicalCaaRecord, canonicalDomainControlRecordList, canonicalDsRecord, canonicalMxRecord, } from "../../../../packages/evidence/domain-control-runtime.mjs";