@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,4 +1,5 @@
1
1
  import { DOMAIN_CONTROL_MANIFEST_COMPATIBILITY } from "../packages/contracts/domain-control-manifest.mjs";
2
+ import { MANAGED_INDICATOR_SET_COMPATIBILITY } from "../packages/contracts/analyst-interchange.mjs";
2
3
  import { CLI_CASE_PACK_COMPATIBILITY, ENCRYPTED_WORKSPACE_ARCHIVE_COMPATIBILITY, WORKSPACE_ARCHIVE_COMPATIBILITY, } from "../packages/contracts/case-portability.mjs";
3
4
  import { LOOKUP_CLAIM_PASSPORT_COMPATIBILITY } from "../packages/contracts/investigation-portability.mjs";
4
5
  import { BRAND_PROFILE_EXPORT_COMPATIBILITY } from "../packages/contracts/workspace-portability.mjs";
@@ -16,6 +17,15 @@ function compatibilityReference(descriptor) {
16
17
  });
17
18
  }
18
19
  export const INTERCHANGE_ARTIFACT_CONTRACTS = Object.freeze([
20
+ Object.freeze({
21
+ id: 'managed_indicators', ...compatibilityReference(MANAGED_INDICATOR_SET_COMPATIBILITY),
22
+ versionField: 'version', nestedSchemaPath: Object.freeze([]),
23
+ browser: Object.freeze({ import: 'supported', export: 'supported' }),
24
+ cli: Object.freeze({ read: 'supported', write: 'supported', verify: 'supported' }),
25
+ fidelity: 'semantic_exact_after_normalisation', requiredAssurance: 'whole_integrity',
26
+ preservedFieldGroups: Object.freeze(['stable_indicator_identities', 'original_observations', 'review_windows', 'withdrawal_records', 'preceding_revision_digest']),
27
+ excludedFieldGroups: Object.freeze(['raw_responses', 'browser_local_records', 'credentials', 'remote_publication', 'author_authentication', 'unavailable_revision_history']),
28
+ }),
19
29
  Object.freeze({
20
30
  id: 'lookup_claim_passport',
21
31
  ...compatibilityReference(LOOKUP_CLAIM_PASSPORT_COMPATIBILITY),
@@ -10,6 +10,7 @@
10
10
  // In-memory, so (like the bootstrap cache and the rate limiter) this
11
11
  // applies globally on server.mts's one long-lived process, but only within
12
12
  // a single warm container on Netlify Functions.
13
+ import { abortable } from "./abort.mjs";
13
14
  const TTL_MS = 3 * 60 * 1000; // 3 minutes
14
15
  const SWEEP_INTERVAL_MS = 5 * 60 * 1000;
15
16
  // Bounds worst-case memory regardless of the TTL/sweep - without this, a
@@ -72,7 +73,7 @@ function getCached(key) {
72
73
  deleteEntry(key);
73
74
  return undefined;
74
75
  }
75
- return entry.value;
76
+ return structuredClone(entry.value);
76
77
  }
77
78
  function setCached(key, value) {
78
79
  sweepExpiredEntries(Date.now());
@@ -80,9 +81,16 @@ function setCached(key, value) {
80
81
  const size = approxByteSize(value);
81
82
  // A value whose serialized size cannot be measured cannot participate in a
82
83
  // byte-bounded cache. Return it to the current caller without retaining it.
83
- if (size === null)
84
- return;
85
- store.set(key, { value, expiresAt: Date.now() + TTL_MS, size });
84
+ if (size === null || size > MAX_TOTAL_BYTES)
85
+ return { value, owned: false };
86
+ let snapshot;
87
+ try {
88
+ snapshot = structuredClone(value);
89
+ }
90
+ catch {
91
+ return { value, owned: false };
92
+ }
93
+ store.set(key, { value: snapshot, expiresAt: Date.now() + TTL_MS, size });
86
94
  totalBytes += size;
87
95
  while (store.size > 0 && (store.size > MAX_ENTRIES || totalBytes > MAX_TOTAL_BYTES)) {
88
96
  const oldestKey = store.keys().next().value;
@@ -90,6 +98,12 @@ function setCached(key, value) {
90
98
  break;
91
99
  deleteEntry(oldestKey);
92
100
  }
101
+ return { value: snapshot, owned: true };
102
+ }
103
+ // Stored data and coalesced results belong to the cache, not to a caller. Each
104
+ // consumer receives its own copy, including the first and concurrent callers.
105
+ function callerResult(result) {
106
+ return result.owned ? structuredClone(result.value) : result.value;
93
107
  }
94
108
  // Concurrent callers for the same key (e.g. a bulk scan's concurrent
95
109
  // workers, or a fast scan immediately followed by a deep-check, hitting the
@@ -103,25 +117,34 @@ const inFlight = new Map();
103
117
  // The cache intentionally holds heterogeneous public lookup results. Callers
104
118
  // supply distinct result contracts, so this internal compatibility boundary
105
119
  // stays dynamically typed rather than asserting one shared payload shape.
106
- async function cached(key, factory) {
120
+ async function cached(key, factory, signal) {
121
+ signal?.throwIfAborted();
107
122
  const hit = getCached(key);
108
123
  if (hit !== undefined)
109
124
  return hit;
125
+ // A deadline-scoped caller may reuse completed public data, but must own
126
+ // its cancellable work rather than aborting another caller's shared request.
127
+ if (signal) {
128
+ const value = await abortable(factory, signal);
129
+ signal.throwIfAborted();
130
+ return callerResult(setCached(key, value));
131
+ }
110
132
  const pending = inFlight.get(key);
111
133
  if (pending)
112
- return pending;
113
- const promise = (async () => {
134
+ return callerResult(await pending);
135
+ // Register pending work before invoking the factory, including factories
136
+ // that throw synchronously. A rejected promise must never poison the key.
137
+ const promise = Promise.resolve().then(async () => {
114
138
  try {
115
139
  const value = await factory();
116
- setCached(key, value);
117
- return value;
140
+ return setCached(key, value);
118
141
  }
119
142
  finally {
120
143
  inFlight.delete(key);
121
144
  }
122
- })();
145
+ });
123
146
  inFlight.set(key, promise);
124
- return promise;
147
+ return callerResult(await promise);
125
148
  }
126
149
  // Expired entries are swept lazily during cache activity. This preserves the
127
150
  // long-running Express cleanup behavior without creating an import-time timer,
@@ -1,19 +1,21 @@
1
1
  // Browser-safe version and collection boundaries shared by Lookup child
2
2
  // profile producers and the client response trust boundary.
3
3
  import { MAX_OBSERVATION_LIMITATIONS, MAX_OBSERVATION_LIMITATION_LENGTH, OBSERVATION_VERSION, normalizeExplicitIsoTimestamp, } from "../packages/evidence/observation.mjs";
4
- import { PAGE_FINGERPRINT_VERSION, PAGE_IDENTITY_VERSION, } from "../packages/contracts/workspace-portability.mjs";
4
+ import { PAGE_FINGERPRINT_PARSERS, PAGE_IDENTITY_VERSION, } from "../packages/contracts/workspace-portability.mjs";
5
5
  import { validPagePublicationMetadata } from "./homepage-metadata-contract.mjs";
6
+ import { PAGE_FINGERPRINT_TOKEN_LIMITS } from "../packages/contracts/page-fingerprints.mjs";
6
7
  import { MAX_LOOKUP_TLS_ALT_NAMES, MAX_LOOKUP_TLS_CERTIFICATE_POLICIES, MAX_LOOKUP_TLS_CHAIN_CERTIFICATES, MAX_LOOKUP_TLS_FINDINGS, MAX_LOOKUP_TLS_NAME_VALUES, TLS_PROFILE_VERSION, } from "./lookup-network-evidence-bounds.mjs";
7
8
  import { isJsonObject, normalizedDomain, validBoundedString, validHttpProvenanceUrl, validObservationDiagnostics, validOptionalNullableText, validStringArray, validUint, } from "./lookup-contract-primitives.mjs";
8
9
  import { TECHNOLOGY_EVIDENCE_ROLE_ORDER } from "./technology-evidence-role.mjs";
9
- export const TECHNOLOGY_PROFILE_VERSION = 11;
10
- export const SUPPORTED_TECHNOLOGY_PROFILE_VERSIONS = Object.freeze([10, TECHNOLOGY_PROFILE_VERSION]);
10
+ import { MAX_HOMEPAGE_BYTES } from "./outbound-request-bounds.mjs";
11
+ export const TECHNOLOGY_PROFILE_VERSION = 12;
12
+ export const SUPPORTED_TECHNOLOGY_PROFILE_VERSIONS = Object.freeze([10, 11, TECHNOLOGY_PROFILE_VERSION]);
11
13
  export const MAX_TECHNOLOGY_FINDINGS = 24;
12
14
  export const MAX_EVIDENCE_PER_TECHNOLOGY = 4;
13
15
  export const MAX_TECHNOLOGY_EVIDENCE_DESCRIPTION_LENGTH = 180;
14
16
  export const BROWSER_LIBRARY_PROFILE_VERSION = 2;
15
17
  export const MAX_LIBRARY_FINDINGS = 16;
16
- export const WEBSITE_SECURITY_POSTURE_VERSION = 2;
18
+ export const WEBSITE_SECURITY_POSTURE_VERSION = 3;
17
19
  export const MAX_SECURITY_POSTURE_FINDINGS = 32;
18
20
  export const CREDENTIAL_SURFACE_PROFILE_VERSION = 1;
19
21
  export const STRUCTURED_DATA_IDENTITY_VERSION = 1;
@@ -169,6 +171,7 @@ function validFingerprintIdentifier(value) {
169
171
  && /^[A-Z0-9-]{1,64}$/u.test(value.value);
170
172
  }
171
173
  function validPageFingerprintProfile(value) {
174
+ const tokenLimit = PAGE_FINGERPRINT_TOKEN_LIMITS[value.fingerprintVersion];
172
175
  const normalizedHtml = value.normalizedHtml;
173
176
  const visibleText = value.visibleText;
174
177
  const domStructure = value.domStructure;
@@ -181,7 +184,7 @@ function validPageFingerprintProfile(value) {
181
184
  || normalizedHtml.algorithm !== 'sha256'
182
185
  || typeof normalizedHtml.value !== 'string'
183
186
  || !SHA256_RE.test(normalizedHtml.value)
184
- || !validUint(normalizedHtml.tokenCount, 4_096)
187
+ || !validUint(normalizedHtml.tokenCount, tokenLimit)
185
188
  || typeof normalizedHtml.truncated !== 'boolean'
186
189
  || !(visibleText === null || isJsonObject(visibleText)
187
190
  && hasExactKeys(visibleText, ['algorithm', 'value', 'tokenCount', 'featureCount', 'truncated'])
@@ -197,16 +200,16 @@ function validPageFingerprintProfile(value) {
197
200
  || domStructure.algorithm !== 'sha256'
198
201
  || typeof domStructure.value !== 'string'
199
202
  || !SHA256_RE.test(domStructure.value)
200
- || !validUint(domStructure.nodeCount, 4_096)
201
- || domStructure.parser !== 'static-tag-sequence-v1'
203
+ || !validUint(domStructure.nodeCount, tokenLimit)
204
+ || domStructure.parser !== PAGE_FINGERPRINT_PARSERS[value.fingerprintVersion]
202
205
  || typeof domStructure.truncated !== 'boolean'
203
206
  || !(domStructure.similarity === undefined || domStructure.similarity === null || isJsonObject(domStructure.similarity)
204
207
  && hasExactKeys(domStructure.similarity, ['algorithm', 'value', 'tokenCount', 'featureCount', 'truncated'])
205
208
  && domStructure.similarity.algorithm === 'simhash64-v1'
206
209
  && typeof domStructure.similarity.value === 'string'
207
210
  && SIMHASH64_RE.test(domStructure.similarity.value)
208
- && validUint(domStructure.similarity.tokenCount, 4_096)
209
- && validUint(domStructure.similarity.featureCount, 4_096)
211
+ && validUint(domStructure.similarity.tokenCount, tokenLimit)
212
+ && validUint(domStructure.similarity.featureCount, tokenLimit)
210
213
  && typeof domStructure.similarity.truncated === 'boolean')
211
214
  || !(formStructure === null || isJsonObject(formStructure)
212
215
  && hasExactKeys(formStructure, ['algorithm', 'value', 'formCount', 'controlCount', 'truncated'])
@@ -461,7 +464,7 @@ function browserLibraryProfileContractState(value) {
461
464
  || !isJsonObject(knownExploitedCatalog)
462
465
  || !hasOnlyKeys(knownExploitedCatalog, ['name', 'version', 'releasedAt'])
463
466
  || !['name', 'version', 'releasedAt'].every((field) => validBoundedString(knownExploitedCatalog[field], 160))
464
- || normalizeExplicitIsoTimestamp(knownExploitedCatalog.releasedAt) !== knownExploitedCatalog.releasedAt
467
+ || normalizeExplicitIsoTimestamp(knownExploitedCatalog.releasedAt) === null
465
468
  || !Array.isArray(profile.findings)
466
469
  || profile.findings.length > MAX_LIBRARY_FINDINGS)
467
470
  return 'invalid';
@@ -488,6 +491,8 @@ function browserLibraryProfileContractState(value) {
488
491
  || !validExactStringArray(candidate.knownExploitedIdentifiers, 16, 80)
489
492
  || !validExactStringArray(candidate.weaknessClasses, 12, 80))
490
493
  return 'invalid';
494
+ // Published profiles could retain legacy upstream identifier spellings.
495
+ // Keep that reader boundary; current catalogue writers validate syntax.
491
496
  if (findingIds.has(candidate.id)
492
497
  || !candidate.advisoryIdentifiers.every((identifier) => typeof identifier === 'string'
493
498
  && /^(?:CVE-[0-9X-]+|GHSA-[A-Z0-9-]+)$/u.test(identifier))
@@ -495,7 +500,9 @@ function browserLibraryProfileContractState(value) {
495
500
  && /^CVE-[0-9X-]+$/u.test(identifier))
496
501
  || !candidate.weaknessClasses.every((identifier) => typeof identifier === 'string'
497
502
  && /^CWE-[0-9]+$/u.test(identifier))
498
- || Number(candidate.advisoryCount) < candidate.advisoryIdentifiers.length
503
+ // One advisory can have several identifiers, including CVE and GHSA
504
+ // aliases. Their count is not the number of matching advisory records.
505
+ || (Number(candidate.advisoryCount) === 0 && candidate.advisoryIdentifiers.length > 0)
499
506
  || Number(candidate.knownExploitedCount) < candidate.knownExploitedIdentifiers.length)
500
507
  return 'invalid';
501
508
  findingIds.add(candidate.id);
@@ -503,7 +510,7 @@ function browserLibraryProfileContractState(value) {
503
510
  return 'supported';
504
511
  }
505
512
  function pageFingerprintContractState(value) {
506
- const versionState = childVersionState(value, 'fingerprintVersion', [PAGE_FINGERPRINT_VERSION]);
513
+ const versionState = childVersionState(value, 'fingerprintVersion', Object.keys(PAGE_FINGERPRINT_PARSERS).map(Number));
507
514
  if (versionState !== 'supported')
508
515
  return versionState;
509
516
  const profile = value;
@@ -520,7 +527,9 @@ function pageFingerprintContractState(value) {
520
527
  || typeof profile.exact.value !== 'string'
521
528
  || !SHA256_RE.test(profile.exact.value)
522
529
  || !['complete-body', 'captured-prefix'].includes(String(profile.exact.scope))
523
- || !validUint(profile.exact.bytes, 300_000)
530
+ // The published first-generation writer had a fixed 300,000-byte ceiling.
531
+ // Current native fingerprints share the collector's source-size policy.
532
+ || !validUint(profile.exact.bytes, profile.fingerprintVersion === 1 ? 300_000 : MAX_HOMEPAGE_BYTES)
524
533
  || !['captured-response-bytes', 'decoded-markup'].includes(String(profile.exact.source))
525
534
  || !validPageFingerprintProfile(profile)
526
535
  || profile.complete !== (profile.truncated !== true))
@@ -647,7 +656,7 @@ function tlsProfileContractState(value) {
647
656
  return 'supported';
648
657
  }
649
658
  function securityPostureContractState(value) {
650
- const versionState = childVersionState(value, 'postureVersion', [WEBSITE_SECURITY_POSTURE_VERSION]);
659
+ const versionState = childVersionState(value, 'postureVersion', [2, WEBSITE_SECURITY_POSTURE_VERSION]);
651
660
  if (versionState !== 'supported')
652
661
  return versionState;
653
662
  const profile = value;
@@ -855,4 +864,4 @@ function sanitizeLookupChildProfiles(value) {
855
864
  }
856
865
  return output;
857
866
  }
858
- export { sanitizeLookupChildProfiles, validSecurityPostureFinding, validTlsChainCertificate, validTlsFinding, };
867
+ export { sanitizeLookupChildProfiles, technologyProfileContractState, validSecurityPostureFinding, validTlsChainCertificate, validTlsFinding, };
@@ -2,7 +2,7 @@
2
2
  // child-profile contracts. These checks validate without normalising or
3
3
  // copying untrusted values.
4
4
  import { MAX_OBSERVATION_DIAGNOSTICS } from "../packages/evidence/observation.mjs";
5
- import { MAX_HTTP_PROVENANCE_URL } from "./http-evidence-bounds.mjs";
5
+ import { MAX_HTTP_PROVENANCE_URL } from "../packages/analysis/http-evidence-bounds.mjs";
6
6
  const LOOKUP_CONTROL_CHAR_RE = /[\u0000-\u001f\u007f-\u009f]|\p{Default_Ignorable_Code_Point}/u;
7
7
  const MAX_LOOKUP_CONTRACT_HOST_LENGTH = 253;
8
8
  function isJsonObject(value) {
@@ -1,6 +1,7 @@
1
1
  // Lookup diagnostics, bounded source errors and optional deep-lookup timing.
2
2
  import { OPERATION_BUDGET_ERROR_CODE } from "./operation-budget.mjs";
3
3
  import { FEATURE_DISABLED_ERROR_CODE } from "./feature-policy.mjs";
4
+ import { LOOKUP_SOURCE_STATES, lookupDiagnosticStates } from "./lookup-progress.mjs";
4
5
  const LOOKUP_DIAGNOSTICS_VERSION = 8;
5
6
  const LOOKUP_LEGACY_DIAGNOSTICS_VERSION = 7;
6
7
  const LOOKUP_TIMING_VERSION = 1;
@@ -93,4 +94,4 @@ function createLookupTimingTracker(enabled, now) {
93
94
  },
94
95
  };
95
96
  }
96
- export { LOOKUP_DIAGNOSTICS_VERSION, LOOKUP_LEGACY_DIAGNOSTICS_VERSION, LOOKUP_TIMING_VERSION, MAX_LOOKUP_TIMING_MS, LOOKUP_ERROR_CODES, boundedSourceDetail, createLookupTimingTracker, errorMessage, };
97
+ export { LOOKUP_DIAGNOSTICS_VERSION, LOOKUP_LEGACY_DIAGNOSTICS_VERSION, LOOKUP_SOURCE_STATES, LOOKUP_TIMING_VERSION, MAX_LOOKUP_TIMING_MS, LOOKUP_ERROR_CODES, boundedSourceDetail, createLookupTimingTracker, errorMessage, lookupDiagnosticStates, };
@@ -1,10 +1 @@
1
- // Browser-safe collection bounds shared by the network evidence producers,
2
- // the Lookup response trust boundary, and the display projection.
3
- export const MAX_LOOKUP_DNS_RECORDS_PER_TYPE = 16;
4
- export const MAX_LOOKUP_REVERSE_DNS_PTR_RECORDS = 8;
5
- export const TLS_PROFILE_VERSION = 3;
6
- export const MAX_LOOKUP_TLS_CHAIN_CERTIFICATES = 8;
7
- export const MAX_LOOKUP_TLS_ALT_NAMES = 50;
8
- export const MAX_LOOKUP_TLS_NAME_VALUES = 4;
9
- export const MAX_LOOKUP_TLS_FINDINGS = 5;
10
- export const MAX_LOOKUP_TLS_CERTIFICATE_POLICIES = 16;
1
+ export * from "../packages/analysis/lookup-network-evidence-bounds.mjs";
@@ -1,6 +1,7 @@
1
1
  // Portable bounded NDJSON Lookup progress contract. Partial events are
2
2
  // presentation-only; only a validated final ordinary Lookup result may cross
3
3
  // the persistence boundary.
4
+ import { scanBoundedJson, MAX_BOUNDED_JSON_DEPTH, MAX_BOUNDED_JSON_KEYS, MAX_BOUNDED_JSON_VALUES } from "../packages/analysis/bounded-json.mjs";
4
5
  export const LOOKUP_PROGRESS_SCHEMA = 'whoisleuth.lookup-progress';
5
6
  export const LOOKUP_PROGRESS_VERSION = 1;
6
7
  export const MAX_LOOKUP_PROGRESS_SOURCES = 16;
@@ -31,7 +32,56 @@ const SOURCE_STATES = new Set([
31
32
  'unavailable',
32
33
  'rate_limited',
33
34
  ]);
35
+ // Final diagnostic states are distinct from the provisional progress subset.
36
+ export const LOOKUP_SOURCE_STATES = Object.freeze([
37
+ 'success', 'partial', 'not_found', 'skipped', 'error', 'unsupported',
38
+ 'not_applicable', 'unavailable', 'rate_limited', 'complete', 'disabled', 'stale',
39
+ ]);
40
+ const FINAL_SOURCE_STATES = new Set(LOOKUP_SOURCE_STATES);
41
+ export function lookupDiagnosticStates(value) {
42
+ const record = (input) => input && typeof input === 'object' && !Array.isArray(input)
43
+ ? input : {};
44
+ const diagnostics = record(value);
45
+ const candidates = {
46
+ rdap: record(diagnostics.rdap).status,
47
+ registrar_rdap: record(record(diagnostics.rdap).registrar).status,
48
+ whois: record(diagnostics.whois).status,
49
+ availability: record(diagnostics.availability).status,
50
+ reverse_dns: record(diagnostics.reverseDns).status,
51
+ network_context: record(diagnostics.network).status,
52
+ security_txt: record(diagnostics.securityTxt).status,
53
+ sslbl: record(diagnostics.sslbl).status,
54
+ };
55
+ return Object.freeze(Object.fromEntries(Object.entries(candidates).map(([source, state]) => [
56
+ source, typeof state === 'string' && FINAL_SOURCE_STATES.has(state) ? state : null,
57
+ ])));
58
+ }
34
59
  const SOURCE_ID_RE = /^[a-z][a-z0-9_]{0,39}$/u;
60
+ /** The existing Lookup recipe, shared by offline plans and source progress.
61
+ * This describes admitted families, not provider health or request counts. */
62
+ export function plannedLookupSources(targetType, mode, options = {}) {
63
+ const sources = ['rdap'];
64
+ if (mode === 'fast') {
65
+ if (targetType === 'domain')
66
+ sources.push('domain_evidence');
67
+ return Object.freeze(sources);
68
+ }
69
+ sources.push('whois');
70
+ if (targetType === 'domain') {
71
+ sources.push('domain_evidence', 'registrar_rdap', 'network_context');
72
+ if (options.securityTxt)
73
+ sources.push('security_txt');
74
+ if (options.externalIntelligence)
75
+ sources.push('external_intelligence');
76
+ if (options.malwareHostIntelligence)
77
+ sources.push('malware_host_intelligence');
78
+ if (options.malwareIocIntelligence)
79
+ sources.push('malware_ioc_intelligence');
80
+ }
81
+ else if (targetType === 'ipv4' || targetType === 'ipv6')
82
+ sources.push('reverse_dns');
83
+ return Object.freeze(sources);
84
+ }
35
85
  const TEXT_ENCODER = new TextEncoder();
36
86
  function byteLength(value) {
37
87
  return TEXT_ENCODER.encode(value).byteLength;
@@ -241,20 +291,25 @@ export function createLookupProgressNdjsonDecoder(onEvent) {
241
291
  throw new TypeError('NDJSON decoder requires an event consumer.');
242
292
  const decoder = new TextDecoder('utf-8', { fatal: true });
243
293
  let pending = '';
294
+ let pendingBytes = 0;
244
295
  let totalBytes = 0;
245
296
  let eventCount = 0;
246
297
  function consumeLine(line) {
247
298
  if (!line)
248
299
  return;
249
- if (byteLength(line) > MAX_LOOKUP_PROGRESS_FINAL_BYTES + MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES) {
250
- throw new TypeError('Lookup progress line exceeds its byte bound.');
251
- }
252
300
  eventCount += 1;
253
301
  if (eventCount > MAX_LOOKUP_PROGRESS_EVENTS) {
254
302
  throw new TypeError('Lookup progress stream contains too many events.');
255
303
  }
256
304
  let parsed;
257
305
  try {
306
+ // The ordinary result keeps its original limits after decoding. Allow
307
+ // only the fixed progress envelope overhead around that same document.
308
+ scanBoundedJson(line, {
309
+ maximumDepth: MAX_BOUNDED_JSON_DEPTH + 1,
310
+ maximumKeys: MAX_BOUNDED_JSON_KEYS + 8,
311
+ maximumValues: MAX_BOUNDED_JSON_VALUES + MAX_LOOKUP_PROGRESS_SOURCES + 16,
312
+ });
258
313
  parsed = JSON.parse(line);
259
314
  }
260
315
  catch {
@@ -269,16 +324,26 @@ export function createLookupProgressNdjsonDecoder(onEvent) {
269
324
  if (totalBytes > MAX_LOOKUP_PROGRESS_STREAM_BYTES) {
270
325
  throw new TypeError('Lookup progress stream exceeds its total byte bound.');
271
326
  }
272
- pending += decoder.decode(chunk, { stream: true });
273
- let newline = pending.indexOf('\n');
274
- while (newline >= 0) {
275
- const line = pending.slice(0, newline).replace(/\r$/u, '');
276
- pending = pending.slice(newline + 1);
277
- consumeLine(line);
278
- newline = pending.indexOf('\n');
279
- }
280
- if (byteLength(pending) > MAX_LOOKUP_PROGRESS_FINAL_BYTES + MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES) {
281
- throw new TypeError('Lookup progress line exceeds its byte bound.');
327
+ // Count incoming bytes once, before decoding or accumulation. LF cannot
328
+ // occur inside a valid UTF-8 sequence; the fatal decoder still receives
329
+ // every byte, including framing, so chunk boundaries cannot hide errors.
330
+ for (let start = 0; start < chunk.length;) {
331
+ const newline = chunk.indexOf(10, start);
332
+ const end = newline < 0 ? chunk.length : newline;
333
+ pendingBytes += end - start;
334
+ const maximum = MAX_LOOKUP_PROGRESS_FINAL_BYTES + MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES;
335
+ const trailingCr = end > start ? chunk[end - 1] === 13 : pending.endsWith('\r');
336
+ if (pendingBytes > maximum + (trailingCr ? 1 : 0)) {
337
+ throw new TypeError('Lookup progress line exceeds its byte bound.');
338
+ }
339
+ const decoded = decoder.decode(chunk.subarray(start, end + (newline < 0 ? 0 : 1)), { stream: true });
340
+ pending += newline < 0 ? decoded : decoded.slice(0, -1);
341
+ if (newline >= 0) {
342
+ consumeLine(pending.replace(/\r$/u, ''));
343
+ pending = '';
344
+ pendingBytes = 0;
345
+ }
346
+ start = end + 1;
282
347
  }
283
348
  }
284
349
  function finish() {
@@ -286,6 +351,7 @@ export function createLookupProgressNdjsonDecoder(onEvent) {
286
351
  if (pending)
287
352
  consumeLine(pending.replace(/\r$/u, ''));
288
353
  pending = '';
354
+ pendingBytes = 0;
289
355
  }
290
356
  return Object.freeze({ push, finish });
291
357
  }