@slicedearth/whoisleuth-cli 2.3.0 → 2.4.0

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 (264) hide show
  1. package/NOTICE +6 -0
  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/arguments.mjs +68 -6
  7. package/cli/artifact-validation/case-response.mjs +47 -41
  8. package/cli/artifact-validation/investigation-capsule.mjs +52 -31
  9. package/cli/artifact-validation/lookup-evidence.mjs +12 -6
  10. package/cli/artifact-validation/signed-review.mjs +83 -61
  11. package/cli/artifact-validation/structure-primitives.mjs +1 -105
  12. package/cli/artifact-verify.mjs +90 -24
  13. package/cli/assurance-command-runner.mjs +39 -4
  14. package/cli/bagit-folder.mjs +57 -0
  15. package/cli/bagit-review.mjs +23 -0
  16. package/cli/bounded-json.mjs +1 -1
  17. package/cli/bulk-command-runner.mjs +2 -0
  18. package/cli/bulk-output.mjs +29 -3
  19. package/cli/case-command.mjs +195 -0
  20. package/cli/case-pack.mjs +39 -31
  21. package/cli/command-argument-grammar.mjs +4 -0
  22. package/cli/command-reference.mjs +145 -75
  23. package/cli/completion.mjs +298 -79
  24. package/cli/config-profile.mjs +7 -5
  25. package/cli/ct-event-intake.mjs +12 -12
  26. package/cli/discover.mjs +4 -3
  27. package/cli/discovery-observation-snapshot.mjs +44 -16
  28. package/cli/discovery-scan-command-runner.mjs +2 -0
  29. package/cli/discovery-scan.mjs +7 -4
  30. package/cli/discovery-workflow.mjs +1 -1
  31. package/cli/domain-control-monitor.mjs +50 -20
  32. package/cli/domain-control-observations.mjs +149 -102
  33. package/cli/errors.mjs +33 -1
  34. package/cli/evidence-command-runner.mjs +16 -5
  35. package/cli/evidence-signing.mjs +2 -2
  36. package/cli/external-observation-mapping.mjs +7 -8
  37. package/cli/formatters/terminal-lookup.mjs +18 -46
  38. package/cli/formatters/terminal-shared.mjs +5 -3
  39. package/cli/formatters/terminal-technology.mjs +114 -0
  40. package/cli/inline-command-families.mjs +1 -0
  41. package/cli/interactive-launcher.mjs +2 -160
  42. package/cli/interchange-report.mjs +4 -8
  43. package/cli/investigation-artifacts.mjs +176 -0
  44. package/cli/investigation-checkpoint.mjs +10 -0
  45. package/cli/investigation-folder.mjs +114 -0
  46. package/cli/investigation-manifest.mjs +1 -112
  47. package/cli/investigation-package-review.mjs +103 -0
  48. package/cli/investigation-plan.mjs +32 -19
  49. package/cli/investigation-run.mjs +188 -33
  50. package/cli/local-document-checkpoint.mjs +170 -0
  51. package/cli/lookup-browser.mjs +5 -12
  52. package/cli/lookup-command-runner.mjs +17 -6
  53. package/cli/lookup-diff.mjs +37 -3
  54. package/cli/lookup-plan.mjs +11 -5
  55. package/cli/lookup-reconcile.mjs +10 -8
  56. package/cli/mail-review.mjs +66 -14
  57. package/cli/manual.mjs +1 -0
  58. package/cli/network-command-runner.mjs +2 -0
  59. package/cli/output-file.mjs +31 -5
  60. package/cli/registry-doctor.mjs +3 -1
  61. package/cli/registry-support.mjs +1 -1
  62. package/cli/retained-artifact-diff.mjs +2 -2
  63. package/cli/review-command-runner.mjs +45 -0
  64. package/cli/runner.mjs +55 -6
  65. package/cli/sharing-review.mjs +21 -34
  66. package/cli/signer-trust.mjs +104 -0
  67. package/cli/source-reliability.mjs +74 -83
  68. package/cli/terminal-input.mjs +168 -0
  69. package/cli/workflow-command-runner.mjs +68 -13
  70. package/docs/cli-reference.md +76 -30
  71. package/docs/cli.md +289 -25
  72. package/lib/abort.mjs +4 -1
  73. package/lib/availability.mjs +161 -112
  74. package/lib/bounded-file.mjs +3 -1
  75. package/lib/bounded-html-document.mjs +125 -0
  76. package/lib/bounded-json.mjs +43 -3
  77. package/lib/browser-library-profile.mjs +42 -51
  78. package/lib/browser-library-worker.mjs +112 -0
  79. package/lib/client-behavior-profile.mjs +3 -3
  80. package/lib/ct-search.mjs +11 -4
  81. package/lib/dns-delegation-health.mjs +24 -14
  82. package/lib/dns-inheritance-review.mjs +217 -0
  83. package/lib/dns-intelligence.mjs +36 -23
  84. package/lib/dns-mx.mjs +1 -1
  85. package/lib/dnssec-evidence-validation.mjs +11 -3
  86. package/lib/domain-change-packet.mjs +7 -2
  87. package/lib/domain-change-review.mjs +14 -8
  88. package/lib/domain-control-flight-recorder.mjs +219 -118
  89. package/lib/domain-control-manifest.mjs +117 -57
  90. package/lib/domain-posture-analysis.mjs +20 -7
  91. package/lib/domain-posture-parsers.mjs +88 -6
  92. package/lib/domain-posture.mjs +113 -26
  93. package/lib/evidence-export-privacy.mjs +561 -0
  94. package/lib/evidence-export.mjs +11 -592
  95. package/lib/evidence-report.mjs +3 -1
  96. package/lib/favicon.mjs +117 -41
  97. package/lib/generated/cisa-kev-catalog.mjs +12 -4
  98. package/lib/generated/registrar-standing-catalogue.mjs +7 -7
  99. package/lib/generated/retire-browser-catalog.mjs +2335 -940
  100. package/lib/hostname.mjs +2 -38
  101. package/lib/html-signals.mjs +82 -85
  102. package/lib/http-evidence-bounds.mjs +2 -1
  103. package/lib/http-intelligence.mjs +39 -26
  104. package/lib/lookup-cache.mjs +34 -11
  105. package/lib/lookup-child-profile-contract.mjs +24 -15
  106. package/lib/lookup-diagnostics.mjs +24 -1
  107. package/lib/lookup-progress.mjs +30 -13
  108. package/lib/lookup-response-contract.mjs +1048 -0
  109. package/lib/lookup-response.mjs +7 -10
  110. package/lib/lookup-source-progress.mjs +17 -14
  111. package/lib/lookup.mjs +48 -27
  112. package/lib/observed-network-context.mjs +43 -27
  113. package/lib/outbound-request-bounds.mjs +4 -1
  114. package/lib/page-fingerprints.mjs +69 -133
  115. package/lib/page-role-profile.mjs +12 -8
  116. package/lib/perceptual-hash.mjs +43 -14
  117. package/lib/rdap-bootstrap.mjs +72 -22
  118. package/lib/rdap-client.mjs +15 -7
  119. package/lib/rdap-normalization.mjs +33 -18
  120. package/lib/rdap-search-workbench.mjs +1 -1
  121. package/lib/rdap-transport.mjs +47 -20
  122. package/lib/rdap-types.mjs +2 -1
  123. package/lib/rdap.mjs +18 -3
  124. package/lib/registrable-domain.mjs +21 -1
  125. package/lib/registrar-standing-contract.mjs +1 -0
  126. package/lib/registry-capabilities.mjs +5 -1
  127. package/lib/registry-capability-catalogue.mjs +2 -1
  128. package/lib/registry-dates.mjs +2 -190
  129. package/lib/registry-insights.mjs +56 -18
  130. package/lib/response-policy.mjs +86 -48
  131. package/lib/risk-calibration-summary.mjs +17 -3
  132. package/lib/rpki-evidence.mjs +8 -5
  133. package/lib/safe-fetch.mjs +54 -47
  134. package/lib/security-txt.mjs +10 -7
  135. package/lib/service-binding-dns.mjs +8 -0
  136. package/lib/smtp-transport-review.mjs +27 -15
  137. package/lib/sslbl-certificates.generated.mjs +136 -135
  138. package/lib/static-html-analysis.mjs +117 -146
  139. package/lib/structured-data-identity.mjs +1 -0
  140. package/lib/technology-markup.mjs +100 -0
  141. package/lib/threat-intelligence-manifests.mjs +11 -139
  142. package/lib/threat-intelligence-runtime.mjs +24 -52
  143. package/lib/threat-intelligence-targets.mjs +6 -34
  144. package/lib/threat-intelligence-types.mjs +39 -0
  145. package/lib/typosquat-generator.mjs +19 -15
  146. package/lib/urlhaus-intelligence.mjs +8 -3
  147. package/lib/urlscan-intelligence.mjs +1 -1
  148. package/lib/website-security-posture.mjs +30 -6
  149. package/lib/website-technology.mjs +111 -71
  150. package/lib/whois-authority.mjs +14 -2
  151. package/lib/whois-chain.mjs +9 -2
  152. package/lib/whois-common-formats.mjs +5 -5
  153. package/lib/whois-contracts.mjs +2 -1
  154. package/lib/whois-dialects.mjs +2 -2
  155. package/lib/whois-parser-support.mjs +8 -1
  156. package/lib/whois-transport.mjs +23 -7
  157. package/package.json +2 -1
  158. package/packages/cases/case-attachment-model.mjs +83 -0
  159. package/packages/cases/case-evidence-model.mjs +156 -62
  160. package/packages/cases/case-export-input.mjs +53 -0
  161. package/packages/cases/case-migration-model.mjs +59 -25
  162. package/packages/cases/case-model.mjs +1 -0
  163. package/packages/cases/case-recheck-model.mjs +91 -0
  164. package/packages/cases/case-record-core.mjs +3 -2
  165. package/packages/cases/case-record-operations.mjs +89 -27
  166. package/packages/cases/case-record-projection.mjs +23 -20
  167. package/packages/cases/case-report.mjs +63 -52
  168. package/packages/cases/case-response-actions.mjs +654 -0
  169. package/packages/cases/case-response-model.mjs +41 -1129
  170. package/packages/cases/case-response-outcomes.mjs +317 -0
  171. package/packages/cases/case-response-packet-render.mjs +153 -0
  172. package/packages/cases/case-response-packet-types.mjs +1 -0
  173. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  174. package/packages/cases/case-response-packet.mjs +38 -587
  175. package/packages/cases/case-response-records.mjs +94 -0
  176. package/packages/cases/case-response-review-inputs.mjs +371 -0
  177. package/packages/cases/case-response-values.mjs +85 -0
  178. package/packages/cases/case-review-package.mjs +45 -0
  179. package/packages/cases/case-selection.mjs +46 -0
  180. package/packages/cases/case-storage-model.mjs +25 -0
  181. package/packages/cases/response-route-freshness.mjs +16 -0
  182. package/packages/comparison/capture-context.mjs +61 -0
  183. package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
  184. package/packages/comparison/page-similarity.mjs +2 -1
  185. package/packages/comparison/relationship-evidence.mjs +85 -8
  186. package/packages/comparison/relationship-provenance.mjs +93 -0
  187. package/packages/contracts/analyst-interchange.mjs +216 -0
  188. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  189. package/packages/contracts/analyst-review-state.mjs +5 -4
  190. package/packages/contracts/case-portability.mjs +279 -116
  191. package/packages/contracts/case-views-contract.mjs +12 -0
  192. package/packages/contracts/case-views.mjs +26 -0
  193. package/packages/contracts/cli-command-semantics.mjs +1 -0
  194. package/packages/contracts/cli-lookup.mjs +0 -1
  195. package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
  196. package/packages/contracts/domain-control-manifest.mjs +127 -73
  197. package/packages/contracts/domain-control-monitor.mjs +48 -45
  198. package/packages/contracts/domain-control-review.mjs +179 -121
  199. package/packages/contracts/domain-name.mjs +21 -0
  200. package/packages/contracts/external-observation-interchange.mjs +2 -1
  201. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  202. package/packages/contracts/http-url.mjs +2 -0
  203. package/packages/contracts/investigation-package-limits.mjs +24 -0
  204. package/packages/contracts/investigation-portability.mjs +56 -27
  205. package/packages/contracts/investigation-run.mjs +16 -0
  206. package/packages/contracts/ip-address.mjs +34 -0
  207. package/packages/contracts/lookup-evidence.mjs +35 -0
  208. package/packages/contracts/offline-comparison.mjs +1 -2
  209. package/packages/contracts/page-fingerprints.mjs +15 -0
  210. package/packages/contracts/risk-calibration.mjs +0 -1
  211. package/packages/contracts/schema-compatibility.mjs +1 -1
  212. package/packages/contracts/schema-lifecycle.mjs +83 -153
  213. package/packages/contracts/selected-file-limits.mjs +6 -0
  214. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  215. package/packages/contracts/web-capture.mjs +10 -0
  216. package/packages/contracts/workspace-portability.mjs +160 -225
  217. package/packages/evidence/artifact-integrity.mjs +8 -2
  218. package/packages/evidence/artifact-structure.mjs +105 -0
  219. package/packages/evidence/decision-fact.mjs +4 -1
  220. package/packages/evidence/domain-control-runtime.mjs +155 -27
  221. package/packages/evidence/domain-posture-context.mjs +79 -0
  222. package/packages/evidence/image-regions.mjs +56 -0
  223. package/packages/evidence/latest-observations.mjs +28 -0
  224. package/packages/evidence/lookup-target.mjs +104 -0
  225. package/packages/evidence/observation.mjs +16 -1
  226. package/packages/evidence/passphrase-encryption.mjs +44 -0
  227. package/packages/evidence/record-identity.mjs +72 -0
  228. package/packages/evidence/registry-dates.mjs +190 -0
  229. package/packages/evidence/retained-file.mjs +69 -0
  230. package/packages/evidence/review-age.mjs +14 -0
  231. package/packages/interchange/bagit.mjs +342 -0
  232. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  233. package/packages/interchange/crc32.mjs +17 -0
  234. package/packages/interchange/external-findings-import.mjs +89 -49
  235. package/packages/interchange/web-capture-import.mjs +382 -0
  236. package/packages/investigation/bulk-mail-exposure.mjs +2 -1
  237. package/packages/investigation/investigation-bagit.mjs +14 -0
  238. package/packages/investigation/investigation-capsule.mjs +33 -5
  239. package/packages/investigation/investigation-manifest.mjs +200 -0
  240. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  241. package/packages/investigation/investigation-package.mjs +200 -0
  242. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  243. package/packages/investigation/lookup-asset-graph.mjs +161 -141
  244. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  245. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  246. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  247. package/packages/monitoring/analyst-review-state.mjs +38 -90
  248. package/packages/workspace/brand-profile-model.mjs +158 -20
  249. package/packages/workspace/bulk-columns.mjs +17 -0
  250. package/packages/workspace/bulk-review-model.mjs +11 -5
  251. package/packages/workspace/bulk-session-model.mjs +95 -20
  252. package/packages/workspace/bulk-sort.mjs +1 -0
  253. package/packages/workspace/case-views.mjs +102 -0
  254. package/packages/workspace/hostile-input.mjs +29 -8
  255. package/packages/workspace/investigation-template-model.mjs +19 -4
  256. package/packages/workspace/page-baseline.mjs +18 -15
  257. package/packages/workspace/relationship-observation-model.mjs +41 -26
  258. package/packages/workspace/watchlist-history.mjs +2 -2
  259. package/packages/workspace/watchlist-store.mjs +8 -2
  260. package/packages/workspace/website-snapshot-model.mjs +70 -30
  261. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  262. package/packages/workspace/workspace-archive.mjs +85 -21
  263. package/third-party-notices.txt +29 -2
  264. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -18,6 +18,8 @@ import { buildAnalystReviewStateExport, emptyAnalystReviewStateStore, mergeAnaly
18
18
  import { isSupportedWorkspaceArchiveVersion, MAX_WORKSPACE_ARCHIVE_BYTES, MAX_WORKSPACE_ARCHIVE_SECTION_BYTES, MAX_WORKSPACE_ARCHIVE_SECTIONS, serialiseWorkspaceArchiveSection, SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS, WORKSPACE_ARCHIVE_CASE_SECTION, WORKSPACE_ARCHIVE_SCHEMA, WORKSPACE_ARCHIVE_SECTION_IDS, PUBLIC_WORKSPACE_ARCHIVE_SECTION_IDS, PUBLIC_WORKSPACE_ARCHIVE_VERSION, WORKSPACE_ARCHIVE_VERSION, WORKSPACE_SETTINGS_COMPATIBILITY, WORKSPACE_SETTINGS_SCHEMA, WORKSPACE_SETTINGS_VERSION, } from "../contracts/case-portability.mjs";
19
19
  import { ANALYST_REVIEW_STATE_COMPATIBILITY } from "../contracts/analyst-review-state.mjs";
20
20
  import { WORKSPACE_PORTABILITY_ARCHIVE_SECTION_REFERENCES } from "../contracts/workspace-portability.mjs";
21
+ import { CASE_VIEWS_COMPATIBILITY } from "../contracts/case-views.mjs";
22
+ import { emptyCaseViewsStore, mergeCaseViews, normalizeCaseViewsStore } from "./case-views.mjs";
21
23
  export { isSupportedWorkspaceArchiveVersion, MAX_WORKSPACE_ARCHIVE_BYTES, MAX_WORKSPACE_ARCHIVE_SECTION_BYTES, MAX_WORKSPACE_ARCHIVE_SECTIONS, SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS, WORKSPACE_ARCHIVE_CASE_SECTION, WORKSPACE_ARCHIVE_SCHEMA, WORKSPACE_ARCHIVE_SECTION_IDS, PUBLIC_WORKSPACE_ARCHIVE_SECTION_IDS, PUBLIC_WORKSPACE_ARCHIVE_VERSION, WORKSPACE_ARCHIVE_VERSION, WORKSPACE_SETTINGS_SCHEMA, WORKSPACE_SETTINGS_VERSION, };
22
24
  const CONTROL_RE = /[\x00-\x1f\x7f]/;
23
25
  const CHECKSUM_RE = /^sha256:[a-f0-9]{64}$/;
@@ -111,6 +113,7 @@ function workspaceArchiveSections(input, now) {
111
113
  investigationTemplates: buildInvestigationTemplateExport(input.investigationTemplates, now),
112
114
  bulkReview: buildBulkReviewExport(input.bulkReview),
113
115
  analystReviewState: buildAnalystReviewStateExport(input.analystReviewState),
116
+ caseViews: normalizeCaseViewsStore(input.caseViews),
114
117
  settings: settingsDocument(input),
115
118
  };
116
119
  }
