@slicedearth/whoisleuth-cli 1.45.0 → 1.47.4
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/DISCLOSURE +7 -3
- package/NOTICE +5 -4
- package/README.md +59 -6
- package/bin/whoisleuth.mjs +5 -3
- package/cli/arguments.mjs +256 -9
- package/cli/artifact-structure.mjs +1833 -0
- package/cli/artifact-verify.mjs +242 -49
- package/cli/bounded-json.mjs +1 -0
- package/cli/bulk-checkpoint.mjs +2 -3
- package/cli/bulk-command-runner.mjs +2 -5
- package/cli/case-pack.mjs +819 -34
- package/cli/command-reference.mjs +48 -24
- package/cli/completion.mjs +16 -8
- package/cli/ct-event-intake.mjs +32 -2
- package/cli/discovery-scan-command-runner.mjs +4 -5
- package/cli/discovery-workflow.mjs +2 -3
- package/cli/doctor.mjs +2 -2
- package/cli/domain-control-monitor.mjs +21 -15
- package/cli/domain-control-observations.mjs +25 -2
- package/cli/evidence-command-runner.mjs +17 -11
- package/cli/evidence-signing.mjs +73 -45
- package/cli/export-evidence.mjs +11 -4
- package/cli/formatters/html.mjs +3 -3
- package/cli/formatters/terminal.mjs +3 -0
- package/cli/input.mjs +57 -0
- package/cli/interchange-report.mjs +226 -0
- package/cli/investigation-manifest.mjs +16 -17
- package/cli/investigation-run.mjs +71 -23
- package/cli/local-mmdb-review.mjs +45 -14
- package/cli/lookup-command-runner.mjs +2 -2
- package/cli/lookup-diff.mjs +9 -0
- package/cli/network-command-runner.mjs +78 -0
- package/cli/offline-evidence-review.mjs +28 -8
- package/cli/page-compare.mjs +8 -5
- package/cli/registry-cohort.mjs +385 -61
- package/cli/registry-support.mjs +2 -1
- package/cli/retained-artifact-diff.mjs +376 -0
- package/cli/risk-calibration.mjs +7 -5
- package/cli/runner.mjs +147 -185
- package/cli/saved-lookup.mjs +26 -3
- package/cli/sharing-review.mjs +12 -6
- package/docs/cli-reference.md +444 -70
- package/docs/cli.md +24 -5
- package/frontend/src/lib/analysis/acquisition-decision-packet.js +4 -4
- package/frontend/src/lib/analysis/artifact-integrity.js +42 -0
- package/frontend/src/lib/analysis/brand-profile-model.js +29 -5
- package/frontend/src/lib/analysis/bulk-domain-comparison.js +25 -5
- package/frontend/src/lib/analysis/bulk-mail-exposure.js +66 -15
- package/frontend/src/lib/analysis/bulk-retry-plan.js +1 -1
- package/frontend/src/lib/analysis/bulk-review-export.js +7 -6
- package/frontend/src/lib/analysis/bulk-review-model.js +3 -2
- package/frontend/src/lib/analysis/bulk-session-model.js +192 -26
- package/frontend/src/lib/analysis/bulk-sort.js +4 -0
- package/frontend/src/lib/analysis/case-brand-profile-references.js +85 -0
- package/frontend/src/lib/analysis/case-evidence-model.js +8 -0
- package/frontend/src/lib/analysis/case-investigation-branch-model.js +134 -0
- package/frontend/src/lib/analysis/case-migration-model.js +83 -31
- package/frontend/src/lib/analysis/case-record-contracts.js +4 -2
- package/frontend/src/lib/analysis/case-record-model.js +2 -0
- package/frontend/src/lib/analysis/case-record-operations.js +42 -12
- package/frontend/src/lib/analysis/case-report.js +39 -6
- package/frontend/src/lib/analysis/case-response-model.js +66 -7
- package/frontend/src/lib/analysis/case-response-packet.js +15 -5
- package/frontend/src/lib/analysis/comparison-ledger-bulk.js +366 -0
- package/frontend/src/lib/analysis/comparison-ledger-contract.js +241 -0
- package/frontend/src/lib/analysis/comparison-ledger-json.js +79 -0
- package/frontend/src/lib/analysis/comparison-ledger-serialization.js +51 -0
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +145 -0
- package/frontend/src/lib/analysis/domain-control-records.js +88 -0
- package/frontend/src/lib/analysis/external-findings-import.js +70 -6
- package/frontend/src/lib/analysis/investigation-capsule.js +47 -13
- package/frontend/src/lib/analysis/investigation-guide.js +52 -10
- package/frontend/src/lib/analysis/investigation-template-model.js +9 -6
- package/frontend/src/lib/analysis/lookup-claim-passport.js +236 -0
- package/frontend/src/lib/analysis/lookup-claim-readiness.js +280 -0
- package/frontend/src/lib/analysis/lookup-decision-support.js +61 -6
- package/frontend/src/lib/analysis/lookup-presentation.js +62 -0
- package/frontend/src/lib/analysis/opaque-reference-id.js +9 -0
- package/frontend/src/lib/analysis/utils.js +26 -3
- package/frontend/src/lib/analysis/visualization-matrix.js +71 -28
- package/frontend/src/lib/analysis/visualization-models.js +63 -94
- package/frontend/src/lib/analysis/visualization-time-series.js +53 -12
- package/frontend/src/lib/analysis/website-snapshot-model.js +6 -0
- package/frontend/src/lib/analysis/workspace-archive.js +104 -14
- package/lib/application-version.mjs +3 -0
- package/lib/availability.mjs +14 -12
- package/lib/bounded-file.mjs +13 -0
- package/lib/bounded-json-response.mjs +17 -4
- package/lib/bounded-json.mjs +249 -0
- package/lib/certificate-extension-profile.mjs +2 -1
- package/lib/cryptographic-assurance.mjs +155 -0
- package/lib/ct-search.mjs +87 -47
- package/lib/dns-intelligence.mjs +3 -2
- package/lib/dns-resolver-selection.mjs +6 -4
- package/lib/dnssec-chain-validation.mjs +1053 -0
- package/lib/dnssec-evidence-validation.mjs +10 -3
- package/lib/domain-change-packet.mjs +7 -5
- package/lib/domain-control-manifest.mjs +17 -129
- package/lib/domain-posture.mjs +7 -4
- package/lib/evidence-export.mjs +751 -78
- package/lib/evidence-report-markdown.mjs +3 -0
- package/lib/evidence-report.mjs +5 -0
- package/lib/favicon.mjs +8 -5
- package/lib/http-evidence-bounds.mjs +3 -0
- package/lib/http-intelligence.mjs +2 -5
- package/lib/interchange-fidelity-registry.mjs +122 -0
- package/lib/local-geoip-evidence.mjs +2 -1
- package/lib/lookup-network-evidence-bounds.mjs +9 -0
- package/lib/lookup-response-contract.mjs +691 -3
- package/lib/lookup.mjs +2 -2
- package/lib/observation.mjs +7 -6
- package/lib/outbound-identity.mjs +2 -3
- package/lib/perceptual-hash.mjs +12 -4
- package/lib/portable-generator.mjs +21 -0
- package/lib/provider-policy-admission.mjs +9 -4
- package/lib/rdap-normalization.mjs +18 -5
- package/lib/rdap-registrar.mjs +5 -1
- package/lib/registry-capabilities.mjs +3 -0
- package/lib/registry-capability-catalogue.mjs +12 -2
- package/lib/registry-comparison.mjs +28 -11
- package/lib/risk-calibration-summary.mjs +338 -0
- package/lib/rpki-evidence.mjs +9 -8
- package/lib/safe-fetch.mjs +59 -10
- package/lib/security-txt.mjs +26 -3
- package/lib/service-binding-dns.mjs +6 -1
- package/lib/smtp-transport-review.mjs +784 -0
- package/lib/sslbl-certificates.generated.mjs +136 -134
- package/lib/threat-intelligence-runtime.mjs +3 -11
- package/lib/threat-intelligence-types.mjs +18 -1
- package/lib/tls-intelligence.mjs +11 -5
- package/lib/tlsa-evidence.mjs +37 -7
- package/package.json +1 -1
- package/third-party-notices.txt +95 -5817
package/cli/artifact-verify.mjs
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import {
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { parseBoundedJsonObject } from "./bounded-json.mjs";
|
|
4
|
+
import { validateInvestigationCapsuleStructure, validateLookupEvidenceArtifactStructure, validateOfflineArtifactStructure, validateSignedDigestArtifactStructure, } from "./artifact-structure.mjs";
|
|
5
|
+
import { CASE_RESPONSE_PACKET_SCHEMA, CASE_RESPONSE_PACKET_VERSION, LEGACY_CASE_RESPONSE_PACKET_VERSION, verifyCaseResponsePacketIntegrity, } from "../frontend/src/lib/analysis/case-response-packet.js";
|
|
3
6
|
import { ACQUISITION_DECISION_PACKET_SCHEMA, ACQUISITION_DECISION_PACKET_VERSION, } from "../frontend/src/lib/analysis/acquisition-decision-packet.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
7
|
+
import { LOOKUP_CLAIM_PASSPORT_SCHEMA, LOOKUP_CLAIM_PASSPORT_VERSION, } from "../frontend/src/lib/analysis/lookup-claim-passport.js";
|
|
8
|
+
import { BULK_DOMAIN_COMPARISON_SCHEMA, BULK_DOMAIN_COMPARISON_EXPORT_VERSION, } from "../frontend/src/lib/analysis/bulk-domain-comparison.js";
|
|
9
|
+
import { BULK_MAIL_EXPOSURE_SCHEMA, BULK_MAIL_EXPOSURE_EXPORT_VERSION, } from "../frontend/src/lib/analysis/bulk-mail-exposure.js";
|
|
6
10
|
import { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION, } from "../frontend/src/lib/analysis/bulk-review-export.js";
|
|
7
11
|
import { decryptWorkspaceArchive, inspectEncryptedWorkspaceArchive, isEncryptedWorkspaceArchive, } from "../frontend/src/lib/analysis/workspace-archive-crypto.js";
|
|
8
|
-
import { WORKSPACE_ARCHIVE_SCHEMA, readWorkspaceArchive, } from "../frontend/src/lib/analysis/workspace-archive.js";
|
|
9
|
-
import {
|
|
12
|
+
import { WORKSPACE_ARCHIVE_SCHEMA, previewWorkspaceArchive, readWorkspaceArchive, } from "../frontend/src/lib/analysis/workspace-archive.js";
|
|
13
|
+
import { canonicalArtifactJsonFor, resolveArtifactCanonicalization, sha256ArtifactDigestFor, SORTED_JSON_V1, SORTED_JSON_V2, } from "../frontend/src/lib/analysis/artifact-integrity.js";
|
|
10
14
|
import { SAVED_LOOKUP_SCHEMA, SAVED_LOOKUP_SCHEMA_VERSION, parseSavedLookupDocument, } from "./saved-lookup.mjs";
|
|
11
15
|
import { DOMAIN_CONTROL_MANIFEST_SCHEMA, DOMAIN_CONTROL_MANIFEST_VERSION, verifyDomainControlManifest, } from "../lib/domain-control-manifest.mjs";
|
|
16
|
+
import { DOMAIN_CONTROL_PASSPORT_VERSION } from "../frontend/src/lib/analysis/domain-control-manifest-core.js";
|
|
12
17
|
import { DOMAIN_CHANGE_PACKET_SCHEMA, DOMAIN_CHANGE_PACKET_VERSION, } from "../lib/domain-change-packet.mjs";
|
|
13
|
-
import { INVESTIGATION_CAPSULE_SCHEMA, INVESTIGATION_CAPSULE_VERSION, verifyInvestigationCapsule, } from "../frontend/src/lib/analysis/investigation-capsule.js";
|
|
18
|
+
import { INVESTIGATION_CAPSULE_SCHEMA, INVESTIGATION_CAPSULE_VERSION, LEGACY_INVESTIGATION_CAPSULE_VERSION, verifyInvestigationCapsule, } from "../frontend/src/lib/analysis/investigation-capsule.js";
|
|
14
19
|
import { INVESTIGATION_MANIFEST_SCHEMA, INVESTIGATION_MANIFEST_VERSION, } from "./investigation-manifest.mjs";
|
|
15
|
-
import { CLI_CASE_PACK_SCHEMA,
|
|
20
|
+
import { CLI_CASE_PACK_SCHEMA, verifyCliCasePack, } from "./case-pack.mjs";
|
|
21
|
+
import { LOOKUP_EVIDENCE_PORTABLE_MAX_BYTES, LOOKUP_EVIDENCE_SCHEMA, SUPPORTED_LOOKUP_EVIDENCE_SCHEMA_VERSIONS, } from "../lib/evidence-export.mjs";
|
|
16
22
|
export const OFFLINE_ARTIFACT_VERIFICATION_SCHEMA = 'whoisleuth.offline-artifact-verification';
|
|
17
|
-
export const OFFLINE_ARTIFACT_VERIFICATION_VERSION =
|
|
23
|
+
export const OFFLINE_ARTIFACT_VERIFICATION_VERSION = 3;
|
|
18
24
|
export const MAX_OFFLINE_ARTIFACT_BYTES = 15 * 1024 * 1024;
|
|
19
25
|
export const MAX_OFFLINE_PASSPHRASE_FILE_BYTES = 1024;
|
|
20
26
|
export class UnsupportedOfflineArtifactError extends TypeError {
|
|
@@ -25,13 +31,24 @@ export class UnsupportedOfflineArtifactError extends TypeError {
|
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
const SIGNED_ARTIFACT_VERSIONS = Object.freeze({
|
|
28
|
-
[ACQUISITION_DECISION_PACKET_SCHEMA]: new Set([ACQUISITION_DECISION_PACKET_VERSION]),
|
|
29
|
-
[
|
|
30
|
-
[
|
|
31
|
-
[
|
|
32
|
-
[
|
|
33
|
-
[
|
|
34
|
-
[
|
|
34
|
+
[ACQUISITION_DECISION_PACKET_SCHEMA]: new Set([1, ACQUISITION_DECISION_PACKET_VERSION]),
|
|
35
|
+
[LOOKUP_CLAIM_PASSPORT_SCHEMA]: new Set([LOOKUP_CLAIM_PASSPORT_VERSION]),
|
|
36
|
+
[BULK_DOMAIN_COMPARISON_SCHEMA]: new Set([3, BULK_DOMAIN_COMPARISON_EXPORT_VERSION]),
|
|
37
|
+
[BULK_MAIL_EXPOSURE_SCHEMA]: new Set([1, BULK_MAIL_EXPOSURE_EXPORT_VERSION]),
|
|
38
|
+
[BULK_REVIEW_MANIFEST_SCHEMA]: new Set([1, BULK_REVIEW_MANIFEST_VERSION]),
|
|
39
|
+
[DOMAIN_CONTROL_MANIFEST_SCHEMA]: new Set([DOMAIN_CONTROL_PASSPORT_VERSION, DOMAIN_CONTROL_MANIFEST_VERSION]),
|
|
40
|
+
[DOMAIN_CHANGE_PACKET_SCHEMA]: new Set([1, DOMAIN_CHANGE_PACKET_VERSION]),
|
|
41
|
+
[INVESTIGATION_MANIFEST_SCHEMA]: new Set([1, INVESTIGATION_MANIFEST_VERSION]),
|
|
42
|
+
});
|
|
43
|
+
const SIGNED_ARTIFACT_CANONICALIZATION = Object.freeze({
|
|
44
|
+
[ACQUISITION_DECISION_PACKET_SCHEMA]: [{ version: 1, canonicalization: SORTED_JSON_V1, explicit: false }, { version: ACQUISITION_DECISION_PACKET_VERSION, canonicalization: SORTED_JSON_V2, explicit: true }],
|
|
45
|
+
[LOOKUP_CLAIM_PASSPORT_SCHEMA]: [{ version: LOOKUP_CLAIM_PASSPORT_VERSION, canonicalization: SORTED_JSON_V2, explicit: true }],
|
|
46
|
+
[BULK_DOMAIN_COMPARISON_SCHEMA]: [{ version: 3, canonicalization: SORTED_JSON_V1, explicit: false }, { version: BULK_DOMAIN_COMPARISON_EXPORT_VERSION, canonicalization: SORTED_JSON_V2, explicit: true }],
|
|
47
|
+
[BULK_MAIL_EXPOSURE_SCHEMA]: [{ version: 1, canonicalization: SORTED_JSON_V1, explicit: false }, { version: BULK_MAIL_EXPOSURE_EXPORT_VERSION, canonicalization: SORTED_JSON_V2, explicit: true }],
|
|
48
|
+
[BULK_REVIEW_MANIFEST_SCHEMA]: [{ version: 1, canonicalization: SORTED_JSON_V1, explicit: false }, { version: BULK_REVIEW_MANIFEST_VERSION, canonicalization: SORTED_JSON_V2, explicit: true }],
|
|
49
|
+
[DOMAIN_CONTROL_MANIFEST_SCHEMA]: [{ version: DOMAIN_CONTROL_PASSPORT_VERSION, canonicalization: SORTED_JSON_V1, explicit: true }, { version: DOMAIN_CONTROL_MANIFEST_VERSION, canonicalization: SORTED_JSON_V2, explicit: true }],
|
|
50
|
+
[DOMAIN_CHANGE_PACKET_SCHEMA]: [{ version: 1, canonicalization: SORTED_JSON_V1, explicit: false }, { version: DOMAIN_CHANGE_PACKET_VERSION, canonicalization: SORTED_JSON_V2, explicit: true }],
|
|
51
|
+
[INVESTIGATION_MANIFEST_SCHEMA]: [{ version: 1, canonicalization: SORTED_JSON_V1, explicit: false }, { version: INVESTIGATION_MANIFEST_VERSION, canonicalization: SORTED_JSON_V2, explicit: true }],
|
|
35
52
|
});
|
|
36
53
|
function record(value) {
|
|
37
54
|
return value && typeof value === 'object' && !Array.isArray(value)
|
|
@@ -39,23 +56,32 @@ function record(value) {
|
|
|
39
56
|
: null;
|
|
40
57
|
}
|
|
41
58
|
function parseJson(raw) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
return parseBoundedJsonObject(raw, { maximumBytes: MAX_OFFLINE_ARTIFACT_BYTES });
|
|
60
|
+
}
|
|
61
|
+
export function hasVerifiedArtifactStructure(report) {
|
|
62
|
+
return report.checks.structure === 'verified';
|
|
63
|
+
}
|
|
64
|
+
export function hasVerifiedApplicableIntegrity(report) {
|
|
65
|
+
return report.checks.contentIntegrity === 'verified'
|
|
66
|
+
&& (report.checks.contentIntegrityScope === 'whole_artifact'
|
|
67
|
+
|| report.checks.contentIntegrityScope === 'embedded_projections');
|
|
68
|
+
}
|
|
69
|
+
export function hasVerifiedWholeArtifactIntegrity(report) {
|
|
70
|
+
return report.checks.contentIntegrity === 'verified'
|
|
71
|
+
&& report.checks.contentIntegrityScope === 'whole_artifact';
|
|
72
|
+
}
|
|
73
|
+
export function offlineArtifactSatisfiesAssurance(report, requirement) {
|
|
74
|
+
if (!hasVerifiedArtifactStructure(report))
|
|
75
|
+
return false;
|
|
76
|
+
if (requirement === 'structure')
|
|
77
|
+
return true;
|
|
78
|
+
if (requirement === 'applicable_integrity')
|
|
79
|
+
return hasVerifiedApplicableIntegrity(report);
|
|
80
|
+
return hasVerifiedWholeArtifactIntegrity(report);
|
|
81
|
+
}
|
|
82
|
+
export function isCompleteOfflineArtifactVerification(report) {
|
|
83
|
+
return (report.state === 'verified' || report.state === 'structure_valid')
|
|
84
|
+
&& (report.manifestIdentity === null || report.manifestIdentity.state === 'identity_verified');
|
|
59
85
|
}
|
|
60
86
|
function artifactVersion(value) {
|
|
61
87
|
const declared = value.version ?? value.schemaVersion;
|
|
@@ -70,7 +96,17 @@ function artifactVersion(value) {
|
|
|
70
96
|
function inputBytes(raw) {
|
|
71
97
|
return Buffer.byteLength(raw, 'utf8');
|
|
72
98
|
}
|
|
73
|
-
function archiveReport(raw, archive, encrypted, ciphertextBytes) {
|
|
99
|
+
async function archiveReport(raw, source, archive, encrypted, ciphertextBytes) {
|
|
100
|
+
const preview = await previewWorkspaceArchive(source, {});
|
|
101
|
+
const readySectionCount = preview.sections.filter((section) => section.status === 'ready').length;
|
|
102
|
+
const unsupportedSectionCount = preview.sections.filter((section) => section.status === 'unsupported').length;
|
|
103
|
+
const blockedSectionCount = preview.sections.filter((section) => section.status === 'blocked').length;
|
|
104
|
+
const skippedRecordCount = preview.sections.reduce((sum, section) => sum + section.skipped, 0);
|
|
105
|
+
const prunedRecordCount = preview.sections.reduce((sum, section) => sum + (section.pruned ?? 0), 0);
|
|
106
|
+
const fullyImportable = unsupportedSectionCount === 0
|
|
107
|
+
&& blockedSectionCount === 0
|
|
108
|
+
&& skippedRecordCount === 0
|
|
109
|
+
&& prunedRecordCount === 0;
|
|
74
110
|
return Object.freeze({
|
|
75
111
|
schema: OFFLINE_ARTIFACT_VERIFICATION_SCHEMA,
|
|
76
112
|
version: OFFLINE_ARTIFACT_VERIFICATION_VERSION,
|
|
@@ -80,10 +116,10 @@ function archiveReport(raw, archive, encrypted, ciphertextBytes) {
|
|
|
80
116
|
version: archive.sourceVersion,
|
|
81
117
|
}),
|
|
82
118
|
state: 'verified',
|
|
83
|
-
valid: true,
|
|
84
119
|
checks: Object.freeze({
|
|
85
120
|
structure: 'verified',
|
|
86
121
|
contentIntegrity: 'verified',
|
|
122
|
+
contentIntegrityScope: 'whole_artifact',
|
|
87
123
|
authenticatedEncryption: encrypted ? 'verified' : 'not_applicable',
|
|
88
124
|
}),
|
|
89
125
|
summary: Object.freeze({
|
|
@@ -91,9 +127,18 @@ function archiveReport(raw, archive, encrypted, ciphertextBytes) {
|
|
|
91
127
|
sectionCount: archive.sections.length,
|
|
92
128
|
recordCount: archive.sections.reduce((sum, section) => sum + section.recordCount, 0),
|
|
93
129
|
ciphertextBytes,
|
|
130
|
+
readySectionCount,
|
|
131
|
+
unsupportedSectionCount,
|
|
132
|
+
blockedSectionCount,
|
|
133
|
+
skippedRecordCount,
|
|
134
|
+
prunedRecordCount,
|
|
135
|
+
fullyImportable,
|
|
94
136
|
}),
|
|
95
137
|
limitations: Object.freeze([
|
|
96
138
|
'Verification checks the retained file against its declared versioned integrity contract; it does not establish that the original observations were accurate or remain current.',
|
|
139
|
+
...(!fullyImportable
|
|
140
|
+
? ['One or more integrity-valid archive sections cannot be imported completely by this version. Inspect the archive before selecting data to restore.']
|
|
141
|
+
: []),
|
|
97
142
|
]),
|
|
98
143
|
});
|
|
99
144
|
}
|
|
@@ -102,6 +147,7 @@ async function verifySignedArtifact(raw, value, schema, version) {
|
|
|
102
147
|
if (!supported?.has(version)) {
|
|
103
148
|
throw new UnsupportedOfflineArtifactError('This signed review-artifact schema or version is not supported.');
|
|
104
149
|
}
|
|
150
|
+
validateSignedDigestArtifactStructure(schema, value);
|
|
105
151
|
const integrity = record(value.integrity);
|
|
106
152
|
if (!integrity
|
|
107
153
|
|| integrity.algorithm !== 'SHA-256'
|
|
@@ -109,8 +155,9 @@ async function verifySignedArtifact(raw, value, schema, version) {
|
|
|
109
155
|
|| !/^sha256:[a-f0-9]{64}$/u.test(integrity.digestSha256)) {
|
|
110
156
|
throw new TypeError('The signed review artefact has a missing or malformed integrity envelope.');
|
|
111
157
|
}
|
|
158
|
+
const canonicalization = resolveArtifactCanonicalization(version, integrity.canonicalization, SIGNED_ARTIFACT_CANONICALIZATION[schema] ?? [], 'Signed review artefact');
|
|
112
159
|
const { integrity: _integrity, ...unsigned } = value;
|
|
113
|
-
if (await
|
|
160
|
+
if (await sha256ArtifactDigestFor(unsigned, canonicalization) !== integrity.digestSha256) {
|
|
114
161
|
throw new TypeError('The signed review artefact failed its SHA-256 integrity check.');
|
|
115
162
|
}
|
|
116
163
|
if (schema === DOMAIN_CONTROL_MANIFEST_SCHEMA)
|
|
@@ -120,10 +167,10 @@ async function verifySignedArtifact(raw, value, schema, version) {
|
|
|
120
167
|
version: OFFLINE_ARTIFACT_VERIFICATION_VERSION,
|
|
121
168
|
artifact: Object.freeze({ kind: 'signed_review_artifact', schema, version }),
|
|
122
169
|
state: 'verified',
|
|
123
|
-
valid: true,
|
|
124
170
|
checks: Object.freeze({
|
|
125
171
|
structure: 'verified',
|
|
126
172
|
contentIntegrity: 'verified',
|
|
173
|
+
contentIntegrityScope: 'whole_artifact',
|
|
127
174
|
authenticatedEncryption: 'not_applicable',
|
|
128
175
|
}),
|
|
129
176
|
summary: Object.freeze({
|
|
@@ -137,7 +184,7 @@ async function verifySignedArtifact(raw, value, schema, version) {
|
|
|
137
184
|
]),
|
|
138
185
|
});
|
|
139
186
|
}
|
|
140
|
-
|
|
187
|
+
async function verifyOfflineArtifactCore(raw, options = {}) {
|
|
141
188
|
const value = parseJson(raw);
|
|
142
189
|
const casePackPacket = record(value.packet);
|
|
143
190
|
if (casePackPacket?.schema === CLI_CASE_PACK_SCHEMA) {
|
|
@@ -148,13 +195,13 @@ export async function verifyOfflineArtifact(raw, options = {}) {
|
|
|
148
195
|
artifact: Object.freeze({
|
|
149
196
|
kind: 'cli_case_pack',
|
|
150
197
|
schema: CLI_CASE_PACK_SCHEMA,
|
|
151
|
-
version:
|
|
198
|
+
version: Number(casePackPacket.version),
|
|
152
199
|
}),
|
|
153
200
|
state: 'verified',
|
|
154
|
-
valid: true,
|
|
155
201
|
checks: Object.freeze({
|
|
156
202
|
structure: 'verified',
|
|
157
203
|
contentIntegrity: 'verified',
|
|
204
|
+
contentIntegrityScope: 'whole_artifact',
|
|
158
205
|
authenticatedEncryption: 'not_applicable',
|
|
159
206
|
}),
|
|
160
207
|
summary: Object.freeze({
|
|
@@ -180,10 +227,10 @@ export async function verifyOfflineArtifact(raw, options = {}) {
|
|
|
180
227
|
version: artifactVersion(value),
|
|
181
228
|
}),
|
|
182
229
|
state: 'envelope_valid',
|
|
183
|
-
valid: true,
|
|
184
230
|
checks: Object.freeze({
|
|
185
231
|
structure: 'verified',
|
|
186
232
|
contentIntegrity: 'not_checked',
|
|
233
|
+
contentIntegrityScope: 'not_checked',
|
|
187
234
|
authenticatedEncryption: 'not_checked',
|
|
188
235
|
}),
|
|
189
236
|
summary: Object.freeze({
|
|
@@ -200,17 +247,18 @@ export async function verifyOfflineArtifact(raw, options = {}) {
|
|
|
200
247
|
}
|
|
201
248
|
const decrypted = await decryptWorkspaceArchive(value, options.passphrase);
|
|
202
249
|
const archive = await readWorkspaceArchive(decrypted);
|
|
203
|
-
return archiveReport(raw, archive, true, inspected.ciphertextBytes);
|
|
250
|
+
return archiveReport(raw, decrypted, archive, true, inspected.ciphertextBytes);
|
|
204
251
|
}
|
|
205
252
|
const schema = typeof value.schema === 'string' ? value.schema : '';
|
|
206
253
|
const version = artifactVersion(value);
|
|
207
254
|
if (schema === WORKSPACE_ARCHIVE_SCHEMA) {
|
|
208
|
-
return archiveReport(raw, await readWorkspaceArchive(value), false, null);
|
|
255
|
+
return archiveReport(raw, value, await readWorkspaceArchive(value), false, null);
|
|
209
256
|
}
|
|
210
257
|
if (schema === CASE_RESPONSE_PACKET_SCHEMA) {
|
|
211
|
-
if (version !== CASE_RESPONSE_PACKET_VERSION) {
|
|
258
|
+
if (version !== LEGACY_CASE_RESPONSE_PACKET_VERSION && version !== CASE_RESPONSE_PACKET_VERSION) {
|
|
212
259
|
throw new UnsupportedOfflineArtifactError('This case-response packet version is not supported.');
|
|
213
260
|
}
|
|
261
|
+
validateOfflineArtifactStructure(schema, value);
|
|
214
262
|
const integrity = record(value.integrity);
|
|
215
263
|
if (!integrity || !await verifyCaseResponsePacketIntegrity(value)) {
|
|
216
264
|
throw new TypeError('The case-response packet failed its manifest integrity check.');
|
|
@@ -220,10 +268,10 @@ export async function verifyOfflineArtifact(raw, options = {}) {
|
|
|
220
268
|
version: OFFLINE_ARTIFACT_VERIFICATION_VERSION,
|
|
221
269
|
artifact: Object.freeze({ kind: 'case_response_packet', schema, version }),
|
|
222
270
|
state: 'verified',
|
|
223
|
-
valid: true,
|
|
224
271
|
checks: Object.freeze({
|
|
225
272
|
structure: 'verified',
|
|
226
273
|
contentIntegrity: 'verified',
|
|
274
|
+
contentIntegrityScope: 'whole_artifact',
|
|
227
275
|
authenticatedEncryption: 'not_applicable',
|
|
228
276
|
}),
|
|
229
277
|
summary: Object.freeze({
|
|
@@ -238,21 +286,23 @@ export async function verifyOfflineArtifact(raw, options = {}) {
|
|
|
238
286
|
});
|
|
239
287
|
}
|
|
240
288
|
if (schema === INVESTIGATION_CAPSULE_SCHEMA) {
|
|
241
|
-
if (version !== INVESTIGATION_CAPSULE_VERSION) {
|
|
289
|
+
if (version !== LEGACY_INVESTIGATION_CAPSULE_VERSION && version !== INVESTIGATION_CAPSULE_VERSION) {
|
|
242
290
|
throw new UnsupportedOfflineArtifactError('This investigation-capsule version is not supported.');
|
|
243
291
|
}
|
|
292
|
+
validateInvestigationCapsuleStructure(value);
|
|
244
293
|
const verification = await verifyInvestigationCapsule(value);
|
|
245
294
|
if (!verification.valid)
|
|
246
295
|
throw new TypeError('The investigation capsule failed its embedded projection integrity checks.');
|
|
296
|
+
const current = version === INVESTIGATION_CAPSULE_VERSION;
|
|
247
297
|
return Object.freeze({
|
|
248
298
|
schema: OFFLINE_ARTIFACT_VERIFICATION_SCHEMA,
|
|
249
299
|
version: OFFLINE_ARTIFACT_VERIFICATION_VERSION,
|
|
250
300
|
artifact: Object.freeze({ kind: 'investigation_capsule', schema, version }),
|
|
251
|
-
state: 'verified',
|
|
252
|
-
valid: true,
|
|
301
|
+
state: current ? 'verified' : 'integrity_valid',
|
|
253
302
|
checks: Object.freeze({
|
|
254
303
|
structure: 'verified',
|
|
255
304
|
contentIntegrity: 'verified',
|
|
305
|
+
contentIntegrityScope: current ? 'whole_artifact' : 'embedded_projections',
|
|
256
306
|
authenticatedEncryption: 'not_applicable',
|
|
257
307
|
}),
|
|
258
308
|
summary: Object.freeze({
|
|
@@ -262,11 +312,45 @@ export async function verifyOfflineArtifact(raw, options = {}) {
|
|
|
262
312
|
ciphertextBytes: null,
|
|
263
313
|
}),
|
|
264
314
|
limitations: Object.freeze([
|
|
265
|
-
|
|
315
|
+
...(current
|
|
316
|
+
? ['The whole capsule matches its declared digest, including metadata and the linked source-contract projection digests. The non-embedded Lookup evidence remains linked by digest and must be retained separately.']
|
|
317
|
+
: ['The embedded brief, graph, and optional analyst-record projections match their declared digests. Capsule metadata and the linked Lookup evidence are outside those projection digests and must not be treated as whole-file integrity verified.']),
|
|
266
318
|
'Digest verification detects changed content but does not authenticate the analyst, signer, collection source, or truth of retained observations and assertions.',
|
|
267
319
|
]),
|
|
268
320
|
});
|
|
269
321
|
}
|
|
322
|
+
if (schema === LOOKUP_EVIDENCE_SCHEMA) {
|
|
323
|
+
if (!SUPPORTED_LOOKUP_EVIDENCE_SCHEMA_VERSIONS.some((candidate) => candidate === version)) {
|
|
324
|
+
throw new UnsupportedOfflineArtifactError('This Lookup-evidence document version is not supported.');
|
|
325
|
+
}
|
|
326
|
+
if (inputBytes(raw) > LOOKUP_EVIDENCE_PORTABLE_MAX_BYTES) {
|
|
327
|
+
throw new TypeError('Lookup-evidence documents are limited to 5 MiB for browser-compatible verification.');
|
|
328
|
+
}
|
|
329
|
+
validateLookupEvidenceArtifactStructure(value);
|
|
330
|
+
return Object.freeze({
|
|
331
|
+
schema: OFFLINE_ARTIFACT_VERIFICATION_SCHEMA,
|
|
332
|
+
version: OFFLINE_ARTIFACT_VERIFICATION_VERSION,
|
|
333
|
+
artifact: Object.freeze({ kind: 'lookup_evidence', schema, version }),
|
|
334
|
+
state: 'structure_valid',
|
|
335
|
+
checks: Object.freeze({
|
|
336
|
+
structure: 'verified',
|
|
337
|
+
contentIntegrity: 'not_checked',
|
|
338
|
+
contentIntegrityScope: 'not_applicable',
|
|
339
|
+
authenticatedEncryption: 'not_applicable',
|
|
340
|
+
}),
|
|
341
|
+
summary: Object.freeze({
|
|
342
|
+
inputBytes: inputBytes(raw),
|
|
343
|
+
sectionCount: null,
|
|
344
|
+
recordCount: null,
|
|
345
|
+
ciphertextBytes: null,
|
|
346
|
+
}),
|
|
347
|
+
limitations: Object.freeze([
|
|
348
|
+
`Lookup-evidence schema ${version} matches its bounded browser-importable structural contract, but this export format has no embedded checksum or signature. Structural validity does not prove that its evidence is accurate, current, or unchanged since collection.`,
|
|
349
|
+
'Use an integrity-verified investigation manifest and an exact manifest-entry identity check when retained-file byte identity must be established.',
|
|
350
|
+
'The export may contain raw registry RDAP publication data, normalised WHOIS values, and bounded contact details; review it before sharing.',
|
|
351
|
+
]),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
270
354
|
if (schema === SAVED_LOOKUP_SCHEMA) {
|
|
271
355
|
if (version !== SAVED_LOOKUP_SCHEMA_VERSION) {
|
|
272
356
|
throw new UnsupportedOfflineArtifactError('This saved Lookup document version is not supported.');
|
|
@@ -277,10 +361,10 @@ export async function verifyOfflineArtifact(raw, options = {}) {
|
|
|
277
361
|
version: OFFLINE_ARTIFACT_VERIFICATION_VERSION,
|
|
278
362
|
artifact: Object.freeze({ kind: 'saved_lookup', schema, version }),
|
|
279
363
|
state: 'structure_valid',
|
|
280
|
-
valid: true,
|
|
281
364
|
checks: Object.freeze({
|
|
282
365
|
structure: 'verified',
|
|
283
366
|
contentIntegrity: 'not_checked',
|
|
367
|
+
contentIntegrityScope: 'not_applicable',
|
|
284
368
|
authenticatedEncryption: 'not_applicable',
|
|
285
369
|
}),
|
|
286
370
|
summary: Object.freeze({
|
|
@@ -296,6 +380,84 @@ export async function verifyOfflineArtifact(raw, options = {}) {
|
|
|
296
380
|
}
|
|
297
381
|
return verifySignedArtifact(raw, value, schema, version);
|
|
298
382
|
}
|
|
383
|
+
function rawContentDigest(raw) {
|
|
384
|
+
return `sha256:${createHash('sha256').update(raw, 'utf8').digest('hex')}`;
|
|
385
|
+
}
|
|
386
|
+
function artifactMetadata(value) {
|
|
387
|
+
const declaredVersion = value.version ?? value.schemaVersion;
|
|
388
|
+
return Object.freeze({
|
|
389
|
+
schema: typeof value.schema === 'string' ? value.schema : null,
|
|
390
|
+
version: Number.isSafeInteger(declaredVersion) && Number(declaredVersion) >= 1 && Number(declaredVersion) <= 1_000
|
|
391
|
+
? Number(declaredVersion)
|
|
392
|
+
: null,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
async function verifyManifestIdentity(artifactRaw, manifestRaw, entryId) {
|
|
396
|
+
const manifestReport = await verifyOfflineArtifactCore(manifestRaw);
|
|
397
|
+
if (manifestReport.artifact.schema !== INVESTIGATION_MANIFEST_SCHEMA
|
|
398
|
+
|| manifestReport.artifact.kind !== 'signed_review_artifact'
|
|
399
|
+
|| manifestReport.state !== 'verified'
|
|
400
|
+
|| manifestReport.checks.contentIntegrity !== 'verified'
|
|
401
|
+
|| manifestReport.checks.contentIntegrityScope !== 'whole_artifact') {
|
|
402
|
+
throw new TypeError('The selected manifest is not a fully integrity-verified investigation manifest.');
|
|
403
|
+
}
|
|
404
|
+
const manifest = parseJson(manifestRaw);
|
|
405
|
+
const entries = Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
|
|
406
|
+
const entry = entries.find((candidate) => record(candidate)?.id === entryId);
|
|
407
|
+
const item = record(entry);
|
|
408
|
+
if (!item)
|
|
409
|
+
throw new TypeError('The requested investigation manifest entry was not found.');
|
|
410
|
+
const artifactValue = parseJson(artifactRaw);
|
|
411
|
+
const metadata = artifactMetadata(artifactValue);
|
|
412
|
+
const manifestVersion = artifactVersion(manifest);
|
|
413
|
+
const canonicalization = manifestVersion === 1 ? SORTED_JSON_V1 : SORTED_JSON_V2;
|
|
414
|
+
const actualByteLength = inputBytes(artifactRaw);
|
|
415
|
+
const expectedByteLength = Number(item.byteLength);
|
|
416
|
+
const checks = Object.freeze({
|
|
417
|
+
manifestIntegrity: 'verified',
|
|
418
|
+
byteLength: actualByteLength === expectedByteLength ? 'verified' : 'mismatch',
|
|
419
|
+
rawContentDigest: rawContentDigest(artifactRaw) === item.contentDigestSha256 ? 'verified' : 'mismatch',
|
|
420
|
+
canonicalDigest: rawContentDigest(canonicalArtifactJsonFor(artifactValue, canonicalization)) === item.canonicalDigestSha256 ? 'verified' : 'mismatch',
|
|
421
|
+
schema: metadata.schema === item.schema ? 'verified' : 'mismatch',
|
|
422
|
+
version: metadata.version === item.version ? 'verified' : 'mismatch',
|
|
423
|
+
});
|
|
424
|
+
const exact = checks.byteLength === 'verified'
|
|
425
|
+
&& checks.rawContentDigest === 'verified'
|
|
426
|
+
&& checks.canonicalDigest === 'verified'
|
|
427
|
+
&& checks.schema === 'verified'
|
|
428
|
+
&& checks.version === 'verified';
|
|
429
|
+
const canonicalOnly = !exact
|
|
430
|
+
&& checks.canonicalDigest === 'verified'
|
|
431
|
+
&& checks.schema === 'verified'
|
|
432
|
+
&& checks.version === 'verified';
|
|
433
|
+
const state = exact
|
|
434
|
+
? 'identity_verified'
|
|
435
|
+
: canonicalOnly
|
|
436
|
+
? 'canonical_match_only'
|
|
437
|
+
: 'mismatch';
|
|
438
|
+
return Object.freeze({
|
|
439
|
+
state,
|
|
440
|
+
manifest: Object.freeze({ schema: INVESTIGATION_MANIFEST_SCHEMA, version: manifestVersion, entryId }),
|
|
441
|
+
checks,
|
|
442
|
+
expectedByteLength,
|
|
443
|
+
actualByteLength,
|
|
444
|
+
limitations: Object.freeze([
|
|
445
|
+
...(state === 'identity_verified'
|
|
446
|
+
? ['The selected artefact matches the manifest entry byte-for-byte and by canonical JSON, schema, and version.']
|
|
447
|
+
: state === 'canonical_match_only'
|
|
448
|
+
? ['Canonical JSON, schema, and version match, but the retained UTF-8 bytes or byte length differ from the manifest entry. This is not exact retained-file identity.']
|
|
449
|
+
: ['One or more manifest identity checks differ. This artefact is not the exact retained file identified by the selected manifest entry.']),
|
|
450
|
+
'Manifest identity does not establish that the retained observations were accurate, complete, authorised, or remain current.',
|
|
451
|
+
]),
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
export async function verifyOfflineArtifact(raw, options = {}) {
|
|
455
|
+
const report = await verifyOfflineArtifactCore(raw, options);
|
|
456
|
+
const manifestIdentity = options.manifest
|
|
457
|
+
? await verifyManifestIdentity(raw, options.manifest.raw, options.manifest.entryId)
|
|
458
|
+
: null;
|
|
459
|
+
return Object.freeze({ ...report, manifestIdentity });
|
|
460
|
+
}
|
|
299
461
|
export function formatOfflineArtifactVerification(report) {
|
|
300
462
|
const lines = [
|
|
301
463
|
'WHOISleuth offline artefact verification',
|
|
@@ -303,6 +465,7 @@ export function formatOfflineArtifactVerification(report) {
|
|
|
303
465
|
`State: ${report.state}`,
|
|
304
466
|
`Structure: ${report.checks.structure}`,
|
|
305
467
|
`Content integrity: ${report.checks.contentIntegrity}`,
|
|
468
|
+
`Content integrity scope: ${report.checks.contentIntegrityScope}`,
|
|
306
469
|
`Authenticated encryption: ${report.checks.authenticatedEncryption}`,
|
|
307
470
|
`Input bytes: ${report.summary.inputBytes}`,
|
|
308
471
|
];
|
|
@@ -312,6 +475,36 @@ export function formatOfflineArtifactVerification(report) {
|
|
|
312
475
|
lines.push(`Records: ${report.summary.recordCount}`);
|
|
313
476
|
if (report.summary.ciphertextBytes !== null)
|
|
314
477
|
lines.push(`Ciphertext bytes: ${report.summary.ciphertextBytes}`);
|
|
478
|
+
if (report.summary.readySectionCount !== undefined && report.summary.readySectionCount !== null) {
|
|
479
|
+
lines.push(`Import-ready sections: ${report.summary.readySectionCount}`);
|
|
480
|
+
}
|
|
481
|
+
if (report.summary.unsupportedSectionCount !== undefined && report.summary.unsupportedSectionCount !== null) {
|
|
482
|
+
lines.push(`Unsupported sections: ${report.summary.unsupportedSectionCount}`);
|
|
483
|
+
}
|
|
484
|
+
if (report.summary.blockedSectionCount !== undefined && report.summary.blockedSectionCount !== null) {
|
|
485
|
+
lines.push(`Blocked sections: ${report.summary.blockedSectionCount}`);
|
|
486
|
+
}
|
|
487
|
+
if (report.summary.skippedRecordCount !== undefined && report.summary.skippedRecordCount !== null) {
|
|
488
|
+
lines.push(`Skipped records: ${report.summary.skippedRecordCount}`);
|
|
489
|
+
}
|
|
490
|
+
if (report.summary.prunedRecordCount !== undefined && report.summary.prunedRecordCount !== null) {
|
|
491
|
+
lines.push(`Pruned records: ${report.summary.prunedRecordCount}`);
|
|
492
|
+
}
|
|
493
|
+
if (report.summary.fullyImportable !== undefined && report.summary.fullyImportable !== null) {
|
|
494
|
+
lines.push(`Importability: ${report.summary.fullyImportable ? 'complete' : 'partial'}`);
|
|
495
|
+
}
|
|
496
|
+
if (report.manifestIdentity) {
|
|
497
|
+
lines.push(`Manifest identity: ${report.manifestIdentity.state}`);
|
|
498
|
+
lines.push(`Manifest entry: ${report.manifestIdentity.manifest.entryId}`);
|
|
499
|
+
lines.push(`Manifest integrity: ${report.manifestIdentity.checks.manifestIntegrity}`);
|
|
500
|
+
lines.push(`Exact bytes: ${report.manifestIdentity.checks.rawContentDigest}`);
|
|
501
|
+
lines.push(`Byte length: ${report.manifestIdentity.checks.byteLength}`);
|
|
502
|
+
lines.push(`Canonical JSON: ${report.manifestIdentity.checks.canonicalDigest}`);
|
|
503
|
+
lines.push(`Schema identity: ${report.manifestIdentity.checks.schema}`);
|
|
504
|
+
lines.push(`Version identity: ${report.manifestIdentity.checks.version}`);
|
|
505
|
+
for (const limitation of report.manifestIdentity.limitations)
|
|
506
|
+
lines.push(`Manifest limitation: ${limitation}`);
|
|
507
|
+
}
|
|
315
508
|
for (const limitation of report.limitations)
|
|
316
509
|
lines.push(`Limitation: ${limitation}`);
|
|
317
510
|
return `${lines.join('\n')}\n`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MAX_BOUNDED_JSON_DEPTH, MAX_BOUNDED_JSON_KEYS, MAX_BOUNDED_JSON_VALUES, parseBoundedJsonObject, scanBoundedJson, } from "../lib/bounded-json.mjs";
|
package/cli/bulk-checkpoint.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { lstat } from 'node:fs/promises';
|
|
3
3
|
import { recordOrNull } from "../lib/bounded-contract-normalizers.mjs";
|
|
4
4
|
import { readBoundedRegularTextFile } from "../lib/bounded-file.mjs";
|
|
5
5
|
import { boundedCliInputError, CliUsageError } from "./errors.mjs";
|
|
@@ -143,8 +143,7 @@ async function readCheckpointFile(path) {
|
|
|
143
143
|
}
|
|
144
144
|
async function checkpointFileExists(path) {
|
|
145
145
|
try {
|
|
146
|
-
|
|
147
|
-
await handle.close();
|
|
146
|
+
await lstat(path);
|
|
148
147
|
return true;
|
|
149
148
|
}
|
|
150
149
|
catch (error) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { createReadStream } from 'node:fs';
|
|
2
1
|
import { classifyQuery } from "../lib/classify.mjs";
|
|
3
2
|
import { runUnifiedLookup } from "../lib/lookup.mjs";
|
|
4
3
|
import { createBulkCheckpointWriter } from "./bulk-checkpoint.mjs";
|
|
5
|
-
import { MAX_BULK_INPUT_BYTES, parseBulkQueries,
|
|
4
|
+
import { MAX_BULK_INPUT_BYTES, parseBulkQueries, runBulkLookups, } from "./bulk.mjs";
|
|
6
5
|
import { formatBulkCsv, formatBulkDomainList, formatBulkQueryList, selectBulkItems } from "./bulk-output.mjs";
|
|
7
6
|
import { boundedCliErrorMessage, CliUsageError } from "./errors.mjs";
|
|
8
7
|
import EXIT_CODES from "./exit-codes.mjs";
|
|
@@ -24,9 +23,7 @@ async function runBulkCommand(args, dependencies, context) {
|
|
|
24
23
|
try {
|
|
25
24
|
input = dependencies.readBulkInput
|
|
26
25
|
? await dependencies.readBulkInput(args.source)
|
|
27
|
-
: await
|
|
28
|
-
? createReadStream(args.source, { highWaterMark: 64 * 1024 })
|
|
29
|
-
: dependencies.stdin || process.stdin, MAX_BULK_INPUT_BYTES, dependencies.signal);
|
|
26
|
+
: await context.readInput(args.source, MAX_BULK_INPUT_BYTES, 'Bulk input');
|
|
30
27
|
}
|
|
31
28
|
catch (error) {
|
|
32
29
|
if (error instanceof CliUsageError)
|