@slicedearth/whoisleuth-cli 2.2.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 (282) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +20 -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 +454 -1748
  7. package/cli/artifact-validation/case-response.mjs +66 -35
  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 +112 -72
  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-checkpoint.mjs +34 -3
  18. package/cli/bulk-command-runner.mjs +2 -0
  19. package/cli/bulk-output.mjs +29 -3
  20. package/cli/case-command.mjs +195 -0
  21. package/cli/case-pack.mjs +66 -111
  22. package/cli/command-argument-grammar.mjs +215 -0
  23. package/cli/command-reference.mjs +342 -285
  24. package/cli/completion.mjs +303 -80
  25. package/cli/config-profile.mjs +7 -5
  26. package/cli/ct-event-intake.mjs +12 -12
  27. package/cli/discover.mjs +4 -3
  28. package/cli/discovery-observation-snapshot.mjs +59 -18
  29. package/cli/discovery-scan-command-runner.mjs +21 -0
  30. package/cli/discovery-scan.mjs +13 -4
  31. package/cli/discovery-workflow.mjs +1 -1
  32. package/cli/domain-control-monitor.mjs +50 -20
  33. package/cli/domain-control-observations.mjs +149 -102
  34. package/cli/errors.mjs +33 -1
  35. package/cli/evidence-command-runner.mjs +16 -5
  36. package/cli/evidence-signing.mjs +2 -2
  37. package/cli/external-observation-mapping.mjs +7 -8
  38. package/cli/formatters/terminal-lookup.mjs +18 -46
  39. package/cli/formatters/terminal-shared.mjs +5 -3
  40. package/cli/formatters/terminal-technology.mjs +114 -0
  41. package/cli/inline-command-families.mjs +2 -0
  42. package/cli/input.mjs +116 -0
  43. package/cli/interactive-launcher.mjs +2 -160
  44. package/cli/interchange-report.mjs +18 -8
  45. package/cli/investigation-artifacts.mjs +176 -0
  46. package/cli/investigation-checkpoint.mjs +10 -0
  47. package/cli/investigation-folder.mjs +114 -0
  48. package/cli/investigation-manifest.mjs +1 -112
  49. package/cli/investigation-package-review.mjs +103 -0
  50. package/cli/investigation-plan.mjs +32 -19
  51. package/cli/investigation-run.mjs +188 -33
  52. package/cli/local-document-checkpoint.mjs +170 -0
  53. package/cli/lookalike-calibration.mjs +7 -2
  54. package/cli/lookup-brief.mjs +4 -2
  55. package/cli/lookup-browser-view.mjs +1 -1
  56. package/cli/lookup-browser.mjs +5 -12
  57. package/cli/lookup-command-runner.mjs +17 -6
  58. package/cli/lookup-diff.mjs +37 -3
  59. package/cli/lookup-plan.mjs +11 -5
  60. package/cli/lookup-reconcile.mjs +10 -8
  61. package/cli/mail-header-review.mjs +467 -0
  62. package/cli/mail-review.mjs +66 -14
  63. package/cli/manual.mjs +1 -0
  64. package/cli/network-command-runner.mjs +2 -0
  65. package/cli/output-file.mjs +31 -5
  66. package/cli/registry-doctor.mjs +3 -1
  67. package/cli/registry-support.mjs +1 -1
  68. package/cli/retained-artifact-diff.mjs +2 -2
  69. package/cli/review-command-runner.mjs +70 -0
  70. package/cli/risk-calibration.mjs +19 -17
  71. package/cli/runner.mjs +62 -7
  72. package/cli/sharing-review.mjs +21 -34
  73. package/cli/signer-trust.mjs +104 -0
  74. package/cli/source-reliability.mjs +74 -83
  75. package/cli/terminal-input.mjs +168 -0
  76. package/cli/workflow-command-runner.mjs +68 -13
  77. package/docs/cli-reference.md +77 -24
  78. package/docs/cli.md +300 -25
  79. package/lib/abort.mjs +4 -1
  80. package/lib/availability.mjs +161 -112
  81. package/lib/bounded-file.mjs +3 -1
  82. package/lib/bounded-html-document.mjs +125 -0
  83. package/lib/bounded-json.mjs +51 -8
  84. package/lib/browser-library-profile.mjs +42 -51
  85. package/lib/browser-library-worker.mjs +112 -0
  86. package/lib/client-behavior-profile.mjs +3 -3
  87. package/lib/ct-search.mjs +11 -4
  88. package/lib/dns-convergence-review.mjs +1 -0
  89. package/lib/dns-delegation-health.mjs +24 -14
  90. package/lib/dns-inheritance-review.mjs +217 -0
  91. package/lib/dns-intelligence.mjs +73 -26
  92. package/lib/dns-mx.mjs +1 -1
  93. package/lib/dnssec-chain-validation.mjs +6 -0
  94. package/lib/dnssec-evidence-validation.mjs +11 -3
  95. package/lib/domain-change-packet.mjs +49 -16
  96. package/lib/domain-change-review.mjs +14 -8
  97. package/lib/domain-control-flight-recorder.mjs +221 -117
  98. package/lib/domain-control-manifest.mjs +129 -69
  99. package/lib/domain-posture-analysis.mjs +20 -7
  100. package/lib/domain-posture-parsers.mjs +201 -8
  101. package/lib/domain-posture.mjs +147 -29
  102. package/lib/evidence-export-privacy.mjs +561 -0
  103. package/lib/evidence-export.mjs +11 -543
  104. package/lib/evidence-report.mjs +5 -3
  105. package/lib/favicon.mjs +117 -41
  106. package/lib/generated/cisa-kev-catalog.mjs +12 -4
  107. package/lib/generated/registrar-standing-catalogue.mjs +7 -7
  108. package/lib/generated/retire-browser-catalog.mjs +2335 -940
  109. package/lib/hostname.mjs +2 -38
  110. package/lib/html-signals.mjs +111 -108
  111. package/lib/http-evidence-bounds.mjs +2 -1
  112. package/lib/http-intelligence.mjs +39 -26
  113. package/lib/local-geoip-evidence.mjs +19 -3
  114. package/lib/lookup-cache.mjs +34 -11
  115. package/lib/lookup-child-profile-contract.mjs +24 -15
  116. package/lib/lookup-diagnostics.mjs +24 -1
  117. package/lib/lookup-progress.mjs +30 -13
  118. package/lib/lookup-response-contract.mjs +1048 -0
  119. package/lib/lookup-response.mjs +8 -10
  120. package/lib/lookup-source-progress.mjs +40 -14
  121. package/lib/lookup.mjs +48 -27
  122. package/lib/nameserver-preflight-review.mjs +1 -0
  123. package/lib/observed-network-context.mjs +43 -27
  124. package/lib/outbound-request-bounds.mjs +4 -1
  125. package/lib/page-fingerprints.mjs +69 -133
  126. package/lib/page-role-profile.mjs +12 -8
  127. package/lib/perceptual-hash.mjs +43 -14
  128. package/lib/rdap-bootstrap.mjs +72 -22
  129. package/lib/rdap-capabilities.mjs +1 -1
  130. package/lib/rdap-client.mjs +15 -7
  131. package/lib/rdap-normalization.mjs +33 -18
  132. package/lib/rdap-search-workbench.mjs +1 -1
  133. package/lib/rdap-transport.mjs +47 -20
  134. package/lib/rdap-types.mjs +2 -1
  135. package/lib/rdap.mjs +18 -3
  136. package/lib/registrable-domain.mjs +21 -1
  137. package/lib/registrar-standing-contract.mjs +1 -0
  138. package/lib/registry-capabilities.mjs +5 -1
  139. package/lib/registry-capability-catalogue.mjs +2 -1
  140. package/lib/registry-dates.mjs +2 -190
  141. package/lib/registry-insights.mjs +85 -34
  142. package/lib/response-policy.mjs +86 -48
  143. package/lib/risk-calibration-summary.mjs +18 -6
  144. package/lib/rpki-evidence.mjs +8 -5
  145. package/lib/safe-fetch.mjs +54 -47
  146. package/lib/security-txt.mjs +42 -7
  147. package/lib/service-binding-dns.mjs +8 -0
  148. package/lib/smtp-transport-review.mjs +27 -15
  149. package/lib/sslbl-certificates.generated.mjs +136 -135
  150. package/lib/static-html-analysis.mjs +178 -151
  151. package/lib/structured-data-identity.mjs +1 -0
  152. package/lib/technology-markup.mjs +100 -0
  153. package/lib/threat-intelligence-manifests.mjs +11 -139
  154. package/lib/threat-intelligence-runtime.mjs +24 -52
  155. package/lib/threat-intelligence-targets.mjs +6 -34
  156. package/lib/threat-intelligence-types.mjs +39 -0
  157. package/lib/typosquat-generator.mjs +19 -15
  158. package/lib/urlhaus-intelligence.mjs +8 -3
  159. package/lib/urlscan-intelligence.mjs +1 -1
  160. package/lib/website-security-posture.mjs +30 -6
  161. package/lib/website-technology.mjs +153 -45
  162. package/lib/whois-authority.mjs +14 -2
  163. package/lib/whois-chain.mjs +9 -2
  164. package/lib/whois-common-formats.mjs +5 -5
  165. package/lib/whois-contracts.mjs +2 -1
  166. package/lib/whois-dialects.mjs +2 -2
  167. package/lib/whois-parser-support.mjs +8 -1
  168. package/lib/whois-transport.mjs +23 -7
  169. package/lib/zone-intent-review.mjs +16 -6
  170. package/package.json +2 -1
  171. package/packages/cases/case-attachment-model.mjs +83 -0
  172. package/packages/cases/case-evidence-model.mjs +156 -62
  173. package/packages/cases/case-export-input.mjs +53 -0
  174. package/packages/cases/case-migration-model.mjs +127 -44
  175. package/packages/cases/case-model.mjs +1 -0
  176. package/packages/cases/case-recheck-model.mjs +91 -0
  177. package/packages/cases/case-record-contracts.mjs +5 -20
  178. package/packages/cases/case-record-core.mjs +12 -22
  179. package/packages/cases/case-record-decisions.mjs +72 -0
  180. package/packages/cases/case-record-model.mjs +1 -0
  181. package/packages/cases/case-record-operations.mjs +95 -31
  182. package/packages/cases/case-record-projection.mjs +221 -0
  183. package/packages/cases/case-report.mjs +70 -54
  184. package/packages/cases/case-response-actions.mjs +654 -0
  185. package/packages/cases/case-response-model.mjs +64 -1115
  186. package/packages/cases/case-response-outcomes.mjs +317 -0
  187. package/packages/cases/case-response-packet-render.mjs +153 -0
  188. package/packages/cases/case-response-packet-types.mjs +1 -0
  189. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  190. package/packages/cases/case-response-packet.mjs +74 -572
  191. package/packages/cases/case-response-records.mjs +94 -0
  192. package/packages/cases/case-response-review-inputs.mjs +371 -0
  193. package/packages/cases/case-response-values.mjs +85 -0
  194. package/packages/cases/case-review-package.mjs +45 -0
  195. package/packages/cases/case-selection.mjs +46 -0
  196. package/packages/cases/case-storage-model.mjs +31 -2
  197. package/packages/cases/response-route-freshness.mjs +16 -0
  198. package/packages/comparison/capture-context.mjs +61 -0
  199. package/packages/comparison/comparison-ledger-bulk.mjs +13 -11
  200. package/packages/comparison/page-similarity.mjs +5 -2
  201. package/packages/comparison/relationship-evidence.mjs +85 -8
  202. package/packages/comparison/relationship-provenance.mjs +93 -0
  203. package/packages/contracts/analyst-interchange.mjs +216 -0
  204. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  205. package/packages/contracts/analyst-review-state.mjs +5 -4
  206. package/packages/contracts/case-portability.mjs +366 -83
  207. package/packages/contracts/case-views-contract.mjs +12 -0
  208. package/packages/contracts/case-views.mjs +26 -0
  209. package/packages/contracts/cli-command-semantics.mjs +94 -0
  210. package/packages/contracts/cli-lookup.mjs +0 -1
  211. package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
  212. package/packages/contracts/domain-control-manifest.mjs +127 -73
  213. package/packages/contracts/domain-control-monitor.mjs +48 -45
  214. package/packages/contracts/domain-control-review.mjs +179 -121
  215. package/packages/contracts/domain-name.mjs +21 -0
  216. package/packages/contracts/external-observation-interchange.mjs +2 -1
  217. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  218. package/packages/contracts/http-url.mjs +2 -0
  219. package/packages/contracts/investigation-package-limits.mjs +24 -0
  220. package/packages/contracts/investigation-portability.mjs +56 -27
  221. package/packages/contracts/investigation-run.mjs +16 -0
  222. package/packages/contracts/ip-address.mjs +34 -0
  223. package/packages/contracts/lookup-evidence.mjs +35 -0
  224. package/packages/contracts/offline-comparison.mjs +1 -2
  225. package/packages/contracts/page-fingerprints.mjs +15 -0
  226. package/packages/contracts/risk-calibration.mjs +15 -11
  227. package/packages/contracts/schema-compatibility.mjs +1 -1
  228. package/packages/contracts/schema-lifecycle.mjs +83 -153
  229. package/packages/contracts/selected-file-limits.mjs +6 -0
  230. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  231. package/packages/contracts/web-capture.mjs +10 -0
  232. package/packages/contracts/workspace-portability.mjs +183 -211
  233. package/packages/evidence/artifact-integrity.mjs +8 -2
  234. package/packages/evidence/artifact-structure.mjs +105 -0
  235. package/packages/evidence/decision-fact.mjs +4 -1
  236. package/packages/evidence/domain-control-runtime.mjs +201 -33
  237. package/packages/evidence/domain-posture-context.mjs +79 -0
  238. package/packages/evidence/image-regions.mjs +56 -0
  239. package/packages/evidence/latest-observations.mjs +28 -0
  240. package/packages/evidence/lookup-target.mjs +104 -0
  241. package/packages/evidence/observation.mjs +16 -1
  242. package/packages/evidence/passphrase-encryption.mjs +44 -0
  243. package/packages/evidence/record-identity.mjs +72 -0
  244. package/packages/evidence/registry-dates.mjs +190 -0
  245. package/packages/evidence/retained-file.mjs +69 -0
  246. package/packages/evidence/review-age.mjs +14 -0
  247. package/packages/interchange/bagit.mjs +342 -0
  248. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  249. package/packages/interchange/crc32.mjs +17 -0
  250. package/packages/interchange/external-findings-import.mjs +92 -51
  251. package/packages/interchange/web-capture-import.mjs +382 -0
  252. package/packages/investigation/bulk-mail-exposure.mjs +2 -1
  253. package/packages/investigation/investigation-bagit.mjs +14 -0
  254. package/packages/investigation/investigation-capsule.mjs +33 -5
  255. package/packages/investigation/investigation-manifest.mjs +200 -0
  256. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  257. package/packages/investigation/investigation-package.mjs +200 -0
  258. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  259. package/packages/investigation/lookup-asset-graph.mjs +193 -165
  260. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  261. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  262. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  263. package/packages/monitoring/analyst-review-state.mjs +38 -90
  264. package/packages/workspace/brand-profile-model.mjs +276 -22
  265. package/packages/workspace/bulk-columns.mjs +17 -0
  266. package/packages/workspace/bulk-review-model.mjs +23 -11
  267. package/packages/workspace/bulk-session-model.mjs +100 -24
  268. package/packages/workspace/bulk-sort.mjs +1 -0
  269. package/packages/workspace/case-views.mjs +102 -0
  270. package/packages/workspace/detection-rule-model.mjs +70 -22
  271. package/packages/workspace/hostile-input.mjs +29 -8
  272. package/packages/workspace/investigation-template-model.mjs +19 -4
  273. package/packages/workspace/page-baseline.mjs +18 -15
  274. package/packages/workspace/relationship-observation-model.mjs +41 -23
  275. package/packages/workspace/watchlist-history.mjs +2 -2
  276. package/packages/workspace/watchlist-store.mjs +8 -2
  277. package/packages/workspace/website-snapshot-model.mjs +164 -26
  278. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  279. package/packages/workspace/workspace-archive.mjs +87 -21
  280. package/third-party-notices.txt +29 -2
  281. package/cli/evidence-command-arguments.mjs +0 -156
  282. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -2,13 +2,13 @@
