@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
@@ -7,6 +7,8 @@
7
7
  import { compareRegistrySources } from "./registry-comparison.mjs";
8
8
  import { inspectRdapCapabilities } from "./rdap-capabilities.mjs";
9
9
  import { isRecord, recordOrEmpty } from "./json-record.mjs";
10
+ import { MAX_RDAP_REDACTIONS, MAX_RDAP_STATUSES } from "./rdap-types.mjs";
11
+ import { MAX_WHOIS_STATUSES } from "./whois-contracts.mjs";
10
12
  export const REGISTRY_INSIGHTS_VERSION = 1;
11
13
  export const MAX_REGISTRY_INSIGHT_STATUSES = 40;
12
14
  export const MAX_REGISTRY_INSIGHT_ISSUES = 12;
@@ -37,6 +39,20 @@ function strings(value, maximum = MAX_REGISTRY_INSIGHT_STATUSES) {
37
39
  function normalizedStatus(value) {
38
40
  return text(value, 160).toLowerCase().replace(/[^\p{L}\p{N}]+/gu, '');
39
41
  }
42
+ function statusInventory(value, maximum) {
43
+ if (!Array.isArray(value))
44
+ return { values: [], partial: value !== undefined };
45
+ const unique = new Set();
46
+ let partial = value.length > maximum;
47
+ for (const item of value.slice(0, maximum)) {
48
+ if (typeof item !== 'string' || item.length > 160 || CONTROL_RE.test(item) || !item.trim()) {
49
+ partial = true;
50
+ continue;
51
+ }
52
+ unique.add(item.trim());
53
+ }
54
+ return { values: [...unique], partial };
55
+ }
40
56
  function sourceState(status, partial) {
41
57
  if (partial || status === 'partial')
42
58
  return 'partial';
@@ -45,9 +61,13 @@ function sourceState(status, partial) {
45
61
  ? 'complete'
46
62
  : 'unavailable';
47
63
  }
48
- function redactionBlob(parsed) {
49
- const redactions = Array.isArray(parsed.redactions) ? parsed.redactions.slice(0, 50) : [];
50
- return redactions.map((item) => {
64
+ function registrantRedactionBlob(parsed) {
65
+ const redactions = Array.isArray(parsed.redactions) ? parsed.redactions.slice(0, MAX_RDAP_REDACTIONS) : [];
66
+ return redactions.filter((item) => {
67
+ // A path or reason can mention a role without selecting it. Do not infer
68
+ // registrant scope from arbitrary JSONPath expressions or other prose.
69
+ return /^(?:registry )?registrant(?:\b|$)/iu.test(text(record(item).name, 240));
70
+ }).map((item) => {
51
71
  const value = record(item);
52
72
  return [
53
73
  value.name,
@@ -92,7 +112,8 @@ function disclosure(source, parsedValue, status) {
92
112
  ? parsed.chainStatus === 'partial' || strings(parsed.fieldsTruncated, 20).length > 0
93
113
  : parsed.serverTruncated === true
94
114
  || parsed.entitiesTruncated === true
95
- || parsed.statusesTruncated === true;
115
+ || parsed.statusesTruncated === true
116
+ || parsed.redactionsTruncated === true;
96
117
  const publicationState = sourceState(status, partial);
97
118
  if (publicationState === 'unavailable') {
98
119
  return {
@@ -110,7 +131,7 @@ function disclosure(source, parsedValue, status) {
110
131
  parsed.registrantPhone,
111
132
  parsed.registrantAddress,
112
133
  ].map((part) => text(part, 320)).filter(Boolean).join(' ');
113
- const redactions = redactionBlob(parsed);
134
+ const redactions = registrantRedactionBlob(parsed);
114
135
  const combined = `${registrant} ${redactions}`.trim();
115
136
  if (WITHHELD_RE.test(combined)) {
116
137
  return { source, state: 'withheld', detail: 'The publication indicates that registrant data was withheld or not disclosed.' };
@@ -124,6 +145,13 @@ function disclosure(source, parsedValue, status) {
124
145
  if (registrant) {
125
146
  return { source, state: 'public', detail: 'The source published at least one usable registrant contact field.' };
126
147
  }
148
+ if (Array.isArray(parsed.redactions) && parsed.redactions.length > 0) {
149
+ return {
150
+ source,
151
+ state: 'unavailable',
152
+ detail: 'Redaction declarations do not establish registrant scope, so missing registrant fields remain unclassified.',
153
+ };
154
+ }
127
155
  if (publicationState === 'partial') {
128
156
  return {
129
157
  source,
@@ -137,9 +165,9 @@ function disclosure(source, parsedValue, status) {
137
165
  detail: 'The source completed without a usable registrant contact or an explicit disclosure marker.',
138
166
  };
139
167
  }
140
- function lifecycle(statusesValue, evidenceState) {
141
- const rawStatuses = strings(statusesValue);
142
- const normalized = rawStatuses.map(normalizedStatus);
168
+ function lifecycle(statuses, evidenceState) {
169
+ const rawStatuses = statuses.slice(0, MAX_REGISTRY_INSIGHT_STATUSES);
170
+ const normalized = statuses.map(normalizedStatus);
143
171
  const has = (value) => normalized.includes(value);
144
172
  const observedState = (value) => has(value) ? true : evidenceState === 'complete' ? false : null;
145
173
  const pendingDelete = observedState('pendingdelete');
@@ -200,16 +228,20 @@ function lifecycle(statusesValue, evidenceState) {
200
228
  serverStatuses: serverLocks,
201
229
  },
202
230
  acquisitionPath,
203
- limitation: 'EPP statuses describe the current registry lifecycle. They do not guarantee deletion, release timing, transfer completion, eligibility, price, or acquisition success.',
231
+ limitation: 'EPP statuses describe the current registry lifecycle. They do not guarantee deletion, release timing, transfer completion, eligibility, price, or acquisition success.'
232
+ + (statuses.length > rawStatuses.length ? ` The display lists ${rawStatuses.length} of ${statuses.length} distinct statuses; lifecycle decisions use the full admitted inventory.` : ''),
204
233
  };
205
234
  }
206
- function publicationDiagnostic(source, parsedValue, status, observedAt) {
235
+ function publicationDiagnostic(source, parsedValue, status, observedAt, statuses) {
207
236
  const usablePublication = isRecord(parsedValue);
208
237
  const parsed = record(parsedValue);
209
238
  const partial = parsed.serverTruncated === true
210
239
  || parsed.chainStatus === 'partial'
211
240
  || parsed.entitiesTruncated === true
212
241
  || parsed.statusesTruncated === true
242
+ || statuses.partial
243
+ || parsed.eventsTruncated === true
244
+ || parsed.redactionsTruncated === true
213
245
  || strings(parsed.fieldsTruncated, 20).length > 0;
214
246
  const state = usablePublication ? sourceState(status, partial) : 'unavailable';
215
247
  const issues = [];
@@ -219,14 +251,18 @@ function publicationDiagnostic(source, parsedValue, status, observedAt) {
219
251
  issues.push('The RDAP server declared a truncated response.');
220
252
  if (parsed.entitiesTruncated === true)
221
253
  issues.push('The normalised entity inventory was capped.');
222
- if (parsed.statusesTruncated === true)
223
- issues.push('The normalised status inventory was capped.');
254
+ if (parsed.statusesTruncated === true || statuses.partial)
255
+ issues.push('The normalised status inventory contains omitted or rejected values.');
256
+ if (parsed.eventsTruncated === true)
257
+ issues.push('The normalised event inventory contains omitted or rejected fields.');
258
+ if (parsed.redactionsTruncated === true)
259
+ issues.push('The normalised redaction inventory contains omitted or rejected declarations.');
224
260
  if (strings(parsed.fieldsTruncated, 20).length)
225
261
  issues.push('One or more normalised WHOIS fields were capped.');
226
262
  if (parsed.chainStatus === 'partial')
227
263
  issues.push('The WHOIS referral chain was incomplete or conflicting.');
228
264
  const conformance = strings(parsed.conformance, 30);
229
- const redactions = Array.isArray(parsed.redactions) ? parsed.redactions.slice(0, 50) : [];
265
+ const redactions = Array.isArray(parsed.redactions) ? parsed.redactions.slice(0, MAX_RDAP_REDACTIONS) : [];
230
266
  return {
231
267
  source,
232
268
  state,
@@ -277,14 +313,16 @@ export function buildRegistryInsights(input) {
277
313
  : counts.equivalent
278
314
  ? 'consistent'
279
315
  : 'unavailable';
316
+ const rdapStatuses = statusInventory(rdap.statuses, MAX_RDAP_STATUSES);
317
+ const whoisStatuses = statusInventory(whois.statuses, MAX_WHOIS_STATUSES);
280
318
  const publications = [
281
- publicationDiagnostic('registry_rdap', input.rdapParsed, input.rdapStatus, input.rdapFetchedAt),
282
- publicationDiagnostic('whois', input.whoisParsed, input.whoisStatus, input.whoisQueriedAt),
283
- publicationDiagnostic('registrar_rdap', input.registrarRdapParsed, input.registrarRdapStatus, input.registrarRdapFetchedAt),
319
+ publicationDiagnostic('registry_rdap', input.rdapParsed, input.rdapStatus, input.rdapFetchedAt, rdapStatuses),
320
+ publicationDiagnostic('whois', input.whoisParsed, input.whoisStatus, input.whoisQueriedAt, whoisStatuses),
321
+ publicationDiagnostic('registrar_rdap', input.registrarRdapParsed, input.registrarRdapStatus, input.registrarRdapFetchedAt, statusInventory(registrarRdap.statuses, MAX_RDAP_STATUSES)),
284
322
  ];
285
323
  const lifecycleCandidates = [
286
- { statuses: strings(rdap.statuses), state: publications[0].state },
287
- { statuses: strings(whois.statuses), state: publications[1].state },
324
+ { statuses: rdapStatuses.values, state: publications[0].state },
325
+ { statuses: whoisStatuses.values, state: publications[1].state },
288
326
  ].filter((candidate) => candidate.state !== 'unavailable');
289
327
  const lifecycleSource = lifecycleCandidates.find((candidate) => candidate.statuses.length)
290
328
  ?? lifecycleCandidates[0]
@@ -1,7 +1,7 @@
1
1
  // Privacy-minimized interpretation of selected response policies. Header and
2
2
  // cookie values are read transiently from the already-selected homepage
3
3
  // response and reduced to fixed signal identifiers plus bounded counts.
4
- export const RESPONSE_POLICY_VERSION = 2;
4
+ export const RESPONSE_POLICY_VERSION = 3;
5
5
  export const MAX_RESPONSE_POLICY_HEADER_BYTES = 8 * 1024;
6
6
  export const MAX_CSP_META_POLICIES = 4;
7
7
  export const MAX_RESPONSE_POLICY_DIRECTIVES = 64;
@@ -13,7 +13,9 @@ export const MIN_RECOMMENDED_HSTS_SECONDS = 180 * 24 * 60 * 60;
13
13
  const CONTROL_RE = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
14
14
  const DIRECTIVE_NAME_RE = /^[a-z][a-z0-9-]{0,63}$/;
15
15
  const ATTRIBUTE_NAME_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]{1,128}$/;
16
- const CSP_NONCE_OR_HASH_RE = /^'(?:nonce-[^']+|sha(?:256|384|512)-[^']+)'$/i;
16
+ // CSP source-list grammar, not an entropy or digest-strength judgement. Invalid
17
+ // source expressions are ignored by browsers and cannot suppress unsafe-inline.
18
+ const CSP_NONCE_OR_HASH_RE = /^'(?:nonce-|sha(?:256|384|512)-)[A-Za-z0-9+/_-]+={0,2}'$/i;
17
19
  const REFERRER_POLICIES = new Set([
18
20
  'no-referrer',
19
21
  'no-referrer-when-downgrade',
@@ -42,7 +44,8 @@ function readHeader(headers, name) {
42
44
  return { state: 'absent', value: null };
43
45
  if (CONTROL_RE.test(raw))
44
46
  return { state: 'malformed', value: null };
45
- if (new TextEncoder().encode(raw).byteLength > MAX_RESPONSE_POLICY_HEADER_BYTES) {
47
+ if (raw.length > MAX_RESPONSE_POLICY_HEADER_BYTES
48
+ || new TextEncoder().encode(raw).byteLength > MAX_RESPONSE_POLICY_HEADER_BYTES) {
46
49
  return { state: 'partial', value: null };
47
50
  }
48
51
  return { state: 'present', value: raw.trim() };
@@ -55,61 +58,96 @@ function addSignal(signals, id, count) {
55
58
  function unavailableHeaderState(header) {
56
59
  return header.state === 'present' ? 'malformed' : header.state;
57
60
  }
58
- function analyzeCsp(header, signals) {
61
+ function inlineControl(sources, scriptsBlocked) {
62
+ if (scriptsBlocked)
63
+ return 'restricted';
64
+ if (!sources)
65
+ return 'uncontrolled';
66
+ return sources.includes("'unsafe-inline'")
67
+ && !sources.includes("'strict-dynamic'")
68
+ && !sources.some((token) => CSP_NONCE_OR_HASH_RE.test(token))
69
+ ? 'unqualified'
70
+ : 'restricted';
71
+ }
72
+ function intersectInlineControls(controls) {
73
+ if (controls.includes('restricted'))
74
+ return 'restricted';
75
+ if (controls.includes('unknown'))
76
+ return 'unknown';
77
+ return controls.includes('unqualified') ? 'unqualified' : 'uncontrolled';
78
+ }
79
+ function analyzeCsp(header, signals, fromMeta = false) {
59
80
  if (header.state !== 'present' || !header.value) {
60
81
  return { state: unavailableHeaderState(header), inlineControl: 'unknown' };
61
82
  }
62
- const directives = new Map();
83
+ // Fetch combines repeated policy headers with commas. Each policy is
84
+ // independently enforced; later policies cannot loosen an earlier one.
85
+ const policies = [];
86
+ let directiveCount = 0;
63
87
  let tokenCount = 0;
64
- let partial = false;
65
- for (const rawDirective of header.value.split(';')) {
66
- const normalized = rawDirective.trim();
67
- if (!normalized)
68
- continue;
69
- const [rawName = '', ...rawTokens] = normalized.split(/\s+/u);
70
- const name = rawName.toLowerCase();
71
- if (!DIRECTIVE_NAME_RE.test(name))
72
- return { state: 'malformed', inlineControl: 'unknown' };
73
- if (directives.has(name))
74
- continue;
75
- if (directives.size >= MAX_RESPONSE_POLICY_DIRECTIVES) {
76
- partial = true;
77
- break;
88
+ for (const rawPolicy of (fromMeta ? [header.value] : header.value.split(','))) {
89
+ const directives = new Map();
90
+ for (const rawDirective of rawPolicy.split(';')) {
91
+ const normalized = rawDirective.trim();
92
+ if (!normalized)
93
+ continue;
94
+ const [rawName = '', ...rawTokens] = normalized.split(/[\t\n\f\r ]+/u);
95
+ const name = rawName.toLowerCase();
96
+ if (!DIRECTIVE_NAME_RE.test(name))
97
+ return { state: 'malformed', inlineControl: 'unknown' };
98
+ // Duplicate directives use their first value. Count even ignored input
99
+ // against the aggregate work bound rather than resetting per policy.
100
+ directiveCount += 1;
101
+ tokenCount += rawTokens.length;
102
+ if (directiveCount > MAX_RESPONSE_POLICY_DIRECTIVES || tokenCount > MAX_RESPONSE_POLICY_TOKENS) {
103
+ return { state: 'partial', inlineControl: 'unknown' };
104
+ }
105
+ if (!directives.has(name))
106
+ directives.set(name, rawTokens.map((token) => token.toLowerCase()));
78
107
  }
79
- const remaining = Math.max(0, MAX_RESPONSE_POLICY_TOKENS - tokenCount);
80
- const tokens = rawTokens.slice(0, remaining).map((token) => token.toLowerCase());
81
- if (tokens.length !== rawTokens.length)
82
- partial = true;
83
- tokenCount += tokens.length;
84
- directives.set(name, tokens);
108
+ if (directives.size)
109
+ policies.push(directives);
85
110
  }
86
- if (!directives.size)
111
+ if (!policies.length)
87
112
  return { state: 'malformed', inlineControl: 'unknown' };
88
- const defaultSources = directives.get('default-src');
89
- const scriptSources = directives.get('script-src') || defaultSources;
90
- if (!defaultSources)
113
+ if (policies.every((policy) => !policy.has('default-src')))
91
114
  addSignal(signals, 'csp_default_source_missing');
92
- if (!directives.has('base-uri'))
115
+ if (policies.every((policy) => !policy.has('base-uri')))
93
116
  addSignal(signals, 'csp_base_uri_missing');
94
- if (!directives.has('object-src') && !defaultSources)
117
+ if (policies.every((policy) => !policy.has('object-src') && !policy.has('default-src'))) {
95
118
  addSignal(signals, 'csp_object_source_unbounded');
96
- let inlineControl = scriptSources ? 'restricted' : 'uncontrolled';
97
- if (scriptSources) {
98
- if (scriptSources.some((token) => ['*', 'http:', 'https:', 'data:', 'blob:'].includes(token))) {
99
- addSignal(signals, 'csp_permissive_script_source');
100
- }
101
- if (scriptSources.includes("'unsafe-eval'"))
102
- addSignal(signals, 'csp_unsafe_eval');
103
- if (scriptSources.includes("'unsafe-inline'")
104
- && !scriptSources.some((token) => CSP_NONCE_OR_HASH_RE.test(token))) {
105
- addSignal(signals, 'csp_unsafe_inline');
106
- inlineControl = 'unqualified';
107
- }
108
119
  }
109
- return {
110
- state: partial ? 'partial' : 'parsed',
111
- inlineControl: partial ? 'unknown' : inlineControl,
112
- };
120
+ const contexts = policies.map((policy) => {
121
+ const scripts = policy.get('script-src') ?? policy.get('default-src');
122
+ const elements = policy.get('script-src-elem') ?? scripts;
123
+ const attributes = policy.get('script-src-attr') ?? scripts;
124
+ const sandbox = fromMeta ? undefined : policy.get('sandbox');
125
+ const scriptsBlocked = sandbox !== undefined && !sandbox.includes('allow-scripts');
126
+ return {
127
+ blocks: inlineControl(elements, scriptsBlocked),
128
+ attributes: inlineControl(attributes, scriptsBlocked),
129
+ // script-src-elem/attr never govern evaluation, even when they contain
130
+ // an unsafe-eval token. An absent script/default source is uncontrolled.
131
+ evaluation: !scriptsBlocked && (!scripts || scripts.includes("'unsafe-eval'")),
132
+ declaresEvaluation: scripts?.includes("'unsafe-eval'") === true,
133
+ broadSource: !scriptsBlocked && (!elements || (!elements.includes("'strict-dynamic'")
134
+ && elements.some((token) => ['*', 'http:', 'https:', 'data:', 'blob:'].includes(token)))),
135
+ declaresBroadSource: elements !== undefined,
136
+ };
137
+ });
138
+ if (contexts.every((context) => context.broadSource) && contexts.some((context) => context.declaresBroadSource)) {
139
+ addSignal(signals, 'csp_permissive_script_source');
140
+ }
141
+ if (contexts.every((context) => context.evaluation) && contexts.some((context) => context.declaresEvaluation)) {
142
+ addSignal(signals, 'csp_unsafe_eval');
143
+ }
144
+ const blocks = intersectInlineControls(contexts.map((context) => context.blocks));
145
+ if (blocks === 'unqualified')
146
+ addSignal(signals, 'csp_unsafe_inline');
147
+ if (intersectInlineControls(contexts.map((context) => context.attributes)) === 'unqualified') {
148
+ addSignal(signals, 'csp_unsafe_inline_attributes');
149
+ }
150
+ return { state: 'parsed', inlineControl: blocks };
113
151
  }
114
152
  export function analyzeCspMetaPolicies(value, sourceTruncated = false) {
115
153
  const candidates = Array.isArray(value) ? value : [];
@@ -127,7 +165,7 @@ export function analyzeCspMetaPolicies(value, sourceTruncated = false) {
127
165
  }
128
166
  const policyContent = policy.content;
129
167
  const content = readHeader({ get: () => policyContent }, 'content-security-policy');
130
- const analysis = analyzeCsp(content, []);
168
+ const analysis = analyzeCsp(content, [], true);
131
169
  if (analysis.state !== 'parsed') {
132
170
  truncated = true;
133
171
  continue;