@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/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,277 +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
68
- whoisleuth verify-artifact lookup.json --json
61
+ whoisleuth discover example.test --preset common --jsonl
62
+ whoisleuth verify-artifact lookup.json --json --strict-exit
69
63
  whoisleuth compare lookup.json --json
70
64
  whoisleuth brief lookup.json
71
- whoisleuth registry-doctor lookup.json --json
72
65
  ```
73
66
 
74
- Only one query is accepted by `lookup`; multi-input processing belongs to
75
- `bulk`. Standard input to `lookup` is capped at 4 KiB and must contain one
76
- non-empty line.
77
-
78
- ## Collection profiles
79
-
80
- Commands are classified as either networked or offline in focused help, the
81
- generated manual, and `commands --json`.
82
-
83
- | Profile | Boundary |
84
- | --- | --- |
85
- | Fast Lookup | Conservative default for domain triage. It does not run WHOIS or deep website and TLS collection. |
86
- | Deep Lookup | Explicitly requested bounded RDAP, WHOIS, DNS, website, TLS, registrar-RDAP, and observed-network work where applicable. |
87
- | Compact deep Bulk | Uses the shared Lookup orchestration but omits full-only response fields and collectors. |
88
- | Offline review | Reads only the named bounded artefact or analyst-authored input and makes no network request. |
89
-
90
- Networked commands run from the local machine and contact the sources declared
91
- by their profile. They do not use the hosted login or hosted usage controls;
92
- upstream services can see and rate-limit the local network address. The CLI
93
- does not implicitly enable the browser Console's optional external intelligence
94
- actions.
95
-
96
- `lookup --plan` performs classification and emits a versioned preflight without
97
- starting collection. It lists planned source families and disclosure targets,
98
- but cannot predict cache state, live configuration, redirects, referrals,
99
- source availability, or exact request count.
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.
100
70
 
101
- `doctor` is offline by default. `doctor --network` adds three independent,
102
- bounded checks against fixed diagnostic infrastructure and retains only state,
103
- HTTP status, and a bounded explanation.
71
+ ## Command groups
104
72
 
105
- ## Command map
73
+ The installed registry groups commands under the same analyst vocabulary as the
74
+ application:
106
75
 
107
- | Goal | Commands |
76
+ | Group | Common commands |
108
77
  | --- | --- |
109
- | Investigate a target | `lookup`, `http`, `tls`, `posture`, `registry-support` |
110
- | Review many targets | `bulk`, `discover`, `discover-scan`, `ct-search`, `ct-intake` |
111
- | Compare saved evidence | `compare`, `page-compare`, `diff`, `reconcile`, `timeline`, `mail-review` |
112
- | Review supplied evidence | `brief`, `review-evidence`, `registry-doctor`, `registry-cohort`, `source-report`, `sharing-review`, `map-observations` |
113
- | Plan and assure changes | `domain-control`, `monitor-once`, `assurance`, `change-packet`, `workflow-plan`, `workflow-run` |
114
- | Verify and package evidence | `case-pack`, `manifest`, `oam-export`, `verify-artifact`, `inspect-archive`, `sign-artifact`, `verify-signature`, `export` |
115
- | Calibrate offline | `risk-calibrate`, `lookalike-calibrate` |
116
- | 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` |
117
82
 
118
- Use focused help for the exact arguments and collection ceiling:
83
+ Filter the canonical index without running the selected commands:
119
84
 
120
85
  ```bash
121
- whoisleuth lookup --help
122
- whoisleuth sharing-review --help
86
+ whoisleuth commands --group investigate
87
+ whoisleuth commands --group respond --mode offline
123
88
  whoisleuth commands --json
124
89
  whoisleuth manual | man -l -
125
90
  ```
126
91
 
