@slicedearth/whoisleuth-cli 2.3.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/NOTICE +8 -2
  2. package/README.md +19 -15
  3. package/bin/whoisleuth.mjs +8 -1
  4. package/cli/archive-content-digest.mjs +14 -0
  5. package/cli/archive-inspect.mjs +20 -53
  6. package/cli/argument-values.mjs +55 -0
  7. package/cli/arguments.mjs +24 -408
  8. package/cli/artifact-validation/case-response.mjs +65 -44
  9. package/cli/artifact-validation/investigation-capsule.mjs +52 -31
  10. package/cli/artifact-validation/lookup-evidence.mjs +15 -9
  11. package/cli/artifact-validation/signed-review.mjs +136 -68
  12. package/cli/artifact-validation/structure-primitives.mjs +1 -105
  13. package/cli/artifact-verify.mjs +92 -24
  14. package/cli/assurance-arguments.mjs +56 -0
  15. package/cli/assurance-command-definitions.mjs +179 -0
  16. package/cli/assurance-command-runner.mjs +40 -5
  17. package/cli/bagit-folder.mjs +57 -0
  18. package/cli/bagit-review.mjs +23 -0
  19. package/cli/bounded-json.mjs +1 -1
  20. package/cli/bulk-checkpoint.mjs +1 -1
  21. package/cli/bulk-command-runner.mjs +4 -2
  22. package/cli/bulk-output.mjs +29 -3
  23. package/cli/bulk.mjs +2 -3
  24. package/cli/case-command.mjs +212 -0
  25. package/cli/case-pack.mjs +50 -32
  26. package/cli/ci-report.mjs +6 -3
  27. package/cli/collection-arguments.mjs +123 -0
  28. package/cli/collection-command-definitions.mjs +330 -0
  29. package/cli/collection-preflight.mjs +8 -1
  30. package/cli/command-argument-grammar.mjs +8 -206
  31. package/cli/command-definition.mjs +329 -0
  32. package/cli/command-reference.mjs +94 -1357
  33. package/cli/command-seeds.mjs +18 -0
  34. package/cli/compare.mjs +1 -1
  35. package/cli/completion.mjs +298 -79
  36. package/cli/config-profile.mjs +8 -6
  37. package/cli/ct-event-intake.mjs +16 -16
  38. package/cli/discover.mjs +5 -4
  39. package/cli/discovery-observation-snapshot.mjs +46 -18
  40. package/cli/discovery-scan-command-runner.mjs +5 -3
  41. package/cli/discovery-scan.mjs +7 -4
  42. package/cli/discovery-snapshot.mjs +2 -2
  43. package/cli/discovery-workflow.mjs +5 -5
  44. package/cli/domain-control-monitor.mjs +50 -20
  45. package/cli/domain-control-observations.mjs +151 -103
  46. package/cli/errors.mjs +37 -6
  47. package/cli/evidence-arguments.mjs +46 -0
  48. package/cli/evidence-command-definitions.mjs +69 -0
  49. package/cli/evidence-command-runner.mjs +18 -8
  50. package/cli/evidence-signing.mjs +5 -4
  51. package/cli/external-observation-mapping.mjs +9 -10
  52. package/cli/formatters/terminal-command-formats.mjs +6 -0
  53. package/cli/formatters/terminal-lookup.mjs +24 -46
  54. package/cli/formatters/terminal-shared.mjs +5 -3
  55. package/cli/formatters/terminal-technology.mjs +114 -0
  56. package/cli/history-arguments.mjs +39 -0
  57. package/cli/history-command-definitions.mjs +88 -0
  58. package/cli/indicator-set-command.mjs +33 -0
  59. package/cli/inline-command-families.mjs +6 -46
  60. package/cli/inline-command-runner.mjs +4 -31
  61. package/cli/input.mjs +2 -2
  62. package/cli/intake-command.mjs +107 -0
  63. package/cli/interactive-launcher.mjs +2 -160
  64. package/cli/interchange-report.mjs +8 -10
  65. package/cli/investigation-artifacts.mjs +176 -0
  66. package/cli/investigation-checkpoint.mjs +10 -0
  67. package/cli/investigation-folder.mjs +114 -0
  68. package/cli/investigation-manifest.mjs +1 -112
  69. package/cli/investigation-package-review.mjs +103 -0
  70. package/cli/investigation-plan.mjs +38 -147
  71. package/cli/investigation-recipes.mjs +177 -0
  72. package/cli/investigation-run.mjs +256 -35
  73. package/cli/local-document-checkpoint.mjs +170 -0
  74. package/cli/local-mmdb-review.mjs +197 -17
  75. package/cli/lookalike-calibration.mjs +1 -1
  76. package/cli/lookup-brief.mjs +2 -2
  77. package/cli/lookup-browser.mjs +5 -12
  78. package/cli/lookup-command-runner.mjs +33 -39
  79. package/cli/lookup-diff.mjs +47 -12
  80. package/cli/lookup-plan.mjs +16 -14
  81. package/cli/lookup-reconcile.mjs +15 -12
  82. package/cli/lookup-timeline.mjs +7 -6
  83. package/cli/mail-header-review.mjs +24 -247
  84. package/cli/mail-review.mjs +75 -21
  85. package/cli/manual.mjs +1 -0
  86. package/cli/network-arguments.mjs +51 -0
  87. package/cli/network-command-definitions.mjs +130 -0
  88. package/cli/network-command-runner.mjs +45 -31
  89. package/cli/offline-evidence-review.mjs +39 -6
  90. package/cli/output-file.mjs +31 -5
  91. package/cli/posture.mjs +1 -1
  92. package/cli/registry-doctor.mjs +3 -1
  93. package/cli/registry-fixture-scaffold.mjs +1 -1
  94. package/cli/registry-support.mjs +1 -1
  95. package/cli/retained-artifact-diff.mjs +10 -9
  96. package/cli/review-arguments.mjs +131 -0
  97. package/cli/review-command-definitions.mjs +273 -0
  98. package/cli/review-command-runner.mjs +104 -149
  99. package/cli/risk-calibration.mjs +8 -8
  100. package/cli/runner.mjs +74 -37
  101. package/cli/saved-lookup.mjs +2 -2
  102. package/cli/selected-input-worker.mjs +63 -0
  103. package/cli/sharing-review.mjs +8 -80
  104. package/cli/signer-trust.mjs +104 -0
  105. package/cli/source-reliability.mjs +75 -84
  106. package/cli/support-arguments.mjs +49 -0
  107. package/cli/support-command-definitions.mjs +209 -0
  108. package/cli/support-command-runner.mjs +2 -2
  109. package/cli/terminal-input.mjs +168 -0
  110. package/cli/workflow-arguments.mjs +86 -0
  111. package/cli/workflow-command-definitions.mjs +78 -0
  112. package/cli/workflow-command-runner.mjs +75 -13
  113. package/docs/cli-reference.md +103 -30
  114. package/docs/cli.md +510 -25
  115. package/docs/contextual-reviews.md +155 -0
  116. package/lib/abort.mjs +4 -1
  117. package/lib/analyst-taxonomy.mjs +2 -40
  118. package/lib/application-version.mjs +1 -1
  119. package/lib/availability.mjs +27 -754
  120. package/lib/bounded-contract-normalizers.mjs +4 -0
  121. package/lib/bounded-file.mjs +3 -1
  122. package/lib/bounded-html-document.mjs +125 -0
  123. package/lib/bounded-json.mjs +2 -290
  124. package/lib/browser-library-profile.mjs +99 -130
  125. package/lib/browser-library-worker.mjs +112 -0
  126. package/lib/candidate-provenance-bounds.mjs +2 -0
  127. package/lib/classify.mjs +1 -1
  128. package/lib/client-behavior-profile.mjs +3 -3
  129. package/lib/ct-query.mjs +2 -27
  130. package/lib/ct-response-bounds.mjs +2 -9
  131. package/lib/ct-search.mjs +89 -46
  132. package/lib/dmarc-discovery.mjs +62 -0
  133. package/lib/dns-convergence-review.mjs +21 -8
  134. package/lib/dns-delegation-health.mjs +36 -22
  135. package/lib/dns-inheritance-review.mjs +207 -0
  136. package/lib/dns-intelligence.mjs +44 -29
  137. package/lib/dns-mx.mjs +1 -1
  138. package/lib/dns-query-coverage.mjs +40 -0
  139. package/lib/dnssec-evidence-validation.mjs +11 -3
  140. package/lib/domain-assurance.mjs +1 -1
  141. package/lib/domain-change-packet.mjs +18 -14
  142. package/lib/domain-change-review.mjs +40 -16
  143. package/lib/domain-control-flight-recorder.mjs +301 -124
  144. package/lib/domain-control-manifest.mjs +126 -58
  145. package/lib/domain-posture-analysis.mjs +41 -11
  146. package/lib/domain-posture-parsers.mjs +88 -6
  147. package/lib/domain-posture.mjs +120 -32
  148. package/lib/domain-registration.mjs +392 -0
  149. package/lib/evidence-export-privacy.mjs +568 -0
  150. package/lib/evidence-export.mjs +12 -593
  151. package/lib/evidence-report.mjs +7 -2
  152. package/lib/external-intelligence-risk.mjs +2 -124
  153. package/lib/favicon.mjs +117 -41
  154. package/lib/generated/cisa-kev-catalog.mjs +50 -4
  155. package/lib/generated/registrar-standing-catalogue.mjs +18 -10
  156. package/lib/generated/retire-browser-catalog.mjs +2742 -1034
  157. package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
  158. package/lib/html-signals.mjs +87 -85
  159. package/lib/http-evidence-bounds.mjs +2 -7
  160. package/lib/http-intelligence.mjs +52 -27
  161. package/lib/idn-confusable-policy.mjs +6 -4
  162. package/lib/idn-confusables.mjs +1 -1
  163. package/lib/interchange-fidelity-registry.mjs +10 -0
  164. package/lib/lookup-cache.mjs +34 -11
  165. package/lib/lookup-child-profile-contract.mjs +24 -15
  166. package/lib/lookup-contract-primitives.mjs +1 -1
  167. package/lib/lookup-diagnostics.mjs +2 -1
  168. package/lib/lookup-network-evidence-bounds.mjs +1 -10
  169. package/lib/lookup-progress.mjs +79 -13
  170. package/lib/lookup-response-contract.mjs +1077 -0
  171. package/lib/lookup-response.mjs +11 -24
  172. package/lib/lookup-source-progress.mjs +24 -35
  173. package/lib/lookup.mjs +49 -28
  174. package/lib/observed-network-context.mjs +43 -27
  175. package/lib/opportunity-scoring.mjs +2 -107
  176. package/lib/outbound-identity.mjs +1 -1
  177. package/lib/outbound-request-bounds.mjs +4 -1
  178. package/lib/page-fingerprints.mjs +87 -134
  179. package/lib/page-role-profile.mjs +12 -8
  180. package/lib/perceptual-hash-comparison.mjs +2 -34
  181. package/lib/perceptual-hash.mjs +79 -24
  182. package/lib/portable-generator.mjs +2 -29
  183. package/lib/project-metadata.mjs +2 -8
  184. package/lib/rdap-bootstrap.mjs +72 -22
  185. package/lib/rdap-client.mjs +26 -14
  186. package/lib/rdap-normalization.mjs +33 -18
  187. package/lib/rdap-search-workbench.mjs +1 -1
  188. package/lib/rdap-transport.mjs +47 -20
  189. package/lib/rdap-types.mjs +2 -1
  190. package/lib/rdap.mjs +18 -3
  191. package/lib/registrable-domain.mjs +2 -36
  192. package/lib/registrar-standing-contract.mjs +2 -1
  193. package/lib/registrar-standing.mjs +9 -13
  194. package/lib/registry-capabilities.mjs +7 -3
  195. package/lib/registry-capability-catalogue.mjs +29 -20
  196. package/lib/registry-dates.mjs +2 -190
  197. package/lib/registry-insights.mjs +56 -18
  198. package/lib/response-policy.mjs +86 -48
  199. package/lib/risk-calibration-summary.mjs +2 -338
  200. package/lib/risk-scoring.mjs +2 -345
  201. package/lib/rpki-evidence.mjs +8 -5
  202. package/lib/safe-fetch.mjs +54 -47
  203. package/lib/scoring-evidence-quality.mjs +2 -107
  204. package/lib/security-txt.mjs +10 -7
  205. package/lib/semantic-version.mjs +2 -54
  206. package/lib/service-binding-dns.mjs +35 -0
  207. package/lib/smtp-transport-review.mjs +75 -34
  208. package/lib/sslbl-certificates.generated.mjs +141 -138
  209. package/lib/static-html-analysis.mjs +117 -146
  210. package/lib/structured-data-identity.mjs +2 -1
  211. package/lib/technology-markup.mjs +100 -0
  212. package/lib/threat-intelligence-manifests.mjs +11 -139
  213. package/lib/threat-intelligence-runtime.mjs +24 -52
  214. package/lib/threat-intelligence-targets.mjs +6 -34
  215. package/lib/threat-intelligence-types.mjs +2 -36
  216. package/lib/typosquat-generator.mjs +29 -16
  217. package/lib/urlhaus-intelligence.mjs +8 -3
  218. package/lib/urlscan-intelligence.mjs +1 -1
  219. package/lib/website-enrichment.mjs +388 -0
  220. package/lib/website-security-posture.mjs +30 -6
  221. package/lib/website-technology.mjs +111 -71
  222. package/lib/whois-authority.mjs +55 -5
  223. package/lib/whois-chain.mjs +9 -2
  224. package/lib/whois-common-formats.mjs +5 -5
  225. package/lib/whois-contracts.mjs +2 -1
  226. package/lib/whois-dialects.mjs +2 -2
  227. package/lib/whois-normalization.mjs +3 -1
  228. package/lib/whois-parser-support.mjs +8 -1
  229. package/lib/whois-parser.mjs +5 -3
  230. package/lib/whois-transport.mjs +23 -7
  231. package/package.json +14 -14
  232. package/packages/analysis/analyst-taxonomy.mjs +40 -0
  233. package/packages/analysis/bounded-json.mjs +330 -0
  234. package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
  235. package/packages/analysis/cli-argument-grammar.mjs +216 -0
  236. package/packages/analysis/ct-query.mjs +27 -0
  237. package/packages/analysis/ct-response-bounds.mjs +9 -0
  238. package/packages/analysis/external-intelligence-risk.mjs +124 -0
  239. package/packages/analysis/http-evidence-bounds.mjs +8 -0
  240. package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
  241. package/packages/analysis/opportunity-scoring.mjs +107 -0
  242. package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
  243. package/packages/analysis/portable-generator.mjs +29 -0
  244. package/packages/analysis/project-metadata.mjs +8 -0
  245. package/packages/analysis/registrable-domain.mjs +56 -0
  246. package/packages/analysis/risk-calibration-summary.mjs +352 -0
  247. package/packages/analysis/risk-scoring.mjs +345 -0
  248. package/packages/analysis/scoring-evidence-quality.mjs +107 -0
  249. package/packages/analysis/semantic-version.mjs +54 -0
  250. package/packages/analysis/threat-intelligence-types.mjs +75 -0
  251. package/packages/cases/case-attachment-model.mjs +83 -0
  252. package/packages/cases/case-evidence-links.mjs +135 -0
  253. package/packages/cases/case-evidence-model.mjs +229 -69
  254. package/packages/cases/case-evidence-quality.mjs +5 -0
  255. package/packages/cases/case-export-input.mjs +55 -0
  256. package/packages/cases/case-incident-context.mjs +52 -0
  257. package/packages/cases/case-markdown.mjs +11 -0
  258. package/packages/cases/case-migration-model.mjs +98 -48
  259. package/packages/cases/case-model.mjs +1 -0
  260. package/packages/cases/case-recheck-model.mjs +149 -0
  261. package/packages/cases/case-record-contracts.mjs +1 -1
  262. package/packages/cases/case-record-core.mjs +4 -3
  263. package/packages/cases/case-record-operations.mjs +123 -133
  264. package/packages/cases/case-record-projection.mjs +30 -20
  265. package/packages/cases/case-record-version-input.mjs +19 -0
  266. package/packages/cases/case-report.mjs +114 -67
  267. package/packages/cases/case-requested-evidence.mjs +146 -0
  268. package/packages/cases/case-response-actions.mjs +756 -0
  269. package/packages/cases/case-response-model.mjs +49 -1129
  270. package/packages/cases/case-response-outcomes.mjs +338 -0
  271. package/packages/cases/case-response-packet-render.mjs +157 -0
  272. package/packages/cases/case-response-packet-types.mjs +1 -0
  273. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  274. package/packages/cases/case-response-packet.mjs +58 -593
  275. package/packages/cases/case-response-records.mjs +93 -0
  276. package/packages/cases/case-response-review-inputs.mjs +382 -0
  277. package/packages/cases/case-response-values.mjs +85 -0
  278. package/packages/cases/case-review-package.mjs +45 -0
  279. package/packages/cases/case-selection.mjs +46 -0
  280. package/packages/cases/case-storage-model.mjs +26 -1
  281. package/packages/cases/case-workflow-metadata.mjs +225 -0
  282. package/packages/cases/case-workflow-migration.mjs +43 -0
  283. package/packages/cases/http-summary.mjs +6 -1
  284. package/packages/cases/response-route-freshness.mjs +16 -0
  285. package/packages/comparison/capture-context.mjs +61 -0
  286. package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
  287. package/packages/comparison/favicon-similarity.mjs +1 -1
  288. package/packages/comparison/page-similarity.mjs +3 -2
  289. package/packages/comparison/relationship-evidence.mjs +103 -14
  290. package/packages/comparison/relationship-provenance.mjs +99 -0
  291. package/packages/contracts/analyst-interchange.mjs +234 -0
  292. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  293. package/packages/contracts/analyst-review-state.mjs +5 -4
  294. package/packages/contracts/case-portability.mjs +370 -114
  295. package/packages/contracts/case-views-contract.mjs +12 -0
  296. package/packages/contracts/case-views.mjs +26 -0
  297. package/packages/contracts/cli-command-semantics.mjs +3 -0
  298. package/packages/contracts/cli-grammar.mjs +6 -0
  299. package/packages/contracts/cli-lookup-brief.mjs +2 -0
  300. package/packages/contracts/cli-lookup.mjs +0 -1
  301. package/packages/contracts/context-review.mjs +24 -0
  302. package/packages/contracts/document-intake.mjs +11 -0
  303. package/packages/contracts/domain-control-flight-recorder.mjs +98 -44
  304. package/packages/contracts/domain-control-manifest.mjs +128 -74
  305. package/packages/contracts/domain-control-monitor.mjs +59 -45
  306. package/packages/contracts/domain-control-review.mjs +200 -121
  307. package/packages/contracts/domain-name.mjs +21 -0
  308. package/packages/contracts/external-observation-interchange.mjs +2 -1
  309. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  310. package/packages/contracts/har-review.mjs +2 -0
  311. package/packages/contracts/http-url.mjs +2 -0
  312. package/packages/contracts/identity-events.mjs +4 -0
  313. package/packages/contracts/investigation-package-limits.mjs +24 -0
  314. package/packages/contracts/investigation-portability.mjs +56 -27
  315. package/packages/contracts/investigation-run.mjs +18 -0
  316. package/packages/contracts/ip-address.mjs +44 -0
  317. package/packages/contracts/lookup-evidence.mjs +35 -0
  318. package/packages/contracts/mail-authentication.mjs +5 -0
  319. package/packages/contracts/message-intake.mjs +31 -0
  320. package/packages/contracts/offline-comparison.mjs +4 -2
  321. package/packages/contracts/page-fingerprints.mjs +15 -0
  322. package/packages/contracts/passive-mail.mjs +10 -0
  323. package/packages/contracts/risk-calibration.mjs +0 -1
  324. package/packages/contracts/schema-compatibility.mjs +1 -1
  325. package/packages/contracts/schema-lifecycle.mjs +83 -153
  326. package/packages/contracts/selected-file-limits.mjs +6 -0
  327. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  328. package/packages/contracts/web-capture.mjs +10 -0
  329. package/packages/contracts/workspace-portability.mjs +219 -238
  330. package/packages/evidence/artifact-integrity.mjs +8 -2
  331. package/packages/evidence/artifact-structure.mjs +105 -0
  332. package/packages/evidence/collection-quality.mjs +67 -0
  333. package/packages/evidence/decision-fact.mjs +4 -1
  334. package/packages/evidence/dns-query-outcome.mjs +72 -0
  335. package/packages/evidence/domain-control-runtime.mjs +155 -27
  336. package/packages/evidence/domain-posture-context.mjs +79 -0
  337. package/packages/evidence/email-recipient.mjs +18 -0
  338. package/packages/evidence/image-regions.mjs +56 -0
  339. package/packages/evidence/latest-observations.mjs +28 -0
  340. package/packages/evidence/lookup-target.mjs +108 -0
  341. package/packages/evidence/observation.mjs +18 -1
  342. package/packages/evidence/passphrase-encryption.mjs +44 -0
  343. package/packages/evidence/png-pixels.mjs +117 -0
  344. package/packages/evidence/public-address-policy.mjs +83 -0
  345. package/packages/evidence/record-identity.mjs +72 -0
  346. package/packages/evidence/registry-dates.mjs +190 -0
  347. package/packages/evidence/retained-file.mjs +69 -0
  348. package/packages/evidence/review-age.mjs +14 -0
  349. package/packages/interchange/bagit.mjs +342 -0
  350. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  351. package/packages/interchange/crc32.mjs +17 -0
  352. package/packages/interchange/defensive-indicator-export.mjs +264 -0
  353. package/packages/interchange/external-findings-import.mjs +93 -61
  354. package/packages/interchange/managed-indicator-export.mjs +35 -0
  355. package/packages/interchange/managed-indicator-set.mjs +210 -0
  356. package/packages/interchange/misp-indicator-export.mjs +93 -0
  357. package/packages/interchange/sharing-policy.mjs +56 -0
  358. package/packages/interchange/stix-indicator-export.mjs +129 -0
  359. package/packages/interchange/web-capture-import.mjs +389 -0
  360. package/packages/investigation/acquisition-decision-packet.mjs +1 -1
  361. package/packages/investigation/bulk-mail-exposure.mjs +9 -14
  362. package/packages/investigation/capture-coverage.mjs +57 -0
  363. package/packages/investigation/connector-provenance-review.mjs +100 -0
  364. package/packages/investigation/context-review.mjs +35 -0
  365. package/packages/investigation/document-intake.mjs +81 -0
  366. package/packages/investigation/docx-intake.mjs +133 -0
  367. package/packages/investigation/domain-history-review.mjs +100 -0
  368. package/packages/investigation/domain-transition-review.mjs +47 -0
  369. package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
  370. package/packages/investigation/har-intake.mjs +68 -0
  371. package/packages/investigation/identity-event-intake.mjs +131 -0
  372. package/packages/investigation/identity-incident-review.mjs +39 -0
  373. package/packages/investigation/incident-sequence-review.mjs +58 -0
  374. package/packages/investigation/intake-report.mjs +19 -0
  375. package/packages/investigation/investigation-bagit.mjs +14 -0
  376. package/packages/investigation/investigation-capsule.mjs +33 -5
  377. package/packages/investigation/investigation-manifest.mjs +200 -0
  378. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  379. package/packages/investigation/investigation-package.mjs +200 -0
  380. package/packages/investigation/link-intake.mjs +179 -0
  381. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  382. package/packages/investigation/lookup-asset-graph.mjs +161 -141
  383. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  384. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  385. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  386. package/packages/investigation/mail-authentication-review.mjs +222 -0
  387. package/packages/investigation/mail-header-identity.mjs +209 -0
  388. package/packages/investigation/message-intake.mjs +195 -0
  389. package/packages/investigation/page-behaviour.mjs +113 -0
  390. package/packages/investigation/pdf-intake.mjs +155 -0
  391. package/packages/investigation/platform-continuity-review.mjs +60 -0
  392. package/packages/investigation/qr-intake.mjs +56 -0
  393. package/packages/investigation/requested-action-hints.mjs +11 -0
  394. package/packages/investigation/selected-input-review.mjs +17 -0
  395. package/packages/investigation/storefront-review.mjs +70 -0
  396. package/packages/monitoring/analyst-review-state.mjs +38 -90
  397. package/packages/workspace/brand-profile-model.mjs +160 -22
  398. package/packages/workspace/bulk-columns.mjs +17 -0
  399. package/packages/workspace/bulk-review-model.mjs +39 -31
  400. package/packages/workspace/bulk-session-model.mjs +112 -27
  401. package/packages/workspace/bulk-sort.mjs +1 -0
  402. package/packages/workspace/campaign-model.mjs +1 -1
  403. package/packages/workspace/case-views.mjs +102 -0
  404. package/packages/workspace/detection-rule-model.mjs +1 -1
  405. package/packages/workspace/hostile-input.mjs +29 -8
  406. package/packages/workspace/investigation-guide.mjs +2 -2
  407. package/packages/workspace/investigation-template-model.mjs +45 -6
  408. package/packages/workspace/page-baseline.mjs +20 -17
  409. package/packages/workspace/relationship-observation-model.mjs +54 -35
  410. package/packages/workspace/shortlist-model.mjs +14 -5
  411. package/packages/workspace/watchlist-history.mjs +30 -14
  412. package/packages/workspace/watchlist-store.mjs +24 -6
  413. package/packages/workspace/website-snapshot-model.mjs +98 -33
  414. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  415. package/packages/workspace/workspace-archive.mjs +90 -25
  416. package/third-party-notices.txt +577 -17
  417. package/lib/hostname.mjs +0 -38
  418. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -0,0 +1,392 @@
