@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.
- package/NOTICE +6 -0
- package/README.md +19 -15
- package/bin/whoisleuth.mjs +8 -1
- package/cli/archive-content-digest.mjs +14 -0
- package/cli/archive-inspect.mjs +20 -53
- package/cli/arguments.mjs +68 -6
- package/cli/artifact-validation/case-response.mjs +47 -41
- package/cli/artifact-validation/investigation-capsule.mjs +52 -31
- package/cli/artifact-validation/lookup-evidence.mjs +12 -6
- package/cli/artifact-validation/signed-review.mjs +83 -61
- package/cli/artifact-validation/structure-primitives.mjs +1 -105
- package/cli/artifact-verify.mjs +90 -24
- package/cli/assurance-command-runner.mjs +39 -4
- package/cli/bagit-folder.mjs +57 -0
- package/cli/bagit-review.mjs +23 -0
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-command-runner.mjs +2 -0
- package/cli/bulk-output.mjs +29 -3
- package/cli/case-command.mjs +195 -0
- package/cli/case-pack.mjs +39 -31
- package/cli/command-argument-grammar.mjs +4 -0
- package/cli/command-reference.mjs +145 -75
- package/cli/completion.mjs +298 -79
- package/cli/config-profile.mjs +7 -5
- package/cli/ct-event-intake.mjs +12 -12
- package/cli/discover.mjs +4 -3
- package/cli/discovery-observation-snapshot.mjs +44 -16
- package/cli/discovery-scan-command-runner.mjs +2 -0
- package/cli/discovery-scan.mjs +7 -4
- package/cli/discovery-workflow.mjs +1 -1
- package/cli/domain-control-monitor.mjs +50 -20
- package/cli/domain-control-observations.mjs +149 -102
- package/cli/errors.mjs +33 -1
- package/cli/evidence-command-runner.mjs +16 -5
- package/cli/evidence-signing.mjs +2 -2
- package/cli/external-observation-mapping.mjs +7 -8
- package/cli/formatters/terminal-lookup.mjs +18 -46
- package/cli/formatters/terminal-shared.mjs +5 -3
- package/cli/formatters/terminal-technology.mjs +114 -0
- package/cli/inline-command-families.mjs +1 -0
- package/cli/interactive-launcher.mjs +2 -160
- package/cli/interchange-report.mjs +4 -8
- package/cli/investigation-artifacts.mjs +176 -0
- package/cli/investigation-checkpoint.mjs +10 -0
- package/cli/investigation-folder.mjs +114 -0
- package/cli/investigation-manifest.mjs +1 -112
- package/cli/investigation-package-review.mjs +103 -0
- package/cli/investigation-plan.mjs +32 -19
- package/cli/investigation-run.mjs +188 -33
- package/cli/local-document-checkpoint.mjs +170 -0
- package/cli/lookup-browser.mjs +5 -12
- package/cli/lookup-command-runner.mjs +17 -6
- package/cli/lookup-diff.mjs +37 -3
- package/cli/lookup-plan.mjs +11 -5
- package/cli/lookup-reconcile.mjs +10 -8
- package/cli/mail-review.mjs +66 -14
- package/cli/manual.mjs +1 -0
- package/cli/network-command-runner.mjs +2 -0
- package/cli/output-file.mjs +31 -5
- package/cli/registry-doctor.mjs +3 -1
- package/cli/registry-support.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +2 -2
- package/cli/review-command-runner.mjs +45 -0
- package/cli/runner.mjs +55 -6
- package/cli/sharing-review.mjs +21 -34
- package/cli/signer-trust.mjs +104 -0
- package/cli/source-reliability.mjs +74 -83
- package/cli/terminal-input.mjs +168 -0
- package/cli/workflow-command-runner.mjs +68 -13
- package/docs/cli-reference.md +76 -30
- package/docs/cli.md +289 -25
- package/lib/abort.mjs +4 -1
- package/lib/availability.mjs +161 -112
- package/lib/bounded-file.mjs +3 -1
- package/lib/bounded-html-document.mjs +125 -0
- package/lib/bounded-json.mjs +43 -3
- package/lib/browser-library-profile.mjs +42 -51
- package/lib/browser-library-worker.mjs +112 -0
- package/lib/client-behavior-profile.mjs +3 -3
- package/lib/ct-search.mjs +11 -4
- package/lib/dns-delegation-health.mjs +24 -14
- package/lib/dns-inheritance-review.mjs +217 -0
- package/lib/dns-intelligence.mjs +36 -23
- package/lib/dns-mx.mjs +1 -1
- package/lib/dnssec-evidence-validation.mjs +11 -3
- package/lib/domain-change-packet.mjs +7 -2
- package/lib/domain-change-review.mjs +14 -8
- package/lib/domain-control-flight-recorder.mjs +219 -118
- package/lib/domain-control-manifest.mjs +117 -57
- package/lib/domain-posture-analysis.mjs +20 -7
- package/lib/domain-posture-parsers.mjs +88 -6
- package/lib/domain-posture.mjs +113 -26
- package/lib/evidence-export-privacy.mjs +561 -0
- package/lib/evidence-export.mjs +11 -592
- package/lib/evidence-report.mjs +3 -1
- package/lib/favicon.mjs +117 -41
- package/lib/generated/cisa-kev-catalog.mjs +12 -4
- package/lib/generated/registrar-standing-catalogue.mjs +7 -7
- package/lib/generated/retire-browser-catalog.mjs +2335 -940
- package/lib/hostname.mjs +2 -38
- package/lib/html-signals.mjs +82 -85
- package/lib/http-evidence-bounds.mjs +2 -1
- package/lib/http-intelligence.mjs +39 -26
- package/lib/lookup-cache.mjs +34 -11
- package/lib/lookup-child-profile-contract.mjs +24 -15
- package/lib/lookup-diagnostics.mjs +24 -1
- package/lib/lookup-progress.mjs +30 -13
- package/lib/lookup-response-contract.mjs +1048 -0
- package/lib/lookup-response.mjs +7 -10
- package/lib/lookup-source-progress.mjs +17 -14
- package/lib/lookup.mjs +48 -27
- package/lib/observed-network-context.mjs +43 -27
- package/lib/outbound-request-bounds.mjs +4 -1
- package/lib/page-fingerprints.mjs +69 -133
- package/lib/page-role-profile.mjs +12 -8
- package/lib/perceptual-hash.mjs +43 -14
- package/lib/rdap-bootstrap.mjs +72 -22
- package/lib/rdap-client.mjs +15 -7
- package/lib/rdap-normalization.mjs +33 -18
- package/lib/rdap-search-workbench.mjs +1 -1
- package/lib/rdap-transport.mjs +47 -20
- package/lib/rdap-types.mjs +2 -1
- package/lib/rdap.mjs +18 -3
- package/lib/registrable-domain.mjs +21 -1
- package/lib/registrar-standing-contract.mjs +1 -0
- package/lib/registry-capabilities.mjs +5 -1
- package/lib/registry-capability-catalogue.mjs +2 -1
- package/lib/registry-dates.mjs +2 -190
- package/lib/registry-insights.mjs +56 -18
- package/lib/response-policy.mjs +86 -48
- package/lib/risk-calibration-summary.mjs +17 -3
- package/lib/rpki-evidence.mjs +8 -5
- package/lib/safe-fetch.mjs +54 -47
- package/lib/security-txt.mjs +10 -7
- package/lib/service-binding-dns.mjs +8 -0
- package/lib/smtp-transport-review.mjs +27 -15
- package/lib/sslbl-certificates.generated.mjs +136 -135
- package/lib/static-html-analysis.mjs +117 -146
- package/lib/structured-data-identity.mjs +1 -0
- package/lib/technology-markup.mjs +100 -0
- package/lib/threat-intelligence-manifests.mjs +11 -139
- package/lib/threat-intelligence-runtime.mjs +24 -52
- package/lib/threat-intelligence-targets.mjs +6 -34
- package/lib/threat-intelligence-types.mjs +39 -0
- package/lib/typosquat-generator.mjs +19 -15
- package/lib/urlhaus-intelligence.mjs +8 -3
- package/lib/urlscan-intelligence.mjs +1 -1
- package/lib/website-security-posture.mjs +30 -6
- package/lib/website-technology.mjs +111 -71
- package/lib/whois-authority.mjs +14 -2
- package/lib/whois-chain.mjs +9 -2
- package/lib/whois-common-formats.mjs +5 -5
- package/lib/whois-contracts.mjs +2 -1
- package/lib/whois-dialects.mjs +2 -2
- package/lib/whois-parser-support.mjs +8 -1
- package/lib/whois-transport.mjs +23 -7
- package/package.json +2 -1
- package/packages/cases/case-attachment-model.mjs +83 -0
- package/packages/cases/case-evidence-model.mjs +156 -62
- package/packages/cases/case-export-input.mjs +53 -0
- package/packages/cases/case-migration-model.mjs +59 -25
- package/packages/cases/case-model.mjs +1 -0
- package/packages/cases/case-recheck-model.mjs +91 -0
- package/packages/cases/case-record-core.mjs +3 -2
- package/packages/cases/case-record-operations.mjs +89 -27
- package/packages/cases/case-record-projection.mjs +23 -20
- package/packages/cases/case-report.mjs +63 -52
- package/packages/cases/case-response-actions.mjs +654 -0
- package/packages/cases/case-response-model.mjs +41 -1129
- package/packages/cases/case-response-outcomes.mjs +317 -0
- package/packages/cases/case-response-packet-render.mjs +153 -0
- package/packages/cases/case-response-packet-types.mjs +1 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
- package/packages/cases/case-response-packet.mjs +38 -587
- package/packages/cases/case-response-records.mjs +94 -0
- package/packages/cases/case-response-review-inputs.mjs +371 -0
- package/packages/cases/case-response-values.mjs +85 -0
- package/packages/cases/case-review-package.mjs +45 -0
- package/packages/cases/case-selection.mjs +46 -0
- package/packages/cases/case-storage-model.mjs +25 -0
- package/packages/cases/response-route-freshness.mjs +16 -0
- package/packages/comparison/capture-context.mjs +61 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
- package/packages/comparison/page-similarity.mjs +2 -1
- package/packages/comparison/relationship-evidence.mjs +85 -8
- package/packages/comparison/relationship-provenance.mjs +93 -0
- package/packages/contracts/analyst-interchange.mjs +216 -0
- package/packages/contracts/analyst-review-state-contract.mjs +11 -0
- package/packages/contracts/analyst-review-state.mjs +5 -4
- package/packages/contracts/case-portability.mjs +279 -116
- package/packages/contracts/case-views-contract.mjs +12 -0
- package/packages/contracts/case-views.mjs +26 -0
- package/packages/contracts/cli-command-semantics.mjs +1 -0
- package/packages/contracts/cli-lookup.mjs +0 -1
- package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
- package/packages/contracts/domain-control-manifest.mjs +127 -73
- package/packages/contracts/domain-control-monitor.mjs +48 -45
- package/packages/contracts/domain-control-review.mjs +179 -121
- package/packages/contracts/domain-name.mjs +21 -0
- package/packages/contracts/external-observation-interchange.mjs +2 -1
- package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
- package/packages/contracts/http-url.mjs +2 -0
- package/packages/contracts/investigation-package-limits.mjs +24 -0
- package/packages/contracts/investigation-portability.mjs +56 -27
- package/packages/contracts/investigation-run.mjs +16 -0
- package/packages/contracts/ip-address.mjs +34 -0
- package/packages/contracts/lookup-evidence.mjs +35 -0
- package/packages/contracts/offline-comparison.mjs +1 -2
- package/packages/contracts/page-fingerprints.mjs +15 -0
- package/packages/contracts/risk-calibration.mjs +0 -1
- package/packages/contracts/schema-compatibility.mjs +1 -1
- package/packages/contracts/schema-lifecycle.mjs +83 -153
- package/packages/contracts/selected-file-limits.mjs +6 -0
- package/packages/contracts/vulnerability-identifiers.mjs +5 -0
- package/packages/contracts/web-capture.mjs +10 -0
- package/packages/contracts/workspace-portability.mjs +160 -225
- package/packages/evidence/artifact-integrity.mjs +8 -2
- package/packages/evidence/artifact-structure.mjs +105 -0
- package/packages/evidence/decision-fact.mjs +4 -1
- package/packages/evidence/domain-control-runtime.mjs +155 -27
- package/packages/evidence/domain-posture-context.mjs +79 -0
- package/packages/evidence/image-regions.mjs +56 -0
- package/packages/evidence/latest-observations.mjs +28 -0
- package/packages/evidence/lookup-target.mjs +104 -0
- package/packages/evidence/observation.mjs +16 -1
- package/packages/evidence/passphrase-encryption.mjs +44 -0
- package/packages/evidence/record-identity.mjs +72 -0
- package/packages/evidence/registry-dates.mjs +190 -0
- package/packages/evidence/retained-file.mjs +69 -0
- package/packages/evidence/review-age.mjs +14 -0
- package/packages/interchange/bagit.mjs +342 -0
- package/packages/interchange/bounded-zip-extraction.mjs +278 -0
- package/packages/interchange/crc32.mjs +17 -0
- package/packages/interchange/external-findings-import.mjs +89 -49
- package/packages/interchange/web-capture-import.mjs +382 -0
- package/packages/investigation/bulk-mail-exposure.mjs +2 -1
- package/packages/investigation/investigation-bagit.mjs +14 -0
- package/packages/investigation/investigation-capsule.mjs +33 -5
- package/packages/investigation/investigation-manifest.mjs +200 -0
- package/packages/investigation/investigation-package-crypto.mjs +98 -0
- package/packages/investigation/investigation-package.mjs +200 -0
- package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
- package/packages/investigation/lookup-asset-graph.mjs +161 -141
- package/packages/investigation/lookup-claim-readiness.mjs +18 -0
- package/packages/investigation/lookup-investigation-brief.mjs +3 -3
- package/packages/investigation/lookup-task-guidance.mjs +58 -0
- package/packages/monitoring/analyst-review-state.mjs +38 -90
- package/packages/workspace/brand-profile-model.mjs +158 -20
- package/packages/workspace/bulk-columns.mjs +17 -0
- package/packages/workspace/bulk-review-model.mjs +11 -5
- package/packages/workspace/bulk-session-model.mjs +95 -20
- package/packages/workspace/bulk-sort.mjs +1 -0
- package/packages/workspace/case-views.mjs +102 -0
- package/packages/workspace/hostile-input.mjs +29 -8
- package/packages/workspace/investigation-template-model.mjs +19 -4
- package/packages/workspace/page-baseline.mjs +18 -15
- package/packages/workspace/relationship-observation-model.mjs +41 -26
- package/packages/workspace/watchlist-history.mjs +2 -2
- package/packages/workspace/watchlist-store.mjs +8 -2
- package/packages/workspace/website-snapshot-model.mjs +70 -30
- package/packages/workspace/workspace-archive-crypto.mjs +41 -61
- package/packages/workspace/workspace-archive.mjs +85 -21
- package/third-party-notices.txt +29 -2
- 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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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;
|
package/third-party-notices.txt
CHANGED
|
@@ -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
|
|
5
|
+
The inventory is the exact transitive closure of the packaged runtime dependencies.
|
|
6
6
|
|
|
7
|
-
Package count:
|
|
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
|
-
}
|