@slicedearth/whoisleuth-cli 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DISCLOSURE +1 -1
- package/NOTICE +2 -2
- package/cli/argument-values.mjs +55 -0
- package/cli/arguments.mjs +24 -470
- package/cli/artifact-validation/case-response.mjs +67 -9
- package/cli/artifact-validation/lookup-evidence.mjs +18 -4
- package/cli/artifact-validation/signed-review.mjs +61 -64
- package/cli/artifact-verify.mjs +11 -2
- 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 +6 -8
- package/cli/bulk.mjs +2 -3
- package/cli/case-command.mjs +99 -6
- package/cli/case-pack.mjs +19 -4
- 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 +330 -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 +7 -9
- 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/domain-feed.mjs +61 -0
- 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/json.mjs +1 -13
- 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 +32 -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 +147 -0
- package/cli/interchange-report.mjs +4 -2
- package/cli/investigation-artifacts.mjs +3 -4
- package/cli/investigation-package-review.mjs +2 -0
- 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 +220 -17
- package/cli/lookalike-calibration.mjs +1 -1
- package/cli/lookup-brief.mjs +2 -2
- package/cli/lookup-command-runner.mjs +19 -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 +85 -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 +132 -0
- package/cli/review-command-definitions.mjs +279 -0
- package/cli/review-command-runner.mjs +72 -159
- package/cli/risk-calibration.mjs +14 -12
- package/cli/runner.mjs +20 -32
- package/cli/saved-lookup.mjs +2 -2
- package/cli/selected-input-worker.mjs +73 -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 +4 -4
- package/cli/watchlist-review.mjs +43 -0
- package/cli/workflow-arguments.mjs +100 -0
- package/cli/workflow-command-definitions.mjs +103 -0
- package/cli/workflow-command-runner.mjs +40 -1
- package/docs/cli-reference.md +27 -0
- package/docs/cli.md +322 -4
- package/docs/contextual-reviews.md +178 -0
- package/docs/infrastructure-observations.md +126 -0
- package/docs/offline-intake-context.md +213 -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/credential-form-attribution.mjs +136 -0
- package/lib/credential-surface-profile.mjs +6 -2
- 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 +35 -15
- package/lib/dns-inheritance-review.mjs +53 -63
- package/lib/dns-intelligence.mjs +27 -11
- 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 +75 -7
- package/lib/domain-posture-parsers.mjs +9 -0
- package/lib/domain-posture.mjs +45 -25
- package/lib/domain-registration.mjs +392 -0
- package/lib/evidence-export-privacy.mjs +27 -17
- package/lib/evidence-export.mjs +3 -3
- package/lib/evidence-report.mjs +5 -1
- package/lib/external-intelligence-risk.mjs +2 -124
- package/lib/favicon.mjs +13 -4
- 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 +13 -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-child-profile-contract.mjs +28 -11
- 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 +70 -40
- package/lib/lookup-response.mjs +5 -15
- package/lib/lookup-source-progress.mjs +8 -22
- package/lib/lookup.mjs +1 -1
- package/lib/operation-budget.mjs +4 -0
- 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 +46 -2
- package/lib/smtp-transport-review.mjs +48 -19
- package/lib/sslbl-certificates.generated.mjs +142 -139
- package/lib/static-html-analysis.mjs +32 -4
- package/lib/structured-data-identity.mjs +1 -1
- package/lib/technology-evidence-role.mjs +1 -51
- 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/tls-intelligence.mjs +30 -5
- package/lib/typosquat-generator.mjs +11 -2
- package/lib/website-enrichment.mjs +404 -0
- package/lib/whois-authority.mjs +43 -5
- package/lib/whois-chain.mjs +12 -3
- package/lib/whois-common-formats.mjs +3 -1
- package/lib/whois-contacts.mjs +13 -2
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-parser.mjs +5 -3
- package/package.json +15 -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 +348 -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-containment-handoff.mjs +195 -0
- package/packages/cases/case-evidence-links.mjs +151 -0
- package/packages/cases/case-evidence-model.mjs +89 -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-object-coverage.mjs +33 -0
- package/packages/cases/case-packet-correction.mjs +106 -0
- package/packages/cases/case-recheck-model.mjs +124 -36
- 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 +111 -118
- package/packages/cases/case-record-projection.mjs +23 -4
- package/packages/cases/case-record-version-input.mjs +41 -0
- package/packages/cases/case-report.mjs +93 -27
- package/packages/cases/case-requested-evidence.mjs +146 -0
- package/packages/cases/case-response-actions.mjs +159 -1
- package/packages/cases/case-response-model.mjs +16 -1
- package/packages/cases/case-response-object.mjs +119 -0
- package/packages/cases/case-response-outcomes.mjs +108 -20
- package/packages/cases/case-response-packet-render.mjs +35 -12
- package/packages/cases/case-response-packet.mjs +55 -13
- package/packages/cases/case-response-records.mjs +0 -1
- package/packages/cases/case-response-review-inputs.mjs +60 -8
- package/packages/cases/case-storage-model.mjs +1 -1
- package/packages/cases/case-workflow-metadata.mjs +217 -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 +35 -15
- package/packages/contracts/candidate-watch-review.mjs +54 -0
- package/packages/contracts/case-portability.mjs +360 -49
- package/packages/contracts/cli-command-semantics.mjs +5 -0
- package/packages/contracts/cli-grammar.mjs +6 -0
- package/packages/contracts/cli-lookup-brief.mjs +2 -0
- package/packages/contracts/context-review.mjs +25 -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/external-observation-interchange.mjs +96 -28
- 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/lookup-evidence.mjs +3 -2
- package/packages/contracts/mail-authentication.mjs +5 -0
- package/packages/contracts/message-intake.mjs +37 -0
- package/packages/contracts/offline-comparison.mjs +4 -1
- package/packages/contracts/passive-mail.mjs +10 -0
- package/packages/contracts/risk-calibration.mjs +44 -29
- package/packages/contracts/schema-lifecycle.mjs +3 -1
- package/packages/contracts/web-capture.mjs +2 -2
- package/packages/contracts/workspace-portability.mjs +144 -28
- package/packages/evidence/collection-quality.mjs +67 -0
- package/packages/evidence/credential-form-attribution.mjs +60 -0
- package/packages/evidence/dns-query-outcome.mjs +72 -0
- package/packages/evidence/email-recipient.mjs +18 -0
- package/packages/evidence/image-regions.mjs +15 -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/evidence/record-identity.mjs +60 -16
- package/packages/evidence/technology-evidence-role.mjs +51 -0
- package/packages/interchange/defensive-indicator-export.mjs +264 -0
- package/packages/interchange/external-findings-import.mjs +36 -29
- package/packages/interchange/json-output.mjs +14 -0
- package/packages/interchange/managed-indicator-export.mjs +36 -0
- package/packages/interchange/managed-indicator-set.mjs +223 -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 +79 -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 +87 -0
- package/packages/investigation/docx-intake.mjs +133 -0
- package/packages/investigation/domain-history-review.mjs +132 -0
- package/packages/investigation/domain-transition-review.mjs +49 -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/infrastructure-observation.mjs +234 -0
- package/packages/investigation/intake-context.mjs +284 -0
- package/packages/investigation/intake-indicators.mjs +116 -0
- package/packages/investigation/intake-phones.mjs +155 -0
- package/packages/investigation/intake-report.mjs +20 -0
- package/packages/investigation/investigation-manifest.mjs +35 -9
- package/packages/investigation/link-intake.mjs +209 -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 +235 -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/monitoring/domain-feed.mjs +321 -0
- package/packages/workspace/brand-candidate-workflow.mjs +351 -0
- package/packages/workspace/brand-keyword-campaign.mjs +231 -0
- package/packages/workspace/brand-profile-model.mjs +48 -4
- package/packages/workspace/bulk-review-model.mjs +28 -26
- package/packages/workspace/bulk-session-model.mjs +27 -13
- package/packages/workspace/campaign-model.mjs +1 -1
- package/packages/workspace/candidate-watch-handoff.mjs +195 -0
- package/packages/workspace/detection-rule-model.mjs +11 -3
- 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 +24 -7
- package/packages/workspace/watchlist-history.mjs +59 -16
- package/packages/workspace/watchlist-store.mjs +152 -11
- 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,12 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readCaseEvidenceRequest, readCasePacketAmendment, assertEvidenceRequestEvent, assertEvidenceRequestHistory, assertPacketAmendmentSelection } from "../../packages/cases/case-requested-evidence.mjs";
|
|
3
|
+
import { readCaseDeliveryPacketReceipt, readCasePacketCorrection, assertDeliveryPacketReceipt, correctionDelivery } from "../../packages/cases/case-packet-correction.mjs";
|
|
4
|
+
import { CASE_SELECTED_EVIDENCE_SOURCE_LIMITATION } from "../../packages/cases/case-evidence-links.mjs";
|
|
5
|
+
import { OBJECT_RESPONSE_CASE_PACKET_VERSION, OBJECT_RESPONSE_CASE_REVIEW_INPUTS_VERSION } from "../../packages/contracts/case-portability.mjs";
|
|
2
6
|
import { canonicalArtifactJsonV2 } from "../../packages/evidence/artifact-integrity.mjs";
|
|
3
|
-
import {
|
|
7
|
+
import { EVIDENCE_FOLLOW_UP_CASE_RESPONSE_PACKET_VERSION, EVIDENCE_FOLLOW_UP_CASE_RESPONSE_REVIEW_INPUTS_VERSION } from "../../packages/contracts/case-portability.mjs";
|
|
8
|
+
import { readCaseResponseObject, readCaseResponseObjects, readCaseResponseObjectOutcome, assertCaseObjectOutcome } from "../../packages/cases/case-response-object.mjs";
|
|
9
|
+
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
10
|
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
11
|
import { HEX_DIGEST_RE, array, boolean, domain, enumeration, exact, exactOptional, fail, integer, iso, optionalText, sameValues, strings, text, } from "./structure-primitives.mjs";
|
|
6
12
|
const CASE_RESPONSE_PREFLIGHT_IDS_V6 = [
|
|
@@ -281,7 +287,8 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
281
287
|
fail('Case-response abusive URLs');
|
|
282
288
|
text(incident.observedHarm, 'Case-response observed harm', 2_000);
|
|
283
289
|
iso(incident.observedAt, 'Case-response observedAt');
|
|
284
|
-
if (profile.subject !== `${selectedProfile?.subjectPrefix}: ${caseRecord.domain} (${incident.category})`
|
|
290
|
+
if (profile.subject !== `${selectedProfile?.subjectPrefix}: ${caseRecord.domain} (${incident.category})`
|
|
291
|
+
&& (version < 13 || profile.subject !== `Correction or retraction request: ${caseRecord.domain} (${incident.category})`))
|
|
285
292
|
fail('Case-response profile subject');
|
|
286
293
|
const contacts = array(root.contacts, 'Case-response contacts', MAX_RESPONSE_CONTACTS);
|
|
287
294
|
const contactKeys = new Set();
|
|
@@ -378,7 +385,10 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
378
385
|
const evidence = array(root.selectedEvidence, 'Case-response selected evidence', MAX_RESPONSE_SELECTED_EVIDENCE);
|
|
379
386
|
const evidenceIds = new Set();
|
|
380
387
|
for (const candidate of evidence) {
|
|
381
|
-
const item = exactOptional(candidate, ['id', 'label', 'source', 'observedAt', 'completeness', 'limitations'], version > PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION ? ['observationHostname'] : [], 'Case-response selected evidence');
|
|
388
|
+
const item = exactOptional(candidate, ['id', 'label', 'source', 'observedAt', 'completeness', 'limitations'], [...(version > PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION ? ['observationHostname', 'webObservationMode'] : []), ...(version >= 12 ? ['responseObject'] : [])], 'Case-response selected evidence');
|
|
389
|
+
readCaseResponseObject(item.responseObject);
|
|
390
|
+
if (item.webObservationMode !== undefined && item.webObservationMode !== 'selected_url')
|
|
391
|
+
fail('Case-response web observation mode');
|
|
382
392
|
const id = text(item.id, 'Case-response evidence id', 64);
|
|
383
393
|
if (evidenceIds.has(id))
|
|
384
394
|
fail('Case-response selected evidence');
|
|
@@ -543,13 +553,17 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
543
553
|
const actionIdOrder = [];
|
|
544
554
|
const providerEvents = [];
|
|
545
555
|
for (const candidate of history) {
|
|
546
|
-
const action =
|
|
556
|
+
const action = exactOptional(candidate, [
|
|
547
557
|
'actionId', 'type', 'recipient', 'contactSource', 'state', 'reference',
|
|
548
558
|
'providerOutcome', 'outcomeDetail', 'originActionId', 'historyOmitted',
|
|
549
559
|
'historyLimitations', 'transitions', 'createdAt', 'updatedAt',
|
|
550
560
|
...(hasActionBinding ? ['routeObservedAt'] : []),
|
|
551
561
|
...(hasPlatformRoutes ? ['routeReviewAfter'] : []),
|
|
552
|
-
], 'Case-response escalation action');
|
|
562
|
+
], [...(Number(root.schemaVersion) >= 11 ? ['amendment'] : []), ...(version >= 12 ? ['responseObjects'] : []), ...(version >= 13 ? ['correction'] : [])], 'Case-response escalation action');
|
|
563
|
+
readCaseResponseObjects(action.responseObjects);
|
|
564
|
+
readCasePacketAmendment(action.amendment);
|
|
565
|
+
if (readCasePacketCorrection(action.correction) && action.amendment)
|
|
566
|
+
throw new TypeError('Correction and provider amendment must remain separate.');
|
|
553
567
|
const actionId = text(action.actionId, 'Case-response action id', 64);
|
|
554
568
|
if (actionIds.has(actionId))
|
|
555
569
|
fail('Case-response action identity');
|
|
@@ -571,6 +585,7 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
571
585
|
integer(action.historyOmitted, 'Case-response action omitted history', 0, 1_000_000);
|
|
572
586
|
strings(action.historyLimitations, 'Case-response action history limitations', 8, 240);
|
|
573
587
|
const transitions = array(action.transitions, 'Case-response action transitions', MAX_CASE_ACTION_EVENTS_PER_ACTION, 1);
|
|
588
|
+
const requestEvents = [];
|
|
574
589
|
const eventIds = new Set();
|
|
575
590
|
let projectedState = null;
|
|
576
591
|
let latestReference = null;
|
|
@@ -580,8 +595,23 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
580
595
|
let previousTime = Number.NEGATIVE_INFINITY;
|
|
581
596
|
let previousEventId = '';
|
|
582
597
|
for (const eventCandidate of transitions) {
|
|
583
|
-
const event =
|
|
598
|
+
const event = exactOptional(eventCandidate, ['id', 'previousState', 'nextState', 'occurredAt', 'sourceClass', 'provenance', 'reference', 'evidencePinId', 'limitations', 'providerOutcome', 'outcomeDetail', 'originActionId', 'applied'], [...(Number(root.schemaVersion) >= 11 ? ['evidenceRequest'] : []), ...(version >= 12 ? ['responseObjects', 'objectOutcome'] : []), ...(version >= 13 ? ['packetReceipt'] : [])], 'Case-response action transition');
|
|
599
|
+
const receipt = readCaseDeliveryPacketReceipt(event.packetReceipt);
|
|
600
|
+
assertDeliveryPacketReceipt(receipt, event, actionId);
|
|
601
|
+
if (receipt && (receipt.caseId !== caseRecord.id || receipt.target !== caseRecord.domain || receipt.recipient !== action.recipient))
|
|
602
|
+
fail('Case-response receipt Case, target or recipient');
|
|
603
|
+
const responseObjects = readCaseResponseObjects(event.responseObjects);
|
|
604
|
+
const objectOutcome = readCaseResponseObjectOutcome(event.objectOutcome);
|
|
605
|
+
if (objectOutcome) {
|
|
606
|
+
if (!responseObjects?.length || !['submitted', 'acknowledged', 'terminal'].includes(String(event.nextState))
|
|
607
|
+
|| !['analyst', 'provider'].includes(String(event.sourceClass)) || event.previousState === 'authorised')
|
|
608
|
+
fail('Case-response object outcome scope');
|
|
609
|
+
responseObjects.forEach(object => assertCaseObjectOutcome(objectOutcome, object));
|
|
610
|
+
}
|
|
611
|
+
const evidenceRequest = readCaseEvidenceRequest(event.evidenceRequest);
|
|
612
|
+
assertEvidenceRequestEvent(evidenceRequest, event);
|
|
584
613
|
const eventId = text(event.id, 'Case-response action event id', 64);
|
|
614
|
+
requestEvents.push({ id: eventId, ...(evidenceRequest ? { evidenceRequest } : {}) });
|
|
585
615
|
if (eventIds.has(eventId))
|
|
586
616
|
fail('Case-response action event identity');
|
|
587
617
|
eventIds.add(eventId);
|
|
@@ -650,6 +680,7 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
650
680
|
}
|
|
651
681
|
}
|
|
652
682
|
}
|
|
683
|
+
assertEvidenceRequestHistory(requestEvents, Number(action.historyOmitted) > 0);
|
|
653
684
|
if ((projectedState ?? 'drafting') !== action.state
|
|
654
685
|
|| latestReference !== action.reference
|
|
655
686
|
|| latestProviderOutcome !== action.providerOutcome
|
|
@@ -667,6 +698,20 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
667
698
|
if (recipientRoute && !actionIds.has(recipientRoute.actionId))
|
|
668
699
|
fail('Case-response recipient action');
|
|
669
700
|
}
|
|
701
|
+
if (version >= 11)
|
|
702
|
+
assertPacketAmendmentSelection(history.map(candidate => {
|
|
703
|
+
const action = candidate;
|
|
704
|
+
return { id: action.actionId, history: action.transitions, originActionId: action.originActionId, amendment: action.amendment };
|
|
705
|
+
}), selectedActionId, evidenceIds);
|
|
706
|
+
if (version >= 13) {
|
|
707
|
+
const actions = history.map(candidate => { const action = candidate; return { ...action, id: action.actionId, history: action.transitions }; });
|
|
708
|
+
const selected = actions.find(action => action.id === selectedActionId);
|
|
709
|
+
if (selected?.correction)
|
|
710
|
+
correctionDelivery(actions, selected, { caseId: caseRecord.id, target: caseRecord.domain, profile: profile.id, selectedPinIds: evidenceIds });
|
|
711
|
+
const expectedPrefix = selected?.correction ? 'Correction or retraction request' : selectedProfile?.subjectPrefix;
|
|
712
|
+
if (profile.subject !== `${expectedPrefix}: ${caseRecord.domain} (${incident.category})`)
|
|
713
|
+
fail('Case-response correction subject');
|
|
714
|
+
}
|
|
670
715
|
const lifecycle = exact(root.responseLifecycle, ['providerOutcomeState', 'latestProviderOutcome', 'observedChangeState', 'latestObservedEffect', 'latestObservedChangeAt', 'closure', 'limitations'], 'Case-response lifecycle');
|
|
671
716
|
const providerOutcomeState = enumeration(lifecycle.providerOutcomeState, ['available', 'missing', 'ambiguous'], 'Case-response lifecycle provider state');
|
|
672
717
|
let actualLatestProvider = null;
|
|
@@ -709,7 +754,10 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
709
754
|
fail('Case-response lifecycle provider state');
|
|
710
755
|
const observedChangeState = enumeration(lifecycle.observedChangeState, ['available', 'missing', 'ambiguous'], 'Case-response lifecycle observed-change state');
|
|
711
756
|
if (lifecycle.latestObservedEffect !== null) {
|
|
712
|
-
const effect =
|
|
757
|
+
const effect = exactOptional(lifecycle.latestObservedEffect, ['reviewId', 'state', 'observedAt', 'sourceClass', 'source'], version >= 12 ? ['responseObject', 'objectOutcome'] : [], 'Case-response lifecycle observed effect');
|
|
758
|
+
const responseObject = readCaseResponseObject(effect.responseObject);
|
|
759
|
+
const objectOutcome = readCaseResponseObjectOutcome(effect.objectOutcome);
|
|
760
|
+
assertCaseObjectOutcome(objectOutcome, responseObject);
|
|
713
761
|
text(effect.reviewId, 'Case-response effect review id', 64);
|
|
714
762
|
enumeration(effect.state, CASE_OBSERVED_EFFECT_STATES, 'Case-response effect state');
|
|
715
763
|
iso(effect.observedAt, 'Case-response effect observedAt');
|
|
@@ -731,8 +779,11 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
731
779
|
contract: CASE_RESPONSE_REVIEW_INPUTS_SCHEMA,
|
|
732
780
|
version: version === CASE_RESPONSE_PACKET_VERSION
|
|
733
781
|
? CASE_RESPONSE_REVIEW_INPUTS_VERSION
|
|
734
|
-
:
|
|
735
|
-
:
|
|
782
|
+
: version === OBJECT_RESPONSE_CASE_PACKET_VERSION ? OBJECT_RESPONSE_CASE_REVIEW_INPUTS_VERSION
|
|
783
|
+
: version === EVIDENCE_FOLLOW_UP_CASE_RESPONSE_PACKET_VERSION ? EVIDENCE_FOLLOW_UP_CASE_RESPONSE_REVIEW_INPUTS_VERSION
|
|
784
|
+
: version === LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION ? LATEST_PUBLIC_CASE_RESPONSE_REVIEW_INPUTS_VERSION
|
|
785
|
+
: hasPlatformRoutes ? PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION
|
|
786
|
+
: hasActionBinding ? PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION : PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION,
|
|
736
787
|
profile: {
|
|
737
788
|
id: profile.id,
|
|
738
789
|
label: profile.label,
|
|
@@ -755,6 +806,7 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
755
806
|
escalationHistoryOmitted: root.escalationHistoryOmitted,
|
|
756
807
|
escalationHistoryLimitations: root.escalationHistoryLimitations,
|
|
757
808
|
responseLifecycle: root.responseLifecycle,
|
|
809
|
+
...(version >= 13 ? { sourceQualifications: strings(root.provenance.limitations, 'Case-response limitations', 8, 600).filter(value => value === CASE_SELECTED_EVIDENCE_SOURCE_LIMITATION) } : {}),
|
|
758
810
|
};
|
|
759
811
|
const expectedReviewDigest = createHash('sha256')
|
|
760
812
|
.update(canonicalArtifactJsonV2(reviewedInputs))
|
|
@@ -781,6 +833,12 @@ function validateVersionedCaseResponsePacket(value, version) {
|
|
|
781
833
|
fail('Case-response integrity');
|
|
782
834
|
}
|
|
783
835
|
export function validateCaseResponsePacket(value) {
|
|
836
|
+
if (value.schemaVersion === OBJECT_RESPONSE_CASE_PACKET_VERSION)
|
|
837
|
+
return validateVersionedCaseResponsePacket(value, OBJECT_RESPONSE_CASE_PACKET_VERSION);
|
|
838
|
+
if (value.schemaVersion === EVIDENCE_FOLLOW_UP_CASE_RESPONSE_PACKET_VERSION)
|
|
839
|
+
return validateVersionedCaseResponsePacket(value, EVIDENCE_FOLLOW_UP_CASE_RESPONSE_PACKET_VERSION);
|
|
840
|
+
if (value.schemaVersion === LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION)
|
|
841
|
+
return validateVersionedCaseResponsePacket(value, LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION);
|
|
784
842
|
if (value.schemaVersion === PUBLIC_CASE_RESPONSE_PACKET_VERSION)
|
|
785
843
|
return validateCaseResponsePacketV6(value);
|
|
786
844
|
if (value.schemaVersion === PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
2
|
import { assertLookupEvidencePrivacySafeTree, assertLookupEvidencePortableTree, LOOKUP_EVIDENCE_SCHEMA, LOOKUP_EVIDENCE_SCHEMA_VERSION, HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION, PRIVACY_MINIMIZED_LOOKUP_EVIDENCE_SCHEMA_VERSION, REGISTRAR_STANDING_LOOKUP_EVIDENCE_SCHEMA_VERSION, V1_PUBLIC_LOOKUP_EVIDENCE_SCHEMA_VERSION, projectLookupEvidenceAvailability, projectLookupEvidenceAvailabilityPublic, projectLookupEvidenceQuery, projectLookupEvidenceRdapPublication, projectLookupEvidenceRegistryInsights, projectLookupEvidenceWhoisPublication, SUPPORTED_LOOKUP_EVIDENCE_SCHEMA_VERSIONS, } from "../../lib/evidence-export.mjs";
|
|
3
3
|
import { validLookupObservationScope } from "../../packages/evidence/lookup-target.mjs";
|
|
4
|
+
import { externalPasswordFormObservation } from "../../packages/evidence/credential-form-attribution.mjs";
|
|
5
|
+
import { credentialSurfaceContractState } from "../../lib/lookup-child-profile-contract.mjs";
|
|
4
6
|
import { registrarStandingObservedBy, resolveRegistrarIanaId, validRegistrarStanding, } from "../../lib/registrar-standing-contract.mjs";
|
|
5
7
|
import { compareRegistrySources } from "../../lib/registry-comparison.mjs";
|
|
6
8
|
import { buildRegistryInsights } from "../../lib/registry-insights.mjs";
|
|
7
|
-
import { WHOISLEUTH_SOURCE_REPOSITORY_URL } from "../../
|
|
9
|
+
import { WHOISLEUTH_SOURCE_REPOSITORY_URL } from "../../packages/analysis/project-metadata.mjs";
|
|
8
10
|
import { validHttpDeliveryMetadata, validPagePublicationMetadata, } from "../../lib/homepage-metadata-contract.mjs";
|
|
9
11
|
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
12
|
const LOOKUP_RDAP_ATTEMPT_OUTCOMES = [
|
|
@@ -24,7 +26,7 @@ const PUBLIC_LOOKUP_AVAILABILITY_ANALYSIS_KEYS = [
|
|
|
24
26
|
'expiresInDays', 'privacyProtected', 'dnssec', 'activityStatus',
|
|
25
27
|
'websiteProbeStatus', 'websiteProbeDetail', 'http', 'deepScanComplete',
|
|
26
28
|
'faviconHash', 'faviconPHash', 'pageTitle', 'hasPasswordField',
|
|
27
|
-
'phishingLanguageMatch', 'hasExternalFormAction', 'externalAssetHosts',
|
|
29
|
+
'phishingLanguageMatch', 'hasExternalFormAction', 'hasExternalPasswordForm', 'externalAssetHosts',
|
|
28
30
|
'pageIdentity', 'credentialSurfaceProfile', 'structuredDataIdentity',
|
|
29
31
|
'technologyProfile', 'pageRoleProfile', 'clientBehaviorProfile',
|
|
30
32
|
'securityPosture', 'dns', 'tls', 'hasMx', 'hasNullMx', 'mxHosts', 'hasSpf',
|
|
@@ -42,6 +44,18 @@ const LOOKUP_REGISTRAR_COMPARISON_STATUSES = [
|
|
|
42
44
|
'registrar_unavailable', 'registry_incomplete', 'registrar_incomplete',
|
|
43
45
|
];
|
|
44
46
|
function validateLookupEvidenceHomepageMetadata(availability, version) {
|
|
47
|
+
const credentialProfile = availability.credentialSurfaceProfile === null || availability.credentialSurfaceProfile === undefined
|
|
48
|
+
? null : record(availability.credentialSurfaceProfile, 'Lookup evidence credential profile');
|
|
49
|
+
if (version < 30 && (Object.hasOwn(availability, 'hasExternalPasswordForm') || credentialProfile?.credentialSurfaceVersion === 2)) {
|
|
50
|
+
fail('Lookup evidence form-attribution epoch');
|
|
51
|
+
}
|
|
52
|
+
if (Object.hasOwn(availability, 'hasExternalPasswordForm') && availability.hasExternalPasswordForm !== null)
|
|
53
|
+
boolean(availability.hasExternalPasswordForm, 'Lookup evidence password-form destination');
|
|
54
|
+
if (credentialProfile?.credentialSurfaceVersion === 2 && credentialSurfaceContractState(credentialProfile) !== 'supported')
|
|
55
|
+
fail('Lookup evidence form-attribution profile');
|
|
56
|
+
if (credentialProfile?.credentialSurfaceVersion === 2 && availability.hasExternalPasswordForm !== undefined
|
|
57
|
+
&& availability.hasExternalPasswordForm !== externalPasswordFormObservation(credentialProfile.formAttribution))
|
|
58
|
+
fail('Lookup evidence form-attribution consistency');
|
|
45
59
|
const pageIdentity = availability.pageIdentity === null || availability.pageIdentity === undefined
|
|
46
60
|
? null
|
|
47
61
|
: record(availability.pageIdentity, 'Lookup evidence page identity');
|
|
@@ -675,7 +689,7 @@ export function validateLookupEvidenceArtifactStructure(value) {
|
|
|
675
689
|
}
|
|
676
690
|
validateLookupEvidenceHomepageMetadata(availability, version);
|
|
677
691
|
if (!isDeepStrictEqual(analysis.availability, currentAvailability
|
|
678
|
-
? projectLookupEvidenceAvailability(analysis.availability)
|
|
692
|
+
? projectLookupEvidenceAvailability(analysis.availability, { legacyUris: version < HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION })
|
|
679
693
|
: projectLookupEvidenceAvailabilityPublic(analysis.availability)))
|
|
680
694
|
fail('Lookup evidence availability analysis');
|
|
681
695
|
}
|
|
@@ -715,7 +729,7 @@ export function validateLookupEvidenceArtifactStructure(value) {
|
|
|
715
729
|
if (!isDeepStrictEqual(registryInsights, expectedInsights))
|
|
716
730
|
fail('Lookup evidence registry insight derivation');
|
|
717
731
|
try {
|
|
718
|
-
assertLookupEvidencePrivacySafeTree(root);
|
|
732
|
+
assertLookupEvidencePrivacySafeTree(root, { legacyUris: version < HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION });
|
|
719
733
|
}
|
|
720
734
|
catch {
|
|
721
735
|
fail('Lookup evidence privacy boundary');
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ACQUISITION_DECISION_PACKET_SCHEMA, validateAcquisitionDecisionPacketStructure, } from "../../packages/investigation/acquisition-decision-packet.mjs";
|
|
2
2
|
import { BULK_DOMAIN_COMPARISON_EXPORT_VERSION, BULK_DOMAIN_COMPARISON_SCHEMA, } from "../../packages/investigation/bulk-domain-comparison.mjs";
|
|
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";
|
|
@@ -24,55 +26,6 @@ const DOMAIN_COMPARISON_ROW_IDS = [
|
|
|
24
26
|
];
|
|
25
27
|
const MAIL_STATES = ['authenticated_mail', 'evidence_incomplete', 'mail_auth_gap', 'mail_auth_incomplete', 'no_explicit_mx', 'null_mx'];
|
|
26
28
|
const DNS_TYPES = ['A', 'AAAA', 'CAA', 'CDNSKEY', 'CDS', 'CNAME', 'CSYNC', 'HTTPS', 'MX', 'NS', 'SRV', 'SVCB', 'TLSA', 'TXT'];
|
|
27
|
-
function validateAcquisitionItem(value, label) {
|
|
28
|
-
const item = exact(value, ['id', 'label', 'state', 'detail', 'provenance'], label);
|
|
29
|
-
const id = enumeration(item.id, ['availability', 'contacts', 'lifecycle', 'mail', 'nameservers', 'operations', 'policy_eligibility', 'policy_lifecycle', 'policy_transfer', 'tls', 'transfer', 'web'], label);
|
|
30
|
-
enumeration(item.state, ['authoritative', 'observed', 'review', 'unavailable'], label);
|
|
31
|
-
text(item.label, label, 200);
|
|
32
|
-
text(item.detail, label, 2_000);
|
|
33
|
-
text(item.provenance, label, 300);
|
|
34
|
-
return id;
|
|
35
|
-
}
|
|
36
|
-
function validateAcquisition(value) {
|
|
37
|
-
const root = exact(value, ['schema', 'version', 'generatedAt', 'target', 'synthetic', 'evidenceObservedAt', 'analystReview', 'evidenceReview', 'limitations', 'integrity'], 'Acquisition decision packet');
|
|
38
|
-
iso(root.generatedAt, 'Acquisition decision packet generatedAt');
|
|
39
|
-
domain(root.target, 'Acquisition decision packet target');
|
|
40
|
-
boolean(root.synthetic, 'Acquisition decision packet synthetic');
|
|
41
|
-
iso(root.evidenceObservedAt, 'Acquisition decision packet evidenceObservedAt', true);
|
|
42
|
-
const review = exact(root.analystReview, ['decision', 'rationale', 'reviewedChecks', 'outstandingChecks', 'state'], 'Acquisition analyst review');
|
|
43
|
-
const decision = enumeration(review.decision, ACQUISITION_DECISIONS, 'Acquisition analyst review decision');
|
|
44
|
-
text(review.rationale, 'Acquisition analyst review rationale', 2_000, true);
|
|
45
|
-
const reviewed = strings(review.reviewedChecks, 'Acquisition reviewed checks', ACQUISITION_MANUAL_CHECKS.length, 40);
|
|
46
|
-
const outstanding = strings(review.outstandingChecks, 'Acquisition outstanding checks', ACQUISITION_MANUAL_CHECKS.length, 40);
|
|
47
|
-
const expectedReviewed = ACQUISITION_MANUAL_CHECKS.filter((item) => reviewed.includes(item));
|
|
48
|
-
const expectedOutstanding = ACQUISITION_MANUAL_CHECKS.filter((item) => !reviewed.includes(item));
|
|
49
|
-
if (!sameValues(reviewed, expectedReviewed) || !sameValues(outstanding, expectedOutstanding))
|
|
50
|
-
fail('Acquisition analyst review checks');
|
|
51
|
-
const expectedState = decision === 'unresolved' || reviewed.length < ACQUISITION_MANUAL_CHECKS.length ? 'draft' : 'reviewed';
|
|
52
|
-
if (review.state !== expectedState)
|
|
53
|
-
fail('Acquisition analyst review state');
|
|
54
|
-
const evidence = exact(root.evidenceReview, ['version', 'label', 'state', 'items', 'transitionDependencies', 'policyChecks', 'nextSteps', 'limitations'], 'Acquisition evidence review');
|
|
55
|
-
if (evidence.version !== 2)
|
|
56
|
-
fail('Acquisition evidence review');
|
|
57
|
-
text(evidence.label, 'Acquisition evidence review label', 200);
|
|
58
|
-
enumeration(evidence.state, ['incomplete', 'registered', 'review_transition', 'sale_signal', 'unregistered_observation'], 'Acquisition evidence review state');
|
|
59
|
-
const expectedEvidenceIds = {
|
|
60
|
-
items: ['availability', 'lifecycle', 'transfer', 'operations', 'contacts'],
|
|
61
|
-
transitionDependencies: ['nameservers', 'web', 'mail', 'tls'],
|
|
62
|
-
policyChecks: ['policy_eligibility', 'policy_lifecycle', 'policy_transfer'],
|
|
63
|
-
};
|
|
64
|
-
for (const key of ['items', 'transitionDependencies', 'policyChecks']) {
|
|
65
|
-
const expected = expectedEvidenceIds[key];
|
|
66
|
-
const values = array(evidence[key], `Acquisition evidence review ${key}`, expected.length, expected.length);
|
|
67
|
-
const ids = values.map((item, index) => validateAcquisitionItem(item, `Acquisition evidence review ${key}[${index}]`));
|
|
68
|
-
if (!sameValues(ids, expected))
|
|
69
|
-
fail(`Acquisition evidence review ${key}`);
|
|
70
|
-
}
|
|
71
|
-
strings(evidence.nextSteps, 'Acquisition next steps', 6, 600);
|
|
72
|
-
strings(evidence.limitations, 'Acquisition evidence limitations', 12, 600);
|
|
73
|
-
strings(root.limitations, 'Acquisition limitations', 8, 600);
|
|
74
|
-
validateIntegrity(root.integrity, 'Acquisition integrity', root.version, ACQUISITION_DECISION_PACKET_VERSION);
|
|
75
|
-
}
|
|
76
29
|
function validateClaimPassportTarget(type, value) {
|
|
77
30
|
const candidate = text(value, 'Claim passport target', 253);
|
|
78
31
|
if (type === 'domain') {
|
|
@@ -334,10 +287,11 @@ function validateBulkReviewManifest(value) {
|
|
|
334
287
|
strings(root.limitations, 'Bulk review manifest limitations', 8, 600);
|
|
335
288
|
validateIntegrity(root.integrity, 'Bulk review manifest integrity', root.version, version);
|
|
336
289
|
}
|
|
337
|
-
function validateSourceObservations(value, label) {
|
|
290
|
+
function validateSourceObservations(value, label, queryCoverage) {
|
|
338
291
|
const names = new Set();
|
|
292
|
+
let queryCount = 0;
|
|
339
293
|
return array(value, label, 16).map((candidate) => {
|
|
340
|
-
const source = exact(candidate, ['label', 'source', 'state', 'observedAt'], label);
|
|
294
|
+
const source = exact(candidate, ['label', 'source', 'state', 'observedAt', ...(queryCoverage ? ['queries'] : [])], label);
|
|
341
295
|
const name = text(source.label, `${label} label`, 120).toLowerCase();
|
|
342
296
|
if (names.has(name))
|
|
343
297
|
fail(`${label} duplicate label`);
|
|
@@ -345,14 +299,37 @@ function validateSourceObservations(value, label) {
|
|
|
345
299
|
text(source.source, `${label} source`, 240);
|
|
346
300
|
enumeration(source.state, ['observed', 'partial', 'unavailable'], `${label} state`);
|
|
347
301
|
iso(source.observedAt, `${label} observedAt`);
|
|
302
|
+
if (queryCoverage) {
|
|
303
|
+
const keys = new Set();
|
|
304
|
+
const queries = array(source.queries, `${label} queries`, 500 - queryCount);
|
|
305
|
+
queryCount += queries.length;
|
|
306
|
+
for (const candidate of queries) {
|
|
307
|
+
const query = exact(candidate, ['owner', 'type', 'state'], `${label} query`);
|
|
308
|
+
text(query.owner, `${label} query owner`, 253);
|
|
309
|
+
enumeration(query.type, DNS_TYPES, `${label} query type`);
|
|
310
|
+
enumeration(query.state, ['observed', 'partial', 'unavailable'], `${label} query state`);
|
|
311
|
+
const key = `${query.owner}\u0000${query.type}`;
|
|
312
|
+
if (keys.has(key))
|
|
313
|
+
fail(`${label} duplicate query`);
|
|
314
|
+
keys.add(key);
|
|
315
|
+
if ((source.state === 'observed' && query.state !== 'observed')
|
|
316
|
+
|| (source.state === 'unavailable' && query.state !== 'unavailable'))
|
|
317
|
+
fail(`${label} query state`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
348
320
|
return source;
|
|
349
321
|
});
|
|
350
322
|
}
|
|
351
|
-
function validateReviewMatrix(value, label, sources) {
|
|
323
|
+
function validateReviewMatrix(value, label, sources, queryCoverage = false) {
|
|
324
|
+
const rowKeys = new Set();
|
|
352
325
|
for (const [rowIndex, candidate] of array(value, label, 500).entries()) {
|
|
353
326
|
const row = exact(candidate, ['owner', 'type', 'state', 'observations'], `${label} row ${rowIndex + 1}`);
|
|
354
327
|
text(row.owner, `${label} owner`, 253);
|
|
355
328
|
enumeration(row.type, DNS_TYPES, `${label} type`);
|
|
329
|
+
const key = `${row.owner}\u0000${row.type}`;
|
|
330
|
+
if (queryCoverage && rowKeys.has(key))
|
|
331
|
+
fail(`${label} duplicate row`);
|
|
332
|
+
rowKeys.add(key);
|
|
356
333
|
const observations = array(row.observations, `${label} observations`, 16);
|
|
357
334
|
if (sources && observations.length !== sources.length)
|
|
358
335
|
fail(`${label} source coverage`);
|
|
@@ -363,7 +340,7 @@ function validateReviewMatrix(value, label, sources) {
|
|
|
363
340
|
const observation = exact(observationCandidate, ['label', 'source', 'state', 'values', 'ttlRange', ...(sources ? ['observedAt'] : [])], `${label} observation`);
|
|
364
341
|
text(observation.label, `${label} observation label`, 120);
|
|
365
342
|
text(observation.source, `${label} observation source`, 240);
|
|
366
|
-
const state = enumeration(observation.state, ['observed', 'partial', 'unavailable'], `${label} observation state`);
|
|
343
|
+
const state = enumeration(observation.state, ['observed', 'partial', 'unavailable', ...(queryCoverage ? ['not_queried'] : [])], `${label} observation state`);
|
|
367
344
|
const values = strings(observation.values, `${label} observation values`, 500, 16_384);
|
|
368
345
|
if (sources) {
|
|
369
346
|
const name = String(observation.label).toLowerCase();
|
|
@@ -371,8 +348,16 @@ function validateReviewMatrix(value, label, sources) {
|
|
|
371
348
|
fail(`${label} duplicate observation`);
|
|
372
349
|
names.add(name);
|
|
373
350
|
const source = sources.find((item) => item.label === observation.label);
|
|
374
|
-
|
|
351
|
+
const provenance = queryCoverage ? ['label', 'source', 'observedAt'] : ['label', 'source', 'state', 'observedAt'];
|
|
352
|
+
if (!source || provenance.some((key) => observation[key] !== source[key]))
|
|
375
353
|
fail(`${label} source provenance`);
|
|
354
|
+
if (queryCoverage) {
|
|
355
|
+
const query = source.queries.find((item) => item.owner === row.owner && item.type === row.type);
|
|
356
|
+
if (state !== (query?.state ?? 'not_queried'))
|
|
357
|
+
fail(`${label} query provenance`);
|
|
358
|
+
if (['not_queried', 'unavailable'].includes(state) && (values.length || observation.ttlRange !== null))
|
|
359
|
+
fail(`${label} unavailable values`);
|
|
360
|
+
}
|
|
376
361
|
iso(observation.observedAt, `${label} observation time`);
|
|
377
362
|
}
|
|
378
363
|
if (state === 'observed') {
|
|
@@ -387,13 +372,19 @@ function validateReviewMatrix(value, label, sources) {
|
|
|
387
372
|
fail(`${label} TTL range`);
|
|
388
373
|
}
|
|
389
374
|
}
|
|
390
|
-
const expected = complete < 2 ? 'insufficient' : signatures.size === 1 ? 'aligned' : 'different';
|
|
375
|
+
const expected = complete < 2 || (queryCoverage && complete !== observations.length) ? 'insufficient' : signatures.size === 1 ? 'aligned' : 'different';
|
|
391
376
|
if (row.state !== expected)
|
|
392
377
|
fail(`${label} state`);
|
|
393
378
|
}
|
|
379
|
+
if (queryCoverage && sources) {
|
|
380
|
+
const expectedKeys = new Set(sources.flatMap((source) => source.queries.map((item) => `${item.owner}\u0000${item.type}`)));
|
|
381
|
+
if (rowKeys.size !== expectedKeys.size || [...rowKeys].some((key) => !expectedKeys.has(key)))
|
|
382
|
+
fail(`${label} query coverage`);
|
|
383
|
+
}
|
|
394
384
|
}
|
|
395
385
|
function validateDomainChangeReview(value, label, version) {
|
|
396
386
|
const retainedTimes = version >= 2;
|
|
387
|
+
const queryCoverage = version >= 3;
|
|
397
388
|
const review = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'state', 'authoritativeRecordMatrix', 'resolverDivergenceMatrix', 'dnssecAutomation', 'acmeDependencies', 'certificate', 'services', 'hsts', 'gate', 'limitations', ...(retainedTimes ? ['sourceObservations'] : [])], label);
|
|
398
389
|
if (review.schema !== DOMAIN_CHANGE_REVIEW_SCHEMA || review.version !== version)
|
|
399
390
|
fail(label);
|
|
@@ -401,8 +392,8 @@ function validateDomainChangeReview(value, label, version) {
|
|
|
401
392
|
domain(review.domain, `${label} domain`);
|
|
402
393
|
enumeration(review.state, ['ready', 'review'], `${label} state`);
|
|
403
394
|
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
|
|
395
|
+
validateReviewMatrix(review.authoritativeRecordMatrix, `${label} authority matrix`, sources ? validateSourceObservations(sources.authorities, `${label} authority sources`, queryCoverage) : undefined, queryCoverage);
|
|
396
|
+
validateReviewMatrix(review.resolverDivergenceMatrix, `${label} resolver matrix`, sources ? validateSourceObservations(sources.resolvers, `${label} resolver sources`, queryCoverage) : undefined, queryCoverage);
|
|
406
397
|
const automation = exact(review.dnssecAutomation, ['state', 'cdsObserved', 'cdnskeyObserved', 'csyncObserved', 'conflictingTypes', 'detail'], `${label} DNSSEC automation`);
|
|
407
398
|
enumeration(automation.state, ['not_observed', 'conflict', 'partial', 'review_ready'], `${label} DNSSEC automation`);
|
|
408
399
|
boolean(automation.cdsObserved, `${label} CDS observed`);
|
|
@@ -514,7 +505,7 @@ function validatePlannedAssurance(value, label) {
|
|
|
514
505
|
strings(assurance.limitations, `${label} limitations`, 8, 600);
|
|
515
506
|
return assurance;
|
|
516
507
|
}
|
|
517
|
-
function expectedDomainChangeSummary(before, after) {
|
|
508
|
+
function expectedDomainChangeSummary(before, after, queryCoverage) {
|
|
518
509
|
const rows = (review) => new Map(review.authoritativeRecordMatrix.map((row) => [`${row.owner}\u0000${row.type}`, row]));
|
|
519
510
|
const beforeRows = rows(before);
|
|
520
511
|
const afterRows = rows(after);
|
|
@@ -526,7 +517,7 @@ function expectedDomainChangeSummary(before, after) {
|
|
|
526
517
|
};
|
|
527
518
|
const rowEvidence = (review, row) => {
|
|
528
519
|
if (!row)
|
|
529
|
-
return { state: collectionComplete(review) ? 'complete' : 'unavailable', values: [] };
|
|
520
|
+
return { state: !queryCoverage && collectionComplete(review) ? 'complete' : 'unavailable', values: [] };
|
|
530
521
|
const observations = row.observations;
|
|
531
522
|
if (observations.some((item) => item.state !== 'observed'))
|
|
532
523
|
return { state: 'partial', values: [] };
|
|
@@ -537,18 +528,21 @@ function expectedDomainChangeSummary(before, after) {
|
|
|
537
528
|
return { state: 'complete', values: [...new Set(observations.flatMap((item) => item.values))].sort() };
|
|
538
529
|
};
|
|
539
530
|
const changed = [];
|
|
531
|
+
const reasons = queryCoverage && keys.length > 500 ? ['The comparison exceeds 500 owner and record-type pairs.'] : [];
|
|
540
532
|
for (const key of keys.slice(0, 500)) {
|
|
541
533
|
const left = rowEvidence(before, beforeRows.get(key));
|
|
542
534
|
const right = rowEvidence(after, afterRows.get(key));
|
|
543
535
|
const [owner, type] = key.split('\u0000');
|
|
544
536
|
if (left.state !== 'complete' || right.state !== 'complete') {
|
|
537
|
+
if (queryCoverage)
|
|
538
|
+
reasons.push(`Comparison evidence is incomplete for ${owner} ${type}.`);
|
|
545
539
|
continue;
|
|
546
540
|
}
|
|
547
541
|
else if (!sameValues(left.values, right.values)) {
|
|
548
542
|
changed.push({ owner: owner ?? '', type: type ?? '', beforeValues: left.values, afterValues: right.values });
|
|
549
543
|
}
|
|
550
544
|
}
|
|
551
|
-
return changed;
|
|
545
|
+
return { changed, reasons };
|
|
552
546
|
}
|
|
553
547
|
function validateDomainChangePacket(value) {
|
|
554
548
|
const root = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'reference', 'state', 'gate', 'summary', 'evidence', 'limitations', 'integrity'], 'Domain change packet');
|
|
@@ -590,13 +584,14 @@ function validateDomainChangePacket(value) {
|
|
|
590
584
|
const expectedReasons = [
|
|
591
585
|
...pre.gate.reasons,
|
|
592
586
|
].map((reason) => `Pre-change evidence: ${reason}`);
|
|
593
|
-
|
|
587
|
+
const comparison = expectedDomainChangeSummary(pre, post, reviewVersion >= 3);
|
|
588
|
+
expectedReasons.push(...post.gate.reasons.map((reason) => `Post-change evidence: ${reason}`), ...(assuranceResult.review.reasons.map((reason) => `Change plan: ${reason}`)), ...comparison.reasons);
|
|
594
589
|
const boundedReasons = expectedReasons.slice(0, 100);
|
|
595
590
|
if (!sameValues(gateReasons, boundedReasons)
|
|
596
591
|
|| gate.pass !== (boundedReasons.length === 0)
|
|
597
592
|
|| root.state !== (boundedReasons.length === 0 ? 'ready' : 'review'))
|
|
598
593
|
fail('Domain change packet gate');
|
|
599
|
-
const expectedChanges =
|
|
594
|
+
const expectedChanges = comparison.changed;
|
|
600
595
|
if (changed.length !== expectedChanges.length || changed.some((candidate, index) => {
|
|
601
596
|
const item = candidate;
|
|
602
597
|
const expected = expectedChanges[index];
|
|
@@ -612,7 +607,9 @@ export function validateSignedDigestArtifactStructure(schema, value) {
|
|
|
612
607
|
if (value.schema !== schema)
|
|
613
608
|
fail('Signed review artefact');
|
|
614
609
|
if (schema === ACQUISITION_DECISION_PACKET_SCHEMA)
|
|
615
|
-
|
|
610
|
+
validateAcquisitionDecisionPacketStructure(value);
|
|
611
|
+
else if (schema === MANAGED_INDICATOR_SET_SCHEMA)
|
|
612
|
+
validateManagedIndicatorSet(value);
|
|
616
613
|
else if (schema === LOOKUP_CLAIM_PASSPORT_SCHEMA)
|
|
617
614
|
validateLookupClaimPassport(value);
|
|
618
615
|
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";
|
|
@@ -18,7 +19,7 @@ import { INVESTIGATION_MANIFEST_SCHEMA, SUPPORTED_INVESTIGATION_MANIFEST_VERSION
|
|
|
18
19
|
import { verifyCliCasePack } from "./case-pack.mjs";
|
|
19
20
|
import { LOOKUP_EVIDENCE_PORTABLE_MAX_BYTES, LOOKUP_EVIDENCE_SCHEMA, SUPPORTED_LOOKUP_EVIDENCE_SCHEMA_VERSIONS, } from "../lib/evidence-export.mjs";
|
|
20
21
|
export const OFFLINE_ARTIFACT_VERIFICATION_SCHEMA = 'whoisleuth.offline-artifact-verification';
|
|
21
|
-
export const OFFLINE_ARTIFACT_VERIFICATION_VERSION =
|
|
22
|
+
export const OFFLINE_ARTIFACT_VERIFICATION_VERSION = 5;
|
|
22
23
|
export const MAX_OFFLINE_ARTIFACT_BYTES = Math.max(MAX_DOMAIN_CONTROL_MANIFEST_BYTES, MAX_ENCRYPTED_WORKSPACE_ARCHIVE_BYTES);
|
|
23
24
|
export const MAX_OFFLINE_PASSPHRASE_FILE_BYTES = 1024;
|
|
24
25
|
export class UnsupportedOfflineArtifactError extends TypeError {
|
|
@@ -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),
|
|
@@ -588,8 +590,15 @@ export function formatOfflineArtifactVerification(report) {
|
|
|
588
590
|
}
|
|
589
591
|
if (report.package) {
|
|
590
592
|
lines.push(`Package bytes: ${report.package.digestSha256}`, 'Storage: unchanged; inspection only', 'Audience: private; review selected file contents before sharing', 'Signature trust: not checked', 'Trusted timestamp: not checked', 'Factual accuracy: not established');
|
|
591
|
-
for (const entry of report.package.entries)
|
|
593
|
+
for (const entry of report.package.entries) {
|
|
592
594
|
lines.push(`${entry.id}: ${entry.state} · ${entry.byteLength} bytes · ${entry.mediaType}${entry.issue ? ` · ${entry.issue}` : ''}`);
|
|
595
|
+
if (entry.imageDerivation) {
|
|
596
|
+
const declaration = entry.imageDerivation;
|
|
597
|
+
lines.push(` Declared image derivation: ${declaration.method}; ${declaration.operations.join(', ')}`, ` Parent: ${declaration.source.digestSha256} · ${declaration.source.byteLength} bytes`);
|
|
598
|
+
}
|
|
599
|
+
else if (entry.mediaType.startsWith('image/'))
|
|
600
|
+
lines.push(' Editing history: not declared');
|
|
601
|
+
}
|
|
593
602
|
for (const link of report.package.links)
|
|
594
603
|
lines.push(`Source link ${link.capsuleEntryId}: ${link.state}${link.sourceEntryId ? ` (${link.sourceEntryId})` : ''}`);
|
|
595
604
|
for (const capture of report.package.captureManifests) {
|
|
@@ -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
|
+
});
|