1
+ // Registration authority, lifecycle dates and positive-only delegation evidence.
2
+ import { promises as dns } from 'node:dns';
3
+ import { abortable } from "./abort.mjs";
4
+ import { fetchRdapRecord } from "./rdap.mjs";
5
+ import { buildWhoisChain, parseWhoisChain } from "./whois.mjs";
6
+ import { featureDecision, networkFeaturePolicy } from "./feature-policy.mjs";
7
+ import { parseRegistryDate, registryDateIso } from "./registry-dates.mjs";
8
+ import { nonEmptyErrorMessage } from "./error-detail.mjs";
9
+ import { registryServiceAdmissionFor } from "./registry-capabilities.mjs";
10
+ import { recordOrEmpty as errorRecord } from "./json-record.mjs";
11
+ const DNS_DELEGATION_TIMEOUT_MS = 4000;
12
+ const MAX_DELEGATION_NAMESERVERS = 50;
13
+ const MISSING_DNS_CODES = new Set(['ENODATA', 'ENOTFOUND', 'ENONAME', 'NXDOMAIN']);
14
+ function registryStatusToken(value) {
15
+ return value.toLowerCase().replace(/[^a-z0-9]+/gu, '');
16
+ }
17
+ function rdapEventDate(events, action) {
18
+ const date = events.find((event) => event.action === action)?.date;
19
+ return typeof date === 'string' ? date : null;
20
+ }
21
+ function computeAgeDays(dateStr) {
22
+ const d = parseRegistryDate(dateStr);
23
+ return d ? Math.floor((Date.now() - d.getTime()) / 86400000) : null;
24
+ }
25
+ function computeDaysUntil(dateStr) {
26
+ const d = parseRegistryDate(dateStr);
27
+ return d ? Math.ceil((d.getTime() - Date.now()) / 86400000) : null;
28
+ }
29
+ const PRIVACY_MARKERS = [
30
+ /redacted for privacy/i,
31
+ /data protected/i,
32
+ /privacy\s*protect/i,
33
+ /whoisguard/i,
34
+ /domains by proxy/i,
35
+ /perfect privacy/i,
36
+ /contact privacy/i,
37
+ /private registration/i,
38
+ /identity protect/i,
39
+ /not disclosed/i,
40
+ /withheld for privacy/i,
41
+ ];
42
+ // Explicit privacy/redaction markers establish a positive privacy signal.
43
+ // Usable public contact data establishes a negative one. A missing or blank
44
+ // contact can instead reflect registry policy, access tier, parser coverage,
45
+ // or ordinary omission, so it remains inconclusive rather than being promoted
46
+ // to an affirmative privacy claim.
47
+ function isPrivacyProtected(registrant) {
48
+ if (!registrant)
49
+ return null;
50
+ const blob = [registrant.name, registrant.org, registrant.email].filter(Boolean).join(' ');
51
+ if (!blob)
52
+ return null;
53
+ return PRIVACY_MARKERS.some((re) => re.test(blob));
54
+ }
55
+ // RDAP Lookup retains a bounded multi-value contact inventory, but Bulk and
56
+ // watchlist availability records deliberately keep the historical compact
57
+ // shape. This prevents repeated contact arrays and registry links from
58
+ // expanding browser-local stores or flowing into case evidence implicitly.
59
+ function compactContact(contact) {
60
+ if (!contact || typeof contact !== 'object' || Array.isArray(contact))
61
+ return null;
62
+ const value = contact;
63
+ const compact = {
64
+ handle: value.handle || null,
65
+ name: value.name || null,
66
+ org: value.org || null,
67
+ email: value.email || null,
68
+ phone: value.phone || null,
69
+ address: value.address || null,
70
+ };
71
+ return Object.values(compact).some(Boolean) ? compact : null;
72
+ }
73
+ // A number of ccTLD registries publish neither an RDAP bootstrap entry nor a
74
+ // generally reachable port-43 WHOIS service. A positive NS answer cannot
75
+ // replace registry registration data, but it does prove that the registrable
76
+ // domain has an active DNS delegation. Use that as a bounded, positive-only
77
+ // fallback: no answer is never interpreted as availability because registered
78
+ // domains can legitimately be undelegated.
79
+ async function checkDnsDelegation(domain, options = {}) {
80
+ options.signal?.throwIfAborted();
81
+ // Cancel only this lookup's c-ares requests, never the shared DNS resolver.
82
+ const ownedResolver = options.resolver ? null : new dns.Resolver();
83
+ const resolve = options.resolver || ((name) => ownedResolver.resolveNs(name));
84
+ const controller = new AbortController();
85
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
86
+ const cancel = () => ownedResolver?.cancel();
87
+ signal.addEventListener('abort', cancel, { once: true });
88
+ const timer = setTimeout(() => controller.abort(new Error('DNS delegation lookup timed out')), DNS_DELEGATION_TIMEOUT_MS);
89
+ try {
90
+ const records = await abortable(() => resolve(domain), signal);
91
+ const validNameservers = [...new Set((Array.isArray(records) ? records : [])
92
+ .filter((value) => typeof value === 'string')
93
+ .map((value) => value.trim().replace(/\.+$/, '').toLowerCase())
94
+ .filter((value) => value.length > 0 && value.length <= 253)
95
+ .filter((value) => value.split('.').every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test(label))))]
96
+ .sort();
97
+ const normalized = validNameservers.slice(0, MAX_DELEGATION_NAMESERVERS);
98
+ return {
99
+ delegated: normalized.length > 0,
100
+ nameservers: normalized,
101
+ nameserversTruncated: validNameservers.length > MAX_DELEGATION_NAMESERVERS,
102
+ error: null,
103
+ };
104
+ }
105
+ catch (err) {
106
+ options.signal?.throwIfAborted();
107
+ const error = errorRecord(err);
108
+ if (typeof error.code === 'string' && MISSING_DNS_CODES.has(error.code)) {
109
+ return { delegated: false, nameservers: [], nameserversTruncated: false, error: null };
110
+ }
111
+ return {
112
+ delegated: false,
113
+ nameservers: [],
114
+ nameserversTruncated: false,
115
+ error: nonEmptyErrorMessage(err, String(err)).slice(0, 180),
116
+ };
117
+ }
118
+ finally {
119
+ clearTimeout(timer);
120
+ signal.removeEventListener('abort', cancel);
121
+ }
122
+ }
123
+ function registryPolicyDetail(domain, fast) {
124
+ const details = [];
125
+ const rdapAdmission = registryServiceAdmissionFor(domain, 'rdap');
126
+ if (rdapAdmission && !rdapAdmission.allowed) {
127
+ details.push('RDAP was not queried because the registry access policy does not permit collection.');
128
+ }
129
+ const whoisAdmission = fast ? null : registryServiceAdmissionFor(domain, 'whois');
130
+ if (whoisAdmission && !whoisAdmission.allowed) {
131
+ details.push(whoisAdmission.state === 'permission_required'
132
+ ? 'WHOIS was not queried because registry permission or source authorisation is required.'
133
+ : 'WHOIS was not queried because IANA publishes no domain WHOIS service for this suffix.');
134
+ }
135
+ return details.length ? ` ${details.join(' ')}` : '';
136
+ }
137
+ // fast: true skips the WHOIS fallback (no TCP:43 chain) and the homepage
138
+ // fetch (no for-sale/parking detection) - just RDAP plus the signals
139
+ // derivable from it (age, expiry proximity, privacy). Meant for scanning
140
+ // large sourcing candidate lists quickly and gently on registry rate
141
+ // limits; anything it can't resolve (state "unknown") is meant to get a
142
+ // follow-up deep check (fast: false, the default) on the shortlist only.
143
+ async function resolveDomainRegistration(domain, options, featurePolicy) {
144
+ options.signal?.throwIfAborted();
145
+ const fast = options.fast === true;
146
+ const rdapEnabled = featureDecision('rdap', featurePolicy).enabled;
147
+ const whoisEnabled = featureDecision('whois', featurePolicy).enabled;
148
+ const dnsIntelligenceEnabled = featureDecision('dns_intelligence', featurePolicy).enabled;
149
+ const hasPreloadedRdap = Object.prototype.hasOwnProperty.call(options, 'rdapRecord');
150
+ const hasPreloadedWhois = Object.prototype.hasOwnProperty.call(options, 'whoisChain');
151
+ const hasPreloadedRdapPromise = Object.prototype.hasOwnProperty.call(options, 'rdapRecordPromise');
152
+ const hasPreloadedWhoisPromise = Object.prototype.hasOwnProperty.call(options, 'whoisChainPromise');
153
+ const hasPreloadedDnsDelegation = Object.prototype.hasOwnProperty.call(options, 'dnsDelegation');
154
+ let nameservers = [];
155
+ let statuses = [];
156
+ let rdapServer = null;
157
+ let rdapFound = false;
158
+ let registrar = null;
159
+ let registrant = null;
160
+ let abuse = null;
161
+ let createdDate = null;
162
+ let expiryDate = null;
163
+ let createdDateIso = null;
164
+ let expiryDateIso = null;
165
+ let registrationSource = null;
166
+ let registrationConfidence = 'high';
167
+ let dnssec = null;
168
+ let registryDnsEvidence = null;
169
+ const assessment = (registration) => ({
170
+ registration, registryDnsEvidence, nameservers, dnssec,
171
+ });
172
+ if (rdapEnabled) {
173
+ try {
174
+ // Shared with /api/rdap (lib/rdap.mts's fetchRdapRecord) rather than a
175
+ // separate fetch+parse here - same registry data either way, and this
176
+ // also picks up that function's short-TTL cache (lib/lookup-cache.mts)
177
+ // and upstream timeout for free.
178
+ const recordValue = hasPreloadedRdapPromise
179
+ ? await abortable(() => options.rdapRecordPromise, options.signal)
180
+ : hasPreloadedRdap
181
+ ? options.rdapRecord
182
+ : await fetchRdapRecord('domain', domain, options.signal ? { signal: options.signal } : {});
183
+ options.signal?.throwIfAborted();
184
+ const record = errorRecord(recordValue);
185
+ const recordRdapServer = typeof record.rdapServer === 'string' ? record.rdapServer : null;
186
+ const upstreamStatus = typeof record.upstreamStatus === 'number' ? record.upstreamStatus : null;
187
+ if (Object.keys(record).length) {
188
+ rdapServer = recordRdapServer;
189
+ if (upstreamStatus === 404) {
190
+ const cleartext = record.transportSecurity === 'http'
191
+ || (recordRdapServer !== null && /^http:\/\//iu.test(recordRdapServer));
192
+ return assessment({
193
+ state: 'available',
194
+ confidence: cleartext ? 'medium' : 'high',
195
+ detail: `The registry's RDAP service has no record for this domain.${cleartext ? ' This answer arrived over unencrypted HTTP, so its transport was not authenticated.' : ''}`,
196
+ source: 'rdap',
197
+ rdapServer: recordRdapServer,
198
+ });
199
+ }
200
+ const parsed = errorRecord(record.parsed);
201
+ if (Object.keys(parsed).length) {
202
+ registryDnsEvidence = parsed;
203
+ statuses = Array.isArray(parsed.statuses)
204
+ ? parsed.statuses.filter((status) => typeof status === 'string').map((status) => status.toLowerCase())
205
+ : [];
206
+ nameservers = Array.isArray(parsed.nameservers)
207
+ ? parsed.nameservers.filter((nameserver) => typeof nameserver === 'string')
208
+ : [];
209
+ registrar = compactContact(parsed.registrar);
210
+ registrant = compactContact(parsed.registrant);
211
+ abuse = compactContact(parsed.abuse);
212
+ const events = Array.isArray(parsed.events)
213
+ ? parsed.events.map(errorRecord)
214
+ : [];
215
+ const lifecycle = errorRecord(parsed.lifecycle);
216
+ createdDate = typeof lifecycle.createdDate === 'string'
217
+ ? lifecycle.createdDate
218
+ : rdapEventDate(events, 'registration');
219
+ expiryDate = typeof lifecycle.expiryDate === 'string'
220
+ ? lifecycle.expiryDate
221
+ : rdapEventDate(events, 'expiration');
222
+ createdDateIso = typeof lifecycle.createdDateIso === 'string'
223
+ ? lifecycle.createdDateIso
224
+ : registryDateIso(createdDate);
225
+ expiryDateIso = typeof lifecycle.expiryDateIso === 'string'
226
+ ? lifecycle.expiryDateIso
227
+ : registryDateIso(expiryDate);
228
+ dnssec = typeof parsed.dnssec === 'string' ? parsed.dnssec : null;
229
+ rdapFound = true;
230
+ registrationSource = 'rdap';
231
+ }
232
+ }
233
+ }
234
+ catch {
235
+ options.signal?.throwIfAborted();
236
+ /* fall through to WHOIS-based detection (deep mode only) */
237
+ }
238
+ }
239
+ const dnsDelegationPromise = !rdapFound && dnsIntelligenceEnabled
240
+ ? hasPreloadedDnsDelegation
241
+ ? Promise.resolve(options.dnsDelegation)
242
+ : checkDnsDelegation(domain, {
243
+ ...(options.resolveNs ? { resolver: options.resolveNs } : {}),
244
+ ...(options.signal ? { signal: options.signal } : {}),
245
+ })
246
+ : null;
247
+ // DNS may settle while the independent WHOIS source is still pending. Keep
248
+ // its rejection observed even if WHOIS supplies an early conclusive return.
249
+ void dnsDelegationPromise?.catch(() => { });
250
+ let whoisChain = null;
251
+ let whoisParsed = null;
252
+ if (!rdapFound && !fast && whoisEnabled) {
253
+ try {
254
+ whoisChain = (hasPreloadedWhoisPromise
255
+ ? await abortable(() => options.whoisChainPromise, options.signal)
256
+ : hasPreloadedWhois
257
+ ? options.whoisChain
258
+ : await buildWhoisChain(domain, options.signal ? { signal: options.signal } : {})) ?? null;
259
+ options.signal?.throwIfAborted();
260
+ if (!Array.isArray(whoisChain))
261
+ throw new Error('WHOIS chain unavailable');
262
+ const parsed = parseWhoisChain(whoisChain, domain);
263
+ whoisParsed = parsed;
264
+ if (parsed.notFound) {
265
+ return assessment({
266
+ state: 'available',
267
+ confidence: 'medium',
268
+ detail: `WHOIS reports no matching record for this domain${parsed.notFoundSource ? ` (per ${parsed.notFoundSource})` : ''}.`,
269
+ source: 'whois',
270
+ });
271
+ }
272
+ if (parsed.registrationStatus === 'registered') {
273
+ if (parsed.nameservers.length)
274
+ nameservers = parsed.nameservers;
275
+ if (parsed.statuses.length)
276
+ statuses = parsed.statuses.map((status) => status.toLowerCase());
277
+ if (parsed.registrar) {
278
+ registrar = {
279
+ handle: null,
280
+ ianaId: parsed.registrarIanaId || null,
281
+ name: parsed.registrar,
282
+ org: null,
283
+ email: parsed.abuseEmail || null,
284
+ phone: parsed.abusePhone || null,
285
+ };
286
+ }
287
+ if (parsed.registrantName || parsed.registrantOrg || parsed.registrantEmail || parsed.registrantPhone) {
288
+ registrant = {
289
+ handle: null,
290
+ name: parsed.registrantName || null,
291
+ org: parsed.registrantOrg || null,
292
+ email: parsed.registrantEmail || null,
293
+ phone: parsed.registrantPhone || null,
294
+ };
295
+ }
296
+ if (parsed.abuseEmail || parsed.abusePhone) {
297
+ abuse = { handle: null, name: null, org: parsed.registrar || null, email: parsed.abuseEmail || null, phone: parsed.abusePhone || null };
298
+ }
299
+ createdDate = parsed.createdDate || null;
300
+ expiryDate = parsed.expiryDate || null;
301
+ createdDateIso = parsed.createdDateIso || parsed.lifecycle?.createdDateIso || registryDateIso(createdDate);
302
+ expiryDateIso = parsed.expiryDateIso || parsed.lifecycle?.expiryDateIso || registryDateIso(expiryDate);
303
+ if (!dnssec)
304
+ dnssec = parsed.dnssec || null;
305
+ registrationSource = 'whois';
306
+ }
307
+ }
308
+ catch {
309
+ options.signal?.throwIfAborted();
310
+ /* if both RDAP and WHOIS fail, we simply can't determine availability */
311
+ }
312
+ }
313
+ // WHOIS ran and was not a confirmed not-found, but produced no positive
314
+ // registration evidence either - e.g. the registry answered inconclusively
315
+ // or every referral hop failed/rate-limited. Report "unknown" rather than
316
+ // fabricating "registered" from an empty record.
317
+ const hasWhoisRegistrationData = whoisParsed?.registrationStatus === 'registered';
318
+ let dnsDelegated = false;
319
+ if (!rdapFound && !hasWhoisRegistrationData && dnsDelegationPromise) {
320
+ const delegation = await dnsDelegationPromise;
321
+ if (delegation && delegation.delegated === true && Array.isArray(delegation.nameservers) && delegation.nameservers.length) {
322
+ dnsDelegated = true;
323
+ nameservers = delegation.nameservers;
324
+ registrationSource = 'dns';
325
+ registrationConfidence = 'medium';
326
+ }
327
+ }
328
+ options.signal?.throwIfAborted();
329
+ if (!rdapFound && !hasWhoisRegistrationData && !dnsDelegated) {
330
+ const disabledSources = [
331
+ !rdapEnabled ? 'RDAP' : null,
332
+ !fast && !whoisEnabled ? 'WHOIS' : null,
333
+ !dnsIntelligenceEnabled ? 'DNS intelligence' : null,
334
+ ].filter(Boolean);
335
+ const disabledDetail = disabledSources.length
336
+ ? ` ${disabledSources.join(', ')} ${disabledSources.length === 1 ? 'is' : 'are'} disabled by deployment policy.`
337
+ : '';
338
+ const policyDetail = registryPolicyDetail(domain, fast);
339
+ return assessment({
340
+ state: 'unknown',
341
+ confidence: 'low',
342
+ detail: fast
343
+ ? `No enabled registration source produced a record or authoritative delegation. A fast scan cannot determine registration status.${disabledDetail}${policyDetail}`
344
+ : whoisParsed && whoisParsed.failedHop
345
+ ? `WHOIS was inconclusive - a referral hop did not answer conclusively (${whoisParsed.failedHop}).${policyDetail}`
346
+ : `No enabled registration source returned conclusive data or an authoritative DNS delegation.${disabledDetail}${policyDetail}`,
347
+ ...(!fast && whoisEnabled ? { source: 'whois' } : {}),
348
+ });
349
+ }
350
+ const domainAgeDays = computeAgeDays(createdDateIso || createdDate);
351
+ const expiresInDays = computeDaysUntil(expiryDateIso || expiryDate);
352
+ // DNS proves delegation only; it says nothing about whether registry
353
+ // contact data is privacy-protected or merely unavailable.
354
+ const privacyProtected = registrationSource === 'dns' ? null : isPrivacyProtected(registrant);
355
+ const baseInfo = {
356
+ nameservers,
357
+ statuses,
358
+ registrar,
359
+ registrant,
360
+ abuse,
361
+ createdDate,
362
+ expiryDate,
363
+ createdDateIso,
364
+ expiryDateIso,
365
+ rdapServer,
366
+ domainAgeDays,
367
+ expiresInDays,
368
+ privacyProtected,
369
+ dnssec,
370
+ source: registrationSource,
371
+ };
372
+ if (statuses.some((status) => {
373
+ const normalizedStatus = registryStatusToken(status);
374
+ return normalizedStatus.includes('pendingdelete') || normalizedStatus.includes('redemptionperiod');
375
+ })) {
376
+ return assessment({
377
+ state: 'expiring',
378
+ confidence: 'medium',
379
+ detail: 'Domain is in redemption/pending-delete status and may become available soon.',
380
+ ...baseInfo,
381
+ });
382
+ }
383
+ return assessment({
384
+ state: 'registered',
385
+ confidence: registrationConfidence,
386
+ detail: registrationSource === 'dns'
387
+ ? 'Authoritative DNS delegation confirms the domain is registered, but RDAP/WHOIS registration details were unavailable.'
388
+ : 'Domain is registered. Run a deep check for parking/for-sale detection.',
389
+ ...baseInfo,
390
+ });
391
+ }
392
+ export { resolveDomainRegistration, checkDnsDelegation, isPrivacyProtected };