@slicedearth/whoisleuth-cli 2.4.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +2 -2
- package/cli/argument-values.mjs +55 -0
- package/cli/arguments.mjs +24 -470
- package/cli/artifact-validation/case-response.mjs +21 -6
- package/cli/artifact-validation/lookup-evidence.mjs +3 -3
- package/cli/artifact-validation/signed-review.mjs +59 -13
- package/cli/artifact-verify.mjs +2 -0
- package/cli/assurance-arguments.mjs +56 -0
- package/cli/assurance-command-definitions.mjs +179 -0
- package/cli/assurance-command-runner.mjs +1 -1
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +1 -1
- package/cli/bulk-command-runner.mjs +2 -2
- package/cli/bulk.mjs +2 -3
- package/cli/case-command.mjs +20 -3
- package/cli/case-pack.mjs +12 -2
- package/cli/ci-report.mjs +6 -3
- package/cli/collection-arguments.mjs +123 -0
- package/cli/collection-command-definitions.mjs +330 -0
- package/cli/collection-preflight.mjs +8 -1
- package/cli/command-argument-grammar.mjs +8 -210
- package/cli/command-definition.mjs +329 -0
- package/cli/command-reference.mjs +55 -1388
- package/cli/command-seeds.mjs +18 -0
- package/cli/compare.mjs +1 -1
- package/cli/config-profile.mjs +1 -1
- package/cli/ct-event-intake.mjs +4 -4
- package/cli/discover.mjs +2 -2
- package/cli/discovery-observation-snapshot.mjs +2 -2
- package/cli/discovery-scan-command-runner.mjs +3 -3
- package/cli/discovery-scan.mjs +1 -1
- package/cli/discovery-snapshot.mjs +2 -2
- package/cli/discovery-workflow.mjs +4 -4
- package/cli/domain-control-monitor.mjs +6 -6
- package/cli/domain-control-observations.mjs +2 -1
- package/cli/errors.mjs +5 -6
- package/cli/evidence-arguments.mjs +46 -0
- package/cli/evidence-command-definitions.mjs +69 -0
- package/cli/evidence-command-runner.mjs +2 -3
- package/cli/evidence-signing.mjs +3 -2
- package/cli/external-observation-mapping.mjs +2 -2
- package/cli/formatters/terminal-command-formats.mjs +6 -0
- package/cli/formatters/terminal-lookup.mjs +6 -0
- package/cli/history-arguments.mjs +39 -0
- package/cli/history-command-definitions.mjs +88 -0
- package/cli/indicator-set-command.mjs +33 -0
- package/cli/inline-command-families.mjs +6 -47
- package/cli/inline-command-runner.mjs +4 -31
- package/cli/input.mjs +2 -2
- package/cli/intake-command.mjs +107 -0
- package/cli/interchange-report.mjs +4 -2
- package/cli/investigation-artifacts.mjs +1 -1
- package/cli/investigation-plan.mjs +32 -154
- package/cli/investigation-recipes.mjs +177 -0
- package/cli/investigation-run.mjs +78 -12
- package/cli/local-mmdb-review.mjs +197 -17
- package/cli/lookalike-calibration.mjs +1 -1
- package/cli/lookup-brief.mjs +2 -2
- package/cli/lookup-command-runner.mjs +18 -35
- package/cli/lookup-diff.mjs +10 -9
- package/cli/lookup-plan.mjs +10 -14
- package/cli/lookup-reconcile.mjs +6 -5
- package/cli/lookup-timeline.mjs +7 -6
- package/cli/mail-header-review.mjs +24 -247
- package/cli/mail-review.mjs +9 -7
- package/cli/network-arguments.mjs +51 -0
- package/cli/network-command-definitions.mjs +130 -0
- package/cli/network-command-runner.mjs +43 -31
- package/cli/offline-evidence-review.mjs +39 -6
- package/cli/posture.mjs +1 -1
- package/cli/registry-fixture-scaffold.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +8 -7
- package/cli/review-arguments.mjs +131 -0
- package/cli/review-command-definitions.mjs +273 -0
- package/cli/review-command-runner.mjs +68 -158
- package/cli/risk-calibration.mjs +8 -8
- package/cli/runner.mjs +20 -32
- package/cli/saved-lookup.mjs +2 -2
- package/cli/selected-input-worker.mjs +63 -0
- package/cli/sharing-review.mjs +5 -64
- package/cli/source-reliability.mjs +2 -2
- package/cli/support-arguments.mjs +49 -0
- package/cli/support-command-definitions.mjs +209 -0
- package/cli/support-command-runner.mjs +2 -2
- package/cli/workflow-arguments.mjs +86 -0
- package/cli/workflow-command-definitions.mjs +78 -0
- package/cli/workflow-command-runner.mjs +8 -1
- package/docs/cli-reference.md +27 -0
- package/docs/cli.md +223 -2
- package/docs/contextual-reviews.md +155 -0
- package/lib/analyst-taxonomy.mjs +2 -40
- package/lib/application-version.mjs +1 -1
- package/lib/availability.mjs +13 -789
- package/lib/bounded-contract-normalizers.mjs +4 -0
- package/lib/bounded-json.mjs +2 -330
- package/lib/browser-library-profile.mjs +62 -84
- package/lib/candidate-provenance-bounds.mjs +2 -0
- package/lib/classify.mjs +1 -1
- package/lib/ct-query.mjs +2 -27
- package/lib/ct-response-bounds.mjs +2 -9
- package/lib/ct-search.mjs +83 -47
- package/lib/dmarc-discovery.mjs +62 -0
- package/lib/dns-convergence-review.mjs +21 -8
- package/lib/dns-delegation-health.mjs +12 -8
- package/lib/dns-inheritance-review.mjs +53 -63
- package/lib/dns-intelligence.mjs +9 -7
- package/lib/dns-query-coverage.mjs +40 -0
- package/lib/domain-assurance.mjs +1 -1
- package/lib/domain-change-packet.mjs +12 -13
- package/lib/domain-change-review.mjs +32 -14
- package/lib/domain-control-flight-recorder.mjs +103 -27
- package/lib/domain-control-manifest.mjs +25 -17
- package/lib/domain-posture-analysis.mjs +22 -5
- package/lib/domain-posture.mjs +16 -15
- package/lib/domain-registration.mjs +392 -0
- package/lib/evidence-export-privacy.mjs +22 -15
- package/lib/evidence-export.mjs +3 -3
- package/lib/evidence-report.mjs +4 -1
- package/lib/external-intelligence-risk.mjs +2 -124
- package/lib/generated/cisa-kev-catalog.mjs +41 -3
- package/lib/generated/registrar-standing-catalogue.mjs +18 -10
- package/lib/generated/retire-browser-catalog.mjs +332 -19
- package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
- package/lib/html-signals.mjs +7 -2
- package/lib/http-evidence-bounds.mjs +2 -8
- package/lib/http-intelligence.mjs +13 -1
- package/lib/idn-confusable-policy.mjs +6 -4
- package/lib/idn-confusables.mjs +1 -1
- package/lib/interchange-fidelity-registry.mjs +10 -0
- package/lib/lookup-contract-primitives.mjs +1 -1
- package/lib/lookup-diagnostics.mjs +1 -23
- package/lib/lookup-network-evidence-bounds.mjs +1 -10
- package/lib/lookup-progress.mjs +50 -1
- package/lib/lookup-response-contract.mjs +69 -40
- package/lib/lookup-response.mjs +5 -15
- package/lib/lookup-source-progress.mjs +8 -22
- package/lib/lookup.mjs +1 -1
- package/lib/opportunity-scoring.mjs +2 -107
- package/lib/outbound-identity.mjs +1 -1
- package/lib/page-fingerprints.mjs +18 -1
- package/lib/perceptual-hash-comparison.mjs +2 -34
- package/lib/perceptual-hash.mjs +36 -10
- package/lib/portable-generator.mjs +2 -29
- package/lib/project-metadata.mjs +2 -8
- package/lib/rdap-client.mjs +11 -7
- package/lib/registrable-domain.mjs +2 -56
- package/lib/registrar-standing-contract.mjs +1 -1
- package/lib/registrar-standing.mjs +9 -13
- package/lib/registry-capabilities.mjs +2 -2
- package/lib/registry-capability-catalogue.mjs +27 -19
- package/lib/risk-calibration-summary.mjs +2 -352
- package/lib/risk-scoring.mjs +2 -345
- package/lib/scoring-evidence-quality.mjs +2 -107
- package/lib/semantic-version.mjs +2 -54
- package/lib/service-binding-dns.mjs +27 -0
- package/lib/smtp-transport-review.mjs +48 -19
- package/lib/sslbl-certificates.generated.mjs +141 -139
- package/lib/static-html-analysis.mjs +1 -1
- package/lib/structured-data-identity.mjs +1 -1
- package/lib/threat-intelligence-manifests.mjs +1 -1
- package/lib/threat-intelligence-runtime.mjs +1 -1
- package/lib/threat-intelligence-targets.mjs +1 -1
- package/lib/threat-intelligence-types.mjs +2 -75
- package/lib/typosquat-generator.mjs +11 -2
- package/lib/website-enrichment.mjs +388 -0
- package/lib/whois-authority.mjs +43 -5
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-parser.mjs +5 -3
- package/package.json +13 -14
- package/packages/analysis/analyst-taxonomy.mjs +40 -0
- package/packages/analysis/bounded-json.mjs +330 -0
- package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
- package/packages/analysis/cli-argument-grammar.mjs +216 -0
- package/packages/analysis/ct-query.mjs +27 -0
- package/packages/analysis/ct-response-bounds.mjs +9 -0
- package/packages/analysis/external-intelligence-risk.mjs +124 -0
- package/packages/analysis/http-evidence-bounds.mjs +8 -0
- package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
- package/packages/analysis/opportunity-scoring.mjs +107 -0
- package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
- package/packages/analysis/portable-generator.mjs +29 -0
- package/packages/analysis/project-metadata.mjs +8 -0
- package/packages/analysis/registrable-domain.mjs +56 -0
- package/packages/analysis/risk-calibration-summary.mjs +352 -0
- package/packages/analysis/risk-scoring.mjs +345 -0
- package/packages/analysis/scoring-evidence-quality.mjs +107 -0
- package/packages/analysis/semantic-version.mjs +54 -0
- package/packages/analysis/threat-intelligence-types.mjs +75 -0
- package/packages/cases/case-evidence-links.mjs +135 -0
- package/packages/cases/case-evidence-model.mjs +78 -12
- package/packages/cases/case-evidence-quality.mjs +5 -0
- package/packages/cases/case-export-input.mjs +7 -5
- package/packages/cases/case-incident-context.mjs +52 -0
- package/packages/cases/case-markdown.mjs +11 -0
- package/packages/cases/case-migration-model.mjs +40 -24
- package/packages/cases/case-recheck-model.mjs +90 -32
- package/packages/cases/case-record-contracts.mjs +1 -1
- package/packages/cases/case-record-core.mjs +1 -1
- package/packages/cases/case-record-operations.mjs +40 -112
- package/packages/cases/case-record-projection.mjs +8 -1
- package/packages/cases/case-record-version-input.mjs +19 -0
- package/packages/cases/case-report.mjs +63 -27
- package/packages/cases/case-requested-evidence.mjs +146 -0
- package/packages/cases/case-response-actions.mjs +103 -1
- package/packages/cases/case-response-model.mjs +9 -1
- package/packages/cases/case-response-outcomes.mjs +29 -8
- package/packages/cases/case-response-packet-render.mjs +13 -9
- package/packages/cases/case-response-packet.mjs +25 -11
- package/packages/cases/case-response-records.mjs +0 -1
- package/packages/cases/case-response-review-inputs.mjs +14 -3
- package/packages/cases/case-storage-model.mjs +1 -1
- package/packages/cases/case-workflow-metadata.mjs +225 -0
- package/packages/cases/case-workflow-migration.mjs +43 -0
- package/packages/cases/http-summary.mjs +6 -1
- package/packages/comparison/favicon-similarity.mjs +1 -1
- package/packages/comparison/page-similarity.mjs +1 -1
- package/packages/comparison/relationship-evidence.mjs +21 -9
- package/packages/comparison/relationship-provenance.mjs +7 -1
- package/packages/contracts/analyst-interchange.mjs +28 -10
- package/packages/contracts/case-portability.mjs +118 -25
- package/packages/contracts/cli-command-semantics.mjs +2 -0
- package/packages/contracts/cli-grammar.mjs +6 -0
- package/packages/contracts/cli-lookup-brief.mjs +2 -0
- package/packages/contracts/context-review.mjs +24 -0
- package/packages/contracts/document-intake.mjs +11 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +30 -14
- package/packages/contracts/domain-control-manifest.mjs +1 -1
- package/packages/contracts/domain-control-monitor.mjs +13 -2
- package/packages/contracts/domain-control-review.mjs +36 -15
- package/packages/contracts/har-review.mjs +2 -0
- package/packages/contracts/identity-events.mjs +4 -0
- package/packages/contracts/investigation-run.mjs +2 -0
- package/packages/contracts/ip-address.mjs +10 -0
- package/packages/contracts/mail-authentication.mjs +5 -0
- package/packages/contracts/message-intake.mjs +31 -0
- package/packages/contracts/offline-comparison.mjs +4 -1
- package/packages/contracts/passive-mail.mjs +10 -0
- package/packages/contracts/web-capture.mjs +2 -2
- package/packages/contracts/workspace-portability.mjs +63 -17
- package/packages/evidence/collection-quality.mjs +67 -0
- package/packages/evidence/dns-query-outcome.mjs +72 -0
- package/packages/evidence/email-recipient.mjs +18 -0
- package/packages/evidence/lookup-target.mjs +5 -1
- package/packages/evidence/observation.mjs +2 -0
- package/packages/evidence/png-pixels.mjs +117 -0
- package/packages/evidence/public-address-policy.mjs +83 -0
- package/packages/interchange/defensive-indicator-export.mjs +264 -0
- package/packages/interchange/external-findings-import.mjs +4 -12
- package/packages/interchange/managed-indicator-export.mjs +35 -0
- package/packages/interchange/managed-indicator-set.mjs +210 -0
- package/packages/interchange/misp-indicator-export.mjs +93 -0
- package/packages/interchange/sharing-policy.mjs +56 -0
- package/packages/interchange/stix-indicator-export.mjs +129 -0
- package/packages/interchange/web-capture-import.mjs +22 -15
- package/packages/investigation/acquisition-decision-packet.mjs +1 -1
- package/packages/investigation/bulk-mail-exposure.mjs +7 -13
- package/packages/investigation/capture-coverage.mjs +57 -0
- package/packages/investigation/connector-provenance-review.mjs +100 -0
- package/packages/investigation/context-review.mjs +35 -0
- package/packages/investigation/document-intake.mjs +81 -0
- package/packages/investigation/docx-intake.mjs +133 -0
- package/packages/investigation/domain-history-review.mjs +100 -0
- package/packages/investigation/domain-transition-review.mjs +47 -0
- package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
- package/packages/investigation/har-intake.mjs +68 -0
- package/packages/investigation/identity-event-intake.mjs +131 -0
- package/packages/investigation/identity-incident-review.mjs +39 -0
- package/packages/investigation/incident-sequence-review.mjs +58 -0
- package/packages/investigation/intake-report.mjs +19 -0
- package/packages/investigation/investigation-manifest.mjs +2 -2
- package/packages/investigation/link-intake.mjs +179 -0
- package/packages/investigation/lookup-asset-graph.mjs +1 -1
- package/packages/investigation/mail-authentication-review.mjs +222 -0
- package/packages/investigation/mail-header-identity.mjs +209 -0
- package/packages/investigation/message-intake.mjs +195 -0
- package/packages/investigation/page-behaviour.mjs +113 -0
- package/packages/investigation/pdf-intake.mjs +155 -0
- package/packages/investigation/platform-continuity-review.mjs +60 -0
- package/packages/investigation/qr-intake.mjs +56 -0
- package/packages/investigation/requested-action-hints.mjs +11 -0
- package/packages/investigation/selected-input-review.mjs +17 -0
- package/packages/investigation/storefront-review.mjs +70 -0
- package/packages/workspace/brand-profile-model.mjs +2 -2
- package/packages/workspace/bulk-review-model.mjs +28 -26
- package/packages/workspace/bulk-session-model.mjs +23 -13
- package/packages/workspace/campaign-model.mjs +1 -1
- package/packages/workspace/detection-rule-model.mjs +1 -1
- package/packages/workspace/hostile-input.mjs +1 -1
- package/packages/workspace/investigation-guide.mjs +2 -2
- package/packages/workspace/investigation-template-model.mjs +26 -2
- package/packages/workspace/page-baseline.mjs +2 -2
- package/packages/workspace/relationship-observation-model.mjs +17 -13
- package/packages/workspace/shortlist-model.mjs +14 -5
- package/packages/workspace/watchlist-history.mjs +28 -12
- package/packages/workspace/watchlist-store.mjs +17 -5
- package/packages/workspace/website-snapshot-model.mjs +28 -3
- package/packages/workspace/workspace-archive-crypto.mjs +1 -1
- package/packages/workspace/workspace-archive.mjs +5 -4
- package/third-party-notices.txt +549 -16
- package/lib/hostname.mjs +0 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readCaseEvidenceRequest, readCasePacketAmendment, assertEvidenceRequestEvent, assertEvidenceRequestHistory, assertPacketAmendmentSelection } from "../../packages/cases/case-requested-evidence.mjs";
|
|
2
3
|
import { canonicalArtifactJsonV2 } from "../../packages/evidence/artifact-integrity.mjs";
|
|
3
|
-
import { CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION, PUBLIC_CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION, CASE_RESPONSE_REVIEW_INPUTS_SCHEMA, CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION, MAX_ABUSIVE_URLS, MAX_RESPONSE_ARTEFACT_REFERENCES, MAX_RESPONSE_AUTHORISATION_CLOCK_SKEW_MS, MAX_RESPONSE_ACTION_HISTORY, MAX_RESPONSE_CONTACTS, MAX_RESPONSE_CONTRADICTIONS, MAX_RESPONSE_SELECTED_EVIDENCE, RESPONSE_AUTHORISATION_CONFIRMATION_IDS, RESPONSE_PACKET_PROFILES, RESPONSE_CONTACT_KINDS, RESPONSE_PACKET_PROFILE_IDS, RESPONSE_READINESS_ROW_IDS, RESPONSE_READINESS_STATES, RESPONSE_ROUTE_STALE_AFTER_DAYS, } from "../../packages/cases/case-response-packet.mjs";
|
|
4
|
+
import { CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION, PUBLIC_CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION, CASE_RESPONSE_REVIEW_INPUTS_SCHEMA, CASE_RESPONSE_REVIEW_INPUTS_VERSION, LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION, LATEST_PUBLIC_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION, MAX_ABUSIVE_URLS, MAX_RESPONSE_ARTEFACT_REFERENCES, MAX_RESPONSE_AUTHORISATION_CLOCK_SKEW_MS, MAX_RESPONSE_ACTION_HISTORY, MAX_RESPONSE_CONTACTS, MAX_RESPONSE_CONTRADICTIONS, MAX_RESPONSE_SELECTED_EVIDENCE, RESPONSE_AUTHORISATION_CONFIRMATION_IDS, RESPONSE_PACKET_PROFILES, RESPONSE_CONTACT_KINDS, RESPONSE_PACKET_PROFILE_IDS, RESPONSE_READINESS_ROW_IDS, RESPONSE_READINESS_STATES, RESPONSE_ROUTE_STALE_AFTER_DAYS, } from "../../packages/cases/case-response-packet.mjs";
|
|
4
5
|
import { CASE_ACTION_EVENT_SOURCE_CLASSES, CASE_ACTION_STATES, CASE_ACTION_TYPES, CASE_CLOSURE_REASONS, CASE_OBSERVED_EFFECT_SOURCE_CLASSES, CASE_OBSERVED_EFFECT_STATES, CASE_PROVIDER_OUTCOMES, isLegalCaseActionTransition, MAX_CASE_ACTION_EVENTS_PER_ACTION, MAX_CASE_ACTIONS, MAX_CASE_ASSERTIONS, MAX_CASE_DECISIONS, MAX_CASE_EVIDENCE_PINS, MAX_RESPONSE_LABEL_LENGTH, MAX_RESPONSE_LIMITATION_LENGTH, MAX_RESPONSE_LIMITATIONS, MAX_RESPONSE_RECIPIENT_LENGTH, } from "../../packages/cases/case-response-model.mjs";
|
|
5
6
|
import { HEX_DIGEST_RE, array, boolean, domain, enumeration, exact, exactOptional, fail, integer, iso, optionalText, sameValues, strings, text, } from "./structure-primitives.mjs";
|
|
6
7
|
const CASE_RESPONSE_PREFLIGHT_IDS_V6 = [
|
|
@@ -543,13 +544,14 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
543
544
|
const actionIdOrder = [];
|
|
544
545
|
const providerEvents = [];
|
|
545
546
|
for (const candidate of history) {
|
|
546
|
-
const action =
|
|
547
|
+
const action = exactOptional(candidate, [
|
|
547
548
|
'actionId', 'type', 'recipient', 'contactSource', 'state', 'reference',
|
|
548
549
|
'providerOutcome', 'outcomeDetail', 'originActionId', 'historyOmitted',
|
|
549
550
|
'historyLimitations', 'transitions', 'createdAt', 'updatedAt',
|
|
550
551
|
...(hasActionBinding ? ['routeObservedAt'] : []),
|
|
551
552
|
...(hasPlatformRoutes ? ['routeReviewAfter'] : []),
|
|
552
|
-
], 'Case-response escalation action');
|
|
553
|
+
], Number(root.schemaVersion) >= 11 ? ['amendment'] : [], 'Case-response escalation action');
|
|
554
|
+
readCasePacketAmendment(action.amendment);
|
|
553
555
|
const actionId = text(action.actionId, 'Case-response action id', 64);
|
|
554
556
|
if (actionIds.has(actionId))
|
|
555
557
|
fail('Case-response action identity');
|
|
@@ -571,6 +573,7 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
571
573
|
integer(action.historyOmitted, 'Case-response action omitted history', 0, 1_000_000);
|
|
572
574
|
strings(action.historyLimitations, 'Case-response action history limitations', 8, 240);
|
|
573
575
|
const transitions = array(action.transitions, 'Case-response action transitions', MAX_CASE_ACTION_EVENTS_PER_ACTION, 1);
|
|
576
|
+
const requestEvents = [];
|
|
574
577
|
const eventIds = new Set();
|
|
575
578
|
let projectedState = null;
|
|
576
579
|
let latestReference = null;
|
|
@@ -580,8 +583,11 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
580
583
|
let previousTime = Number.NEGATIVE_INFINITY;
|
|
581
584
|
let previousEventId = '';
|
|
582
585
|
for (const eventCandidate of transitions) {
|
|
583
|
-
const event =
|
|
586
|
+
const event = exactOptional(eventCandidate, ['id', 'previousState', 'nextState', 'occurredAt', 'sourceClass', 'provenance', 'reference', 'evidencePinId', 'limitations', 'providerOutcome', 'outcomeDetail', 'originActionId', 'applied'], Number(root.schemaVersion) >= 11 ? ['evidenceRequest'] : [], 'Case-response action transition');
|
|
587
|
+
const evidenceRequest = readCaseEvidenceRequest(event.evidenceRequest);
|
|
588
|
+
assertEvidenceRequestEvent(evidenceRequest, event);
|
|
584
589
|
const eventId = text(event.id, 'Case-response action event id', 64);
|
|
590
|
+
requestEvents.push({ id: eventId, ...(evidenceRequest ? { evidenceRequest } : {}) });
|
|
585
591
|
if (eventIds.has(eventId))
|
|
586
592
|
fail('Case-response action event identity');
|
|
587
593
|
eventIds.add(eventId);
|
|
@@ -650,6 +656,7 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
650
656
|
}
|
|
651
657
|
}
|
|
652
658
|
}
|
|
659
|
+
assertEvidenceRequestHistory(requestEvents, Number(action.historyOmitted) > 0);
|
|
653
660
|
if ((projectedState ?? 'drafting') !== action.state
|
|
654
661
|
|| latestReference !== action.reference
|
|
655
662
|
|| latestProviderOutcome !== action.providerOutcome
|
|
@@ -667,6 +674,11 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
667
674
|
if (recipientRoute && !actionIds.has(recipientRoute.actionId))
|
|
668
675
|
fail('Case-response recipient action');
|
|
669
676
|
}
|
|
677
|
+
if (version >= 11)
|
|
678
|
+
assertPacketAmendmentSelection(history.map(candidate => {
|
|
679
|
+
const action = candidate;
|
|
680
|
+
return { id: action.actionId, history: action.transitions, originActionId: action.originActionId, amendment: action.amendment };
|
|
681
|
+
}), selectedActionId, evidenceIds);
|
|
670
682
|
const lifecycle = exact(root.responseLifecycle, ['providerOutcomeState', 'latestProviderOutcome', 'observedChangeState', 'latestObservedEffect', 'latestObservedChangeAt', 'closure', 'limitations'], 'Case-response lifecycle');
|
|
671
683
|
const providerOutcomeState = enumeration(lifecycle.providerOutcomeState, ['available', 'missing', 'ambiguous'], 'Case-response lifecycle provider state');
|
|
672
684
|
let actualLatestProvider = null;
|
|
@@ -731,8 +743,9 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
731
743
|
contract: CASE_RESPONSE_REVIEW_INPUTS_SCHEMA,
|
|
732
744
|
version: version === CASE_RESPONSE_PACKET_VERSION
|
|
733
745
|
? CASE_RESPONSE_REVIEW_INPUTS_VERSION
|
|
734
|
-
:
|
|
735
|
-
:
|
|
746
|
+
: version === LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION ? LATEST_PUBLIC_CASE_RESPONSE_REVIEW_INPUTS_VERSION
|
|
747
|
+
: hasPlatformRoutes ? PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION
|
|
748
|
+
: hasActionBinding ? PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION : PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION,
|
|
736
749
|
profile: {
|
|
737
750
|
id: profile.id,
|
|
738
751
|
label: profile.label,
|
|
@@ -781,6 +794,8 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
781
794
|
fail('Case-response integrity');
|
|
782
795
|
}
|
|
783
796
|
export function validateCaseResponsePacket(value) {
|
|
797
|
+
if (value.schemaVersion === LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION)
|
|
798
|
+
return validateVersionedCaseResponsePacket(value, LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION);
|
|
784
799
|
if (value.schemaVersion === PUBLIC_CASE_RESPONSE_PACKET_VERSION)
|
|
785
800
|
return validateCaseResponsePacketV6(value);
|
|
786
801
|
if (value.schemaVersion === PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION) {
|
|
@@ -4,7 +4,7 @@ import { validLookupObservationScope } from "../../packages/evidence/lookup-targ
|
|
|
4
4
|
import { registrarStandingObservedBy, resolveRegistrarIanaId, validRegistrarStanding, } from "../../lib/registrar-standing-contract.mjs";
|
|
5
5
|
import { compareRegistrySources } from "../../lib/registry-comparison.mjs";
|
|
6
6
|
import { buildRegistryInsights } from "../../lib/registry-insights.mjs";
|
|
7
|
-
import { WHOISLEUTH_SOURCE_REPOSITORY_URL } from "../../
|
|
7
|
+
import { WHOISLEUTH_SOURCE_REPOSITORY_URL } from "../../packages/analysis/project-metadata.mjs";
|
|
8
8
|
import { validHttpDeliveryMetadata, validPagePublicationMetadata, } from "../../lib/homepage-metadata-contract.mjs";
|
|
9
9
|
import { HEX_DIGEST_RE, SEMANTIC_VERSION_RE, absoluteUrl, array, boolean, domain, enumeration, exact, exactOptional, fail, integer, iso, nullableRecord, optionalText, record, strings, text, } from "./structure-primitives.mjs";
|
|
10
10
|
const LOOKUP_RDAP_ATTEMPT_OUTCOMES = [
|
|
@@ -675,7 +675,7 @@ export function validateLookupEvidenceArtifactStructure(value) {
|
|
|
675
675
|
}
|
|
676
676
|
validateLookupEvidenceHomepageMetadata(availability, version);
|
|
677
677
|
if (!isDeepStrictEqual(analysis.availability, currentAvailability
|
|
678
|
-
? projectLookupEvidenceAvailability(analysis.availability)
|
|
678
|
+
? projectLookupEvidenceAvailability(analysis.availability, { legacyUris: version < HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION })
|
|
679
679
|
: projectLookupEvidenceAvailabilityPublic(analysis.availability)))
|
|
680
680
|
fail('Lookup evidence availability analysis');
|
|
681
681
|
}
|
|
@@ -715,7 +715,7 @@ export function validateLookupEvidenceArtifactStructure(value) {
|
|
|
715
715
|
if (!isDeepStrictEqual(registryInsights, expectedInsights))
|
|
716
716
|
fail('Lookup evidence registry insight derivation');
|
|
717
717
|
try {
|
|
718
|
-
assertLookupEvidencePrivacySafeTree(root);
|
|
718
|
+
assertLookupEvidencePrivacySafeTree(root, { legacyUris: version < HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION });
|
|
719
719
|
}
|
|
720
720
|
catch {
|
|
721
721
|
fail('Lookup evidence privacy boundary');
|
|
@@ -3,6 +3,8 @@ import { BULK_DOMAIN_COMPARISON_EXPORT_VERSION, BULK_DOMAIN_COMPARISON_SCHEMA, }
|
|
|
3
3
|
import { BULK_MAIL_EXPOSURE_EXPORT_VERSION, BULK_MAIL_EXPOSURE_SCHEMA, MAX_BULK_MAIL_EXPOSURE_ROWS, } from "../../packages/investigation/bulk-mail-exposure.mjs";
|
|
4
4
|
import { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION, SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS, } from "../../packages/contracts/investigation-portability.mjs";
|
|
5
5
|
import { MAX_BULK_SESSION_ROWS, MAX_BULK_SESSION_SOURCES } from "../../packages/contracts/workspace-portability.mjs";
|
|
6
|
+
import { MANAGED_INDICATOR_SET_SCHEMA } from "../../packages/contracts/analyst-interchange.mjs";
|
|
7
|
+
import { validateManagedIndicatorSet } from "../../packages/interchange/managed-indicator-set.mjs";
|
|
6
8
|
import { BULK_SORT_KEYS } from "../../packages/workspace/bulk-sort.mjs";
|
|
7
9
|
import { normalizeExplicitIsoTimestamp } from "../../packages/evidence/observation.mjs";
|
|
8
10
|
import { LOOKUP_CLAIM_PASSPORT_SCHEMA, LOOKUP_CLAIM_PASSPORT_TARGET_TYPES, LOOKUP_CLAIM_PASSPORT_VERSION, MAX_LOOKUP_CLAIM_PASSPORT_LIMITATIONS, MAX_LOOKUP_CLAIM_PASSPORT_REQUIREMENTS, } from "../../packages/investigation/lookup-claim-passport.mjs";
|
|
@@ -334,10 +336,11 @@ function validateBulkReviewManifest(value) {
|
|
|
334
336
|
strings(root.limitations, 'Bulk review manifest limitations', 8, 600);
|
|
335
337
|
validateIntegrity(root.integrity, 'Bulk review manifest integrity', root.version, version);
|
|
336
338
|
}
|
|
337
|
-
function validateSourceObservations(value, label) {
|
|
339
|
+
function validateSourceObservations(value, label, queryCoverage) {
|
|
338
340
|
const names = new Set();
|
|
341
|
+
let queryCount = 0;
|
|
339
342
|
return array(value, label, 16).map((candidate) => {
|
|
340
|
-
const source = exact(candidate, ['label', 'source', 'state', 'observedAt'], label);
|
|
343
|
+
const source = exact(candidate, ['label', 'source', 'state', 'observedAt', ...(queryCoverage ? ['queries'] : [])], label);
|
|
341
344
|
const name = text(source.label, `${label} label`, 120).toLowerCase();
|
|
342
345
|
if (names.has(name))
|
|
343
346
|
fail(`${label} duplicate label`);
|
|
@@ -345,14 +348,37 @@ function validateSourceObservations(value, label) {
|
|
|
345
348
|
text(source.source, `${label} source`, 240);
|
|
346
349
|
enumeration(source.state, ['observed', 'partial', 'unavailable'], `${label} state`);
|
|
347
350
|
iso(source.observedAt, `${label} observedAt`);
|
|
351
|
+
if (queryCoverage) {
|
|
352
|
+
const keys = new Set();
|
|
353
|
+
const queries = array(source.queries, `${label} queries`, 500 - queryCount);
|
|
354
|
+
queryCount += queries.length;
|
|
355
|
+
for (const candidate of queries) {
|
|
356
|
+
const query = exact(candidate, ['owner', 'type', 'state'], `${label} query`);
|
|
357
|
+
text(query.owner, `${label} query owner`, 253);
|
|
358
|
+
enumeration(query.type, DNS_TYPES, `${label} query type`);
|
|
359
|
+
enumeration(query.state, ['observed', 'partial', 'unavailable'], `${label} query state`);
|
|
360
|
+
const key = `${query.owner}\u0000${query.type}`;
|
|
361
|
+
if (keys.has(key))
|
|
362
|
+
fail(`${label} duplicate query`);
|
|
363
|
+
keys.add(key);
|
|
364
|
+
if ((source.state === 'observed' && query.state !== 'observed')
|
|
365
|
+
|| (source.state === 'unavailable' && query.state !== 'unavailable'))
|
|
366
|
+
fail(`${label} query state`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
348
369
|
return source;
|
|
349
370
|
});
|
|
350
371
|
}
|
|
351
|
-
function validateReviewMatrix(value, label, sources) {
|
|
372
|
+
function validateReviewMatrix(value, label, sources, queryCoverage = false) {
|
|
373
|
+
const rowKeys = new Set();
|
|
352
374
|
for (const [rowIndex, candidate] of array(value, label, 500).entries()) {
|
|
353
375
|
const row = exact(candidate, ['owner', 'type', 'state', 'observations'], `${label} row ${rowIndex + 1}`);
|
|
354
376
|
text(row.owner, `${label} owner`, 253);
|
|
355
377
|
enumeration(row.type, DNS_TYPES, `${label} type`);
|
|
378
|
+
const key = `${row.owner}\u0000${row.type}`;
|
|
379
|
+
if (queryCoverage && rowKeys.has(key))
|
|
380
|
+
fail(`${label} duplicate row`);
|
|
381
|
+
rowKeys.add(key);
|
|
356
382
|
const observations = array(row.observations, `${label} observations`, 16);
|
|
357
383
|
if (sources && observations.length !== sources.length)
|
|
358
384
|
fail(`${label} source coverage`);
|
|
@@ -363,7 +389,7 @@ function validateReviewMatrix(value, label, sources) {
|
|
|
363
389
|
const observation = exact(observationCandidate, ['label', 'source', 'state', 'values', 'ttlRange', ...(sources ? ['observedAt'] : [])], `${label} observation`);
|
|
364
390
|
text(observation.label, `${label} observation label`, 120);
|
|
365
391
|
text(observation.source, `${label} observation source`, 240);
|
|
366
|
-
const state = enumeration(observation.state, ['observed', 'partial', 'unavailable'], `${label} observation state`);
|
|
392
|
+
const state = enumeration(observation.state, ['observed', 'partial', 'unavailable', ...(queryCoverage ? ['not_queried'] : [])], `${label} observation state`);
|
|
367
393
|
const values = strings(observation.values, `${label} observation values`, 500, 16_384);
|
|
368
394
|
if (sources) {
|
|
369
395
|
const name = String(observation.label).toLowerCase();
|
|
@@ -371,8 +397,16 @@ function validateReviewMatrix(value, label, sources) {
|
|
|
371
397
|
fail(`${label} duplicate observation`);
|
|
372
398
|
names.add(name);
|
|
373
399
|
const source = sources.find((item) => item.label === observation.label);
|
|
374
|
-
|
|
400
|
+
const provenance = queryCoverage ? ['label', 'source', 'observedAt'] : ['label', 'source', 'state', 'observedAt'];
|
|
401
|
+
if (!source || provenance.some((key) => observation[key] !== source[key]))
|
|
375
402
|
fail(`${label} source provenance`);
|
|
403
|
+
if (queryCoverage) {
|
|
404
|
+
const query = source.queries.find((item) => item.owner === row.owner && item.type === row.type);
|
|
405
|
+
if (state !== (query?.state ?? 'not_queried'))
|
|
406
|
+
fail(`${label} query provenance`);
|
|
407
|
+
if (['not_queried', 'unavailable'].includes(state) && (values.length || observation.ttlRange !== null))
|
|
408
|
+
fail(`${label} unavailable values`);
|
|
409
|
+
}
|
|
376
410
|
iso(observation.observedAt, `${label} observation time`);
|
|
377
411
|
}
|
|
378
412
|
if (state === 'observed') {
|
|
@@ -387,13 +421,19 @@ function validateReviewMatrix(value, label, sources) {
|
|
|
387
421
|
fail(`${label} TTL range`);
|
|
388
422
|
}
|
|
389
423
|
}
|
|
390
|
-
const expected = complete < 2 ? 'insufficient' : signatures.size === 1 ? 'aligned' : 'different';
|
|
424
|
+
const expected = complete < 2 || (queryCoverage && complete !== observations.length) ? 'insufficient' : signatures.size === 1 ? 'aligned' : 'different';
|
|
391
425
|
if (row.state !== expected)
|
|
392
426
|
fail(`${label} state`);
|
|
393
427
|
}
|
|
428
|
+
if (queryCoverage && sources) {
|
|
429
|
+
const expectedKeys = new Set(sources.flatMap((source) => source.queries.map((item) => `${item.owner}\u0000${item.type}`)));
|
|
430
|
+
if (rowKeys.size !== expectedKeys.size || [...rowKeys].some((key) => !expectedKeys.has(key)))
|
|
431
|
+
fail(`${label} query coverage`);
|
|
432
|
+
}
|
|
394
433
|
}
|
|
395
434
|
function validateDomainChangeReview(value, label, version) {
|
|
396
435
|
const retainedTimes = version >= 2;
|
|
436
|
+
const queryCoverage = version >= 3;
|
|
397
437
|
const review = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'state', 'authoritativeRecordMatrix', 'resolverDivergenceMatrix', 'dnssecAutomation', 'acmeDependencies', 'certificate', 'services', 'hsts', 'gate', 'limitations', ...(retainedTimes ? ['sourceObservations'] : [])], label);
|
|
398
438
|
if (review.schema !== DOMAIN_CHANGE_REVIEW_SCHEMA || review.version !== version)
|
|
399
439
|
fail(label);
|
|
@@ -401,8 +441,8 @@ function validateDomainChangeReview(value, label, version) {
|
|
|
401
441
|
domain(review.domain, `${label} domain`);
|
|
402
442
|
enumeration(review.state, ['ready', 'review'], `${label} state`);
|
|
403
443
|
const sources = retainedTimes ? exact(review.sourceObservations, ['authorities', 'resolvers'], `${label} source observations`) : null;
|
|
404
|
-
validateReviewMatrix(review.authoritativeRecordMatrix, `${label} authority matrix`, sources ? validateSourceObservations(sources.authorities, `${label} authority sources
|
|
405
|
-
validateReviewMatrix(review.resolverDivergenceMatrix, `${label} resolver matrix`, sources ? validateSourceObservations(sources.resolvers, `${label} resolver sources
|
|
444
|
+
validateReviewMatrix(review.authoritativeRecordMatrix, `${label} authority matrix`, sources ? validateSourceObservations(sources.authorities, `${label} authority sources`, queryCoverage) : undefined, queryCoverage);
|
|
445
|
+
validateReviewMatrix(review.resolverDivergenceMatrix, `${label} resolver matrix`, sources ? validateSourceObservations(sources.resolvers, `${label} resolver sources`, queryCoverage) : undefined, queryCoverage);
|
|
406
446
|
const automation = exact(review.dnssecAutomation, ['state', 'cdsObserved', 'cdnskeyObserved', 'csyncObserved', 'conflictingTypes', 'detail'], `${label} DNSSEC automation`);
|
|
407
447
|
enumeration(automation.state, ['not_observed', 'conflict', 'partial', 'review_ready'], `${label} DNSSEC automation`);
|
|
408
448
|
boolean(automation.cdsObserved, `${label} CDS observed`);
|
|
@@ -514,7 +554,7 @@ function validatePlannedAssurance(value, label) {
|
|
|
514
554
|
strings(assurance.limitations, `${label} limitations`, 8, 600);
|
|
515
555
|
return assurance;
|
|
516
556
|
}
|
|
517
|
-
function expectedDomainChangeSummary(before, after) {
|
|
557
|
+
function expectedDomainChangeSummary(before, after, queryCoverage) {
|
|
518
558
|
const rows = (review) => new Map(review.authoritativeRecordMatrix.map((row) => [`${row.owner}\u0000${row.type}`, row]));
|
|
519
559
|
const beforeRows = rows(before);
|
|
520
560
|
const afterRows = rows(after);
|
|
@@ -526,7 +566,7 @@ function expectedDomainChangeSummary(before, after) {
|
|
|
526
566
|
};
|
|
527
567
|
const rowEvidence = (review, row) => {
|
|
528
568
|
if (!row)
|
|
529
|
-
return { state: collectionComplete(review) ? 'complete' : 'unavailable', values: [] };
|
|
569
|
+
return { state: !queryCoverage && collectionComplete(review) ? 'complete' : 'unavailable', values: [] };
|
|
530
570
|
const observations = row.observations;
|
|
531
571
|
if (observations.some((item) => item.state !== 'observed'))
|
|
532
572
|
return { state: 'partial', values: [] };
|
|
@@ -537,18 +577,21 @@ function expectedDomainChangeSummary(before, after) {
|
|
|
537
577
|
return { state: 'complete', values: [...new Set(observations.flatMap((item) => item.values))].sort() };
|
|
538
578
|
};
|
|
539
579
|
const changed = [];
|
|
580
|
+
const reasons = queryCoverage && keys.length > 500 ? ['The comparison exceeds 500 owner and record-type pairs.'] : [];
|
|
540
581
|
for (const key of keys.slice(0, 500)) {
|
|
541
582
|
const left = rowEvidence(before, beforeRows.get(key));
|
|
542
583
|
const right = rowEvidence(after, afterRows.get(key));
|
|
543
584
|
const [owner, type] = key.split('\u0000');
|
|
544
585
|
if (left.state !== 'complete' || right.state !== 'complete') {
|
|
586
|
+
if (queryCoverage)
|
|
587
|
+
reasons.push(`Comparison evidence is incomplete for ${owner} ${type}.`);
|
|
545
588
|
continue;
|
|
546
589
|
}
|
|
547
590
|
else if (!sameValues(left.values, right.values)) {
|
|
548
591
|
changed.push({ owner: owner ?? '', type: type ?? '', beforeValues: left.values, afterValues: right.values });
|
|
549
592
|
}
|
|
550
593
|
}
|
|
551
|
-
return changed;
|
|
594
|
+
return { changed, reasons };
|
|
552
595
|
}
|
|
553
596
|
function validateDomainChangePacket(value) {
|
|
554
597
|
const root = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'reference', 'state', 'gate', 'summary', 'evidence', 'limitations', 'integrity'], 'Domain change packet');
|
|
@@ -590,13 +633,14 @@ function validateDomainChangePacket(value) {
|
|
|
590
633
|
const expectedReasons = [
|
|
591
634
|
...pre.gate.reasons,
|
|
592
635
|
].map((reason) => `Pre-change evidence: ${reason}`);
|
|
593
|
-
|
|
636
|
+
const comparison = expectedDomainChangeSummary(pre, post, reviewVersion >= 3);
|
|
637
|
+
expectedReasons.push(...post.gate.reasons.map((reason) => `Post-change evidence: ${reason}`), ...(assuranceResult.review.reasons.map((reason) => `Change plan: ${reason}`)), ...comparison.reasons);
|
|
594
638
|
const boundedReasons = expectedReasons.slice(0, 100);
|
|
595
639
|
if (!sameValues(gateReasons, boundedReasons)
|
|
596
640
|
|| gate.pass !== (boundedReasons.length === 0)
|
|
597
641
|
|| root.state !== (boundedReasons.length === 0 ? 'ready' : 'review'))
|
|
598
642
|
fail('Domain change packet gate');
|
|
599
|
-
const expectedChanges =
|
|
643
|
+
const expectedChanges = comparison.changed;
|
|
600
644
|
if (changed.length !== expectedChanges.length || changed.some((candidate, index) => {
|
|
601
645
|
const item = candidate;
|
|
602
646
|
const expected = expectedChanges[index];
|
|
@@ -613,6 +657,8 @@ export function validateSignedDigestArtifactStructure(schema, value) {
|
|
|
613
657
|
fail('Signed review artefact');
|
|
614
658
|
if (schema === ACQUISITION_DECISION_PACKET_SCHEMA)
|
|
615
659
|
validateAcquisition(value);
|
|
660
|
+
else if (schema === MANAGED_INDICATOR_SET_SCHEMA)
|
|
661
|
+
validateManagedIndicatorSet(value);
|
|
616
662
|
else if (schema === LOOKUP_CLAIM_PASSPORT_SCHEMA)
|
|
617
663
|
validateLookupClaimPassport(value);
|
|
618
664
|
else if (schema === BULK_DOMAIN_COMPARISON_SCHEMA)
|
package/cli/artifact-verify.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { MANAGED_INDICATOR_SET_SCHEMA, MANAGED_INDICATOR_SET_VERSION } from "../packages/contracts/analyst-interchange.mjs";
|
|
2
3
|
import { createHash } from 'node:crypto';
|
|
3
4
|
import { readEditableCaseExport } from "../packages/cases/case-export-input.mjs";
|
|
4
5
|
import { boundedJsonLimitsForBytes, parseBoundedJsonObject } from "./bounded-json.mjs";
|
|
@@ -36,6 +37,7 @@ function currentCanonicalizationRoutes(versions) {
|
|
|
36
37
|
})));
|
|
37
38
|
}
|
|
38
39
|
const SIGNED_ARTIFACT_ROUTES = Object.freeze({
|
|
40
|
+
[MANAGED_INDICATOR_SET_SCHEMA]: currentCanonicalizationRoutes([MANAGED_INDICATOR_SET_VERSION]),
|
|
39
41
|
[ACQUISITION_DECISION_PACKET_SCHEMA]: currentCanonicalizationRoutes(SUPPORTED_ACQUISITION_DECISION_PACKET_VERSIONS),
|
|
40
42
|
[LOOKUP_CLAIM_PASSPORT_SCHEMA]: currentCanonicalizationRoutes([LOOKUP_CLAIM_PASSPORT_VERSION]),
|
|
41
43
|
[BULK_DOMAIN_COMPARISON_SCHEMA]: currentCanonicalizationRoutes(SUPPORTED_BULK_DOMAIN_COMPARISON_EXPORT_VERSIONS),
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {} from "./command-argument-grammar.mjs";
|
|
2
|
+
import { CliUsageError } from "./errors.mjs";
|
|
3
|
+
import { MAX_INVESTIGATION_MANIFEST_ARTIFACTS } from "../packages/contracts/investigation-package-limits.mjs";
|
|
4
|
+
import { terminalOptions, jsonOutput, singleInputAction } from "./argument-values.mjs";
|
|
5
|
+
function parseManifestArguments(parsed) {
|
|
6
|
+
const sources = parsed.positionalValues('artefacts');
|
|
7
|
+
if (sources.some((source) => !source))
|
|
8
|
+
throw new CliUsageError(`manifest requires from 1 to ${MAX_INVESTIGATION_MANIFEST_ARTIFACTS} artefact files.`);
|
|
9
|
+
if (new Set(sources).size !== sources.length)
|
|
10
|
+
throw new CliUsageError('manifest artefact files must be different.');
|
|
11
|
+
const configurationDigestSha256 = parsed.optionValue('--configuration-digest');
|
|
12
|
+
if (configurationDigestSha256 !== null && !/^sha256:[a-f0-9]{64}$/u.test(configurationDigestSha256)) {
|
|
13
|
+
throw new CliUsageError('--configuration-digest requires a sha256: hexadecimal digest.');
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
action: 'manifest',
|
|
17
|
+
sources,
|
|
18
|
+
workflow: parsed.optionValue('--workflow'),
|
|
19
|
+
configurationDigestSha256,
|
|
20
|
+
...(parsed.optionValue('--passphrase-file')
|
|
21
|
+
? { passphraseSource: parsed.optionValue('--passphrase-file') }
|
|
22
|
+
: {}),
|
|
23
|
+
...(parsed.hasOption('--package') ? { package: true } : {}),
|
|
24
|
+
...(parsed.hasOption('--bagit') ? { bagit: true } : {}),
|
|
25
|
+
...(parsed.optionValue('--folder') ? { folder: parsed.optionValue('--folder') } : {}),
|
|
26
|
+
output: jsonOutput(parsed),
|
|
27
|
+
...terminalOptions(parsed),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function parseSharingReviewArguments(parsed) {
|
|
31
|
+
const purpose = parsed.optionValue('--purpose').replace(/\s+/gu, ' ').trim();
|
|
32
|
+
if (!purpose || purpose.length > 200)
|
|
33
|
+
throw new CliUsageError('--purpose is limited to 200 characters.');
|
|
34
|
+
return {
|
|
35
|
+
action: 'sharing-review',
|
|
36
|
+
source: parsed.positionalValue('source'),
|
|
37
|
+
output: jsonOutput(parsed),
|
|
38
|
+
marking: parsed.optionValue('--marking'),
|
|
39
|
+
recipientScope: parsed.optionValue('--recipient-scope'),
|
|
40
|
+
purpose,
|
|
41
|
+
humanReviewed: parsed.hasOption('--human-reviewed'),
|
|
42
|
+
personalDataReviewed: parsed.hasOption('--personal-data-reviewed'),
|
|
43
|
+
redactionsConfirmed: parsed.hasOption('--redactions-confirmed'),
|
|
44
|
+
...terminalOptions(parsed),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export const ASSURANCE_ARGUMENT_PARSERS = Object.freeze({
|
|
48
|
+
manifest: parseManifestArguments,
|
|
49
|
+
'map-observations': (parsed) => singleInputAction('map-observations', parsed),
|
|
50
|
+
'oam-export': (parsed) => singleInputAction('oam-export', parsed),
|
|
51
|
+
'ct-intake': (parsed) => singleInputAction('ct-intake', parsed),
|
|
52
|
+
'domain-control': (parsed) => singleInputAction('domain-control', parsed),
|
|
53
|
+
assurance: (parsed) => singleInputAction('assurance', parsed),
|
|
54
|
+
'change-packet': (parsed) => singleInputAction('change-packet', parsed),
|
|
55
|
+
'sharing-review': parseSharingReviewArguments,
|
|
56
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { MAX_INVESTIGATION_MANIFEST_ARTIFACTS, MAX_INVESTIGATION_MANIFEST_ARTIFACT_BYTES, MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES } from "../packages/investigation/investigation-manifest.mjs";
|
|
2
|
+
import { positional, OPTIONAL_FILE_POSITIONAL, constraint, EMPTY_CONSTRAINTS, commandSeed, flag, file } from "./command-definition.mjs";
|
|
3
|
+
export const ASSURANCE_COMMAND_DEFINITIONS = Object.freeze({
|
|
4
|
+
manifest: commandSeed({
|
|
5
|
+
reference: {
|
|
6
|
+
description: `Record an ordered, path-free manifest for up to ${MAX_INVESTIGATION_MANIFEST_ARTIFACTS} local files. Use --package --output evidence.zip for a ZIP, add --passphrase-file to encrypt the ordinary package, or use --folder ./evidence for a new unencrypted folder. Add --bagit to ZIP or folder output for BagIt 1.0 with SHA-512 checksums.`,
|
|
7
|
+
example: 'whoisleuth manifest lookup.json comparison.json --workflow "domain review" --json',
|
|
8
|
+
boundary: 'Ordinary output contains metadata only. ZIP and folder output include unchanged selected bytes and are private until reviewed for sharing. Folders must be new; existing destinations are never replaced and a failed write may leave explicit partial output. BagIt output is unencrypted; its checksums do not authenticate evidence. Filenames ending in .json are parsed as JSON; other files are opaque and never executed. Original paths are omitted. No network request is made.',
|
|
9
|
+
},
|
|
10
|
+
collection: { scope: `Reads 1 to ${MAX_INVESTIGATION_MANIFEST_ARTIFACTS} local files, at most ${MAX_INVESTIGATION_MANIFEST_ARTIFACT_BYTES / 1024 / 1024} MiB each and ${MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES.toLocaleString('en-AU')} bytes combined; retains no source paths.` },
|
|
11
|
+
summary: 'Build an evidence manifest offline',
|
|
12
|
+
options: ['--workflow', '--configuration-digest', '--package', '--bagit', '--passphrase-file', '--folder', '--json', '--quiet', '--no-color'],
|
|
13
|
+
optionOverrides: {
|
|
14
|
+
'--package': flag('Create a portable evidence ZIP containing the selected files.'),
|
|
15
|
+
'--folder': file('Create a new evidence folder containing the selected files.'),
|
|
16
|
+
'--bagit': flag('Create a BagIt 1.0 package with SHA-512 checksums.'),
|
|
17
|
+
},
|
|
18
|
+
positionals: Object.freeze([positional('artefacts', 'file', 1, MAX_INVESTIGATION_MANIFEST_ARTIFACTS)]),
|
|
19
|
+
constraints: Object.freeze([
|
|
20
|
+
constraint({ kind: 'required', options: ['--workflow'] }),
|
|
21
|
+
constraint({ kind: 'requires_any', option: '--bagit', requiredOptions: ['--package', '--folder'] }),
|
|
22
|
+
constraint({ kind: 'excludes_all', option: '--bagit', excludedOptions: ['--passphrase-file'] }),
|
|
23
|
+
constraint({ kind: 'requires_all', option: '--package', requiredOptions: ['--output'] }),
|
|
24
|
+
constraint({ kind: 'requires_all', option: '--passphrase-file', requiredOptions: ['--package'] }),
|
|
25
|
+
constraint({ kind: 'mutually_exclusive', options: ['--package', '--json'] }),
|
|
26
|
+
constraint({ kind: 'excludes_all', option: '--folder', excludedOptions: ['--package', '--output', '--force'] }),
|
|
27
|
+
]),
|
|
28
|
+
handlerOwner: 'assurance',
|
|
29
|
+
networkEffect: 'offline',
|
|
30
|
+
common: false,
|
|
31
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.investigation-manifest']),
|
|
32
|
+
primaryArtefacts: Object.freeze([]),
|
|
33
|
+
planSupport: false,
|
|
34
|
+
additionalOutputFormats: Object.freeze([]),
|
|
35
|
+
bootstrapProfile: 'allowed',
|
|
36
|
+
}),
|
|
37
|
+
"map-observations": commandSeed({
|
|
38
|
+
reference: {
|
|
39
|
+
description: 'Apply one bounded declarative field-mapping profile to local source observations.',
|
|
40
|
+
example: 'whoisleuth map-observations mapping.json --json',
|
|
41
|
+
boundary: 'Profiles select allowlisted dotted fields only. They execute no scripts, make no requests, and emit the browser-compatible external-findings contract.',
|
|
42
|
+
},
|
|
43
|
+
collection: { scope: 'Reads one mapping document capped at 4 MiB and executes no scripts or requests.' },
|
|
44
|
+
summary: 'Apply a declarative observation map offline',
|
|
45
|
+
options: ['--json', '--quiet', '--no-color'],
|
|
46
|
+
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
47
|
+
constraints: EMPTY_CONSTRAINTS,
|
|
48
|
+
handlerOwner: 'assurance',
|
|
49
|
+
networkEffect: 'offline',
|
|
50
|
+
common: false,
|
|
51
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.external-observation-mapping']),
|
|
52
|
+
primaryArtefacts: Object.freeze([]),
|
|
53
|
+
planSupport: false,
|
|
54
|
+
additionalOutputFormats: Object.freeze([]),
|
|
55
|
+
bootstrapProfile: 'allowed',
|
|
56
|
+
}),
|
|
57
|
+
"oam-export": commandSeed({
|
|
58
|
+
reference: {
|
|
59
|
+
description: 'Project browser-compatible external findings into a bounded Open Asset Model bridge document.',
|
|
60
|
+
example: 'whoisleuth oam-export external-findings.json --json',
|
|
61
|
+
boundary: 'The projection is offline, preserves source completeness without inventing confidence, and covers only bounded FQDN, IP address, certificate, and related edge vocabulary.',
|
|
62
|
+
},
|
|
63
|
+
collection: { scope: 'Reads one browser-compatible external-findings document and projects bounded graph records locally.' },
|
|
64
|
+
summary: 'Project external findings to Open Asset Model',
|
|
65
|
+
options: ['--json', '--quiet', '--no-color'],
|
|
66
|
+
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
67
|
+
constraints: EMPTY_CONSTRAINTS,
|
|
68
|
+
handlerOwner: 'assurance',
|
|
69
|
+
networkEffect: 'offline',
|
|
70
|
+
common: false,
|
|
71
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.open-asset-model-bridge']),
|
|
72
|
+
primaryArtefacts: Object.freeze([]),
|
|
73
|
+
planSupport: false,
|
|
74
|
+
additionalOutputFormats: Object.freeze([]),
|
|
75
|
+
bootstrapProfile: 'allowed',
|
|
76
|
+
}),
|
|
77
|
+
"ct-intake": commandSeed({
|
|
78
|
+
reference: {
|
|
79
|
+
description: 'Normalise source-qualified local certificate events into browser-compatible findings.',
|
|
80
|
+
example: 'whoisleuth ct-intake certificate-events.json --json',
|
|
81
|
+
boundary: 'The command is offline, caps output at 100 findings, and treats every event as a review lead rather than proof of serving or control.',
|
|
82
|
+
},
|
|
83
|
+
collection: { scope: 'Reads one source-qualified event batch capped at 4 MiB and makes no request.' },
|
|
84
|
+
summary: 'Normalise certificate observations offline',
|
|
85
|
+
options: ['--json', '--quiet', '--no-color'],
|
|
86
|
+
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
87
|
+
constraints: EMPTY_CONSTRAINTS,
|
|
88
|
+
handlerOwner: 'assurance',
|
|
89
|
+
networkEffect: 'offline',
|
|
90
|
+
common: false,
|
|
91
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.ct-event-batch', 'whoisleuth.external-findings']),
|
|
92
|
+
primaryArtefacts: Object.freeze([]),
|
|
93
|
+
planSupport: false,
|
|
94
|
+
additionalOutputFormats: Object.freeze([]),
|
|
95
|
+
bootstrapProfile: 'allowed',
|
|
96
|
+
}),
|
|
97
|
+
"domain-control": commandSeed({
|
|
98
|
+
reference: {
|
|
99
|
+
description: 'Build an integrity-protected desired-state manifest or compare one with supplied observations.',
|
|
100
|
+
example: 'whoisleuth domain-control domain-control-input.json --json',
|
|
101
|
+
boundary: 'The command is offline and changes no registrar, DNS, mail, or certificate configuration. Only complete, recent source observations can establish drift or expected absence.',
|
|
102
|
+
},
|
|
103
|
+
collection: { scope: 'Reads one bounded desired-state or review document and performs no collection or configuration change.' },
|
|
104
|
+
summary: 'Build or review a domain control manifest',
|
|
105
|
+
options: ['--json', '--quiet', '--no-color'],
|
|
106
|
+
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
107
|
+
constraints: EMPTY_CONSTRAINTS,
|
|
108
|
+
handlerOwner: 'assurance',
|
|
109
|
+
networkEffect: 'offline',
|
|
110
|
+
common: false,
|
|
111
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.cli.domain-control-review-input', 'whoisleuth.cli.domain-control-review']),
|
|
112
|
+
primaryArtefacts: Object.freeze([]),
|
|
113
|
+
planSupport: false,
|
|
114
|
+
additionalOutputFormats: Object.freeze([]),
|
|
115
|
+
bootstrapProfile: 'allowed',
|
|
116
|
+
}),
|
|
117
|
+
assurance: commandSeed({
|
|
118
|
+
reference: {
|
|
119
|
+
description: 'Review a versioned domain change, recovery-dependency, or retirement plan.',
|
|
120
|
+
example: 'whoisleuth assurance domain-assurance.json --json',
|
|
121
|
+
boundary: 'The command is offline and treats every provider label, readiness state, and evidence reference as analyst-authored input. It changes no configuration.',
|
|
122
|
+
},
|
|
123
|
+
collection: { scope: 'Reads one versioned plan capped at 2 MiB and makes no request or configuration change.' },
|
|
124
|
+
summary: 'Review domain change, recovery, or retirement plans',
|
|
125
|
+
options: ['--json', '--quiet', '--no-color'],
|
|
126
|
+
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
127
|
+
constraints: EMPTY_CONSTRAINTS,
|
|
128
|
+
handlerOwner: 'assurance',
|
|
129
|
+
networkEffect: 'offline',
|
|
130
|
+
common: false,
|
|
131
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.domain-assurance.input', 'whoisleuth.domain-assurance']),
|
|
132
|
+
primaryArtefacts: Object.freeze([]),
|
|
133
|
+
planSupport: false,
|
|
134
|
+
additionalOutputFormats: Object.freeze([]),
|
|
135
|
+
bootstrapProfile: 'allowed',
|
|
136
|
+
}),
|
|
137
|
+
"change-packet": commandSeed({
|
|
138
|
+
reference: {
|
|
139
|
+
description: 'Assemble pre-change, post-change, and planning evidence into one integrity-protected packet.',
|
|
140
|
+
example: 'whoisleuth change-packet change-review.json --json',
|
|
141
|
+
boundary: 'Assembly is offline. Readiness reflects only the supplied bounded evidence and does not authorise or perform a domain change.',
|
|
142
|
+
},
|
|
143
|
+
collection: { scope: 'Reads one versioned packet input capped at 6 MiB and makes no request or configuration change.' },
|
|
144
|
+
summary: 'Build a reviewed change packet offline',
|
|
145
|
+
options: ['--json', '--quiet', '--no-color'],
|
|
146
|
+
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
147
|
+
constraints: EMPTY_CONSTRAINTS,
|
|
148
|
+
handlerOwner: 'assurance',
|
|
149
|
+
networkEffect: 'offline',
|
|
150
|
+
common: false,
|
|
151
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.domain-change-packet.input', 'whoisleuth.domain-change-packet']),
|
|
152
|
+
primaryArtefacts: Object.freeze([]),
|
|
153
|
+
planSupport: false,
|
|
154
|
+
additionalOutputFormats: Object.freeze([]),
|
|
155
|
+
bootstrapProfile: 'allowed',
|
|
156
|
+
}),
|
|
157
|
+
"sharing-review": commandSeed({
|
|
158
|
+
reference: {
|
|
159
|
+
description: 'Lint one reviewed artefact against local integrity, marking, recipient, personal-data, and redaction controls.',
|
|
160
|
+
example: 'whoisleuth sharing-review packet.json --marking amber --recipient-scope organization --purpose "Reviewed incident handoff" --human-reviewed --personal-data-reviewed --redactions-confirmed --json',
|
|
161
|
+
boundary: 'The command is offline and emits only bounded schema/version metadata, no content values, and no raw evidence. Its result is a review aid, not legal advice or recipient authorisation.',
|
|
162
|
+
},
|
|
163
|
+
collection: { scope: 'Reads one bounded artefact, emits only schema/version metadata and no content values, and performs no transmission.' },
|
|
164
|
+
summary: 'Lint an artefact before deliberate sharing',
|
|
165
|
+
options: ['--marking', '--recipient-scope', '--purpose', '--human-reviewed', '--personal-data-reviewed', '--redactions-confirmed', '--json', '--quiet', '--no-color'],
|
|
166
|
+
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
167
|
+
constraints: Object.freeze([
|
|
168
|
+
constraint({ kind: 'required', options: ['--marking', '--recipient-scope', '--purpose'] }),
|
|
169
|
+
]),
|
|
170
|
+
handlerOwner: 'assurance',
|
|
171
|
+
networkEffect: 'offline',
|
|
172
|
+
common: false,
|
|
173
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.cli.sharing-review']),
|
|
174
|
+
primaryArtefacts: Object.freeze([]),
|
|
175
|
+
planSupport: false,
|
|
176
|
+
additionalOutputFormats: Object.freeze([]),
|
|
177
|
+
bootstrapProfile: 'allowed',
|
|
178
|
+
}),
|
|
179
|
+
});
|
|
@@ -4,7 +4,7 @@ import { buildInvestigationPackage } from "../packages/investigation/investigati
|
|
|
4
4
|
import { encryptInvestigationPackage } from "../packages/investigation/investigation-package-crypto.mjs";
|
|
5
5
|
import { writeInvestigationFolder, writePreparedEvidenceFolder } from "./investigation-folder.mjs";
|
|
6
6
|
import { buildInvestigationBagIt, prepareInvestigationBagIt } from "../packages/investigation/investigation-bagit.mjs";
|
|
7
|
-
import { scanBoundedJson } from "../
|
|
7
|
+
import { scanBoundedJson } from "../packages/analysis/bounded-json.mjs";
|
|
8
8
|
import { MAX_ASSURANCE_INPUT_BYTES, buildDomainAssurance, formatDomainAssurance, } from "../lib/domain-assurance.mjs";
|
|
9
9
|
import { MAX_DOMAIN_CHANGE_PACKET_INPUT_BYTES, buildDomainChangePacket, formatDomainChangePacket, } from "../lib/domain-change-packet.mjs";
|
|
10
10
|
import { DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA, buildDomainControlFlightRecorder, formatDomainControlFlightRecorder, serializeDomainControlFlightRecorder, } from "../lib/domain-control-flight-recorder.mjs";
|
package/cli/bounded-json.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { boundedJsonLimitsForBytes, MAX_BOUNDED_JSON_DEPTH, MAX_BOUNDED_JSON_KEYS, MAX_BOUNDED_JSON_VALUES, parseBoundedJson, parseBoundedJsonObject, scanBoundedJson, } from "../
|
|
1
|
+
export { boundedJsonLimitsForBytes, MAX_BOUNDED_JSON_DEPTH, MAX_BOUNDED_JSON_KEYS, MAX_BOUNDED_JSON_VALUES, parseBoundedJson, parseBoundedJsonObject, scanBoundedJson, } from "../packages/analysis/bounded-json.mjs";
|
package/cli/bulk-checkpoint.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
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
|
-
import { scanBoundedJson } from "../
|
|
5
|
+
import { scanBoundedJson } from "../packages/analysis/bounded-json.mjs";
|
|
6
6
|
import { normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
|
|
7
7
|
import { boundedCliInputError, CliUsageError } from "./errors.mjs";
|
|
8
8
|
import { writePrivateFile } from "./output-file.mjs";
|
|
@@ -28,12 +28,12 @@ async function runBulkCommand(args, dependencies, context) {
|
|
|
28
28
|
catch (error) {
|
|
29
29
|
if (error instanceof CliUsageError)
|
|
30
30
|
throw error;
|
|
31
|
-
throw new CliUsageError(`Could not read bulk input: ${boundedCliErrorMessage(error, 'Input could not be read')}
|
|
31
|
+
throw new CliUsageError(`Could not read bulk input: ${boundedCliErrorMessage(error, 'Input could not be read')}`, 'input_unavailable');
|
|
32
32
|
}
|
|
33
33
|
const parsed = parseBulkQueries(input, { deep: args.deep });
|
|
34
34
|
if (args.plan) {
|
|
35
35
|
const document = buildCollectionPreflight({
|
|
36
|
-
command: 'bulk', targetCount: parsed.queries.length,
|
|
36
|
+
command: 'bulk', targetCount: parsed.queries.length,
|
|
37
37
|
deep: args.deep, concurrency: args.concurrency, output: args.output, checkpoint: false,
|
|
38
38
|
});
|
|
39
39
|
context.writeStdout(args.output === 'json' ? formatJsonDocument(document) : context.terminal(formatCollectionPreflight(document), args.color));
|
package/cli/bulk.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { abortable } from "../lib/abort.mjs";
|
|
3
3
|
import { decodeBoundedUtf8 } from "../lib/bounded-file.mjs";
|
|
4
|
-
import { CliUsageError } from "./
|
|
5
|
-
import { hasUnsafeCliText } from "./errors.mjs";
|
|
4
|
+
import { CliUsageError, hasUnsafeCliText } from "./errors.mjs";
|
|
6
5
|
import { MAX_DEEP_BULK_QUERIES, MAX_FAST_BULK_QUERIES, } from "../lib/bulk-limits.mjs";
|
|
7
6
|
const MAX_BULK_INPUT_BYTES = 1024 * 1024;
|
|
8
7
|
async function readTextStreamBounded(stream, limit = MAX_BULK_INPUT_BYTES, signal) {
|
|
@@ -65,7 +64,7 @@ function parseBulkQueries(text, { deep = false } = {}) {
|
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
if (!queries.length)
|
|
68
|
-
throw new CliUsageError('Bulk input did not contain any queries.');
|
|
67
|
+
throw new CliUsageError('Bulk input did not contain any queries.', 'missing_input');
|
|
69
68
|
return { queries, duplicates, limit };
|
|
70
69
|
}
|
|
71
70
|
function boundedLookupError(error) {
|