127
- ## Output and automation
128
-
129
- Human-readable terminal output is the default. Interactive terminals use
130
- restrained semantic colour and transient progress on stderr. Redirected output,
131
- JSON, and JSONL contain no ANSI sequences or progress text. State remains
132
- explicit in text, so colour is never the only distinction.
133
-
134
- - `--json` and `--jsonl` provide bounded machine-readable documents.
135
- - `--summary` and `--verbose` change human detail without changing collection.
136
- - `--no-color` or `NO_COLOR` disables colour.
137
- - `WHOISLEUTH_NO_PROGRESS=1` disables the transient progress indicator.
138
- - `--events` keeps the final document on stdout and writes versioned JSONL
139
- lifecycle events to stderr.
140
- - `--strict-exit` treats failures in explicitly requested source families as a
141
- partial failure while keeping skipped or unsupported sources neutral.
142
- - `--fail-on` applies explicit `source-failure`, `inconclusive`, `danger`, or
143
- `material-drift` automation policy where the selected command supports it.
144
- - `--junit` emits bounded CI test cases for Lookup, Bulk, and one-shot control
145
- reviews; owned-domain Posture can emit `--sarif` only with
146
- `--owned-domain`.
147
-
148
- `--config <file> --profile <name>` applies a strict version-1 local profile.
149
- The default path is `$XDG_CONFIG_HOME/whoisleuth/config.json`. Profiles can set
150
- only presentation mode, Fast collection, bounded concurrency, and observer or
151
- vantage labels. They cannot add targets, enable Deep collection, select output
152
- paths, approve network work, or set failure policy. Explicit command options
153
- override defaults from the same option group.
154
-
155
- Every command accepts `--output <file>`. WHOISleuth buffers bounded output,
156
- creates a private temporary file beside the destination, syncs it, and publishes
157
- it atomically with mode `0600`. Existing destinations are refused unless
158
- `--force` is deliberately combined with `--output`. Failed or cancelled
159
- commands do not publish partial files. Output is capped at 32 MiB.
160
-
161
- ## Exit codes
162
-
163
- | Code | Meaning |
164
- | ---: | --- |
165
- | 0 | Command completed. Individual sources can still be partial or inconclusive; inspect diagnostics. |
166
- | 2 | Invalid command, option, input, or stdin shape. |
167
- | 3 | The requested lookup, collection, or comparison could not run. |
168
- | 4 | A bounded operation completed partially. |
169
- | 70 | Unexpected CLI bootstrap failure. |
170
- | 130 | The analyst cancelled the command; no partial final result was emitted. |
171
- | 143 | The process received SIGTERM; no partial final result was emitted. |
172
-
173
- ## Detailed command reference
174
-
175
- The headings below preserve the established guide anchors while moving their
176
- full contracts into the packaged reference.
177
-
178
- ### Registry capability coverage
179
-
180
- [Read the complete registry capability reference.](cli-reference.md#registry-capability-coverage)
181
-
182
- ### Offline Risk calibration
183
-
184
- [Read the calibration dataset and replay contract.](cli-reference.md#offline-risk-calibration)
185
-
186
- ### Lookalike review-yield calibration
187
-
188
- [Read the reviewed candidate calibration contract.](cli-reference.md#lookalike-review-yield-calibration)
189
-
190
- ### Offline artefact verification
191
-
192
- [Read the archive, saved Lookup, and signed-artefact verification contract.](cli-reference.md#offline-artefact-verification)
193
-
194
- ### Privacy-safe source reliability report
195
-
196
- [Read the target-free source diagnostic contract.](cli-reference.md#privacy-safe-source-reliability-report)
197
-
198
- ### Workspace archive inspection
199
-
200
- [Read the redacted archive summary, stable content-digest, transfer-check, and
201
- search contract.](cli-reference.md#workspace-archive-inspection)
202
-
203
- ### Optional evidence-package signing
204
-
205
- [Read the local signing and verification contract.](cli-reference.md#optional-evidence-package-signing)
206
-
207
- ### Reproducible investigation manifest
208
-
209
- [Read the ordered, path-free artefact digest contract.](cli-reference.md#reproducible-investigation-manifest)
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.
210
95
 
211
- ### Bulk lookup
96
+ ## Collection boundaries
212
97
 
213
- [Read Bulk bounds, filters, formats, checkpoints, and cancellation behaviour.](cli-reference.md#bulk-lookup)
214
-
215
- ### Certificate Transparency search
216
-
217
- [Read the CT query and source-limit contract.](cli-reference.md#certificate-transparency-search)
218
-
219
- ### Local certificate event intake
220
-
221
- [Read the offline, source-qualified event import contract.](cli-reference.md#local-certificate-event-intake)
222
-
223
- ### Declarative observation interchange
224
-
225
- [Read the bounded field-mapping and Open Asset Model bridge contracts.](cli-reference.md#declarative-observation-interchange)
226
-
227
- ### Lookalike discovery
228
-
229
- [Read candidate generation, dictionaries, snapshots, and output behaviour.](cli-reference.md#lookalike-discovery)
230
-
231
- ### Supervised candidate scan
232
-
233
- [Read the bounded candidate collection workflow.](cli-reference.md#supervised-candidate-scan)
234
-
235
- ### Domain posture audit
236
-
237
- [Read the DNS, mail, and certificate posture contract.](cli-reference.md#domain-posture-audit)
238
-
239
- ### HTTP intelligence
240
-
241
- [Read the bounded HTTP collection contract.](cli-reference.md#http-intelligence)
242
-
243
- ### TLS intelligence
244
-
245
- [Read the bounded TLS collection contract.](cli-reference.md#tls-intelligence)
246
-
247
- ### Registry-source comparison
248
-
249
- [Read the normalised RDAP and WHOIS comparison contract.](cli-reference.md#registry-source-comparison)
250
-
251
- ### Static page comparison
252
-
253
- [Read saved page-identity comparison behaviour.](cli-reference.md#static-page-comparison)
254
-
255
- ### Passive mail exposure review
256
-
257
- [Read the saved-evidence mail review contract.](cli-reference.md#passive-mail-exposure-review)
258
-
259
- ### Offline supplied-evidence review
260
-
261
- [Read the DNSSEC, DANE, zone-intent, portfolio, domain-change, and supplied-observation review contract.](cli-reference.md#offline-supplied-evidence-review)
262
-
263
- ### Domain control manifests
264
-
265
- [Read desired-state manifest and drift-review behaviour.](cli-reference.md#domain-control-manifests)
266
-
267
- ### Domain assurance review
268
-
269
- [Read planned-change, recovery-dependency, and retirement contracts.](cli-reference.md#domain-assurance-review)
270
-
271
- ### Domain change assurance packet
272
-
273
- [Read the bounded pre-change, post-change, planning, and integrity contract.](cli-reference.md#domain-change-assurance-packet)
274
-
275
- ### 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. |
276
105
 
277
- [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.
278
110
 
279
- ### Fixed investigation plans
111
+ ## Output and automation
280
112
 
281
- [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.
282
116
 
283
- ### 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.
284
123
 
285
- [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.
286
128
 
287
- ### Optional local rendered capture
129
+ ## Exit codes
288
130
 
289
- [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. |
290
140
 
291
- ### Lookup evidence export
141
+ ## Command details
292
142
 
293
- [Read normalised JSON, Markdown, and HTML export behaviour.](cli-reference.md#lookup-evidence-export)
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.
294
147
 
295
148
  ## Safety and limitations
296
149
 
297
150
  WHOISleuth is a defensive investigation tool, not an authorisation mechanism.
298
- Operators remain responsible for applicable law, registry and provider terms,
299
- and permission to assess a target. Missing, unsupported, partial, or failed
300
- evidence never establishes absence, availability, safety, ownership, or intent.
301
- Offline analyst assertions remain separately attributed and are not converted
302
- 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.
@@ -0,0 +1,3 @@
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";
@@ -0,0 +1,3 @@
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";
@@ -0,0 +1,3 @@
1
+ import packageDocument from '../package.json' with { type: 'json' };
2
+ import { normalizeBoundedSemanticVersion } from "./semantic-version.mjs";
3
+ export const WHOISLEUTH_APPLICATION_VERSION = normalizeBoundedSemanticVersion(packageDocument.version, 'Application manifest');
@@ -32,10 +32,24 @@ function errorRecord(value) {
32
32
  ? value
33
33
  : {};
34
34
  }
35
+ function registryStatusToken(value) {
36
+ return value.toLowerCase().replace(/[^a-z0-9]+/gu, '');
37
+ }
35
38
  function rdapEventDate(events, action) {
36
39
  const date = events.find((event) => event.action === action)?.date;
37
40
  return typeof date === 'string' ? date : null;
38
41
  }
42
+ function withoutHttpDeliveryMetadata(value) {
43
+ if (!value || typeof value !== 'object' || Array.isArray(value))
44
+ return value;
45
+ const output = { ...value };
46
+ if (output.response && typeof output.response === 'object' && !Array.isArray(output.response)) {
47
+ const response = { ...output.response };
48
+ delete response.deliveryMetadata;
49
+ output.response = response;
50
+ }
51
+ return output;
52
+ }
39
53
  // No marketplace (Afternic/Sedo/Dan.com/GoDaddy Auctions/etc.) offers a
40
54
  // free, no-auth API to check "is this specific domain listed for sale" -
41
55
  // what's checkable without credentials is the parking/landing-page
@@ -115,18 +129,17 @@ const PRIVACY_MARKERS = [
115
129
  /not disclosed/i,
116
130
  /withheld for privacy/i,
117
131
  ];
118
- // Only ever called after a lookup has already succeeded (this runs inside
119
- // checkDomainAvailability once RDAP or WHOIS has returned real data), so a
120
- // missing/empty registrant here isn't "we don't know" - it means the
121
- // registry gave us a response with no usable contact in it, which for
122
- // sourcing purposes (can I reach this owner from this data alone?) is the
123
- // same practical answer as an explicit privacy-proxy service: no.
132
+ // Explicit privacy/redaction markers establish a positive privacy signal.
133
+ // Usable public contact data establishes a negative one. A missing or blank
134
+ // contact can instead reflect registry policy, access tier, parser coverage,
135
+ // or ordinary omission, so it remains inconclusive rather than being promoted
136
+ // to an affirmative privacy claim.
124
137
  function isPrivacyProtected(registrant) {
125
138
  if (!registrant)
126
- return true;
139
+ return null;
127
140
  const blob = [registrant.name, registrant.org, registrant.email].filter(Boolean).join(' ');
128
141
  if (!blob)
129
- return true; // record exists but every contact field is blank - that's redaction
142
+ return null;
130
143
  return PRIVACY_MARKERS.some((re) => re.test(blob));
131
144
  }
132
145
  // RDAP Lookup retains a bounded multi-value contact inventory, but Bulk and
@@ -197,7 +210,10 @@ async function checkDnsDelegation(domain, { resolver = dns.resolveNs } = {}) {
197
210
  // domain has "no site": transient DNS/TLS/network failures, a slow origin, or
198
211
  // an HTTP error can all produce the same null body. Keeping that distinction
199
212
  // avoids turning an inconclusive probe into a false inactivity claim.
200
- async function fetchHomepage(domain, { fetcher = safeFetchDetailed } = {}) {
213
+ async function fetchHomepage(domain, { fetcher = safeFetchDetailed, timeoutMs = HOMEPAGE_FETCH_TIMEOUT_MS } = {}) {
214
+ const requestTimeoutMs = Number.isInteger(timeoutMs) && timeoutMs >= 10 && timeoutMs <= HOMEPAGE_FETCH_TIMEOUT_MS
215
+ ? timeoutMs
216
+ : HOMEPAGE_FETCH_TIMEOUT_MS;
201
217
  const headers = whoisleuthRequestHeaders();
202
218
  const failures = [];
203
219
  const probeStartedAt = Date.now();
@@ -205,7 +221,7 @@ async function fetchHomepage(domain, { fetcher = safeFetchDetailed } = {}) {
205
221
  const requestUrl = `${scheme}://${domain}`;
206
222
  const attemptStartedAt = Date.now();
207
223
  const controller = new AbortController();
208
- const timeout = setTimeout(() => controller.abort(), HOMEPAGE_FETCH_TIMEOUT_MS);
224
+ const timeout = setTimeout(() => controller.abort(), requestTimeoutMs);
209
225
  try {
210
226
  const fetched = await fetcher(requestUrl, { signal: controller.signal, headers });
211
227
  const fetchedResponse = fetched instanceof Response ? fetched : fetched.response;
@@ -274,7 +290,7 @@ async function fetchHomepage(domain, { fetcher = safeFetchDetailed } = {}) {
274
290
  catch (err) {
275
291
  const error = errorRecord(err);
276
292
  const reason = error.name === 'AbortError'
277
- ? 'timed out after 6 seconds'
293
+ ? `timed out after ${requestTimeoutMs} milliseconds`
278
294
  : nonEmptyErrorMessage(err, 'request failed')
279
295
  .replace(/[\u0000-\u001f\u007f]+/g, ' ')
280
296
  .slice(0, 180);
@@ -446,40 +462,39 @@ async function checkDomainAvailability(domain, options = {}) {
446
462
  source: 'whois',
447
463
  };
448
464
  }
449
- if (parsed.nameservers.length)
450
- nameservers = parsed.nameservers;
451
- if (parsed.statuses.length)
452
- statuses = parsed.statuses.map((status) => status.toLowerCase());
453
- if (parsed.registrar) {
454
- registrar = {
455
- handle: null,
456
- ianaId: parsed.registrarIanaId || null,
457
- name: parsed.registrar,
458
- org: null,
459
- email: parsed.abuseEmail || null,
460
- phone: parsed.abusePhone || null,
461
- };
462
- }
463
- if (parsed.registrantName || parsed.registrantOrg || parsed.registrantEmail || parsed.registrantPhone) {
464
- registrant = {
465
- handle: null,
466
- name: parsed.registrantName || null,
467
- org: parsed.registrantOrg || null,
468
- email: parsed.registrantEmail || null,
469
- phone: parsed.registrantPhone || null,
470
- };
471
- }
472
- if (parsed.abuseEmail || parsed.abusePhone) {
473
- abuse = { handle: null, name: null, org: parsed.registrar || null, email: parsed.abuseEmail || null, phone: parsed.abusePhone || null };
474
- }
475
- createdDate = parsed.createdDate || null;
476
- expiryDate = parsed.expiryDate || null;
477
- createdDateIso = parsed.createdDateIso || parsed.lifecycle?.createdDateIso || registryDateIso(createdDate);
478
- expiryDateIso = parsed.expiryDateIso || parsed.lifecycle?.expiryDateIso || registryDateIso(expiryDate);
479
- if (!dnssec)
480
- dnssec = parsed.dnssec || null;
481
- if (parsed.registrationStatus === 'registered'
482
- || registrar || createdDate || expiryDate || nameservers.length > 0 || statuses.length > 0) {
465
+ if (parsed.registrationStatus === 'registered') {
466
+ if (parsed.nameservers.length)
467
+ nameservers = parsed.nameservers;
468
+ if (parsed.statuses.length)
469
+ statuses = parsed.statuses.map((status) => status.toLowerCase());
470
+ if (parsed.registrar) {
471
+ registrar = {
472
+ handle: null,
473
+ ianaId: parsed.registrarIanaId || null,
474
+ name: parsed.registrar,
475
+ org: null,
476
+ email: parsed.abuseEmail || null,
477
+ phone: parsed.abusePhone || null,
478
+ };
479
+ }
480
+ if (parsed.registrantName || parsed.registrantOrg || parsed.registrantEmail || parsed.registrantPhone) {
481
+ registrant = {
482
+ handle: null,
483
+ name: parsed.registrantName || null,
484
+ org: parsed.registrantOrg || null,
485
+ email: parsed.registrantEmail || null,
486
+ phone: parsed.registrantPhone || null,
487
+ };
488
+ }
489
+ if (parsed.abuseEmail || parsed.abusePhone) {
490
+ abuse = { handle: null, name: null, org: parsed.registrar || null, email: parsed.abuseEmail || null, phone: parsed.abusePhone || null };
491
+ }
492
+ createdDate = parsed.createdDate || null;
493
+ expiryDate = parsed.expiryDate || null;
494
+ createdDateIso = parsed.createdDateIso || parsed.lifecycle?.createdDateIso || registryDateIso(createdDate);
495
+ expiryDateIso = parsed.expiryDateIso || parsed.lifecycle?.expiryDateIso || registryDateIso(expiryDate);
496
+ if (!dnssec)
497
+ dnssec = parsed.dnssec || null;
483
498
  registrationSource = 'whois';
484
499
  }
485
500
  }
@@ -491,9 +506,7 @@ async function checkDomainAvailability(domain, options = {}) {
491
506
  // registration evidence either - e.g. the registry answered inconclusively
492
507
  // or every referral hop failed/rate-limited. Report "unknown" rather than
493
508
  // fabricating "registered" from an empty record.
494
- const hasWhoisRegistrationData = whoisParsed?.registrationStatus === 'registered'
495
- || Boolean(registrar) || Boolean(createdDate) || Boolean(expiryDate)
496
- || nameservers.length > 0 || statuses.length > 0;
509
+ const hasWhoisRegistrationData = whoisParsed?.registrationStatus === 'registered';
497
510
  let dnsDelegated = false;
498
511
  if (!rdapFound && !hasWhoisRegistrationData && dnsDelegationPromise) {
499
512
  const delegation = await dnsDelegationPromise;
@@ -547,7 +560,10 @@ async function checkDomainAvailability(domain, options = {}) {
547
560
  dnssec,
548
561
  source: registrationSource,
549
562
  };
550
- if (statuses.some((s) => s.includes('pendingdelete') || s.includes('redemptionperiod'))) {
563
+ if (statuses.some((status) => {
564
+ const normalizedStatus = registryStatusToken(status);
565
+ return normalizedStatus.includes('pendingdelete') || normalizedStatus.includes('redemptionperiod');
566
+ })) {
551
567
  return {
552
568
  state: 'expiring',
553
569
  confidence: 'medium',
@@ -646,6 +662,7 @@ async function checkDomainAvailability(domain, options = {}) {
646
662
  responseHeaders: homepage.technologyHeaders,
647
663
  activityStatus,
648
664
  includePageIdentity: pageIdentityEligible,
665
+ includePublicationMetadata: options.includePublicationMetadata !== false,
649
666
  ...(options.includeCredentialSurfaceProfile !== undefined
650
667
  ? { includeCredentialSurfaceProfile: options.includeCredentialSurfaceProfile }
651
668
  : {}),
@@ -659,6 +676,9 @@ async function checkDomainAvailability(domain, options = {}) {
659
676
  }
660
677
  }
661
678
  const responsePolicy = qualifyResponsePolicyWithCspMeta(homepage.responsePolicy, htmlSignals.cspMetaPolicy);
679
+ const retainedHttp = options.includeDeliveryMetadata === false
680
+ ? withoutHttpDeliveryMetadata(homepage.http)
681
+ : homepage.http;
662
682
  const { cspMetaPolicy: _cspMetaPolicy, ...retainedHtmlSignals } = htmlSignals;
663
683
  const securityPosture = options.includeSecurityPosture === false ? null : analyzeWebsiteSecurityPosture({
664
684
  http: homepage.http,
@@ -692,7 +712,7 @@ async function checkDomainAvailability(domain, options = {}) {
692
712
  activityStatus,
693
713
  websiteProbeStatus,
694
714
  websiteProbeDetail,
695
- http: homepage.http,
715
+ http: retainedHttp,
696
716
  deepScanComplete,
697
717
  faviconHash,
698
718
  faviconPHash,
@@ -716,7 +736,7 @@ async function checkDomainAvailability(domain, options = {}) {
716
736
  activityStatus,
717
737
  websiteProbeStatus,
718
738
  websiteProbeDetail,
719
- http: homepage.http,
739
+ http: retainedHttp,
720
740
  deepScanComplete,
721
741
  faviconHash,
722
742
  faviconPHash,
@@ -733,4 +753,4 @@ async function checkDomainAvailability(domain, options = {}) {
733
753
  hasDmarc: dnsIntelligence.hasDmarc,
734
754
  };
735
755
  }
736
- export { checkDomainAvailability, checkDnsDelegation, fetchHomepage, deriveWebsiteActivity, forSaleRedirectSignal, parseRegistryDate as parseWhoisDate, };
756
+ export { checkDomainAvailability, checkDnsDelegation, fetchHomepage, isPrivacyProtected, deriveWebsiteActivity, forSaleRedirectSignal, parseRegistryDate as parseWhoisDate, };
@@ -2,6 +2,7 @@
2
2
  // not infer missing values and keep every string, object, enum, and counter
3
3
  // bounded before a provider or connector result reaches its public model.
4
4
  import { domainToASCII } from 'node:url';
5
+ import { normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
5
6
  export function isRecord(value) {
6
7
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
7
8
  }
@@ -26,10 +27,7 @@ export function strictBoundedString(value, maxLength) {
26
27
  return normalized && normalized.length <= maxLength ? normalized : null;
27
28
  }
28
29
  export function isoTimestamp(value) {
29
- if (typeof value !== 'string' || value.length > 64 || /[\u0000-\u001f\u007f]/u.test(value))
30
- return null;
31
- const timestamp = Date.parse(value);
32
- return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
30
+ return normalizeExplicitIsoTimestamp(value);
33
31
  }
34
32
  export function boundedHttpsUrl(value, maxLength) {
35
33
  const raw = strictBoundedString(value, maxLength);