@slicedearth/whoisleuth-cli 2.3.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/NOTICE +8 -2
  2. package/README.md +19 -15
  3. package/bin/whoisleuth.mjs +8 -1
  4. package/cli/archive-content-digest.mjs +14 -0
  5. package/cli/archive-inspect.mjs +20 -53
  6. package/cli/argument-values.mjs +55 -0
  7. package/cli/arguments.mjs +24 -408
  8. package/cli/artifact-validation/case-response.mjs +65 -44
  9. package/cli/artifact-validation/investigation-capsule.mjs +52 -31
  10. package/cli/artifact-validation/lookup-evidence.mjs +15 -9
  11. package/cli/artifact-validation/signed-review.mjs +136 -68
  12. package/cli/artifact-validation/structure-primitives.mjs +1 -105
  13. package/cli/artifact-verify.mjs +92 -24
  14. package/cli/assurance-arguments.mjs +56 -0
  15. package/cli/assurance-command-definitions.mjs +179 -0
  16. package/cli/assurance-command-runner.mjs +40 -5
  17. package/cli/bagit-folder.mjs +57 -0
  18. package/cli/bagit-review.mjs +23 -0
  19. package/cli/bounded-json.mjs +1 -1
  20. package/cli/bulk-checkpoint.mjs +1 -1
  21. package/cli/bulk-command-runner.mjs +4 -2
  22. package/cli/bulk-output.mjs +29 -3
  23. package/cli/bulk.mjs +2 -3
  24. package/cli/case-command.mjs +212 -0
  25. package/cli/case-pack.mjs +50 -32
  26. package/cli/ci-report.mjs +6 -3
  27. package/cli/collection-arguments.mjs +123 -0
  28. package/cli/collection-command-definitions.mjs +330 -0
  29. package/cli/collection-preflight.mjs +8 -1
  30. package/cli/command-argument-grammar.mjs +8 -206
  31. package/cli/command-definition.mjs +329 -0
  32. package/cli/command-reference.mjs +94 -1357
  33. package/cli/command-seeds.mjs +18 -0
  34. package/cli/compare.mjs +1 -1
  35. package/cli/completion.mjs +298 -79
  36. package/cli/config-profile.mjs +8 -6
  37. package/cli/ct-event-intake.mjs +16 -16
  38. package/cli/discover.mjs +5 -4
  39. package/cli/discovery-observation-snapshot.mjs +46 -18
  40. package/cli/discovery-scan-command-runner.mjs +5 -3
  41. package/cli/discovery-scan.mjs +7 -4
  42. package/cli/discovery-snapshot.mjs +2 -2
  43. package/cli/discovery-workflow.mjs +5 -5
  44. package/cli/domain-control-monitor.mjs +50 -20
  45. package/cli/domain-control-observations.mjs +151 -103
  46. package/cli/errors.mjs +37 -6
  47. package/cli/evidence-arguments.mjs +46 -0
  48. package/cli/evidence-command-definitions.mjs +69 -0
  49. package/cli/evidence-command-runner.mjs +18 -8
  50. package/cli/evidence-signing.mjs +5 -4
  51. package/cli/external-observation-mapping.mjs +9 -10
  52. package/cli/formatters/terminal-command-formats.mjs +6 -0
  53. package/cli/formatters/terminal-lookup.mjs +24 -46
  54. package/cli/formatters/terminal-shared.mjs +5 -3
  55. package/cli/formatters/terminal-technology.mjs +114 -0
  56. package/cli/history-arguments.mjs +39 -0
  57. package/cli/history-command-definitions.mjs +88 -0
  58. package/cli/indicator-set-command.mjs +33 -0
  59. package/cli/inline-command-families.mjs +6 -46
  60. package/cli/inline-command-runner.mjs +4 -31
  61. package/cli/input.mjs +2 -2
  62. package/cli/intake-command.mjs +107 -0
  63. package/cli/interactive-launcher.mjs +2 -160
  64. package/cli/interchange-report.mjs +8 -10
  65. package/cli/investigation-artifacts.mjs +176 -0
  66. package/cli/investigation-checkpoint.mjs +10 -0
  67. package/cli/investigation-folder.mjs +114 -0
  68. package/cli/investigation-manifest.mjs +1 -112
  69. package/cli/investigation-package-review.mjs +103 -0
  70. package/cli/investigation-plan.mjs +38 -147
  71. package/cli/investigation-recipes.mjs +177 -0
  72. package/cli/investigation-run.mjs +256 -35
  73. package/cli/local-document-checkpoint.mjs +170 -0
  74. package/cli/local-mmdb-review.mjs +197 -17
  75. package/cli/lookalike-calibration.mjs +1 -1
  76. package/cli/lookup-brief.mjs +2 -2
  77. package/cli/lookup-browser.mjs +5 -12
  78. package/cli/lookup-command-runner.mjs +33 -39
  79. package/cli/lookup-diff.mjs +47 -12
  80. package/cli/lookup-plan.mjs +16 -14
  81. package/cli/lookup-reconcile.mjs +15 -12
  82. package/cli/lookup-timeline.mjs +7 -6
  83. package/cli/mail-header-review.mjs +24 -247
  84. package/cli/mail-review.mjs +75 -21
  85. package/cli/manual.mjs +1 -0
  86. package/cli/network-arguments.mjs +51 -0
  87. package/cli/network-command-definitions.mjs +130 -0
  88. package/cli/network-command-runner.mjs +45 -31
  89. package/cli/offline-evidence-review.mjs +39 -6
  90. package/cli/output-file.mjs +31 -5
  91. package/cli/posture.mjs +1 -1
  92. package/cli/registry-doctor.mjs +3 -1
  93. package/cli/registry-fixture-scaffold.mjs +1 -1
  94. package/cli/registry-support.mjs +1 -1
  95. package/cli/retained-artifact-diff.mjs +10 -9
  96. package/cli/review-arguments.mjs +131 -0
  97. package/cli/review-command-definitions.mjs +273 -0
  98. package/cli/review-command-runner.mjs +104 -149
  99. package/cli/risk-calibration.mjs +8 -8
  100. package/cli/runner.mjs +74 -37
  101. package/cli/saved-lookup.mjs +2 -2
  102. package/cli/selected-input-worker.mjs +63 -0
  103. package/cli/sharing-review.mjs +8 -80
  104. package/cli/signer-trust.mjs +104 -0
  105. package/cli/source-reliability.mjs +75 -84
  106. package/cli/support-arguments.mjs +49 -0
  107. package/cli/support-command-definitions.mjs +209 -0
  108. package/cli/support-command-runner.mjs +2 -2
  109. package/cli/terminal-input.mjs +168 -0
  110. package/cli/workflow-arguments.mjs +86 -0
  111. package/cli/workflow-command-definitions.mjs +78 -0
  112. package/cli/workflow-command-runner.mjs +75 -13
  113. package/docs/cli-reference.md +103 -30
  114. package/docs/cli.md +510 -25
  115. package/docs/contextual-reviews.md +155 -0
  116. package/lib/abort.mjs +4 -1
  117. package/lib/analyst-taxonomy.mjs +2 -40
  118. package/lib/application-version.mjs +1 -1
  119. package/lib/availability.mjs +27 -754
  120. package/lib/bounded-contract-normalizers.mjs +4 -0
  121. package/lib/bounded-file.mjs +3 -1
  122. package/lib/bounded-html-document.mjs +125 -0
  123. package/lib/bounded-json.mjs +2 -290
  124. package/lib/browser-library-profile.mjs +99 -130
  125. package/lib/browser-library-worker.mjs +112 -0
  126. package/lib/candidate-provenance-bounds.mjs +2 -0
  127. package/lib/classify.mjs +1 -1
  128. package/lib/client-behavior-profile.mjs +3 -3
  129. package/lib/ct-query.mjs +2 -27
  130. package/lib/ct-response-bounds.mjs +2 -9
  131. package/lib/ct-search.mjs +89 -46
  132. package/lib/dmarc-discovery.mjs +62 -0
  133. package/lib/dns-convergence-review.mjs +21 -8
  134. package/lib/dns-delegation-health.mjs +36 -22
  135. package/lib/dns-inheritance-review.mjs +207 -0
  136. package/lib/dns-intelligence.mjs +44 -29
  137. package/lib/dns-mx.mjs +1 -1
  138. package/lib/dns-query-coverage.mjs +40 -0
  139. package/lib/dnssec-evidence-validation.mjs +11 -3
  140. package/lib/domain-assurance.mjs +1 -1
  141. package/lib/domain-change-packet.mjs +18 -14
  142. package/lib/domain-change-review.mjs +40 -16
  143. package/lib/domain-control-flight-recorder.mjs +301 -124
  144. package/lib/domain-control-manifest.mjs +126 -58
  145. package/lib/domain-posture-analysis.mjs +41 -11
  146. package/lib/domain-posture-parsers.mjs +88 -6
  147. package/lib/domain-posture.mjs +120 -32
  148. package/lib/domain-registration.mjs +392 -0
  149. package/lib/evidence-export-privacy.mjs +568 -0
  150. package/lib/evidence-export.mjs +12 -593
  151. package/lib/evidence-report.mjs +7 -2
  152. package/lib/external-intelligence-risk.mjs +2 -124
  153. package/lib/favicon.mjs +117 -41
  154. package/lib/generated/cisa-kev-catalog.mjs +50 -4
  155. package/lib/generated/registrar-standing-catalogue.mjs +18 -10
  156. package/lib/generated/retire-browser-catalog.mjs +2742 -1034
  157. package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
  158. package/lib/html-signals.mjs +87 -85
  159. package/lib/http-evidence-bounds.mjs +2 -7
  160. package/lib/http-intelligence.mjs +52 -27
  161. package/lib/idn-confusable-policy.mjs +6 -4
  162. package/lib/idn-confusables.mjs +1 -1
  163. package/lib/interchange-fidelity-registry.mjs +10 -0
  164. package/lib/lookup-cache.mjs +34 -11
  165. package/lib/lookup-child-profile-contract.mjs +24 -15
  166. package/lib/lookup-contract-primitives.mjs +1 -1
  167. package/lib/lookup-diagnostics.mjs +2 -1
  168. package/lib/lookup-network-evidence-bounds.mjs +1 -10
  169. package/lib/lookup-progress.mjs +79 -13
  170. package/lib/lookup-response-contract.mjs +1077 -0
  171. package/lib/lookup-response.mjs +11 -24
  172. package/lib/lookup-source-progress.mjs +24 -35
  173. package/lib/lookup.mjs +49 -28
  174. package/lib/observed-network-context.mjs +43 -27
  175. package/lib/opportunity-scoring.mjs +2 -107
  176. package/lib/outbound-identity.mjs +1 -1
  177. package/lib/outbound-request-bounds.mjs +4 -1
  178. package/lib/page-fingerprints.mjs +87 -134
  179. package/lib/page-role-profile.mjs +12 -8
  180. package/lib/perceptual-hash-comparison.mjs +2 -34
  181. package/lib/perceptual-hash.mjs +79 -24
  182. package/lib/portable-generator.mjs +2 -29
  183. package/lib/project-metadata.mjs +2 -8
  184. package/lib/rdap-bootstrap.mjs +72 -22
  185. package/lib/rdap-client.mjs +26 -14
  186. package/lib/rdap-normalization.mjs +33 -18
  187. package/lib/rdap-search-workbench.mjs +1 -1
  188. package/lib/rdap-transport.mjs +47 -20
  189. package/lib/rdap-types.mjs +2 -1
  190. package/lib/rdap.mjs +18 -3
  191. package/lib/registrable-domain.mjs +2 -36
  192. package/lib/registrar-standing-contract.mjs +2 -1
  193. package/lib/registrar-standing.mjs +9 -13
  194. package/lib/registry-capabilities.mjs +7 -3
  195. package/lib/registry-capability-catalogue.mjs +29 -20
  196. package/lib/registry-dates.mjs +2 -190
  197. package/lib/registry-insights.mjs +56 -18
  198. package/lib/response-policy.mjs +86 -48
  199. package/lib/risk-calibration-summary.mjs +2 -338
  200. package/lib/risk-scoring.mjs +2 -345
  201. package/lib/rpki-evidence.mjs +8 -5
  202. package/lib/safe-fetch.mjs +54 -47
  203. package/lib/scoring-evidence-quality.mjs +2 -107
  204. package/lib/security-txt.mjs +10 -7
  205. package/lib/semantic-version.mjs +2 -54
  206. package/lib/service-binding-dns.mjs +35 -0
  207. package/lib/smtp-transport-review.mjs +75 -34
  208. package/lib/sslbl-certificates.generated.mjs +141 -138
  209. package/lib/static-html-analysis.mjs +117 -146
  210. package/lib/structured-data-identity.mjs +2 -1
  211. package/lib/technology-markup.mjs +100 -0
  212. package/lib/threat-intelligence-manifests.mjs +11 -139
  213. package/lib/threat-intelligence-runtime.mjs +24 -52
  214. package/lib/threat-intelligence-targets.mjs +6 -34
  215. package/lib/threat-intelligence-types.mjs +2 -36
  216. package/lib/typosquat-generator.mjs +29 -16
  217. package/lib/urlhaus-intelligence.mjs +8 -3
  218. package/lib/urlscan-intelligence.mjs +1 -1
  219. package/lib/website-enrichment.mjs +388 -0
  220. package/lib/website-security-posture.mjs +30 -6
  221. package/lib/website-technology.mjs +111 -71
  222. package/lib/whois-authority.mjs +55 -5
  223. package/lib/whois-chain.mjs +9 -2
  224. package/lib/whois-common-formats.mjs +5 -5
  225. package/lib/whois-contracts.mjs +2 -1
  226. package/lib/whois-dialects.mjs +2 -2
  227. package/lib/whois-normalization.mjs +3 -1
  228. package/lib/whois-parser-support.mjs +8 -1
  229. package/lib/whois-parser.mjs +5 -3
  230. package/lib/whois-transport.mjs +23 -7
  231. package/package.json +14 -14
  232. package/packages/analysis/analyst-taxonomy.mjs +40 -0
  233. package/packages/analysis/bounded-json.mjs +330 -0
  234. package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
  235. package/packages/analysis/cli-argument-grammar.mjs +216 -0
  236. package/packages/analysis/ct-query.mjs +27 -0
  237. package/packages/analysis/ct-response-bounds.mjs +9 -0
  238. package/packages/analysis/external-intelligence-risk.mjs +124 -0
  239. package/packages/analysis/http-evidence-bounds.mjs +8 -0
  240. package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
  241. package/packages/analysis/opportunity-scoring.mjs +107 -0
  242. package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
  243. package/packages/analysis/portable-generator.mjs +29 -0
  244. package/packages/analysis/project-metadata.mjs +8 -0
  245. package/packages/analysis/registrable-domain.mjs +56 -0
  246. package/packages/analysis/risk-calibration-summary.mjs +352 -0
  247. package/packages/analysis/risk-scoring.mjs +345 -0
  248. package/packages/analysis/scoring-evidence-quality.mjs +107 -0
  249. package/packages/analysis/semantic-version.mjs +54 -0
  250. package/packages/analysis/threat-intelligence-types.mjs +75 -0
  251. package/packages/cases/case-attachment-model.mjs +83 -0
  252. package/packages/cases/case-evidence-links.mjs +135 -0
  253. package/packages/cases/case-evidence-model.mjs +229 -69
  254. package/packages/cases/case-evidence-quality.mjs +5 -0
  255. package/packages/cases/case-export-input.mjs +55 -0
  256. package/packages/cases/case-incident-context.mjs +52 -0
  257. package/packages/cases/case-markdown.mjs +11 -0
  258. package/packages/cases/case-migration-model.mjs +98 -48
  259. package/packages/cases/case-model.mjs +1 -0
  260. package/packages/cases/case-recheck-model.mjs +149 -0
  261. package/packages/cases/case-record-contracts.mjs +1 -1
  262. package/packages/cases/case-record-core.mjs +4 -3
  263. package/packages/cases/case-record-operations.mjs +123 -133
  264. package/packages/cases/case-record-projection.mjs +30 -20
  265. package/packages/cases/case-record-version-input.mjs +19 -0
  266. package/packages/cases/case-report.mjs +114 -67
  267. package/packages/cases/case-requested-evidence.mjs +146 -0
  268. package/packages/cases/case-response-actions.mjs +756 -0
  269. package/packages/cases/case-response-model.mjs +49 -1129
  270. package/packages/cases/case-response-outcomes.mjs +338 -0
  271. package/packages/cases/case-response-packet-render.mjs +157 -0
  272. package/packages/cases/case-response-packet-types.mjs +1 -0
  273. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  274. package/packages/cases/case-response-packet.mjs +58 -593
  275. package/packages/cases/case-response-records.mjs +93 -0
  276. package/packages/cases/case-response-review-inputs.mjs +382 -0
  277. package/packages/cases/case-response-values.mjs +85 -0
  278. package/packages/cases/case-review-package.mjs +45 -0
  279. package/packages/cases/case-selection.mjs +46 -0
  280. package/packages/cases/case-storage-model.mjs +26 -1
  281. package/packages/cases/case-workflow-metadata.mjs +225 -0
  282. package/packages/cases/case-workflow-migration.mjs +43 -0
  283. package/packages/cases/http-summary.mjs +6 -1
  284. package/packages/cases/response-route-freshness.mjs +16 -0
  285. package/packages/comparison/capture-context.mjs +61 -0
  286. package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
  287. package/packages/comparison/favicon-similarity.mjs +1 -1
  288. package/packages/comparison/page-similarity.mjs +3 -2
  289. package/packages/comparison/relationship-evidence.mjs +103 -14
  290. package/packages/comparison/relationship-provenance.mjs +99 -0
  291. package/packages/contracts/analyst-interchange.mjs +234 -0
  292. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  293. package/packages/contracts/analyst-review-state.mjs +5 -4
  294. package/packages/contracts/case-portability.mjs +370 -114
  295. package/packages/contracts/case-views-contract.mjs +12 -0
  296. package/packages/contracts/case-views.mjs +26 -0
  297. package/packages/contracts/cli-command-semantics.mjs +3 -0
  298. package/packages/contracts/cli-grammar.mjs +6 -0
  299. package/packages/contracts/cli-lookup-brief.mjs +2 -0
  300. package/packages/contracts/cli-lookup.mjs +0 -1
  301. package/packages/contracts/context-review.mjs +24 -0
  302. package/packages/contracts/document-intake.mjs +11 -0
  303. package/packages/contracts/domain-control-flight-recorder.mjs +98 -44
  304. package/packages/contracts/domain-control-manifest.mjs +128 -74
  305. package/packages/contracts/domain-control-monitor.mjs +59 -45
  306. package/packages/contracts/domain-control-review.mjs +200 -121
  307. package/packages/contracts/domain-name.mjs +21 -0
  308. package/packages/contracts/external-observation-interchange.mjs +2 -1
  309. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  310. package/packages/contracts/har-review.mjs +2 -0
  311. package/packages/contracts/http-url.mjs +2 -0
  312. package/packages/contracts/identity-events.mjs +4 -0
  313. package/packages/contracts/investigation-package-limits.mjs +24 -0
  314. package/packages/contracts/investigation-portability.mjs +56 -27
  315. package/packages/contracts/investigation-run.mjs +18 -0
  316. package/packages/contracts/ip-address.mjs +44 -0
  317. package/packages/contracts/lookup-evidence.mjs +35 -0
  318. package/packages/contracts/mail-authentication.mjs +5 -0
  319. package/packages/contracts/message-intake.mjs +31 -0
  320. package/packages/contracts/offline-comparison.mjs +4 -2
  321. package/packages/contracts/page-fingerprints.mjs +15 -0
  322. package/packages/contracts/passive-mail.mjs +10 -0
  323. package/packages/contracts/risk-calibration.mjs +0 -1
  324. package/packages/contracts/schema-compatibility.mjs +1 -1
  325. package/packages/contracts/schema-lifecycle.mjs +83 -153
  326. package/packages/contracts/selected-file-limits.mjs +6 -0
  327. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  328. package/packages/contracts/web-capture.mjs +10 -0
  329. package/packages/contracts/workspace-portability.mjs +219 -238
  330. package/packages/evidence/artifact-integrity.mjs +8 -2
  331. package/packages/evidence/artifact-structure.mjs +105 -0
  332. package/packages/evidence/collection-quality.mjs +67 -0
  333. package/packages/evidence/decision-fact.mjs +4 -1
  334. package/packages/evidence/dns-query-outcome.mjs +72 -0
  335. package/packages/evidence/domain-control-runtime.mjs +155 -27
  336. package/packages/evidence/domain-posture-context.mjs +79 -0
  337. package/packages/evidence/email-recipient.mjs +18 -0
  338. package/packages/evidence/image-regions.mjs +56 -0
  339. package/packages/evidence/latest-observations.mjs +28 -0
  340. package/packages/evidence/lookup-target.mjs +108 -0
  341. package/packages/evidence/observation.mjs +18 -1
  342. package/packages/evidence/passphrase-encryption.mjs +44 -0
  343. package/packages/evidence/png-pixels.mjs +117 -0
  344. package/packages/evidence/public-address-policy.mjs +83 -0
  345. package/packages/evidence/record-identity.mjs +72 -0
  346. package/packages/evidence/registry-dates.mjs +190 -0
  347. package/packages/evidence/retained-file.mjs +69 -0
  348. package/packages/evidence/review-age.mjs +14 -0
  349. package/packages/interchange/bagit.mjs +342 -0
  350. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  351. package/packages/interchange/crc32.mjs +17 -0
  352. package/packages/interchange/defensive-indicator-export.mjs +264 -0
  353. package/packages/interchange/external-findings-import.mjs +93 -61
  354. package/packages/interchange/managed-indicator-export.mjs +35 -0
  355. package/packages/interchange/managed-indicator-set.mjs +210 -0
  356. package/packages/interchange/misp-indicator-export.mjs +93 -0
  357. package/packages/interchange/sharing-policy.mjs +56 -0
  358. package/packages/interchange/stix-indicator-export.mjs +129 -0
  359. package/packages/interchange/web-capture-import.mjs +389 -0
  360. package/packages/investigation/acquisition-decision-packet.mjs +1 -1
  361. package/packages/investigation/bulk-mail-exposure.mjs +9 -14
  362. package/packages/investigation/capture-coverage.mjs +57 -0
  363. package/packages/investigation/connector-provenance-review.mjs +100 -0
  364. package/packages/investigation/context-review.mjs +35 -0
  365. package/packages/investigation/document-intake.mjs +81 -0
  366. package/packages/investigation/docx-intake.mjs +133 -0
  367. package/packages/investigation/domain-history-review.mjs +100 -0
  368. package/packages/investigation/domain-transition-review.mjs +47 -0
  369. package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
  370. package/packages/investigation/har-intake.mjs +68 -0
  371. package/packages/investigation/identity-event-intake.mjs +131 -0
  372. package/packages/investigation/identity-incident-review.mjs +39 -0
  373. package/packages/investigation/incident-sequence-review.mjs +58 -0
  374. package/packages/investigation/intake-report.mjs +19 -0
  375. package/packages/investigation/investigation-bagit.mjs +14 -0
  376. package/packages/investigation/investigation-capsule.mjs +33 -5
  377. package/packages/investigation/investigation-manifest.mjs +200 -0
  378. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  379. package/packages/investigation/investigation-package.mjs +200 -0
  380. package/packages/investigation/link-intake.mjs +179 -0
  381. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  382. package/packages/investigation/lookup-asset-graph.mjs +161 -141
  383. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  384. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  385. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  386. package/packages/investigation/mail-authentication-review.mjs +222 -0
  387. package/packages/investigation/mail-header-identity.mjs +209 -0
  388. package/packages/investigation/message-intake.mjs +195 -0
  389. package/packages/investigation/page-behaviour.mjs +113 -0
  390. package/packages/investigation/pdf-intake.mjs +155 -0
  391. package/packages/investigation/platform-continuity-review.mjs +60 -0
  392. package/packages/investigation/qr-intake.mjs +56 -0
  393. package/packages/investigation/requested-action-hints.mjs +11 -0
  394. package/packages/investigation/selected-input-review.mjs +17 -0
  395. package/packages/investigation/storefront-review.mjs +70 -0
  396. package/packages/monitoring/analyst-review-state.mjs +38 -90
  397. package/packages/workspace/brand-profile-model.mjs +160 -22
  398. package/packages/workspace/bulk-columns.mjs +17 -0
  399. package/packages/workspace/bulk-review-model.mjs +39 -31
  400. package/packages/workspace/bulk-session-model.mjs +112 -27
  401. package/packages/workspace/bulk-sort.mjs +1 -0
  402. package/packages/workspace/campaign-model.mjs +1 -1
  403. package/packages/workspace/case-views.mjs +102 -0
  404. package/packages/workspace/detection-rule-model.mjs +1 -1
  405. package/packages/workspace/hostile-input.mjs +29 -8
  406. package/packages/workspace/investigation-guide.mjs +2 -2
  407. package/packages/workspace/investigation-template-model.mjs +45 -6
  408. package/packages/workspace/page-baseline.mjs +20 -17
  409. package/packages/workspace/relationship-observation-model.mjs +54 -35
  410. package/packages/workspace/shortlist-model.mjs +14 -5
  411. package/packages/workspace/watchlist-history.mjs +30 -14
  412. package/packages/workspace/watchlist-store.mjs +24 -6
  413. package/packages/workspace/website-snapshot-model.mjs +98 -33
  414. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  415. package/packages/workspace/workspace-archive.mjs +90 -25
  416. package/third-party-notices.txt +577 -17
  417. package/lib/hostname.mjs +0 -38
  418. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -0,0 +1,568 @@
