@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
@@ -1,10 +1,22 @@
1
+ import { runIntakeCommand } from "./intake-command.mjs";
2
+ import { runIndicatorSetCommand } from "./indicator-set-command.mjs";
3
+ import { CONTEXT_REVIEW_KINDS } from "../packages/contracts/context-review.mjs";
4
+ import { readBoundedRegularFile } from "../lib/bounded-file.mjs";
5
+ import { MAX_ENCRYPTED_INVESTIGATION_PACKAGE_BYTES } from "../packages/contracts/investigation-package-limits.mjs";
6
+ import { verifyOfflineInvestigationFolder, verifyOfflineInvestigationPackage } from "./investigation-package-review.mjs";
7
+ import { readInvestigationFolder } from "./investigation-folder.mjs";
8
+ import { readBagItFolder } from "./bagit-folder.mjs";
9
+ import { verifyOfflineBagIt } from "./bagit-review.mjs";
10
+ import { MAX_BAGIT_ZIP_BYTES } from "../packages/interchange/bagit.mjs";
1
11
  import { MAX_OFFLINE_ARTIFACT_BYTES, formatOfflineArtifactVerification, isCompleteOfflineArtifactVerification, verifyOfflineArtifact, } from "./artifact-verify.mjs";
2
12
  import { MAX_CASE_PACK_INPUT_BYTES, buildCliCasePack, formatCliCasePack } from "./case-pack.mjs";
3
13
  import { MAX_COMPARE_INPUT_BYTES, compareLookupDocument, parseCliLookupDocument, } from "./compare.mjs";
4
14
  import { boundedCliErrorMessage, CliUsageError } from "./errors.mjs";
15
+ import { LOCAL_MMDB_REVIEW_SCHEMA, LOCAL_MMDB_REVIEW_VERSION } from "./local-mmdb-review.mjs";
5
16
  import EXIT_CODES from "./exit-codes.mjs";
6
17
  import { MAX_OFFLINE_EVIDENCE_INPUT_BYTES, buildOfflineEvidenceReview, buildOfflineEvidenceReviewWithLocalResources, formatOfflineEvidenceReview, } from "./offline-evidence-review.mjs";
7
18
  import { buildCliLookupBrief, formatCliLookupBrief } from "./lookup-brief.mjs";
19
+ import { runCaseCommand } from "./case-command.mjs";
8
20
  import { MAX_MAIL_REVIEW_INPUT_BYTES, buildCliMailReview, formatCliMailReview, } from "./mail-review.mjs";
9
21
  import { MAX_MAIL_HEADER_INPUT_BYTES, buildCliMailHeaderReview, formatCliMailHeaderReview, } from "./mail-header-review.mjs";
10
22
  import { buildCliPageComparison, formatCliPageComparison } from "./page-compare.mjs";
@@ -15,21 +27,67 @@ import { formatTerminalCompare } from "./formatters/terminal.mjs";
15
27
  import { MAX_SAVED_LOOKUP_INPUT_BYTES } from "./saved-lookup.mjs";
16
28
  import { REVIEW_INLINE_COMMANDS } from "./inline-command-families.mjs";
17
29
  import { runDiscriminatedCommandHandler } from "./discriminated-command-handlers.mjs";