@@ -150,7 +153,7 @@ const SECTION_DEFINITIONS = [
150
153
  merge: (local, data) => {
151
154
  const result = mergeCases(local.cases, data);
152
155
  const bounded = enforceStoreBudget(result.cases);
153
- return { ...result, cases: bounded.cases, pruned: bounded.pruned };
156
+ return { ...result, document: bounded.cases, pruned: bounded.pruned };
154
157
  },
155
158
  },
156
159
  {
@@ -158,7 +161,7 @@ const SECTION_DEFINITIONS = [
158
161
  count: (data) => arrayCount(data, 'campaigns'),
159
162
  merge: (local, data) => {
160
163
  const result = mergeCampaigns(local.campaigns, data);
161
- return { ...result, campaigns: assertCampaignStoreBudget(result.campaigns).campaigns };
164
+ return { ...result, document: assertCampaignStoreBudget(result.campaigns).campaigns };
162
165
  },
163
166
  },
164
167
  {
@@ -166,7 +169,8 @@ const SECTION_DEFINITIONS = [
166
169
  count: (data) => arrayCount(data, 'profiles'),
167
170
  merge: (local, data, now) => {
168
171
  const result = mergeBrandProfiles(local.brandProfiles, data, { nowIso: now });
169
- return { ...result, profiles: assertBrandProfileStoreBudget(result.profiles).profiles };
172
+ const profiles = assertBrandProfileStoreBudget(result.profiles).profiles;
173
+ return { ...result, profiles, document: profiles };
170
174
  },
171
175
  },
172
176
  {
@@ -174,7 +178,7 @@ const SECTION_DEFINITIONS = [
174
178
  count: (data) => objectCount(data, 'watchlists'),
175
179
  merge: (local, data) => {
176
180
  const result = mergeWatchlistStores(local.watchlists, data);
177
- return { ...result, watchlists: assertWatchlistStoreBudget(result.watchlists).watchlists };
181
+ return { ...result, document: assertWatchlistStoreBudget(result.watchlists).watchlists };
178
182
  },
179
183
  },
180
184
  {
@@ -182,7 +186,7 @@ const SECTION_DEFINITIONS = [
182
186
  count: (data) => arrayCount(data, 'entries'),
183
187
  merge: (local, data) => {
184
188
  const result = mergeShortlistStores(local.shortlist, data);
185
- return { ...result, entries: assertShortlistStoreBudget(result.entries).entries };
189
+ return { ...result, document: assertShortlistStoreBudget(result.entries).entries };
186
190
  },
187
191
  },
188
192
  {
@@ -190,14 +194,17 @@ const SECTION_DEFINITIONS = [
190
194
  count: (data) => arrayCount(data, 'rules'),
191
195
  merge: (local, data) => {
192
196
  const result = mergeDetectionRules(local.detectionRules, data);
193
- return { ...result, rules: assertDetectionRuleStoreBudget(result.rules).rules };
197
+ return { ...result, document: assertDetectionRuleStoreBudget(result.rules).rules };
194
198
  },
195
199
  },
196
200
  {
197
201
  ...sectionContract('relationshipObservations'),
198
202
  label: 'Retained relationship observations',
199
203
  count: (data) => arrayCount(data, 'observations'),
200
- merge: (local, data) => mergeRelationshipObservations(local.relationshipObservations, data),
204
+ merge: (local, data) => {
205
+ const result = mergeRelationshipObservations(local.relationshipObservations, data);
206
+ return { ...result, document: result.observations };
207
+ },
201
208
  },
202
209
  {
203
210
  ...sectionContract('bulkSessions'),
@@ -205,27 +212,36 @@ const SECTION_DEFINITIONS = [
205
212
  count: (data) => arrayCount(data, 'sessions'),
206
213
  merge: (local, data) => {
207
214
  const result = mergeBulkSessions(local.bulkSessions, data);
208
- enforceBulkSessionStoreBudget(result.sessions);
209
- return result;
215
+ const bounded = enforceBulkSessionStoreBudget(result.sessions);
216
+ return { ...result, document: bounded.store.sessions };
210
217
  },
211
218
  },
212
219
  {
213
220
  ...sectionContract('websiteSnapshots'),
214
221
  label: 'Website profile snapshots',
215
222
  count: (data) => arrayCount(data, 'snapshots'),
216
- merge: (local, data) => mergeWebsiteSnapshots(local.websiteSnapshots, data),
223
+ merge: (local, data) => {
224
+ const result = mergeWebsiteSnapshots(local.websiteSnapshots, data);
225
+ return { ...result, document: result.snapshots };
226
+ },
217
227
  },
218
228
  {
219
229
  ...sectionContract('investigationTemplates'),
220
230
  label: 'Investigation templates',
221
231
  count: (data) => arrayCount(data, 'templates'),
222
- merge: (local, data) => mergeInvestigationTemplates(local.investigationTemplates, data),
232
+ merge: (local, data) => {
233
+ const result = mergeInvestigationTemplates(local.investigationTemplates, data);
234
+ return { ...result, document: result.templates };
235
+ },
223
236
  },
224
237
  {
225
238
  ...sectionContract('bulkReview'),
226
239
  label: 'Bulk saved views and review queue',
227
240
  count: (data) => arrayCount(data, 'presets') + arrayCount(data, 'rows'),
228
- merge: (local, data) => mergeBulkReviewStores(local.bulkReview, data),
241
+ merge: (local, data) => {
242
+ const result = mergeBulkReviewStores(local.bulkReview, data);
243
+ return { ...result, document: result.store };
244
+ },
229
245
  },
230
246
  {
231
247
  id: 'analystReviewState',
@@ -234,7 +250,19 @@ const SECTION_DEFINITIONS = [
234
250
  version: ANALYST_REVIEW_STATE_COMPATIBILITY.currentVersion,
235
251
  supportedVersions: ANALYST_REVIEW_STATE_COMPATIBILITY.supportedVersions,
236
252
  count: (data) => arrayCount(data, 'records'),
237
- merge: (local, data) => mergeAnalystReviewStateStores(local.analystReviewState, data),
253
+ merge: (local, data) => {
254
+ const result = mergeAnalystReviewStateStores(local.analystReviewState, data);
255
+ return { ...result, document: result.store };
256
+ },
257
+ },
258
+ {
259
+ id: 'caseViews', label: 'Saved Case views', schema: compatibilitySchema(CASE_VIEWS_COMPATIBILITY),
260
+ version: CASE_VIEWS_COMPATIBILITY.currentVersion, supportedVersions: CASE_VIEWS_COMPATIBILITY.supportedVersions,
261
+ count: (data) => arrayCount(data, 'views'),
262
+ merge: (local, data) => {
263
+ const result = mergeCaseViews(local.caseViews, data);
264
+ return { ...result, document: result.store };
265
+ },
238
266
  },
239
267
  {
240
268
  id: 'settings', label: 'Workspace settings', schema: compatibilitySchema(WORKSPACE_SETTINGS_COMPATIBILITY),
@@ -245,6 +273,19 @@ const SECTION_DEFINITIONS = [
245
273
  },
246
274
  ];
247
275
  const DEFINITION_BY_ID = new Map(SECTION_DEFINITIONS.map((definition) => [definition.id, definition]));
276
+ /** Apply previously verified data sections through the same owners as preview. */
277
+ export function mergeReadyWorkspaceArchiveData(localInput, sections, generatedAt) {
278
+ if (sections.length > MAX_WORKSPACE_ARCHIVE_SECTIONS || new Set(sections.map(section => section.id)).size !== sections.length) {
279
+ throw new Error('Workspace data sections exceed their count or contain duplicate identities.');
280
+ }
281
+ const local = normalizedInput(localInput);
282
+ return sections.map(section => {
283
+ const definition = DEFINITION_BY_ID.get(section.id);
284
+ if (section.status !== 'ready' || !definition?.merge)
285
+ throw new Error('Only verified, supported workspace data sections can be merged.');
286
+ return { ...definition.merge(local, section.data, generatedAt), id: section.id };
287
+ });
288
+ }
248
289
  const SECTION_ORDER = new Map(WORKSPACE_ARCHIVE_SECTION_IDS.map((id, index) => [id, index]));
249
290
  function canonicalSectionOrder(left, right) {
250
291
  const leftIndex = SECTION_ORDER.get(left.id) ?? Number.MAX_SAFE_INTEGER;
@@ -266,6 +307,7 @@ function normalizedInput(input) {
266
307
  investigationTemplates: Array.isArray(value.investigationTemplates) ? value.investigationTemplates : [],
267
308
  bulkReview: record(value.bulkReview) || {},
268
309
  analystReviewState: record(value.analystReviewState) || emptyAnalystReviewStateStore(),
310
+ caseViews: value.caseViews ?? emptyCaseViewsStore(),
269
311
  settings: record(value.settings) || {},
270
312
  };
271
313
  }
@@ -273,7 +315,7 @@ function ensureArchiveBudget(value) {
273
315
  const serialized = serialize(value);
274
316
  const bytes = byteLength(serialized);
275
317
  if (bytes > MAX_WORKSPACE_ARCHIVE_BYTES) {
276
- throw new Error('Workspace archives are limited to 10 MiB. Export smaller collections separately before trying again.');
318
+ throw new Error(`Workspace archives are limited to ${MAX_WORKSPACE_ARCHIVE_BYTES / 1024 / 1024} MiB. Export smaller collections separately before trying again.`);
277
319
  }
278
320
  return { serialized, bytes };
279
321
  }
@@ -290,7 +332,7 @@ export async function buildWorkspaceArchive(input, options = {}) {
290
332
  const data = sections[definition.id];
291
333
  const sectionBytes = byteLength(serialize(data));
292
334
  if (sectionBytes > MAX_WORKSPACE_ARCHIVE_SECTION_BYTES) {
293
- throw new Error(`${definition.label} exceeds the 5 MiB workspace archive section limit.`);
335
+ throw new Error(`${definition.label} exceeds the ${MAX_WORKSPACE_ARCHIVE_SECTION_BYTES / 1024 / 1024} MiB workspace archive section limit.`);
294
336
  }
295
337
  const recordCount = definition.count(data);
296
338
  manifestSections.push({
@@ -338,8 +380,11 @@ function manifestEntry(raw) {
338
380
  }
339
381
  /** Validate structure, section byte counts, and checksums without applying data. */
340
382
  export async function readWorkspaceArchive(raw, options = {}) {
341
- assertWorkspaceInputGraph(raw, 'Workspace archive');
342
- const value = record(raw);
383
+ assertWorkspaceInputGraph(raw, 'Workspace archive', { maximumBytes: MAX_WORKSPACE_ARCHIVE_BYTES });
384
+ // Snapshot before the first asynchronous digest: caller-owned data must not
385
+ // change between its checksum, contract checks and eventual import preview.
386
+ const { serialized, bytes } = ensureArchiveBudget(raw);
387
+ const value = record(JSON.parse(serialized));
343
388
  if (!value || value.schema !== WORKSPACE_ARCHIVE_SCHEMA) {
344
389
  throw new Error('This file is not a WHOISleuth workspace archive.');
345
390
  }
@@ -355,11 +400,10 @@ export async function readWorkspaceArchive(raw, options = {}) {
355
400
  const sourceVersion = value.version;
356
401
  const expectedSectionIds = sourceVersion === PUBLIC_WORKSPACE_ARCHIVE_VERSION
357
402
  ? PUBLIC_WORKSPACE_ARCHIVE_SECTION_IDS
358
- : WORKSPACE_ARCHIVE_SECTION_IDS;
403
+ : sourceVersion < 9 ? WORKSPACE_ARCHIVE_SECTION_IDS.filter(id => id !== 'caseViews') : WORKSPACE_ARCHIVE_SECTION_IDS;
359
404
  const generatedAt = timestamp(value.generatedAt);
360
405
  if (!generatedAt)
361
406
  throw new Error('The workspace archive generation time is invalid.');
362
- const { bytes } = ensureArchiveBudget(value);
363
407
  assertExactKeys(value, WORKSPACE_ARCHIVE_ROOT_KEYS, `version ${sourceVersion} envelope`);
364
408
  const manifest = record(value.manifest);
365
409
  const sectionValues = record(value.sections);
@@ -423,7 +467,7 @@ export async function readWorkspaceArchive(raw, options = {}) {
423
467
  else if (definition.count(data) !== entry.recordCount) {
424
468
  throw new Error(`${entry.id} does not match its manifest record count.`);
425
469
  }
426
- sections.push({ ...entry, label: definition?.label || entry.id, status, reason, data: clone(data) });
470
+ sections.push({ ...entry, label: definition?.label || entry.id, status, reason, data });
427
471
  totalRecords += entry.recordCount;
428
472
  }
429
473
  if (Object.keys(sectionValues).some((id) => !seen.has(id))) {
@@ -450,6 +494,15 @@ export async function readWorkspaceArchive(raw, options = {}) {
450
494
  data,
451
495
  });
452
496
  }
497
+ if (sourceVersion < 9) {
498
+ const data = emptyCaseViewsStore();
499
+ sections.push({
500
+ id: 'caseViews', label: 'Saved Case views', schema: compatibilitySchema(CASE_VIEWS_COMPATIBILITY),
501
+ version: CASE_VIEWS_COMPATIBILITY.currentVersion, recordCount: 0,
502
+ bytes: byteLength(serialize(data)), checksum: await checksum(data, options.cryptoProvider), status: 'ready',
503
+ reason: 'This archive predates saved Case views. Import supplies an empty section and does not remove existing views.', data,
504
+ });
505
+ }
453
506
  sections.sort(canonicalSectionOrder);
454
507
  return {
455
508
  schema: WORKSPACE_ARCHIVE_SCHEMA,
@@ -491,9 +544,20 @@ function settingsPreview(data, local, mergedProfiles) {
491
544
  || (skipped === 0 && activeProfileId !== retainedActiveProfileId));
492
545
  return { added: 0, updated, skipped, reason, settings: { activeProfileId, theme } };
493
546
  }
547
+ /** Retain one verified snapshot; each read or preview returns an independent copy. */
548
+ export async function prepareWorkspaceArchive(raw, options = {}) {
549
+ const archive = await readWorkspaceArchive(raw, options);
550
+ return Object.freeze({
551
+ read: () => clone(archive),
552
+ preview: (localInput, selection = {}) => previewVerifiedWorkspaceArchive(clone(archive), localInput, selection),
553
+ });
554
+ }
494
555
  /** Preview section-specific non-destructive merge outcomes without writing. */
495
556
  export async function previewWorkspaceArchive(raw, localInput, options = {}) {
496
- const archive = await readWorkspaceArchive(raw, options);
557
+ // A one-shot preview owns the reader result directly; retained readers copy it.
558
+ return previewVerifiedWorkspaceArchive(await readWorkspaceArchive(raw, options), localInput, options);
559
+ }
560
+ function previewVerifiedWorkspaceArchive(archive, localInput, options) {
497
561
  const local = normalizedInput(localInput);
498
562
  const results = [];
499
563
  let mergedProfiles = local.brandProfiles;
@@ -2,9 +2,9 @@ WHOISleuth CLI third-party production dependency notices
2
2
 
3
3
  Generated deterministically from package-lock.json and the installed production
4
4
  dependency packages. Do not edit this file directly; run npm run licenses:update.
5
- The inventory is the exact transitive closure of the packaged CLI runtime dependencies.
5
+ The inventory is the exact transitive closure of the packaged runtime dependencies.
6
6
 
7
- Package count: 27
7
+ Package count: 28
8
8
 
9
9
  ================================================================================
10
10
  @peculiar/asn1-cms@2.9.4
@@ -384,6 +384,33 @@ Redistributions in binary form must reproduce the above copyright notice, this l
384
384
  THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS,
385
385
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
386
386
  ================================================================================
387
+ fflate@0.8.3
388
+ Relationship: direct production dependency
389
+ Declared licence: MIT
390
+ Licence source: LICENSE
391
+
392
+ MIT License
393
+
394
+ Copyright (c) 2026 Arjun Barrett
395
+
396
+ Permission is hereby granted, free of charge, to any person obtaining a copy
397
+ of this software and associated documentation files (the "Software"), to deal
398
+ in the Software without restriction, including without limitation the rights
399
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
400
+ copies of the Software, and to permit persons to whom the Software is
401
+ furnished to do so, subject to the following conditions:
402
+
403
+ The above copyright notice and this permission notice shall be included in all
404
+ copies or substantial portions of the Software.
405
+
406
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
407
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
408
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
409
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
410
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
411
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
412
+ SOFTWARE.
413
+ ================================================================================
387
414
  maxmind@5.0.7
388
415
  Relationship: direct production dependency
389
416
  Declared licence: MIT
@@ -1,63 +0,0 @@
1
- import { normalizeBulkSessionResult, } from "../workspace/bulk-session-model.mjs";
2
- import { BULK_REVIEW_STATES, } from "../workspace/bulk-review-model.mjs";
3
- import { SORTED_JSON_V2, sha256ArtifactDigestV2 } from "../evidence/artifact-integrity.mjs";
4
- import { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION, } from "../contracts/investigation-portability.mjs";
5
- export { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION };
6
- const MAX_MANIFEST_ROWS = 2_000;
7
- const REVIEW_STATE_SET = new Set(BULK_REVIEW_STATES);
8
- function timestamp(value, fallback) {
9
- if (typeof value !== 'string' || !Number.isFinite(Date.parse(value)))
10
- return fallback;
11
- return new Date(value).toISOString();
12
- }
13
- function reviewStateMap(values) {
14
- return new Map(values.slice(0, MAX_MANIFEST_ROWS * 2).flatMap((item) => (REVIEW_STATE_SET.has(item.state)
15
- ? [[item.domain, item.state]]
16
- : [])));
17
- }
18
- export async function buildBulkReviewManifest(input) {
19
- const now = new Date().toISOString();
20
- const generatedAt = timestamp(input.generatedAt, now);
21
- const observedAt = timestamp(input.observedAt, generatedAt);
22
- const states = reviewStateMap(input.reviewStates);
23
- const rows = input.rows
24
- .map((row) => normalizeBulkSessionResult(row))
25
- .filter((item) => Boolean(item))
26
- .slice(0, MAX_MANIFEST_ROWS)
27
- .map((item) => ({
28
- domain: item.domain,
29
- reviewState: states.get(item.domain) || 'unreviewed',
30
- resultState: item.status,
31
- scanDepth: item.scanDepth,
32
- sourceCoverage: item.sourceCoverage,
33
- profileContext: { ...item.profileContext },
34
- }));
35
- const unsigned = {
36
- schema: BULK_REVIEW_MANIFEST_SCHEMA,
37
- version: BULK_REVIEW_MANIFEST_VERSION,
38
- generatedAt,
39
- observedAt,
40
- lookupProfile: input.lookupProfile,
41
- selection: {
42
- count: rows.length,
43
- domains: rows.map((item) => item.domain),
44
- },
45
- view: input.view,
46
- rows,
47
- limitations: [
48
- 'This manifest records the explicit review selection and view context for a separate CSV export.',
49
- 'It contains compact source states and bounded row-level Brand Profile provenance only, and excludes raw payloads, Profile contents, contact records, notes, and transient request state.',
50
- 'Reproducing the filters does not reproduce upstream responses or imply that evidence remains current.',
51
- ],
52
- };
53
- const digestSha256 = await sha256ArtifactDigestV2(unsigned);
54
- const document = {
55
- ...unsigned,
56
- integrity: { algorithm: 'SHA-256', canonicalization: SORTED_JSON_V2, digestSha256 },
57
- };
58
- return {
59
- document,
60
- content: `${JSON.stringify(document, null, 2)}\n`,
61
- filename: `whoisleuth-bulk-review-${generatedAt.slice(0, 10)}.manifest.json`,
62
- };
63
- }