1
+ // Portable tree bounds, private-field exclusion and availability projection.
2
+ // Source-specific publication assembly remains in evidence-export.mts.
3
+ import { isSafeJsonObjectKey } from "../packages/analysis/bounded-json.mjs";
4
+ import { recordOrNull } from "./json-record.mjs";
5
+ import { isUriShapedLabel } from "../packages/analysis/portable-generator.mjs";
6
+ import { validHttpDeliveryMetadata, validPagePublicationMetadata } from "./homepage-metadata-contract.mjs";
7
+ import { normalizeWebCollectionQuality } from "../packages/evidence/collection-quality.mjs";
8
+ import { LOOKUP_EVIDENCE_PORTABLE_MAX_BYTES, LOOKUP_EVIDENCE_PORTABLE_MAX_ENTRIES, LOOKUP_EVIDENCE_PORTABLE_MAX_DEPTH, LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS, LOOKUP_EVIDENCE_PORTABLE_MAX_KEY_LENGTH, LOOKUP_EVIDENCE_PORTABLE_MAX_STRING_LENGTH, } from "../packages/contracts/lookup-evidence.mjs";
9
+ const PUBLIC_LOOKUP_AVAILABILITY_ANALYSIS_KEYS = new Set([
10
+ 'applicable', 'type', 'domain', 'state', 'confidence', 'detail', 'source',
11
+ 'rdapServer', 'nameservers', 'statuses', 'registrar', 'registrant', 'abuse',
12
+ 'createdDate', 'expiryDate', 'createdDateIso', 'expiryDateIso', 'domainAgeDays',
13
+ 'expiresInDays', 'privacyProtected', 'dnssec', 'activityStatus',
14
+ 'websiteProbeStatus', 'websiteProbeDetail', 'http', 'deepScanComplete',
15
+ 'faviconHash', 'faviconPHash', 'pageTitle', 'hasPasswordField',
16
+ 'phishingLanguageMatch', 'hasExternalFormAction', 'externalAssetHosts',
17
+ 'pageIdentity', 'credentialSurfaceProfile', 'structuredDataIdentity',
18
+ 'technologyProfile', 'pageRoleProfile', 'clientBehaviorProfile',
19
+ 'securityPosture', 'dns', 'tls', 'hasMx', 'hasNullMx', 'mxHosts', 'hasSpf',
20
+ 'hasDmarc', 'bulkComparison', 'limitations',
21
+ ]);
22
+ const LOOKUP_AVAILABILITY_ANALYSIS_KEYS = new Set([
23
+ ...[...PUBLIC_LOOKUP_AVAILABILITY_ANALYSIS_KEYS]
24
+ .filter((key) => !['registrar', 'registrant', 'abuse'].includes(key)),
25
+ 'registryContactsExcluded', 'observationHostname', 'webObservationMode',
26
+ 'webCollectionQuality',
27
+ ]);
28
+ const HISTORICAL_LOOKUP_AVAILABILITY_ANALYSIS_KEYS = new Set([...LOOKUP_AVAILABILITY_ANALYSIS_KEYS].filter(key => key !== 'webCollectionQuality'));
29
+ const PRIVATE_EVIDENCE_KEYS = new Set([
30
+ 'authorization', 'proxyauthorization', 'cookie', 'cookies', 'setcookie',
31
+ 'session', 'sessionid', 'sessiontoken', 'token', 'accesstoken', 'refreshtoken',
32
+ 'apikey', 'password', 'secret', 'credentials', 'requestheaders', 'responseheaders',
33
+ 'headers', 'rawbody', 'responsebody',
34
+ ]);
35
+ const SAFE_EVIDENCE_KEYS = new Set([
36
+ 'credentialsurfaceprofile', 'credentialsurfaceversion', 'haspasswordfield', 'tokencount',
37
+ ]);
38
+ const PORTABLE_URL_KEYS = new Set([
39
+ 'endpoint', 'url', 'uri', 'href', 'rdapserver', 'finalurl', 'requestedurl',
40
+ 'requesturl', 'officiallookupurl', 'declaredorigin', 'actionurl', 'from', 'to',
41
+ ]);
42
+ const PORTABLE_ORIGIN_COLLECTION_KEYS = new Set([
43
+ 'embeddedorigins', 'externalactionorigins', 'externalorigins',
44
+ ]);
45
+ // Availability is assembled from a finite set of versioned, bounded analysis
46
+ // models. Portable exports retain only their declared output vocabulary rather
47
+ // than recursively copying arbitrary browser-local extensions. This set is
48
+ // intentionally exact and case-sensitive; new model fields must be reviewed at
49
+ // this privacy boundary before they can enter a portable artefact.
50
+ const LOOKUP_AVAILABILITY_PORTABLE_NESTED_KEYS = new Set([
51
+ // Shared observation and summary fields.
52
+ 'version', 'profileVersion', 'status', 'state', 'source', 'observedAt',
53
+ 'scanMode', 'durationMs', 'complete', 'truncated', 'limitations',
54
+ 'diagnostics', 'detail', 'error', 'count', 'total', 'discarded', 'summary',
55
+ 'findings', 'id', 'label', 'tone', 'category', 'confidence', 'compatibility', 'role', 'roles', 'evidence',
56
+ 'description',
57
+ 'value', 'values', 'name', 'type', 'types', 'mode', 'owner', 'target',
58
+ 'url', 'uri', 'href',
59
+ 'priority', 'ttl', 'records',
60
+ // HTTP observation and retained response metadata.
61
+ 'requestUrl', 'finalUrl', 'transportSecurity', 'redirectCount',
62
+ 'redirectLimitReached', 'redirects', 'crossOriginRedirect', 'httpsDowngrade',
63
+ 'attempts', 'response', 'contentType', 'contentLanguage', 'server',
64
+ 'declaredContentLength', 'capturedBodyBytes', 'bodyInspected', 'bodyTruncated',
65
+ 'bodyHash', 'securityHeaders', 'strictTransportSecurity',
66
+ 'contentSecurityPolicy', 'xFrameOptions', 'xContentTypeOptions',
67
+ 'referrerPolicy', 'algorithm', 'scope', 'bytes', 'from', 'to',
68
+ 'queryOmitted', 'pathTruncated', 'outcome', 'httpStatus', 'attemptCount',
69
+ // DNS observation, effective CAA, and HTTPS/SVCB parameter summaries.
70
+ 'a', 'aaaa', 'caa', 'cname', 'dmarc', 'https', 'mx', 'ns', 'ptr', 'soa',
71
+ 'spf', 'delegation', 'caaPolicy', 'policyVersion', 'queryLimit',
72
+ 'queriedOwners', 'effectiveOwner', 'inherited', 'tree', 'registryEvidence',
73
+ 'exchange', 'nsname', 'hostmaster', 'serial', 'refresh', 'retry', 'expire', 'minttl',
74
+ 'tag', 'critical', 'parameters', 'parametersIgnored', 'mandatory', 'alpn',
75
+ 'noDefaultAlpn', 'port', 'ipv4hint', 'ipv6hint', 'opaque', 'key', 'length',
76
+ 'unknownKeys', 'unsupportedMandatoryKeys', 'targetIsOwner',
77
+ 'serviceUnavailable', 'compatible', 'effectiveOwner', 'queriedOwners',
78
+ // TLS profile, certificate, extension, and connection summaries. Runtime
79
+ // authorisation objects are deliberately absent from this portable set.
80
+ 'connectedAddress', 'connectedFamily', 'sniHost', 'protocol', 'alpnProtocol',
81
+ 'cipher', 'ephemeralKey', 'hostname', 'matches', 'validity', 'certificate',
82
+ 'chain', 'chainTruncated', 'standardName', 'size', 'subject', 'issuer',
83
+ 'serialNumber', 'validFrom', 'validTo', 'fingerprintSha1',
84
+ 'fingerprintSha256', 'isCertificateAuthority', 'subjectAltNames',
85
+ 'dnsNames', 'ipAddresses', 'otherNames', 'classes', 'dns', 'ip', 'directoryName',
86
+ 'registeredId', 'otherName', 'publicKey', 'bits', 'curve',
87
+ 'signature', 'oid', 'extendedKeyUsage', 'authorityInformationAccess',
88
+ 'ocsp', 'caIssuers', 'unknownMethods', 'extensionProfile', 'parsed', 'partial',
89
+ 'certificatePolicies', 'crlDistributionPoints', 'commonNames',
90
+ 'organizations', 'organizationalUnits', 'countries', 'localities', 'states',
91
+ 'connectionAttempts', 'resolvedAddressCount', 'discardedFields',
92
+ // Page identity, relationships, and bounded fingerprints.
93
+ 'identityVersion', 'documentLanguage', 'canonical', 'metaRefresh',
94
+ 'openGraph', 'title', 'siteName', 'generator', 'forms', 'postCount',
95
+ 'insecureActionCount', 'externalActionOrigins', 'resources', 'byType',
96
+ 'image', 'script', 'stylesheet', 'link', 'frame', 'media', 'object',
97
+ 'externalOrigins', 'embeddedOrigins', 'contactDomains', 'downloads',
98
+ 'explicitCount', 'riskyCount', 'riskyFileTypes', 'trackingIdentifiers',
99
+ 'fingerprints', 'fingerprintVersion', 'exact', 'normalizedHtml',
100
+ 'visibleText', 'domStructure', 'similarity', 'formStructure', 'resourceHosts',
101
+ 'identifiers', 'featureCount', 'nodeCount', 'formCount', 'controlCount',
102
+ 'parser', 'tokenCount', 'tagsExamined', 'discardedUrls', 'formsObserved',
103
+ 'relationshipTagsExamined', 'relationshipUrlsDiscarded',
104
+ 'trackingIdentifiersTruncated',
105
+ // Credential, structured identity, role, behaviour, technology, and posture
106
+ // projections. Only fixed categories/counts and bounded descriptions enter.
107
+ 'credentialSurfaceVersion', 'methods', 'actions', 'missing', 'get', 'post',
108
+ 'dialog', 'other', 'sameOrigin', 'external', 'cleartext', 'unclassified',
109
+ 'inputs', 'classifiedCount', 'categories', 'password', 'email', 'username',
110
+ 'one_time_code', 'payment', 'inputsObserved', 'classifiedInputs',
111
+ 'unclassifiedActions', 'structuredDataVersion', 'scriptsObserved',
112
+ 'scriptsExamined', 'charactersExamined', 'documentsParsed', 'malformedScripts',
113
+ 'externalScriptsSkipped', 'objectsExamined', 'arrayItemsExamined',
114
+ 'discardedProperties', 'entities', 'declaredOrigin', 'sameAsHosts',
115
+ 'pageRoleProfileVersion', 'primaryRole', 'role', 'rolesObserved',
116
+ 'clientBehaviorProfileVersion', 'scriptSummary', 'elementsObserved',
117
+ 'referencedScripts', 'inlineScripts', 'moduleScripts', 'indicators',
118
+ 'evidenceClass', 'occurrences', 'explanation', 'indicatorsObserved',
119
+ 'scriptElementsExamined', 'inlineCharactersExamined', 'technologyProfile',
120
+ 'browserLibraryProfile', 'catalog', 'knownExploitedCatalog', 'releasedAt',
121
+ 'sourceRevision', 'apparentVersion', 'detectionMethods', 'advisoryCount',
122
+ 'highestSeverity', 'advisoryIdentifiers', 'knownExploitedCount',
123
+ 'knownExploitedIdentifiers', 'weaknessClasses', 'htmlEvaluated',
124
+ 'generatorEvaluated', 'serverEvaluated', 'resourceOriginsEvaluated',
125
+ 'passiveHeadersEvaluated', 'tagLimitReached', 'referencesExamined',
126
+ 'inlineScriptsExamined', 'inlineSignatureCharactersExamined',
127
+ 'inlineSignatureTimedOut', 'inlineSignatureUnavailable', 'catalogComponents',
128
+ 'advisoryMatches', 'knownExploitedMatches', 'postureVersion',
129
+ 'potentialExposure', 'observedAbsence', 'unavailable',
130
+ // Bounded cross-row comparison evidence.
131
+ 'bulkComparison', 'technology', 'tls', 'ids', 'issuerLabel', 'spkiSha256',
132
+ ]);
133
+ const LOOKUP_AVAILABILITY_CREDENTIAL_CATEGORY_KEYS = new Set([
134
+ 'password', 'email', 'username', 'one_time_code', 'payment',
135
+ ]);
136
+ const LOOKUP_AVAILABILITY_TLS_SAN_CLASS_KEYS = new Set([
137
+ 'dns', 'ip', 'email', 'uri', 'directoryName', 'registeredId', 'otherName', 'unclassified',
138
+ ]);
139
+ const LOOKUP_AVAILABILITY_VALUE_PATHS = new Set([
140
+ 'dns.records.caa',
141
+ 'dns.caaPolicy.records',
142
+ 'http.response.bodyHash',
143
+ 'pageIdentity.trackingIdentifiers',
144
+ 'pageIdentity.fingerprints.exact',
145
+ 'pageIdentity.fingerprints.normalizedHtml',
146
+ 'pageIdentity.fingerprints.visibleText',
147
+ 'pageIdentity.fingerprints.domStructure',
148
+ 'pageIdentity.fingerprints.domStructure.similarity',
149
+ 'pageIdentity.fingerprints.formStructure',
150
+ 'pageIdentity.fingerprints.resourceHosts',
151
+ 'pageIdentity.fingerprints.identifiers',
152
+ 'pageIdentity.fingerprints.identifiers.values',
153
+ ]);
154
+ const LOOKUP_AVAILABILITY_OWNER_PATHS = new Set([
155
+ 'dns.records.https',
156
+ 'dns.caaPolicy.queriedOwners',
157
+ ]);
158
+ function normalizedEvidenceKey(value) {
159
+ return value.toLowerCase().replace(/[^a-z0-9]/gu, '');
160
+ }
161
+ function privateEvidenceKey(value, item) {
162
+ const normalized = normalizedEvidenceKey(value);
163
+ if (SAFE_EVIDENCE_KEYS.has(normalized))
164
+ return false;
165
+ if (normalized === 'password' && (typeof item === 'number' || typeof item === 'boolean'))
166
+ return false;
167
+ return PRIVATE_EVIDENCE_KEYS.has(normalized)
168
+ || normalized.includes('authorization')
169
+ || normalized.includes('authheader')
170
+ || normalized.includes('cookie')
171
+ || normalized.includes('apikey')
172
+ || normalized.includes('secret')
173
+ || normalized.includes('sessionkey')
174
+ || normalized.includes('passwordvalue')
175
+ || normalized.endsWith('password')
176
+ || /(?:access|auth|bearer|csrf|refresh|session)token$/u.test(normalized)
177
+ || /(?:request|response|authorization|proxy)headers?$/u.test(normalized)
178
+ || (normalized.includes('credential') && !normalized.startsWith('credentialsurface'));
179
+ }
180
+ export function portableUri(value, legacyUris = false) {
181
+ const text = boundedString(value, 2048);
182
+ if (!text)
183
+ return null;
184
+ try {
185
+ const url = new URL(text);
186
+ if (!['http:', 'https:', 'mailto:', 'tel:', 'dns:', 'openpgp4fpr:'].includes(url.protocol)
187
+ || url.username
188
+ || url.password)
189
+ return null;
190
+ if (!legacyUris || url.protocol === 'http:' || url.protocol === 'https:') {
191
+ url.search = '';
192
+ url.hash = '';
193
+ }
194
+ return url.toString().slice(0, 2048);
195
+ }
196
+ catch {
197
+ return null;
198
+ }
199
+ }
200
+ function portableOrigin(value) {
201
+ const text = boundedString(value, 2048);
202
+ if (!text)
203
+ return null;
204
+ try {
205
+ const url = new URL(text);
206
+ return ['http:', 'https:'].includes(url.protocol) && !url.username && !url.password
207
+ ? url.origin
208
+ : null;
209
+ }
210
+ catch {
211
+ return null;
212
+ }
213
+ }
214
+ function consumePortableProjectionEntry(state, depth) {
215
+ state.entries += 1;
216
+ if (state.entries > LOOKUP_EVIDENCE_PORTABLE_MAX_ENTRIES) {
217
+ throw new TypeError(`Lookup evidence projection exceeds the ${LOOKUP_EVIDENCE_PORTABLE_MAX_ENTRIES.toLocaleString('en')}-entry portable limit.`);
218
+ }
219
+ if (depth > LOOKUP_EVIDENCE_PORTABLE_MAX_DEPTH) {
220
+ throw new TypeError(`Lookup evidence projection exceeds the ${LOOKUP_EVIDENCE_PORTABLE_MAX_DEPTH}-level portable nesting limit.`);
221
+ }
222
+ }
223
+ function projectPortableString(value) {
224
+ const sanitized = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, ' ');
225
+ return sanitized.length <= LOOKUP_EVIDENCE_PORTABLE_MAX_STRING_LENGTH
226
+ ? sanitized
227
+ : sanitized.slice(0, LOOKUP_EVIDENCE_PORTABLE_MAX_STRING_LENGTH);
228
+ }
229
+ function projectLookupEvidenceAvailabilityString(value) {
230
+ const sanitized = projectPortableString(value)
231
+ .replace(/[\u0080-\u009f]/gu, ' ')
232
+ .replace(/\p{Default_Ignorable_Code_Point}/gu, '');
233
+ return isUriShapedLabel(value) ? null : sanitized;
234
+ }
235
+ function projectLookupEvidencePrivacySafeTreeValue(value, state, depth) {
236
+ consumePortableProjectionEntry(state, depth);
237
+ if (value === null || typeof value === 'boolean' || typeof value === 'number')
238
+ return value;
239
+ if (typeof value === 'string')
240
+ return projectPortableString(value);
241
+ if (Array.isArray(value)) {
242
+ return value.slice(0, LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS)
243
+ .map((item) => projectLookupEvidencePrivacySafeTreeValue(item, state, depth + 1));
244
+ }
245
+ const source = recordOrNull(value);
246
+ if (!source)
247
+ return null;
248
+ const output = {};
249
+ for (const [key, item] of Object.entries(source).slice(0, LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS)) {
250
+ if (!isSafeJsonObjectKey(key))
251
+ throw new TypeError('Lookup evidence projection contains an unsafe object key.');
252
+ const normalized = normalizedEvidenceKey(key);
253
+ if (privateEvidenceKey(normalized, item))
254
+ continue;
255
+ if (typeof item === 'string' && PORTABLE_URL_KEYS.has(normalized)) {
256
+ consumePortableProjectionEntry(state, depth + 1);
257
+ output[key] = portableUri(item);
258
+ continue;
259
+ }
260
+ output[key] = projectLookupEvidencePrivacySafeTreeValue(item, state, depth + 1);
261
+ }
262
+ return output;
263
+ }
264
+ export function projectLookupEvidencePrivacySafeTree(value) {
265
+ return projectLookupEvidencePrivacySafeTreeValue(value, { entries: 0 }, 0);
266
+ }
267
+ function projectLookupEvidenceAvailabilityPublicValue(value, path, state, depth) {
268
+ consumePortableProjectionEntry(state, depth);
269
+ if (value === null || typeof value === 'boolean' || typeof value === 'number')
270
+ return value;
271
+ if (typeof value === 'string') {
272
+ const normalizedParent = normalizedEvidenceKey(path.at(-1) || '');
273
+ if (PORTABLE_URL_KEYS.has(normalizedParent))
274
+ return portableUri(value, true);
275
+ if (PORTABLE_ORIGIN_COLLECTION_KEYS.has(normalizedParent))
276
+ return portableOrigin(value);
277
+ return projectPortableString(value);
278
+ }
279
+ if (Array.isArray(value)) {
280
+ return value.slice(0, LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS)
281
+ .map((item) => projectLookupEvidenceAvailabilityPublicValue(item, path, state, depth + 1));
282
+ }
283
+ const source = recordOrNull(value);
284
+ if (!source)
285
+ return null;
286
+ const output = {};
287
+ for (const [key, item] of Object.entries(source).slice(0, LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS)) {
288
+ if (!LOOKUP_AVAILABILITY_PORTABLE_NESTED_KEYS.has(key)
289
+ || privateEvidenceKey(key, item))
290
+ continue;
291
+ const parentPath = path.join('.');
292
+ const sanClassCount = parentPath === 'tls.certificate.subjectAltNames.classes'
293
+ && LOOKUP_AVAILABILITY_TLS_SAN_CLASS_KEYS.has(key)
294
+ && Number.isSafeInteger(item)
295
+ && Number(item) >= 0
296
+ && Number(item) <= LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS;
297
+ if (['dns', 'ip', 'directoryName', 'registeredId', 'otherName'].includes(key)
298
+ && !sanClassCount)
299
+ continue;
300
+ if (parentPath === 'tls.certificate.subjectAltNames.classes'
301
+ && LOOKUP_AVAILABILITY_TLS_SAN_CLASS_KEYS.has(key)
302
+ && !sanClassCount)
303
+ continue;
304
+ if (key === 'classes'
305
+ && (parentPath !== 'tls.certificate.subjectAltNames' || !recordOrNull(item)))
306
+ continue;
307
+ if (key === 'similarity'
308
+ && (parentPath !== 'pageIdentity.fingerprints.domStructure'
309
+ || (item !== null && !recordOrNull(item))))
310
+ continue;
311
+ if (key === 'description'
312
+ && (parentPath !== 'technologyProfile.findings.evidence'
313
+ || typeof item !== 'string'))
314
+ continue;
315
+ output[key] = projectLookupEvidenceAvailabilityPublicValue(item, [...path, key], state, depth + 1);
316
+ }
317
+ return output;
318
+ }
319
+ function projectLookupEvidenceAvailabilityValue(value, path, state, depth, legacyUris) {
320
+ consumePortableProjectionEntry(state, depth);
321
+ if (value === null || typeof value === 'boolean' || typeof value === 'number')
322
+ return value;
323
+ if (typeof value === 'string') {
324
+ const normalizedParent = normalizedEvidenceKey(path.at(-1) || '');
325
+ if (PORTABLE_URL_KEYS.has(normalizedParent))
326
+ return portableUri(value, legacyUris);
327
+ if (PORTABLE_ORIGIN_COLLECTION_KEYS.has(normalizedParent))
328
+ return portableOrigin(value);
329
+ return projectLookupEvidenceAvailabilityString(value);
330
+ }
331
+ if (Array.isArray(value)) {
332
+ return value.slice(0, LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS)
333
+ .map((item) => projectLookupEvidenceAvailabilityValue(item, path, state, depth + 1, legacyUris));
334
+ }
335
+ const source = recordOrNull(value);
336
+ if (!source)
337
+ return null;
338
+ const output = {};
339
+ for (const [key, item] of Object.entries(source).slice(0, LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS)) {
340
+ if (!LOOKUP_AVAILABILITY_PORTABLE_NESTED_KEYS.has(key)
341
+ || privateEvidenceKey(key, item))
342
+ continue;
343
+ const parentPath = path.join('.');
344
+ const boundedCount = Number.isSafeInteger(item)
345
+ && Number(item) >= 0
346
+ && Number(item) <= LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS;
347
+ const credentialCategoryCount = parentPath === 'credentialSurfaceProfile.inputs.categories'
348
+ && boundedCount;
349
+ const sanClassCount = parentPath === 'tls.certificate.subjectAltNames.classes'
350
+ && LOOKUP_AVAILABILITY_TLS_SAN_CLASS_KEYS.has(key)
351
+ && boundedCount;
352
+ if (LOOKUP_AVAILABILITY_CREDENTIAL_CATEGORY_KEYS.has(key)
353
+ && !credentialCategoryCount
354
+ && !sanClassCount)
355
+ continue;
356
+ if (['dns', 'ip', 'directoryName', 'registeredId', 'otherName'].includes(key)
357
+ && !sanClassCount)
358
+ continue;
359
+ if (key === 'classes'
360
+ && (parentPath !== 'tls.certificate.subjectAltNames' || !recordOrNull(item)))
361
+ continue;
362
+ if (key === 'similarity'
363
+ && (parentPath !== 'pageIdentity.fingerprints.domStructure'
364
+ || (item !== null && !recordOrNull(item))))
365
+ continue;
366
+ if (key === 'description'
367
+ && (parentPath !== 'technologyProfile.findings.evidence'
368
+ || typeof item !== 'string'))
369
+ continue;
370
+ if (key === 'owner'
371
+ && (!LOOKUP_AVAILABILITY_OWNER_PATHS.has(parentPath) || typeof item !== 'string'))
372
+ continue;
373
+ if (key === 'value'
374
+ && (!LOOKUP_AVAILABILITY_VALUE_PATHS.has(parentPath)
375
+ || (item !== null && typeof item !== 'string')))
376
+ continue;
377
+ output[key] = projectLookupEvidenceAvailabilityValue(item, [...path, key], state, depth + 1, legacyUris);
378
+ }
379
+ return output;
380
+ }
381
+ /**
382
+ * Canonical portable projection for the bounded Lookup availability models.
383
+ * The root and every nested record are positive allowlists, so an imported
384
+ * extension cannot become authenticated evidence merely by avoiding a
385
+ * credential-key denylist.
386
+ */
387
+ function projectLookupEvidenceAvailabilityWithKeys(value, rootKeys, registryContactsExcluded, currentPrivacyRules, legacyUris = false) {
388
+ const source = recordOrNull(value);
389
+ if (!source)
390
+ return null;
391
+ const state = { entries: 0 };
392
+ consumePortableProjectionEntry(state, 0);
393
+ const output = {};
394
+ for (const key of rootKeys) {
395
+ if (!Object.hasOwn(source, key))
396
+ continue;
397
+ const item = source[key];
398
+ if (privateEvidenceKey(key, item))
399
+ continue;
400
+ if (key === 'webCollectionQuality') {
401
+ output[key] = normalizeWebCollectionQuality(item);
402
+ continue;
403
+ }
404
+ output[key] = currentPrivacyRules
405
+ ? projectLookupEvidenceAvailabilityValue(item, [key], state, 1, legacyUris)
406
+ : projectLookupEvidenceAvailabilityPublicValue(item, [key], state, 1);
407
+ }
408
+ if (registryContactsExcluded)
409
+ output.registryContactsExcluded = true;
410
+ const pageIdentity = recordOrNull(source.pageIdentity);
411
+ const publicationMetadata = pageIdentity?.publicationMetadata;
412
+ if (publicationMetadata !== undefined) {
413
+ if (!['success', 'partial'].includes(String(pageIdentity?.status))
414
+ || !validPagePublicationMetadata(publicationMetadata)) {
415
+ throw new TypeError('Lookup evidence contains invalid page publication metadata.');
416
+ }
417
+ const projectedPageIdentity = recordOrNull(output.pageIdentity);
418
+ if (projectedPageIdentity) {
419
+ projectedPageIdentity.publicationMetadata = projectLookupEvidencePrivacySafeTree(publicationMetadata);
420
+ }
421
+ }
422
+ const httpResponse = recordOrNull(recordOrNull(source.http)?.response);
423
+ const sourceHttp = recordOrNull(source.http);
424
+ const deliveryMetadata = httpResponse?.deliveryMetadata;
425
+ if (deliveryMetadata !== undefined) {
426
+ if (!['success', 'partial'].includes(String(sourceHttp?.status))
427
+ || !validHttpDeliveryMetadata(deliveryMetadata)) {
428
+ throw new TypeError('Lookup evidence contains invalid HTTP delivery metadata.');
429
+ }
430
+ const projectedHttp = recordOrNull(output.http);
431
+ const projectedResponse = recordOrNull(projectedHttp?.response);
432
+ if (projectedResponse) {
433
+ projectedResponse.deliveryMetadata = projectLookupEvidencePrivacySafeTree(deliveryMetadata);
434
+ }
435
+ }
436
+ return output;
437
+ }
438
+ /** Exact availability projection used only to validate public schema 26. */
439
+ export function projectLookupEvidenceAvailabilityPublic(value) {
440
+ return projectLookupEvidenceAvailabilityWithKeys(value, PUBLIC_LOOKUP_AVAILABILITY_ANALYSIS_KEYS, false, false);
441
+ }
442
+ /**
443
+ * Current availability projection. Registry-derived registrar, registrant,
444
+ * and abuse contact routes are deliberately excluded at the root boundary;
445
+ * the marker prevents that privacy omission from being read as source absence.
446
+ */
447
+ export function projectLookupEvidenceAvailability(value, { legacyUris = false } = {}) {
448
+ return projectLookupEvidenceAvailabilityWithKeys(value, legacyUris ? HISTORICAL_LOOKUP_AVAILABILITY_ANALYSIS_KEYS : LOOKUP_AVAILABILITY_ANALYSIS_KEYS, true, true, legacyUris);
449
+ }
450
+ export function assertLookupEvidencePrivacySafeTree(value, { legacyUris = false } = {}) {
451
+ assertLookupEvidencePortableTree(value);
452
+ const pending = [value];
453
+ while (pending.length) {
454
+ const current = pending.pop();
455
+ if (current === null || typeof current === 'boolean' || typeof current === 'number')
456
+ continue;
457
+ if (typeof current === 'string') {
458
+ if (projectLookupEvidencePrivacySafeTree(current) !== current) {
459
+ throw new TypeError('Lookup evidence contains a non-portable string value.');
460
+ }
461
+ continue;
462
+ }
463
+ if (Array.isArray(current)) {
464
+ for (const item of current)
465
+ pending.push(item);
466
+ continue;
467
+ }
468
+ const source = recordOrNull(current);
469
+ if (!source)
470
+ throw new TypeError('Lookup evidence contains a non-JSON value.');
471
+ for (const [key, item] of Object.entries(source)) {
472
+ const normalized = normalizedEvidenceKey(key);
473
+ if (privateEvidenceKey(normalized, item)) {
474
+ throw new TypeError('Lookup evidence contains excluded private request or session material.');
475
+ }
476
+ if (typeof item === 'string' && PORTABLE_URL_KEYS.has(normalized) && portableUri(item, legacyUris) !== item) {
477
+ throw new TypeError('Lookup evidence contains a credential-bearing or non-portable URL.');
478
+ }
479
+ pending.push(item);
480
+ }
481
+ }
482
+ }
483
+ export function projectedKnownRecord(value, keys) {
484
+ const source = recordOrNull(value);
485
+ if (!source)
486
+ return null;
487
+ const output = {};
488
+ for (const key of keys) {
489
+ if (Object.hasOwn(source, key))
490
+ output[key] = projectLookupEvidencePrivacySafeTree(source[key]);
491
+ }
492
+ return output;
493
+ }
494
+ export function assertLookupEvidencePortableTree(value) {
495
+ const pending = [{ value, depth: 0 }];
496
+ let entries = 0;
497
+ while (pending.length) {
498
+ const current = pending.pop();
499
+ if (!current)
500
+ break;
501
+ entries += 1;
502
+ if (entries > LOOKUP_EVIDENCE_PORTABLE_MAX_ENTRIES) {
503
+ throw new TypeError(`Lookup evidence exceeds the ${LOOKUP_EVIDENCE_PORTABLE_MAX_ENTRIES.toLocaleString('en')}-entry portable limit.`);
504
+ }
505
+ if (current.depth > LOOKUP_EVIDENCE_PORTABLE_MAX_DEPTH) {
506
+ throw new TypeError(`Lookup evidence exceeds the ${LOOKUP_EVIDENCE_PORTABLE_MAX_DEPTH}-level portable nesting limit.`);
507
+ }
508
+ if (typeof current.value === 'string') {
509
+ if (current.value.length > LOOKUP_EVIDENCE_PORTABLE_MAX_STRING_LENGTH) {
510
+ throw new TypeError('Lookup evidence contains an over-bound string.');
511
+ }
512
+ continue;
513
+ }
514
+ if (current.value === null || typeof current.value === 'boolean')
515
+ continue;
516
+ if (typeof current.value === 'number') {
517
+ if (!Number.isFinite(current.value)) {
518
+ throw new TypeError('Lookup evidence contains a non-finite number.');
519
+ }
520
+ continue;
521
+ }
522
+ if (Array.isArray(current.value)) {
523
+ if (current.value.length > LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS) {
524
+ throw new TypeError('Lookup evidence contains an over-bound array.');
525
+ }
526
+ for (const item of current.value)
527
+ pending.push({ value: item, depth: current.depth + 1 });
528
+ continue;
529
+ }
530
+ if (current.value && typeof current.value === 'object') {
531
+ const source = current.value;
532
+ const prototype = Object.getPrototypeOf(source);
533
+ if (prototype !== Object.prototype && prototype !== null) {
534
+ throw new TypeError('Lookup evidence contains a non-JSON object.');
535
+ }
536
+ const keys = Object.keys(source);
537
+ if (keys.length > LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS
538
+ || keys.some((key) => key.length > LOOKUP_EVIDENCE_PORTABLE_MAX_KEY_LENGTH
539
+ || /[\u0000-\u001f\u007f]/u.test(key)
540
+ || !isSafeJsonObjectKey(key))) {
541
+ throw new TypeError('Lookup evidence contains an over-bound object.');
542
+ }
543
+ for (const item of Object.values(source))
544
+ pending.push({ value: item, depth: current.depth + 1 });
545
+ continue;
546
+ }
547
+ throw new TypeError('Lookup evidence contains a non-JSON value.');
548
+ }
549
+ }
550
+ export function serializeLookupEvidence(value, pretty = false) {
551
+ assertLookupEvidencePortableTree(value);
552
+ let output;
553
+ try {
554
+ output = JSON.stringify(value, null, pretty ? 2 : 0);
555
+ }
556
+ catch {
557
+ throw new TypeError('Lookup evidence could not be serialized as JSON.');
558
+ }
559
+ if (new TextEncoder().encode(output).byteLength > LOOKUP_EVIDENCE_PORTABLE_MAX_BYTES) {
560
+ throw new TypeError('Lookup evidence exceeds the 5 MiB portable file limit.');
561
+ }
562
+ return output;
563
+ }
564
+ export function boundedString(value, maximum) {
565
+ if (typeof value !== 'string' || value.length > maximum || /[\u0000-\u001f\u007f]/.test(value))
566
+ return null;
567
+ return value.replace(/\s+/g, ' ').trim() || null;
568
+ }