18
- async function runVerifyArtifactCommand(args, dependencies, context) {
19
- context.setFailureLabel('Artefact verification');
30
+ /** Readers retain their command-specific bounds and injection points. Only
31
+ * usage-error presentation and empty-input handling are shared. */
32
+ async function readReviewInput(read, label, emptyMessage) {
20
33
  let input;
21
34
  try {
22
- input = dependencies.readArtifactInput
23
- ? await dependencies.readArtifactInput(args.source)
24
- : await context.readInput(args.source, MAX_OFFLINE_ARTIFACT_BYTES, 'Artefact input');
35
+ input = await read();
25
36
  }
26
37
  catch (error) {
27
38
  if (error instanceof CliUsageError)
28
39
  throw error;
29
- throw new CliUsageError(`Could not read artifact input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
40
+ throw new CliUsageError(`Could not read ${label}: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
30
41
  }
31
- if (!input.trim())
32
- throw new CliUsageError('verify-artifact requires one JSON file or an artefact on stdin.');
42
+ if (emptyMessage && !input.trim())
43
+ throw new CliUsageError(emptyMessage);
44
+ return input;
45
+ }
46
+ function writeReviewReport(context, args, report, format) {
47
+ if (!args.quiet)
48
+ context.writeStdout(args.output === 'json'
49
+ ? formatJsonDocument(report)
50
+ : context.terminal(format(report), args.color));
51
+ }
52
+ async function runVerifyArtifactCommand(args, dependencies, context) {
53
+ context.setFailureLabel('Artefact verification');
54
+ if (args.folder) {
55
+ let files;
56
+ try {
57
+ files = await (args.bagit ? readBagItFolder : readInvestigationFolder)(args.folder, dependencies.signal);
58
+ }
59
+ catch (cause) {
60
+ dependencies.signal?.throwIfAborted();
61
+ const reason = cause instanceof TypeError ? boundedCliErrorMessage(cause) : 'The selected folder is unavailable or could not be read.';
62
+ throw new CliUsageError(`Could not read evidence folder: ${reason}`);
63
+ }
64
+ const report = await (args.bagit ? verifyOfflineBagIt : verifyOfflineInvestigationFolder)(files);
65
+ dependencies.signal?.throwIfAborted();
66
+ writeReviewReport(context, args, report, formatOfflineArtifactVerification);
67
+ return args.strictExit && !isCompleteOfflineArtifactVerification(report) ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
68
+ }
69
+ if (args.package) {
70
+ if (!args.source || args.source === '-')
71
+ throw new CliUsageError('--package requires a selected ZIP file; binary stdin is not accepted.');
72
+ let bytes;
73
+ try {
74
+ bytes = dependencies.readBinaryArtifactInput ? await dependencies.readBinaryArtifactInput(args.source)
75
+ : await readBoundedRegularFile(args.source, { maximumBytes: args.bagit ? MAX_BAGIT_ZIP_BYTES : MAX_ENCRYPTED_INVESTIGATION_PACKAGE_BYTES, minimumBytes: 22, label: 'Investigation package', ...(dependencies.signal ? { signal: dependencies.signal } : {}) });
76
+ }
77
+ catch (error) {
78
+ if (error instanceof CliUsageError)
79
+ throw error;
80
+ throw new CliUsageError(`Could not read package input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
81
+ }
82
+ const passphrase = args.passphraseSource ? await context.readPassphraseSource(args.passphraseSource) : undefined;
83
+ const report = args.bagit ? await verifyOfflineBagIt(bytes) : await verifyOfflineInvestigationPackage(bytes, passphrase);
84
+ dependencies.signal?.throwIfAborted();
85
+ writeReviewReport(context, args, report, formatOfflineArtifactVerification);
86
+ return args.strictExit && !isCompleteOfflineArtifactVerification(report) ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
87
+ }
88
+ const input = await readReviewInput(async () => dependencies.readArtifactInput
89
+ ? await dependencies.readArtifactInput(args.source)
90
+ : await context.readInput(args.source, MAX_OFFLINE_ARTIFACT_BYTES, 'Artefact input'), 'artifact input', 'verify-artifact requires one JSON file or an artefact on stdin.');
33
91
  const passphrase = args.passphraseSource ? await context.readPassphraseSource(args.passphraseSource) : null;
34
92
  let manifest = null;
35
93
  if (args.manifestSource && args.manifestEntryId) {
@@ -49,90 +107,44 @@ async function runVerifyArtifactCommand(args, dependencies, context) {
49
107
  manifest = Object.freeze({ raw, entryId: args.manifestEntryId });
50
108
  }
51
109
  const report = await verifyOfflineArtifact(input, { passphrase, manifest });
52
- if (!args.quiet) {
53
- context.writeStdout(args.output === 'json'
54
- ? formatJsonDocument(report)
55
- : context.terminal(formatOfflineArtifactVerification(report), args.color));
56
- }
110
+ writeReviewReport(context, args, report, formatOfflineArtifactVerification);
57
111
  return args.strictExit && !isCompleteOfflineArtifactVerification(report)
58
112
  ? EXIT_CODES.PARTIAL_FAILURE
59
113
  : EXIT_CODES.SUCCESS;
60
114
  }
61
115
  async function runInterchangeReportCommand(args, dependencies, context) {
62
116
  context.setFailureLabel('Interchange fidelity report');
63
- let input;
64
- try {
65
- input = dependencies.readArtifactInput
66
- ? await dependencies.readArtifactInput(args.source)
67
- : await context.readInput(args.source, MAX_OFFLINE_ARTIFACT_BYTES, 'Interchange input');
68
- }
69
- catch (error) {
70
- if (error instanceof CliUsageError)
71
- throw error;
72
- throw new CliUsageError(`Could not read interchange input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
73
- }
74
- if (!input.trim())
75
- throw new CliUsageError('interchange-report requires one JSON file or an artefact on stdin.');
117
+ const input = await readReviewInput(async () => dependencies.readArtifactInput
118
+ ? await dependencies.readArtifactInput(args.source)
119
+ : await context.readInput(args.source, MAX_OFFLINE_ARTIFACT_BYTES, 'Interchange input'), 'interchange input', 'interchange-report requires one JSON file or an artefact on stdin.');
76
120
  const passphrase = args.passphraseSource ? await context.readPassphraseSource(args.passphraseSource) : null;
77
121
  const report = await buildInterchangeFidelityReport(input, {
78
122
  generatedAt: context.now(),
79
123
  passphrase,
80
124
  });
81
- if (!args.quiet) {
82
- context.writeStdout(args.output === 'json'
83
- ? formatJsonDocument(report)
84
- : context.terminal(formatInterchangeFidelityReport(report), args.color));
85
- }
125
+ writeReviewReport(context, args, report, formatInterchangeFidelityReport);
86
126
  return EXIT_CODES.SUCCESS;
87
127
  }
88
128
  async function runSourceReportCommand(args, dependencies, context) {
89
129
  context.setFailureLabel('Source reliability report');
90
- let input;
91
- try {
92
- input = dependencies.readSourceReliabilityInput
93
- ? await dependencies.readSourceReliabilityInput(args.source)
94
- : await context.readInput(args.source, MAX_SOURCE_RELIABILITY_INPUT_BYTES, 'Source reliability input');
95
- }
96
- catch (error) {
97
- if (error instanceof CliUsageError)
98
- throw error;
99
- throw new CliUsageError(`Could not read source reliability input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
100
- }
101
- if (!input.trim())
102
- throw new CliUsageError('source-report requires one JSON file or lookup documents on stdin.');
130
+ const input = await readReviewInput(async () => dependencies.readSourceReliabilityInput
131
+ ? await dependencies.readSourceReliabilityInput(args.source)
132
+ : await context.readInput(args.source, MAX_SOURCE_RELIABILITY_INPUT_BYTES, 'Source reliability input'), 'source reliability input', 'source-report requires one JSON file or lookup documents on stdin.');
103
133
  const report = buildSourceReliabilityReport(input, context.now());
104
- if (!args.quiet) {
105
- context.writeStdout(args.output === 'json'
106
- ? formatJsonDocument(report)
107
- : context.terminal(formatSourceReliabilityReport(report), args.color));
108
- }
134
+ writeReviewReport(context, args, report, formatSourceReliabilityReport);
109
135
  return EXIT_CODES.SUCCESS;
110
136
  }
111
137
  async function runCompareCommand(args, dependencies, context) {
112
138
  context.setFailureLabel('Registry comparison');
113
- let input;
114
- try {
115
- input = dependencies.readCompareInput
116
- ? await dependencies.readCompareInput(args.source)
117
- : await context.readInput(args.source, MAX_COMPARE_INPUT_BYTES, 'Comparison input');
118
- }
119
- catch (error) {
120
- if (error instanceof CliUsageError)
121
- throw error;
122
- throw new CliUsageError(`Could not read comparison input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
123
- }
124
- if (!input.trim())
125
- throw new CliUsageError('compare requires one lookup JSON file or a lookup document on stdin.');
139
+ const input = await readReviewInput(async () => dependencies.readCompareInput
140
+ ? await dependencies.readCompareInput(args.source)
141
+ : await context.readInput(args.source, MAX_COMPARE_INPUT_BYTES, 'Comparison input'), 'comparison input', 'compare requires one lookup JSON file or a lookup document on stdin.');
126
142
  const parsed = parseCliLookupDocument(input);
127
143
  const loadComparison = dependencies.loadRegistryComparison || (() => import("../lib/registry-comparison.mjs"));
128
144
  const comparisonModule = await loadComparison();
129
145
  const result = compareLookupDocument(parsed, comparisonModule.compareRegistrySources, comparisonModule.compareRdapPublications);
130
146
  const document = buildCliCompareDocument(result, context.now());
131
- if (!args.quiet) {
132
- context.writeStdout(args.output === 'json'
133
- ? formatJsonDocument(document)
134
- : context.terminal(formatTerminalCompare(document), args.color));
135
- }
147
+ writeReviewReport(context, args, document, formatTerminalCompare);
136
148
  return EXIT_CODES.SUCCESS;
137
149
  }
138
150
  async function runPageCompareCommand(args, dependencies, context) {
@@ -153,80 +165,36 @@ async function runPageCompareCommand(args, dependencies, context) {
153
165
  throw new CliUsageError(`Could not read page comparison input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
154
166
  }
155
167
  const document = buildCliPageComparison(leftInput, rightInput, context.now());
156
- if (!args.quiet) {
157
- context.writeStdout(args.output === 'json'
158
- ? formatJsonDocument(document)
159
- : context.terminal(formatCliPageComparison(document), args.color));
160
- }
168
+ writeReviewReport(context, args, document, formatCliPageComparison);
161
169
  return EXIT_CODES.SUCCESS;
162
170
  }
163
171
  async function runMailReviewCommand(args, dependencies, context) {
164
172
  context.setFailureLabel('Passive mail review');
165
- let input;
166
- try {
167
- input = dependencies.readMailReviewInput
168
- ? await dependencies.readMailReviewInput(args.source)
169
- : await context.readInput(args.source, MAX_MAIL_REVIEW_INPUT_BYTES, 'Mail review input');
170
- }
171
- catch (error) {
172
- if (error instanceof CliUsageError)
173
- throw error;
174
- throw new CliUsageError(`Could not read mail review input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
175
- }
173
+ const input = await readReviewInput(async () => dependencies.readMailReviewInput
174
+ ? await dependencies.readMailReviewInput(args.source)
175
+ : await context.readInput(args.source, MAX_MAIL_REVIEW_INPUT_BYTES, 'Mail review input'), 'mail review input');
176
176
  const document = buildCliMailReview(input, context.now());
177
- if (!args.quiet) {
178
- context.writeStdout(args.output === 'json'
179
- ? formatJsonDocument(document)
180
- : context.terminal(formatCliMailReview(document), args.color));
181
- }
177
+ writeReviewReport(context, args, document, formatCliMailReview);
182
178
  return EXIT_CODES.SUCCESS;
183
179
  }
184
180
  async function runMailHeadersCommand(args, dependencies, context) {
185
181
  context.setFailureLabel('Mail-header review');
186
- let input;
187
- try {
188
- input = dependencies.readMailHeaderInput
189
- ? await dependencies.readMailHeaderInput(args.source)
190
- : await context.readHeaderInput(args.source, MAX_MAIL_HEADER_INPUT_BYTES, 'Mail-header input');
191
- }
192
- catch (error) {
193
- if (error instanceof CliUsageError)
194
- throw error;
195
- throw new CliUsageError(`Could not read mail-header input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
196
- }
197
- if (!input.trim())
198
- throw new CliUsageError('mail-headers requires one message or header file, or headers on stdin.');
199
- const document = buildCliMailHeaderReview(input, context.now());
200
- if (!args.quiet) {
201
- context.writeStdout(args.output === 'json'
202
- ? formatJsonDocument(document)
203
- : context.terminal(formatCliMailHeaderReview(document), args.color));
204
- }
182
+ const input = await readReviewInput(async () => dependencies.readMailHeaderInput
183
+ ? await dependencies.readMailHeaderInput(args.source)
184
+ : await context.readHeaderInput(args.source, MAX_MAIL_HEADER_INPUT_BYTES, 'Mail-header input'), 'mail-header input', 'mail-headers requires one message or header file, or headers on stdin.');
185
+ const document = buildCliMailHeaderReview(input, context.now(), args.trustedAuthHeaders);
186
+ writeReviewReport(context, args, document, formatCliMailHeaderReview);
205
187
  return EXIT_CODES.SUCCESS;
206
188
  }
207
189
  async function runOfflineEvidenceReviewCommand(args, dependencies, context) {
208
190
  context.setFailureLabel('Offline evidence review');
209
- let input;
210
- try {
211
- input = dependencies.readArtifactInput
212
- ? await dependencies.readArtifactInput(args.source)
213
- : await context.readInput(args.source, MAX_OFFLINE_EVIDENCE_INPUT_BYTES, 'Offline evidence input');
214
- }
215
- catch (error) {
216
- if (error instanceof CliUsageError)
217
- throw error;
218
- throw new CliUsageError(`Could not read offline evidence input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
219
- }
220
- if (!input.trim())
221
- throw new CliUsageError('review-evidence requires one JSON file or a document on stdin.');
191
+ const input = await readReviewInput(async () => dependencies.readArtifactInput
192
+ ? await dependencies.readArtifactInput(args.source)
193
+ : await context.readInput(args.source, MAX_OFFLINE_EVIDENCE_INPUT_BYTES, 'Offline evidence input'), 'offline evidence input', 'review-evidence requires one JSON file or a document on stdin.');
222
194
  const document = args.mmdbSource
223
195
  ? await buildOfflineEvidenceReviewWithLocalResources(input, context.now(), { mmdbPath: args.mmdbSource })
224
196
  : buildOfflineEvidenceReview(input, context.now());
225
- if (!args.quiet) {
226
- context.writeStdout(args.output === 'json'
227
- ? formatJsonDocument(document)
228
- : context.terminal(formatOfflineEvidenceReview(document), args.color));
229
- }
197
+ writeReviewReport(context, args, document, formatOfflineEvidenceReview);
230
198
  if (args.strictExit) {
231
199
  const result = document.result && typeof document.result === 'object' && !Array.isArray(document.result)
232
200
  ? document.result
@@ -237,7 +205,9 @@ async function runOfflineEvidenceReviewCommand(args, dependencies, context) {
237
205
  const zoneMismatch = document.kind === 'zone_intent' && (result.complete !== true
238
206
  || (result.counts && typeof result.counts === 'object' && !Array.isArray(result.counts)
239
207
  && ['different', 'missing', 'unexpected', 'incomplete'].some((key) => Number(result.counts[key]) > 0)));
240
- if (gate?.pass === false || zoneMismatch)
208
+ const contextPartial = CONTEXT_REVIEW_KINDS.includes(document.kind) && result.state === 'partial';
209
+ const mmdbIncomplete = result.schema === LOCAL_MMDB_REVIEW_SCHEMA && result.version === LOCAL_MMDB_REVIEW_VERSION && result.completeness !== 'complete';
210
+ if (gate?.pass === false || zoneMismatch || contextPartial || mmdbIncomplete)
241
211
  return EXIT_CODES.PARTIAL_FAILURE;
242
212
  }
243
213
  return EXIT_CODES.SUCCESS;
@@ -245,34 +215,16 @@ async function runOfflineEvidenceReviewCommand(args, dependencies, context) {
245
215
  async function runBriefOrCasePackCommand(args, dependencies, context) {
246
216
  const isBrief = args.action === 'brief';
247
217
  context.setFailureLabel(isBrief ? 'Lookup brief' : 'Case pack');
248
- let input;
249
- try {
250
- input = dependencies.readArtifactInput
251
- ? await dependencies.readArtifactInput(args.source)
252
- : await context.readInput(args.source, isBrief ? MAX_SAVED_LOOKUP_INPUT_BYTES : MAX_CASE_PACK_INPUT_BYTES, isBrief ? 'Lookup brief input' : 'Case-pack input');
253
- }
254
- catch (error) {
255
- if (error instanceof CliUsageError)
256
- throw error;
257
- throw new CliUsageError(`Could not read ${isBrief ? 'Lookup brief' : 'case-pack'} input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
258
- }
259
- if (!input.trim())
260
- throw new CliUsageError(`${args.action} requires one JSON file or a document on stdin.`);
218
+ const input = await readReviewInput(async () => dependencies.readArtifactInput
219
+ ? await dependencies.readArtifactInput(args.source)
220
+ : await context.readInput(args.source, isBrief ? MAX_SAVED_LOOKUP_INPUT_BYTES : MAX_CASE_PACK_INPUT_BYTES, isBrief ? 'Lookup brief input' : 'Case-pack input'), `${isBrief ? 'Lookup brief' : 'case-pack'} input`, `${args.action} requires one JSON file or a document on stdin.`);
261
221
  if (args.action === 'brief') {
262
222
  const document = buildCliLookupBrief(input, context.now());
263
- if (!args.quiet) {
264
- context.writeStdout(args.output === 'json'
265
- ? formatJsonDocument(document)
266
- : context.terminal(formatCliLookupBrief(document), args.color));
267
- }
223
+ writeReviewReport(context, args, document, formatCliLookupBrief);
268
224
  }
269
225
  else {
270
226
  const document = buildCliCasePack(input, { audience: args.audience, reviewed: args.reviewed }, context.now());
271
- if (!args.quiet) {
272
- context.writeStdout(args.output === 'json'
273
- ? formatJsonDocument(document)
274
- : context.terminal(formatCliCasePack(document), args.color));
275
- }
227
+ writeReviewReport(context, args, document, formatCliCasePack);
276
228
  }
277
229
  return EXIT_CODES.SUCCESS;
278
230
  }
@@ -284,9 +236,12 @@ const REVIEW_COMMAND_HANDLERS = Object.freeze({
284
236
  'page-compare': runPageCompareCommand,
285
237
  'mail-review': runMailReviewCommand,
286
238
  'mail-headers': runMailHeadersCommand,
239
+ intake: runIntakeCommand,
287
240
  'review-evidence': runOfflineEvidenceReviewCommand,
288
241
  'brief': runBriefOrCasePackCommand,
242
+ 'case': runCaseCommand,
289
243
  'case-pack': runBriefOrCasePackCommand,
244
+ 'indicator-set': runIndicatorSetCommand,
290
245
  });
291
246
  function runReviewCommand(args, dependencies, context) {
292
247
  return runDiscriminatedCommandHandler(REVIEW_COMMAND_HANDLERS, args, dependencies, context);
@@ -1,14 +1,14 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { decodeBoundedUtf8 } from "../lib/bounded-file.mjs";
3
- import { isValidAsciiDomainName } from "../lib/hostname.mjs";
4
- import { scanBoundedJson } from "../lib/bounded-json.mjs";
5
- import { canonicalRegistrableDomain } from "../lib/registrable-domain.mjs";
6
- import { ANALYST_REVIEW_REASON_VALUES, analystInteroperabilityTags } from "../lib/analyst-taxonomy.mjs";
7
- import { buildRiskCalibrationSummaryReport, parseRiskCalibrationSummaryReport, RISK_CALIBRATION_SUMMARY_INTERPRETATION, RISK_CALIBRATION_SUMMARY_THRESHOLDS, } from "../lib/risk-calibration-summary.mjs";
3
+ import { isValidAsciiDomainName } from "../packages/contracts/domain-name.mjs";
4
+ import { scanBoundedJson } from "../packages/analysis/bounded-json.mjs";
5
+ import { canonicalRegistrableDomain } from "../packages/analysis/registrable-domain.mjs";
6
+ import { ANALYST_REVIEW_REASON_VALUES, analystInteroperabilityTags } from "../packages/analysis/analyst-taxonomy.mjs";
7
+ import { buildRiskCalibrationSummaryReport, parseRiskCalibrationSummaryReport, RISK_CALIBRATION_SUMMARY_INTERPRETATION, RISK_CALIBRATION_SUMMARY_THRESHOLDS, } from "../packages/analysis/risk-calibration-summary.mjs";
8
8
  import { MAX_RISK_CALIBRATION_ACTIVITY_LENGTH, MAX_RISK_CALIBRATION_AVAILABILITY_LENGTH, MAX_RISK_CALIBRATION_DISPOSITION_LENGTH, MAX_RISK_CALIBRATION_DOMAIN_AGE_DAYS, MAX_RISK_CALIBRATION_DOMAIN_LENGTH, MAX_RISK_CALIBRATION_FINDING_CATEGORY_LENGTH, MAX_RISK_CALIBRATION_FINDINGS_PER_PROVIDER, MAX_RISK_CALIBRATION_INPUT_BYTES, MAX_RISK_CALIBRATION_JSON_CONTAINER_ITEMS, MAX_RISK_CALIBRATION_JSON_DEPTH, MAX_RISK_CALIBRATION_JSON_KEYS, MAX_RISK_CALIBRATION_JSON_VALUES, MAX_RISK_CALIBRATION_MUTATIONS, MAX_RISK_CALIBRATION_PROVIDER_ID_LENGTH, MAX_RISK_CALIBRATION_PROVIDER_STATE_LENGTH, MAX_RISK_CALIBRATION_PROVIDERS, MAX_RISK_CALIBRATION_RECORD_ID_LENGTH, MAX_RISK_CALIBRATION_RECORDS, MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH, MAX_RISK_CALIBRATION_SCAN_DEPTH_LENGTH, MAX_RISK_CALIBRATION_STRING_LENGTH, MAX_RISK_CALIBRATION_TIMESTAMP_LENGTH, RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_DATASET_VERSION, RISK_CALIBRATION_REPORT_SCHEMA, RISK_CALIBRATION_REPORT_VERSION, RISK_CALIBRATION_DISPOSITIONS, serializeRiskCalibrationSnapshot, snapshotRiskCalibrationReportForSerialization, SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS, } from "../packages/contracts/risk-calibration.mjs";
9
- import { CliUsageError } from "./arguments.mjs";
10
- import { RISK_MUTATION_TYPES } from "../lib/risk-scoring.mjs";
11
- import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_ENVELOPE_VERSION, THREAT_INTELLIGENCE_SCHEMA, } from "../lib/threat-intelligence-types.mjs";
9
+ import { CliUsageError } from "./errors.mjs";
10
+ import { RISK_MUTATION_TYPES } from "../packages/analysis/risk-scoring.mjs";
11
+ import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_ENVELOPE_VERSION, THREAT_INTELLIGENCE_SCHEMA, } from "../packages/analysis/threat-intelligence-types.mjs";
12
12
  export { MAX_RISK_CALIBRATION_INPUT_BYTES, MAX_RISK_CALIBRATION_RECORDS, MAX_RISK_CALIBRATION_STRING_LENGTH, RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_DATASET_VERSION, RISK_CALIBRATION_REPORT_SCHEMA, RISK_CALIBRATION_REPORT_VERSION, SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS, } from "../packages/contracts/risk-calibration.mjs";
13
13
  export const RISK_CALIBRATION_THRESHOLDS = RISK_CALIBRATION_SUMMARY_THRESHOLDS;
14
14
  const CONTROL_RE = /[\x00-\x1f\x7f]/;
package/cli/runner.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { abortable } from "../lib/abort.mjs";
3
- import { parseCliArguments } from "./arguments.mjs";
3
+ import { isCliCommandForOwner, parseCliArguments } from "./arguments.mjs";
4
4
  import { HELP, commandDefinition, commandHelp, } from "./command-reference.mjs";
5
5
  import { boundedCliErrorMessage, CliUsageError } from "./errors.mjs";
6
6
  import { evidenceCommandFailureLabel, isEvidenceCommand, runEvidenceCommand, } from "./evidence-command-runner.mjs";
@@ -9,7 +9,7 @@ import { readCliHeaderInput, readCliTextInput } from "./input.mjs";
9
9
  import { INLINE_CLI_COMMANDS, runInlineCommand } from "./inline-command-runner.mjs";
10
10
  import { canLaunchInteractiveCli, launchInteractiveCli, } from "./interactive-launcher.mjs";
11
11
  import { MAX_OFFLINE_PASSPHRASE_FILE_BYTES, } from "./artifact-verify.mjs";
12
- import { cleanupPendingOutputFiles, createBufferedOutput, writePrivateFile } from "./output-file.mjs";
12
+ import { cleanupPendingOutputFiles, createBufferedOutput, writePrivateFile, MAX_CLI_OUTPUT_BYTES } from "./output-file.mjs";
13
13
  import { createTerminalProgress } from "./progress.mjs";
14
14
  import { presentTerminalOutput, terminalPresentation, } from "./terminal-presentation.mjs";
15
15
  const require = createRequire(import.meta.url);
@@ -40,17 +40,7 @@ function isCancellation(error, signal) {
40
40
  return signal?.aborted === true
41
41
  || Boolean(error && typeof error === 'object' && 'name' in error && error.name === 'AbortError');
42
42
  }
43
- function usageEventReason(error) {
44
- const message = boundedCliErrorMessage(error, 'Invalid command input').toLowerCase();
45
- if (message.includes('could not read'))
46
- return 'input_unavailable';
47
- if (message.includes('cannot be combined') || message.includes('mutually exclusive'))
48
- return 'conflicting_options';
49
- if (message.includes('requires') || message.includes('did not contain'))
50
- return 'missing_input';
51
- return 'invalid_input';
52
- }
53
- async function runParsedCli(args, dependencies = {}) {
43
+ async function runParsedCli(args, dependencies = {}, writeBinaryOutput) {
54
44
  const stdout = dependencies.stdout || process.stdout;
55
45
  const stderr = dependencies.stderr || process.stderr;
56
46
  const environment = dependencies.environment || process.env;
@@ -113,7 +103,7 @@ async function runParsedCli(args, dependencies = {}) {
113
103
  catch (error) {
114
104
  if (error instanceof CliUsageError)
115
105
  throw error;
116
- throw new CliUsageError(`Could not read passphrase file: ${boundedCliErrorMessage(error, 'File could not be read')}`);
106
+ throw new CliUsageError(`Could not read passphrase file: ${boundedCliErrorMessage(error, 'File could not be read')}`, 'input_unavailable');
117
107
  }
118
108
  const passphrase = passphraseText.replace(/\r?\n$/u, '');
119
109
  if (!passphrase || /[\r\n\u0000]/u.test(passphrase)) {
@@ -128,6 +118,7 @@ async function runParsedCli(args, dependencies = {}) {
128
118
  terminal,
129
119
  presentation: (color) => terminalPresentation(stdout, color, environment, palette),
130
120
  writeStdout: (value) => write(stdout, value),
121
+ ...(writeBinaryOutput ? { writeBinaryOutput } : {}),
131
122
  writeStderr: (value) => write(stderr, value),
132
123
  readSingleInput,
133
124
  readInput,
@@ -197,25 +188,9 @@ async function runParsedCli(args, dependencies = {}) {
197
188
  });
198
189
  }
199
190
  if (handlerOwner === 'network') {
200
- if (args.action !== 'ct-search'
201
- && args.action !== 'posture'
202
- && args.action !== 'http'
203
- && args.action !== 'tls'
204
- && args.action !== 'dnssec-validate'
205
- && args.action !== 'mail-transport') {
191
+ if (!isCliCommandForOwner(args, 'network')) {
206
192
  throw new Error('Network command registry ownership is inconsistent.');
207
193
  }
208
- failureLabel = args.action === 'ct-search'
209
- ? 'Certificate Transparency search'
210
- : args.action === 'posture'
211
- ? 'Domain posture audit'
212
- : args.action === 'http'
213
- ? 'HTTP probe'
214
- : args.action === 'tls'
215
- ? 'TLS evidence collection'
216
- : args.action === 'dnssec-validate'
217
- ? 'DNSSEC chain validation'
218
- : 'Mail transport review';
219
194
  const { runNetworkCommand } = await import("./network-command-runner.mjs");
220
195
  return await runNetworkCommand(args, dependencies, commandContext);
221
196
  }
@@ -243,7 +218,7 @@ async function runParsedCli(args, dependencies = {}) {
243
218
  eventProgress.current?.emit({
244
219
  event: 'failed',
245
220
  state: 'usage',
246
- reason: usageEventReason(error),
221
+ reason: error.reason,
247
222
  exitCode: EXIT_CODES.USAGE,
248
223
  });
249
224
  if (!eventProgress.current?.enabled)
@@ -305,15 +280,72 @@ async function runCliCommand(argv, dependencies = {}) {
305
280
  }
306
281
  if (!args.destination)
307
282
  return runParsedCli(args, dependencies);
308
- const buffered = createBufferedOutput();
309
- const code = await runParsedCli(args, { ...dependencies, stdout: buffered.stream });
310
- if (code !== EXIT_CODES.SUCCESS && code !== EXIT_CODES.PARTIAL_FAILURE)
311
- return code;
283
+ const buffered = createBufferedOutput({ binary: args.action === 'manifest' && args.package === true });
284
+ let checkpoint = null;
285
+ let capturedSource = null;
312
286
  try {
313
- await writePrivateFile(args.destination, buffered.value(), { force: args.force === true });
287
+ if (args.action === 'workflow-run') {
288
+ const { prepareInvestigationCheckpoint } = await import("./investigation-checkpoint.mjs");
289
+ const workflowCheckpoint = await prepareInvestigationCheckpoint({
290
+ destination: args.destination, resumeSource: args.resumeSource, force: args.force === true,
291
+ ...(dependencies.signal ? { signal: dependencies.signal } : {}),
292
+ });
293
+ checkpoint = workflowCheckpoint;
294
+ capturedSource = workflowCheckpoint.resumeInput;
295
+ }
296
+ else if (args.action === 'case') {
297
+ const { prepareLocalDocumentWrite } = await import("./local-document-checkpoint.mjs");
298
+ const { MAX_EDITABLE_CASE_INPUT_BYTES, MAX_EDITABLE_CASE_OUTPUT_BYTES } = await import("../packages/contracts/case-portability.mjs");
299
+ const caseCheckpoint = await prepareLocalDocumentWrite({
300
+ destination: args.destination, source: args.source, force: args.force === true, label: 'Case',
301
+ allowSourceReplacement: args.operation !== 'show',
302
+ maximumInputBytes: MAX_EDITABLE_CASE_INPUT_BYTES, maximumOutputBytes: MAX_EDITABLE_CASE_OUTPUT_BYTES,
303
+ ...(dependencies.signal ? { signal: dependencies.signal } : {}),
304
+ });
305
+ checkpoint = caseCheckpoint;
306
+ capturedSource = caseCheckpoint.sourceInput;
307
+ }
308
+ else if (args.action === 'indicator-set') {
309
+ const { prepareLocalDocumentWrite } = await import("./local-document-checkpoint.mjs");
310
+ const { MAX_MANAGED_INDICATOR_PLAN_BYTES, MAX_MANAGED_INDICATOR_SET_BYTES } = await import("../packages/contracts/analyst-interchange.mjs");
311
+ const indicatorCheckpoint = await prepareLocalDocumentWrite({
312
+ destination: args.destination, source: args.source,
313
+ force: args.force === true, label: 'Indicator revision', allowSourceReplacement: false,
314
+ maximumInputBytes: args.operation === 'revise' ? MAX_MANAGED_INDICATOR_PLAN_BYTES : MAX_MANAGED_INDICATOR_SET_BYTES,
315
+ maximumOutputBytes: MAX_CLI_OUTPUT_BYTES,
316
+ ...(dependencies.signal ? { signal: dependencies.signal } : {}),
317
+ });
318
+ checkpoint = indicatorCheckpoint;
319
+ capturedSource = indicatorCheckpoint.sourceInput;
320
+ }
321
+ const code = await runParsedCli(args, {
322
+ ...dependencies, stdout: buffered.stream,
323
+ ...(args.action === 'workflow-run' && args.resumeSource && capturedSource !== null
324
+ ? { workflowResumeInput: capturedSource }
325
+ : {}),
326
+ ...(args.action === 'case' && capturedSource !== null ? { caseFileInput: capturedSource } : {}),
327
+ ...(args.action === 'indicator-set' && capturedSource !== null ? { readArtifactInput: async () => capturedSource } : {}),
328
+ }, buffered.writeBinary);
329
+ if (code !== EXIT_CODES.SUCCESS && code !== EXIT_CODES.PARTIAL_FAILURE)
330
+ return code;
331
+ const content = buffered.value();
332
+ if (checkpoint) {
333
+ if (typeof content !== 'string')
334
+ throw new TypeError('Local document output must be text.');
335
+ await checkpoint.publish(content);
336
+ }
337
+ else
338
+ await writePrivateFile(args.destination, content, {
339
+ force: args.force === true,
340
+ beforePublish: async () => { dependencies.signal?.throwIfAborted(); },
341
+ });
314
342
  return code;
315
343
  }
316
344
  catch (error) {
345
+ if (isCancellation(error, dependencies.signal)) {
346
+ write(stderr, 'Cancelled by analyst.\n');
347
+ return EXIT_CODES.CANCELLED;
348
+ }
317
349
  if (error instanceof CliUsageError) {
318
350
  write(stderr, `Usage error: ${boundedCliErrorMessage(error, 'Output file could not be written')}\n`);
319
351
  return EXIT_CODES.USAGE;
@@ -321,6 +353,11 @@ async function runCliCommand(argv, dependencies = {}) {
321
353
  write(stderr, `Output failed: ${boundedCliErrorMessage(error, 'Output file could not be written')}\n`);
322
354
  return EXIT_CODES.LOOKUP_FAILED;
323
355
  }
356
+ finally {
357
+ if (checkpoint && await checkpoint.release() > 0) {
358
+ write(stderr, `${args.action === 'case' ? 'Case' : args.action === 'indicator-set' ? 'Indicator revision' : 'Workflow'} cleanup warning: File ownership changed or a lease could not be removed. Inspect the selected directory before resuming.\n`);
359
+ }
360
+ }
324
361
  }
325
362
  async function runCli(argv, dependencies = {}) {
326
363
  const stderr = dependencies.stderr || process.stderr;
@@ -1,9 +1,9 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { types as utilTypes } from 'node:util';
3
3
  import { classifyQuery } from "../lib/classify.mjs";
4
- import { isSafeJsonObjectKey, scanBoundedJson, } from "../lib/bounded-json.mjs";
4
+ import { isSafeJsonObjectKey, scanBoundedJson, } from "../packages/analysis/bounded-json.mjs";
5
5
  import { decodeBoundedUtf8 } from "../lib/bounded-file.mjs";
6
- import { CliUsageError } from "./arguments.mjs";
6
+ import { CliUsageError } from "./errors.mjs";
7
7
  import { validHttpDeliveryMetadata, validPagePublicationMetadata, } from "../lib/homepage-metadata-contract.mjs";
8
8
  import { registrarStandingObservedBy, resolveRegistrarIanaId, validRegistrarStanding, } from "../lib/registrar-standing-contract.mjs";
9
9
  import { normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
@@ -0,0 +1,63 @@
1
+ import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
2
+ import { reviewSelectedInput } from "../packages/investigation/selected-input-review.mjs";
3
+ import { assertMessageBytes } from "../packages/investigation/intake-report.mjs";
4
+ import { DOCUMENT_REVIEW_DEADLINE_MS } from "../packages/contracts/document-intake.mjs";
5
+ import { MESSAGE_INTAKE_SCHEMA, MESSAGE_INTAKE_VERSION } from "../packages/contracts/message-intake.mjs";
6
+ if (!isMainThread && parentPort) {
7
+ const port = parentPort, request = workerData;
8
+ try {
9
+ port.postMessage({ result: await reviewSelectedInput(request.bytes, request.kind, request.reviewedAt) });
10
+ }
11
+ catch {
12
+ port.postMessage({ error: true });
13
+ }
14
+ finally {
15
+ if (request.bytes instanceof Uint8Array)
16
+ request.bytes.fill(0);
17
+ port.close();
18
+ }
19
+ }
20
+ /** A disposable worker makes synchronous parser work cancellable, not a sandbox. */
21
+ export async function reviewSelectedInputInWorker(bytes, kind, reviewedAt, signal) {
22
+ assertMessageBytes(bytes);
23
+ signal?.throwIfAborted();
24
+ // Buffer.slice() can retain a pooled, non-transferable backing store.
25
+ const copy = Uint8Array.from(bytes);
26
+ const worker = new Worker(new URL(import.meta.url), { workerData: { bytes: copy, kind, reviewedAt },
27
+ transferList: [copy.buffer], env: {}, execArgv: [], stdout: true, stderr: true,
28
+ resourceLimits: { maxOldGenerationSizeMb: 512, maxYoungGenerationSizeMb: 32, stackSizeMb: 4 } });
29
+ worker.stdout.resume();
30
+ worker.stderr.resume();
31
+ try {
32
+ return await new Promise((resolve, reject) => {
33
+ let settled = false;
34
+ const finish = (value, cause) => {
35
+ if (settled)
36
+ return;
37
+ settled = true;
38
+ clearTimeout(timer);
39
+ signal?.removeEventListener('abort', abort);
40
+ if (value)
41
+ resolve(value);
42
+ else
43
+ reject(cause ?? new TypeError('The selected input could not be reviewed within its format and processing bounds.'));
44
+ };
45
+ const abort = () => finish(undefined, new Error('Selected input review was cancelled. Nothing was saved.'));
46
+ const timer = setTimeout(() => finish(undefined, new Error('Selected input review exceeded its processing deadline. Nothing was saved.')), DOCUMENT_REVIEW_DEADLINE_MS);
47
+ worker.once('message', (reply) => {
48
+ if (reply && typeof reply === 'object' && 'result' in reply && reply.result?.report.schema === MESSAGE_INTAKE_SCHEMA && reply.result.report.schemaVersion === MESSAGE_INTAKE_VERSION)
49
+ finish(reply.result);
50
+ else
51
+ finish();
52
+ });
53
+ worker.once('error', () => finish());
54
+ worker.once('exit', () => finish());
55
+ signal?.addEventListener('abort', abort, { once: true });
56
+ if (signal?.aborted)
57
+ abort();
58
+ });
59
+ }
60
+ finally {
61
+ await worker.terminate();
62
+ }
63
+ }