2
2
  // This protects a downloaded file while it is locked. It does not encrypt the
3
3
  // active IndexedDB workspace or defend an unlocked page from same-origin code.
4
4
  import { WORKSPACE_ARCHIVE_SCHEMA, WORKSPACE_ARCHIVE_VERSION, SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS, isSupportedWorkspaceArchiveVersion, readWorkspaceArchive, } from "./workspace-archive.mjs";
5
- import { parseBoundedJson } from "../../lib/bounded-json.mjs";
5
+ import { boundedJsonLimitsForBytes, parseBoundedJson } from "../../lib/bounded-json.mjs";
6
6
  import { ENCRYPTED_WORKSPACE_ARCHIVE_SCHEMA, ENCRYPTED_WORKSPACE_ARCHIVE_VERSION, MAX_ENCRYPTED_WORKSPACE_ARCHIVE_BYTES, MAX_WORKSPACE_ARCHIVE_BYTES, MAX_WORKSPACE_ARCHIVE_PASSPHRASE_BYTES, MIN_WORKSPACE_ARCHIVE_PASSPHRASE_CHARACTERS, WORKSPACE_ARCHIVE_PBKDF2_ITERATIONS, } from "../contracts/case-portability.mjs";
7
7
  import { assertWorkspaceInputGraph } from "./hostile-input.mjs";
8
+ import { AES_KEY_BITS, arrayBuffer, assertPassphrase, cryptoProvider, deriveArchiveKey } from "../evidence/passphrase-encryption.mjs";
8
9
  export { ENCRYPTED_WORKSPACE_ARCHIVE_SCHEMA, ENCRYPTED_WORKSPACE_ARCHIVE_VERSION, MAX_ENCRYPTED_WORKSPACE_ARCHIVE_BYTES, MAX_WORKSPACE_ARCHIVE_PASSPHRASE_BYTES, MIN_WORKSPACE_ARCHIVE_PASSPHRASE_CHARACTERS, WORKSPACE_ARCHIVE_PBKDF2_ITERATIONS, };
9
10
  const SALT_BYTES = 16;
10
11
  const IV_BYTES = 12;
11
- const AES_KEY_BITS = 256;
12
12
  const AES_GCM_TAG_BITS = 128;
13
13
  const MAX_CIPHERTEXT_BASE64URL_CHARACTERS = Math.ceil((MAX_WORKSPACE_ARCHIVE_BYTES + AES_GCM_TAG_BITS / 8) * 4 / 3);
14
14
  const BASE64URL_RE = /^[A-Za-z0-9_-]+$/;
@@ -25,25 +25,6 @@ function hasExactKeys(value, expected) {
25
25
  const keys = Object.keys(value);
26
26
  return keys.length === expected.length && keys.every((key) => expected.includes(key));
27
27
  }
28
- function arrayBuffer(bytes) {
29
- return bytes.slice().buffer;
30
- }
31
- function assertPassphrase(passphrase) {
32
- if (typeof passphrase !== 'string') {
33
- throw new Error(`Use a backup passphrase with at least ${MIN_WORKSPACE_ARCHIVE_PASSPHRASE_CHARACTERS} characters.`);
34
- }
35
- if (passphrase.length > MAX_WORKSPACE_ARCHIVE_PASSPHRASE_BYTES) {
36
- throw new Error('Backup passphrases are limited to 1024 UTF-8 bytes.');
37
- }
38
- if (Array.from(passphrase).length < MIN_WORKSPACE_ARCHIVE_PASSPHRASE_CHARACTERS) {
39
- throw new Error(`Use a backup passphrase with at least ${MIN_WORKSPACE_ARCHIVE_PASSPHRASE_CHARACTERS} characters.`);
40
- }
41
- const bytes = encoder.encode(passphrase);
42
- if (bytes.byteLength > MAX_WORKSPACE_ARCHIVE_PASSPHRASE_BYTES) {
43
- throw new Error('Backup passphrases are limited to 1024 UTF-8 bytes.');
44
- }
45
- return bytes;
46
- }
47
28
  function base64url(bytes) {
48
29
  let encoded = '';
49
30
  for (let index = 0; index < bytes.length; index += 3) {
@@ -112,7 +93,7 @@ function validTimestamp(value) {
112
93
  return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
113
94
  }
114
95
  function validateEnvelope(raw) {
115
- assertWorkspaceInputGraph(raw, 'Encrypted workspace archive');
96
+ assertWorkspaceInputGraph(raw, 'Encrypted workspace archive', { maximumBytes: MAX_ENCRYPTED_WORKSPACE_ARCHIVE_BYTES });
116
97
  const value = record(raw);
117
98
  if (!value || value.schema !== ENCRYPTED_WORKSPACE_ARCHIVE_SCHEMA) {
118
99
  throw new Error('This file is not an encrypted WHOISleuth workspace archive.');
@@ -197,26 +178,6 @@ function validateEnvelope(raw) {
197
178
  ciphertext,
198
179
  };
199
180
  }
200
- function cryptoProvider(provider = globalThis.crypto) {
201
- if (!provider
202
- || typeof provider.getRandomValues !== 'function'
203
- || typeof provider.subtle?.importKey !== 'function'
204
- || typeof provider.subtle?.deriveKey !== 'function'
205
- || typeof provider.subtle?.encrypt !== 'function'
206
- || typeof provider.subtle?.decrypt !== 'function') {
207
- throw new Error('Encrypted workspace archives are unavailable in this browser.');
208
- }
209
- return provider;
210
- }
211
- async function deriveArchiveKey(provider, passphraseBytes, salt, usages) {
212
- const material = await provider.subtle.importKey('raw', arrayBuffer(passphraseBytes), 'PBKDF2', false, ['deriveKey']);
213
- return provider.subtle.deriveKey({
214
- name: 'PBKDF2',
215
- hash: 'SHA-256',
216
- salt: arrayBuffer(salt),
217
- iterations: WORKSPACE_ARCHIVE_PBKDF2_ITERATIONS,
218
- }, material, { name: 'AES-GCM', length: AES_KEY_BITS }, false, usages);
219
- }
220
181
  export function isEncryptedWorkspaceArchive(raw) {
221
182
  return record(raw)?.schema === ENCRYPTED_WORKSPACE_ARCHIVE_SCHEMA;
222
183
  }
@@ -227,15 +188,19 @@ export function inspectEncryptedWorkspaceArchive(raw) {
227
188
  }
228
189
  /** Encrypt one already-built workspace archive using browser-native Web Crypto. */
229
190
  export async function encryptWorkspaceArchive(archive, passphrase, provider) {
230
- await readWorkspaceArchive(archive);
231
- const archiveVersion = record(archive)?.version;
232
- if (!isSupportedWorkspaceArchiveVersion(archiveVersion)) {
233
- throw new Error('The workspace archive declares an unsupported content contract.');
234
- }
191
+ assertWorkspaceInputGraph(archive, 'Workspace archive', { maximumBytes: MAX_WORKSPACE_ARCHIVE_BYTES });
235
192
  const plaintext = JSON.stringify(archive);
236
193
  const plaintextBytes = encoder.encode(plaintext);
237
194
  if (plaintextBytes.byteLength > MAX_WORKSPACE_ARCHIVE_BYTES) {
238
- throw new Error('Workspace archives are limited to 10 MiB. Export smaller collections separately before trying again.');
195
+ throw new Error(`Workspace archives are limited to ${MAX_WORKSPACE_ARCHIVE_BYTES / 1024 / 1024} MiB. Export smaller collections separately before trying again.`);
196
+ }
197
+ // Validation and encryption consume the same snapshot, including when the
198
+ // caller changes the original object while checksum verification is pending.
199
+ const snapshot = JSON.parse(plaintext);
200
+ await readWorkspaceArchive(snapshot);
201
+ const archiveVersion = record(snapshot)?.version;
202
+ if (!isSupportedWorkspaceArchiveVersion(archiveVersion)) {
203
+ throw new Error('The workspace archive declares an unsupported content contract.');
239
204
  }
240
205
  const crypto = cryptoProvider(provider);
241
206
  const passphraseBytes = assertPassphrase(passphrase);
@@ -278,30 +243,45 @@ export async function encryptWorkspaceArchive(archive, passphrase, provider) {
278
243
  }
279
244
  /** Decrypt and parse one encrypted archive. The ordinary archive reader remains authoritative. */
280
245
  export async function decryptWorkspaceArchive(raw, passphrase, provider) {
246
+ return (await decryptWorkspaceArchiveWithMetadata(raw, passphrase, provider)).archive;
247
+ }
248
+ /** Authenticate one envelope and return its inner JSON with verified byte metadata. */
249
+ export async function decryptWorkspaceArchiveWithMetadata(raw, passphrase, provider) {
281
250
  const { envelope, salt, iv, ciphertext } = validateEnvelope(raw);
282
251
  const crypto = cryptoProvider(provider);
283
252
  const passphraseBytes = assertPassphrase(passphrase);
284
253
  const { ciphertext: _ciphertext, ...metadata } = envelope;
285
254
  try {
286
- const key = await deriveArchiveKey(crypto, passphraseBytes, salt, ['decrypt']);
287
- const plaintext = await crypto.subtle.decrypt({
288
- name: 'AES-GCM',
289
- iv: arrayBuffer(iv),
290
- additionalData: arrayBuffer(encoder.encode(canonicalMetadata(metadata))),
291
- tagLength: AES_GCM_TAG_BITS,
292
- }, key, arrayBuffer(ciphertext));
255
+ let plaintext;
256
+ try {
257
+ const key = await deriveArchiveKey(crypto, passphraseBytes, salt, ['decrypt']);
258
+ plaintext = await crypto.subtle.decrypt({
259
+ name: 'AES-GCM',
260
+ iv: arrayBuffer(iv),
261
+ additionalData: arrayBuffer(encoder.encode(canonicalMetadata(metadata))),
262
+ tagLength: AES_GCM_TAG_BITS,
263
+ }, key, arrayBuffer(ciphertext));
264
+ }
265
+ catch {
266
+ // Authentication failure cannot distinguish a wrong key from tampering.
267
+ throw new Error('The backup passphrase is incorrect or the encrypted file is corrupted.');
268
+ }
293
269
  if (plaintext.byteLength > MAX_WORKSPACE_ARCHIVE_BYTES) {
294
270
  throw new Error('The decrypted workspace archive exceeds its byte limit.');
295
271
  }
296
- return parseBoundedJson(decoder.decode(plaintext), {
272
+ let json;
273
+ try {
274
+ json = decoder.decode(plaintext);
275
+ }
276
+ catch {
277
+ throw new Error('The backup decrypted, but its contents are not valid UTF-8.');
278
+ }
279
+ const archive = parseBoundedJson(json, {
297
280
  label: 'Decrypted workspace archive',
298
281
  maximumBytes: MAX_WORKSPACE_ARCHIVE_BYTES,
282
+ limits: boundedJsonLimitsForBytes(MAX_WORKSPACE_ARCHIVE_BYTES),
299
283
  });
300
- }
301
- catch (cause) {
302
- if (cause instanceof Error && cause.message === 'The decrypted workspace archive exceeds its byte limit.')
303
- throw cause;
304
- throw new Error('The backup passphrase is incorrect or the encrypted file is corrupted.');
284
+ return { archive, ciphertextBytes: ciphertext.byteLength };
305
285
  }
306
286
  finally {
307
287
  passphraseBytes.fill(0);
@@ -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;
@@ -528,6 +592,7 @@ export async function previewWorkspaceArchive(raw, localInput, options = {}) {
528
592
  skipped: result.skipped ?? 0,
529
593
  pruned: result.pruned ?? 0,
530
594
  brandProfileReferencesOmitted: result.brandProfileReferencesOmitted ?? 0,
595
+ authoredHistoryOmitted: result.authoredHistoryOmitted ?? 0,
531
596
  selected,
532
597
  normalizedSettings: result.settings || null,
533
598
  });
@@ -567,6 +632,7 @@ export async function previewWorkspaceArchive(raw, localInput, options = {}) {
567
632
  skipped: dependentSection.recordCount,
568
633
  pruned: 0,
569
634
  brandProfileReferencesOmitted: 0,
635
+ authoredHistoryOmitted: 0,
570
636
  selected: false,
571
637
  normalizedSettings: dependentId === 'settings' ? null : dependentSection.normalizedSettings ?? null,
572
638
  };
@@ -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,156 +0,0 @@
1
- import { CliUsageError } from "./errors.mjs";
2
- export function parseInspectArchiveArguments(argv) {
3
- let source = null;
4
- let passphraseSource = null;
5
- let search = null;
6
- let reveal = false;
7
- let requireMatch = false;
8
- let expectedContentDigest = null;
9
- let output = 'terminal';
10
- let quiet = false;
11
- let color = true;
12
- for (let index = 0; index < argv.length; index++) {
13
- const argument = argv[index];
14
- if (argument === undefined)
15
- break;
16
- if (argument === '--json') {
17
- if (output !== 'terminal')
18
- throw new CliUsageError('--json may be supplied only once.');
19
- output = 'json';
20
- }
21
- else if (argument === '--passphrase-file') {
22
- if (passphraseSource !== null)
23
- throw new CliUsageError('--passphrase-file may be supplied only once.');
24
- const value = argv[++index];
25
- if (!value || value.startsWith('-'))
26
- throw new CliUsageError('--passphrase-file requires one bounded UTF-8 file.');
27
- passphraseSource = value;
28
- }
29
- else if (argument === '--search') {
30
- if (search !== null)
31
- throw new CliUsageError('--search may be supplied only once.');
32
- const value = argv[++index];
33
- if (!value || value.startsWith('-'))
34
- throw new CliUsageError('--search requires one exact bounded value.');
35
- search = value;
36
- }
37
- else if (argument === '--reveal') {
38
- if (reveal)
39
- throw new CliUsageError('--reveal may be supplied only once.');
40
- reveal = true;
41
- }
42
- else if (argument === '--require-match') {
43
- if (requireMatch)
44
- throw new CliUsageError('--require-match may be supplied only once.');
45
- requireMatch = true;
46
- }
47
- else if (argument === '--expect-content-digest') {
48
- if (expectedContentDigest !== null)
49
- throw new CliUsageError('--expect-content-digest may be supplied only once.');
50
- const value = argv[++index];
51
- if (!value || !/^sha256:[a-f0-9]{64}$/u.test(value)) {
52
- throw new CliUsageError('--expect-content-digest requires sha256 followed by 64 lowercase hexadecimal characters.');
53
- }
54
- expectedContentDigest = value;
55
- }
56
- else if (argument === '--quiet')
57
- quiet = true;
58
- else if (argument === '--no-color')
59
- color = false;
60
- else if (argument.startsWith('-'))
61
- throw new CliUsageError(`Unknown option "${argument}".`);
62
- else if (source === null)
63
- source = argument;
64
- else
65
- throw new CliUsageError('inspect-archive accepts one optional JSON file. Otherwise pipe one archive on stdin.');
66
- }
67
- if (reveal && search === null)
68
- throw new CliUsageError('--reveal requires --search.');
69
- if (requireMatch && search === null)
70
- throw new CliUsageError('--require-match requires --search.');
71
- if (quiet && output !== 'terminal')
72
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
73
- return {
74
- action: 'inspect-archive',
75
- source,
76
- passphraseSource,
77
- search,
78
- reveal,
79
- requireMatch,
80
- expectedContentDigest,
81
- output,
82
- quiet,
83
- color,
84
- };
85
- }
86
- export function parseSignArtifactArguments(argv) {
87
- let source = null;
88
- let privateKeySource = null;
89
- for (let index = 0; index < argv.length; index++) {
90
- const argument = argv[index];
91
- if (argument === undefined)
92
- break;
93
- if (argument === '--private-key-file') {
94
- if (privateKeySource !== null)
95
- throw new CliUsageError('--private-key-file may be supplied only once.');
96
- const value = argv[++index];
97
- if (!value || value.startsWith('-'))
98
- throw new CliUsageError('--private-key-file requires one bounded PEM file.');
99
- privateKeySource = value;
100
- }
101
- else if (argument.startsWith('-'))
102
- throw new CliUsageError(`Unknown option "${argument}".`);
103
- else if (source === null)
104
- source = argument;
105
- else
106
- throw new CliUsageError('sign-artifact accepts one optional JSON file. Otherwise pipe one artefact on stdin.');
107
- }
108
- if (!privateKeySource)
109
- throw new CliUsageError('sign-artifact requires --private-key-file.');
110
- return { action: 'sign-artifact', source, privateKeySource };
111
- }
112
- export function parseVerifySignatureArguments(argv) {
113
- let source = null;
114
- let publicKeySource = null;
115
- let output = 'terminal';
116
- let quiet = false;
117
- let color = true;
118
- for (let index = 0; index < argv.length; index++) {
119
- const argument = argv[index];
120
- if (argument === undefined)
121
- break;
122
- if (argument === '--json') {
123
- if (output !== 'terminal')
124
- throw new CliUsageError('--json may be supplied only once.');
125
- output = 'json';
126
- }
127
- else if (argument === '--public-key-file') {
128
- if (publicKeySource !== null)
129
- throw new CliUsageError('--public-key-file may be supplied only once.');
130
- const value = argv[++index];
131
- if (!value || value.startsWith('-'))
132
- throw new CliUsageError('--public-key-file requires one bounded PEM file.');
133
- publicKeySource = value;
134
- }
135
- else if (argument === '--quiet')
136
- quiet = true;
137
- else if (argument === '--no-color')
138
- color = false;
139
- else if (argument.startsWith('-'))
140
- throw new CliUsageError(`Unknown option "${argument}".`);
141
- else if (source === null)
142
- source = argument;
143
- else
144
- throw new CliUsageError('verify-signature accepts one optional JSON file. Otherwise pipe one package on stdin.');
145
- }
146
- if (quiet && output !== 'terminal')
147
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
148
- return {
149
- action: 'verify-signature',
150
- source,
151
- publicKeySource,
152
- output,
153
- quiet,
154
- color,
155
- };
156
- }