@slicedearth/whoisleuth-cli 2.3.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 +8 -2
- package/README.md +19 -15
- package/bin/whoisleuth.mjs +8 -1
- package/cli/archive-content-digest.mjs +14 -0
- package/cli/archive-inspect.mjs +20 -53
- package/cli/argument-values.mjs +55 -0
- package/cli/arguments.mjs +24 -408
- package/cli/artifact-validation/case-response.mjs +65 -44
- package/cli/artifact-validation/investigation-capsule.mjs +52 -31
- package/cli/artifact-validation/lookup-evidence.mjs +15 -9
- package/cli/artifact-validation/signed-review.mjs +136 -68
- package/cli/artifact-validation/structure-primitives.mjs +1 -105
- package/cli/artifact-verify.mjs +92 -24
- package/cli/assurance-arguments.mjs +56 -0
- package/cli/assurance-command-definitions.mjs +179 -0
- package/cli/assurance-command-runner.mjs +40 -5
- package/cli/bagit-folder.mjs +57 -0
- package/cli/bagit-review.mjs +23 -0
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +1 -1
- package/cli/bulk-command-runner.mjs +4 -2
- package/cli/bulk-output.mjs +29 -3
- package/cli/bulk.mjs +2 -3
- package/cli/case-command.mjs +212 -0
- package/cli/case-pack.mjs +50 -32
- 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 -206
- package/cli/command-definition.mjs +329 -0
- package/cli/command-reference.mjs +94 -1357
- package/cli/command-seeds.mjs +18 -0
- package/cli/compare.mjs +1 -1
- package/cli/completion.mjs +298 -79
- package/cli/config-profile.mjs +8 -6
- package/cli/ct-event-intake.mjs +16 -16
- package/cli/discover.mjs +5 -4
- package/cli/discovery-observation-snapshot.mjs +46 -18
- package/cli/discovery-scan-command-runner.mjs +5 -3
- package/cli/discovery-scan.mjs +7 -4
- package/cli/discovery-snapshot.mjs +2 -2
- package/cli/discovery-workflow.mjs +5 -5
- package/cli/domain-control-monitor.mjs +50 -20
- package/cli/domain-control-observations.mjs +151 -103
- package/cli/errors.mjs +37 -6
- package/cli/evidence-arguments.mjs +46 -0
- package/cli/evidence-command-definitions.mjs +69 -0
- package/cli/evidence-command-runner.mjs +18 -8
- package/cli/evidence-signing.mjs +5 -4
- package/cli/external-observation-mapping.mjs +9 -10
- package/cli/formatters/terminal-command-formats.mjs +6 -0
- package/cli/formatters/terminal-lookup.mjs +24 -46
- package/cli/formatters/terminal-shared.mjs +5 -3
- package/cli/formatters/terminal-technology.mjs +114 -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 -46
- package/cli/inline-command-runner.mjs +4 -31
- package/cli/input.mjs +2 -2
- package/cli/intake-command.mjs +107 -0
- package/cli/interactive-launcher.mjs +2 -160
- package/cli/interchange-report.mjs +8 -10
- package/cli/investigation-artifacts.mjs +176 -0
- package/cli/investigation-checkpoint.mjs +10 -0
- package/cli/investigation-folder.mjs +114 -0
- package/cli/investigation-manifest.mjs +1 -112
- package/cli/investigation-package-review.mjs +103 -0
- package/cli/investigation-plan.mjs +38 -147
- package/cli/investigation-recipes.mjs +177 -0
- package/cli/investigation-run.mjs +256 -35
- package/cli/local-document-checkpoint.mjs +170 -0
- 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-browser.mjs +5 -12
- package/cli/lookup-command-runner.mjs +33 -39
- package/cli/lookup-diff.mjs +47 -12
- package/cli/lookup-plan.mjs +16 -14
- package/cli/lookup-reconcile.mjs +15 -12
- package/cli/lookup-timeline.mjs +7 -6
- package/cli/mail-header-review.mjs +24 -247
- package/cli/mail-review.mjs +75 -21
- package/cli/manual.mjs +1 -0
- package/cli/network-arguments.mjs +51 -0
- package/cli/network-command-definitions.mjs +130 -0
- package/cli/network-command-runner.mjs +45 -31
- package/cli/offline-evidence-review.mjs +39 -6
- package/cli/output-file.mjs +31 -5
- package/cli/posture.mjs +1 -1
- package/cli/registry-doctor.mjs +3 -1
- package/cli/registry-fixture-scaffold.mjs +1 -1
- package/cli/registry-support.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +10 -9
- package/cli/review-arguments.mjs +131 -0
- package/cli/review-command-definitions.mjs +273 -0
- package/cli/review-command-runner.mjs +104 -149
- package/cli/risk-calibration.mjs +8 -8
- package/cli/runner.mjs +74 -37
- package/cli/saved-lookup.mjs +2 -2
- package/cli/selected-input-worker.mjs +63 -0
- package/cli/sharing-review.mjs +8 -80
- package/cli/signer-trust.mjs +104 -0
- package/cli/source-reliability.mjs +75 -84
- 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/terminal-input.mjs +168 -0
- package/cli/workflow-arguments.mjs +86 -0
- package/cli/workflow-command-definitions.mjs +78 -0
- package/cli/workflow-command-runner.mjs +75 -13
- package/docs/cli-reference.md +103 -30
- package/docs/cli.md +510 -25
- package/docs/contextual-reviews.md +155 -0
- package/lib/abort.mjs +4 -1
- package/lib/analyst-taxonomy.mjs +2 -40
- package/lib/application-version.mjs +1 -1
- package/lib/availability.mjs +27 -754
- package/lib/bounded-contract-normalizers.mjs +4 -0
- package/lib/bounded-file.mjs +3 -1
- package/lib/bounded-html-document.mjs +125 -0
- package/lib/bounded-json.mjs +2 -290
- package/lib/browser-library-profile.mjs +99 -130
- package/lib/browser-library-worker.mjs +112 -0
- package/lib/candidate-provenance-bounds.mjs +2 -0
- package/lib/classify.mjs +1 -1
- package/lib/client-behavior-profile.mjs +3 -3
- package/lib/ct-query.mjs +2 -27
- package/lib/ct-response-bounds.mjs +2 -9
- package/lib/ct-search.mjs +89 -46
- package/lib/dmarc-discovery.mjs +62 -0
- package/lib/dns-convergence-review.mjs +21 -8
- package/lib/dns-delegation-health.mjs +36 -22
- package/lib/dns-inheritance-review.mjs +207 -0
- package/lib/dns-intelligence.mjs +44 -29
- package/lib/dns-mx.mjs +1 -1
- package/lib/dns-query-coverage.mjs +40 -0
- package/lib/dnssec-evidence-validation.mjs +11 -3
- package/lib/domain-assurance.mjs +1 -1
- package/lib/domain-change-packet.mjs +18 -14
- package/lib/domain-change-review.mjs +40 -16
- package/lib/domain-control-flight-recorder.mjs +301 -124
- package/lib/domain-control-manifest.mjs +126 -58
- package/lib/domain-posture-analysis.mjs +41 -11
- package/lib/domain-posture-parsers.mjs +88 -6
- package/lib/domain-posture.mjs +120 -32
- package/lib/domain-registration.mjs +392 -0
- package/lib/evidence-export-privacy.mjs +568 -0
- package/lib/evidence-export.mjs +12 -593
- package/lib/evidence-report.mjs +7 -2
- package/lib/external-intelligence-risk.mjs +2 -124
- package/lib/favicon.mjs +117 -41
- package/lib/generated/cisa-kev-catalog.mjs +50 -4
- package/lib/generated/registrar-standing-catalogue.mjs +18 -10
- package/lib/generated/retire-browser-catalog.mjs +2742 -1034
- package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
- package/lib/html-signals.mjs +87 -85
- package/lib/http-evidence-bounds.mjs +2 -7
- package/lib/http-intelligence.mjs +52 -27
- 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-cache.mjs +34 -11
- package/lib/lookup-child-profile-contract.mjs +24 -15
- package/lib/lookup-contract-primitives.mjs +1 -1
- package/lib/lookup-diagnostics.mjs +2 -1
- package/lib/lookup-network-evidence-bounds.mjs +1 -10
- package/lib/lookup-progress.mjs +79 -13
- package/lib/lookup-response-contract.mjs +1077 -0
- package/lib/lookup-response.mjs +11 -24
- package/lib/lookup-source-progress.mjs +24 -35
- package/lib/lookup.mjs +49 -28
- package/lib/observed-network-context.mjs +43 -27
- package/lib/opportunity-scoring.mjs +2 -107
- package/lib/outbound-identity.mjs +1 -1
- package/lib/outbound-request-bounds.mjs +4 -1
- package/lib/page-fingerprints.mjs +87 -134
- package/lib/page-role-profile.mjs +12 -8
- package/lib/perceptual-hash-comparison.mjs +2 -34
- package/lib/perceptual-hash.mjs +79 -24
- package/lib/portable-generator.mjs +2 -29
- package/lib/project-metadata.mjs +2 -8
- package/lib/rdap-bootstrap.mjs +72 -22
- package/lib/rdap-client.mjs +26 -14
- package/lib/rdap-normalization.mjs +33 -18
- package/lib/rdap-search-workbench.mjs +1 -1
- package/lib/rdap-transport.mjs +47 -20
- package/lib/rdap-types.mjs +2 -1
- package/lib/rdap.mjs +18 -3
- package/lib/registrable-domain.mjs +2 -36
- package/lib/registrar-standing-contract.mjs +2 -1
- package/lib/registrar-standing.mjs +9 -13
- package/lib/registry-capabilities.mjs +7 -3
- package/lib/registry-capability-catalogue.mjs +29 -20
- package/lib/registry-dates.mjs +2 -190
- package/lib/registry-insights.mjs +56 -18
- package/lib/response-policy.mjs +86 -48
- package/lib/risk-calibration-summary.mjs +2 -338
- package/lib/risk-scoring.mjs +2 -345
- package/lib/rpki-evidence.mjs +8 -5
- package/lib/safe-fetch.mjs +54 -47
- package/lib/scoring-evidence-quality.mjs +2 -107
- package/lib/security-txt.mjs +10 -7
- package/lib/semantic-version.mjs +2 -54
- package/lib/service-binding-dns.mjs +35 -0
- package/lib/smtp-transport-review.mjs +75 -34
- package/lib/sslbl-certificates.generated.mjs +141 -138
- package/lib/static-html-analysis.mjs +117 -146
- package/lib/structured-data-identity.mjs +2 -1
- package/lib/technology-markup.mjs +100 -0
- package/lib/threat-intelligence-manifests.mjs +11 -139
- package/lib/threat-intelligence-runtime.mjs +24 -52
- package/lib/threat-intelligence-targets.mjs +6 -34
- package/lib/threat-intelligence-types.mjs +2 -36
- package/lib/typosquat-generator.mjs +29 -16
- package/lib/urlhaus-intelligence.mjs +8 -3
- package/lib/urlscan-intelligence.mjs +1 -1
- package/lib/website-enrichment.mjs +388 -0
- package/lib/website-security-posture.mjs +30 -6
- package/lib/website-technology.mjs +111 -71
- package/lib/whois-authority.mjs +55 -5
- package/lib/whois-chain.mjs +9 -2
- package/lib/whois-common-formats.mjs +5 -5
- package/lib/whois-contracts.mjs +2 -1
- package/lib/whois-dialects.mjs +2 -2
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-parser-support.mjs +8 -1
- package/lib/whois-parser.mjs +5 -3
- package/lib/whois-transport.mjs +23 -7
- package/package.json +14 -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-attachment-model.mjs +83 -0
- package/packages/cases/case-evidence-links.mjs +135 -0
- package/packages/cases/case-evidence-model.mjs +229 -69
- package/packages/cases/case-evidence-quality.mjs +5 -0
- package/packages/cases/case-export-input.mjs +55 -0
- package/packages/cases/case-incident-context.mjs +52 -0
- package/packages/cases/case-markdown.mjs +11 -0
- package/packages/cases/case-migration-model.mjs +98 -48
- package/packages/cases/case-model.mjs +1 -0
- package/packages/cases/case-recheck-model.mjs +149 -0
- package/packages/cases/case-record-contracts.mjs +1 -1
- package/packages/cases/case-record-core.mjs +4 -3
- package/packages/cases/case-record-operations.mjs +123 -133
- package/packages/cases/case-record-projection.mjs +30 -20
- package/packages/cases/case-record-version-input.mjs +19 -0
- package/packages/cases/case-report.mjs +114 -67
- package/packages/cases/case-requested-evidence.mjs +146 -0
- package/packages/cases/case-response-actions.mjs +756 -0
- package/packages/cases/case-response-model.mjs +49 -1129
- package/packages/cases/case-response-outcomes.mjs +338 -0
- package/packages/cases/case-response-packet-render.mjs +157 -0
- package/packages/cases/case-response-packet-types.mjs +1 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
- package/packages/cases/case-response-packet.mjs +58 -593
- package/packages/cases/case-response-records.mjs +93 -0
- package/packages/cases/case-response-review-inputs.mjs +382 -0
- package/packages/cases/case-response-values.mjs +85 -0
- package/packages/cases/case-review-package.mjs +45 -0
- package/packages/cases/case-selection.mjs +46 -0
- package/packages/cases/case-storage-model.mjs +26 -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/cases/response-route-freshness.mjs +16 -0
- package/packages/comparison/capture-context.mjs +61 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
- package/packages/comparison/favicon-similarity.mjs +1 -1
- package/packages/comparison/page-similarity.mjs +3 -2
- package/packages/comparison/relationship-evidence.mjs +103 -14
- package/packages/comparison/relationship-provenance.mjs +99 -0
- package/packages/contracts/analyst-interchange.mjs +234 -0
- package/packages/contracts/analyst-review-state-contract.mjs +11 -0
- package/packages/contracts/analyst-review-state.mjs +5 -4
- package/packages/contracts/case-portability.mjs +370 -114
- package/packages/contracts/case-views-contract.mjs +12 -0
- package/packages/contracts/case-views.mjs +26 -0
- package/packages/contracts/cli-command-semantics.mjs +3 -0
- package/packages/contracts/cli-grammar.mjs +6 -0
- package/packages/contracts/cli-lookup-brief.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +0 -1
- package/packages/contracts/context-review.mjs +24 -0
- package/packages/contracts/document-intake.mjs +11 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +98 -44
- package/packages/contracts/domain-control-manifest.mjs +128 -74
- package/packages/contracts/domain-control-monitor.mjs +59 -45
- package/packages/contracts/domain-control-review.mjs +200 -121
- package/packages/contracts/domain-name.mjs +21 -0
- package/packages/contracts/external-observation-interchange.mjs +2 -1
- package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
- package/packages/contracts/har-review.mjs +2 -0
- package/packages/contracts/http-url.mjs +2 -0
- package/packages/contracts/identity-events.mjs +4 -0
- package/packages/contracts/investigation-package-limits.mjs +24 -0
- package/packages/contracts/investigation-portability.mjs +56 -27
- package/packages/contracts/investigation-run.mjs +18 -0
- package/packages/contracts/ip-address.mjs +44 -0
- package/packages/contracts/lookup-evidence.mjs +35 -0
- package/packages/contracts/mail-authentication.mjs +5 -0
- package/packages/contracts/message-intake.mjs +31 -0
- package/packages/contracts/offline-comparison.mjs +4 -2
- package/packages/contracts/page-fingerprints.mjs +15 -0
- package/packages/contracts/passive-mail.mjs +10 -0
- package/packages/contracts/risk-calibration.mjs +0 -1
- package/packages/contracts/schema-compatibility.mjs +1 -1
- package/packages/contracts/schema-lifecycle.mjs +83 -153
- package/packages/contracts/selected-file-limits.mjs +6 -0
- package/packages/contracts/vulnerability-identifiers.mjs +5 -0
- package/packages/contracts/web-capture.mjs +10 -0
- package/packages/contracts/workspace-portability.mjs +219 -238
- package/packages/evidence/artifact-integrity.mjs +8 -2
- package/packages/evidence/artifact-structure.mjs +105 -0
- package/packages/evidence/collection-quality.mjs +67 -0
- package/packages/evidence/decision-fact.mjs +4 -1
- package/packages/evidence/dns-query-outcome.mjs +72 -0
- package/packages/evidence/domain-control-runtime.mjs +155 -27
- package/packages/evidence/domain-posture-context.mjs +79 -0
- package/packages/evidence/email-recipient.mjs +18 -0
- package/packages/evidence/image-regions.mjs +56 -0
- package/packages/evidence/latest-observations.mjs +28 -0
- package/packages/evidence/lookup-target.mjs +108 -0
- package/packages/evidence/observation.mjs +18 -1
- package/packages/evidence/passphrase-encryption.mjs +44 -0
- package/packages/evidence/png-pixels.mjs +117 -0
- package/packages/evidence/public-address-policy.mjs +83 -0
- package/packages/evidence/record-identity.mjs +72 -0
- package/packages/evidence/registry-dates.mjs +190 -0
- package/packages/evidence/retained-file.mjs +69 -0
- package/packages/evidence/review-age.mjs +14 -0
- package/packages/interchange/bagit.mjs +342 -0
- package/packages/interchange/bounded-zip-extraction.mjs +278 -0
- package/packages/interchange/crc32.mjs +17 -0
- package/packages/interchange/defensive-indicator-export.mjs +264 -0
- package/packages/interchange/external-findings-import.mjs +93 -61
- 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 +389 -0
- package/packages/investigation/acquisition-decision-packet.mjs +1 -1
- package/packages/investigation/bulk-mail-exposure.mjs +9 -14
- 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-bagit.mjs +14 -0
- package/packages/investigation/investigation-capsule.mjs +33 -5
- package/packages/investigation/investigation-manifest.mjs +200 -0
- package/packages/investigation/investigation-package-crypto.mjs +98 -0
- package/packages/investigation/investigation-package.mjs +200 -0
- package/packages/investigation/link-intake.mjs +179 -0
- package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
- package/packages/investigation/lookup-asset-graph.mjs +161 -141
- package/packages/investigation/lookup-claim-readiness.mjs +18 -0
- package/packages/investigation/lookup-investigation-brief.mjs +3 -3
- package/packages/investigation/lookup-task-guidance.mjs +58 -0
- package/packages/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/monitoring/analyst-review-state.mjs +38 -90
- package/packages/workspace/brand-profile-model.mjs +160 -22
- package/packages/workspace/bulk-columns.mjs +17 -0
- package/packages/workspace/bulk-review-model.mjs +39 -31
- package/packages/workspace/bulk-session-model.mjs +112 -27
- package/packages/workspace/bulk-sort.mjs +1 -0
- package/packages/workspace/campaign-model.mjs +1 -1
- package/packages/workspace/case-views.mjs +102 -0
- package/packages/workspace/detection-rule-model.mjs +1 -1
- package/packages/workspace/hostile-input.mjs +29 -8
- package/packages/workspace/investigation-guide.mjs +2 -2
- package/packages/workspace/investigation-template-model.mjs +45 -6
- package/packages/workspace/page-baseline.mjs +20 -17
- package/packages/workspace/relationship-observation-model.mjs +54 -35
- package/packages/workspace/shortlist-model.mjs +14 -5
- package/packages/workspace/watchlist-history.mjs +30 -14
- package/packages/workspace/watchlist-store.mjs +24 -6
- package/packages/workspace/website-snapshot-model.mjs +98 -33
- package/packages/workspace/workspace-archive-crypto.mjs +41 -61
- package/packages/workspace/workspace-archive.mjs +90 -25
- package/third-party-notices.txt +577 -17
- package/lib/hostname.mjs +0 -38
- package/packages/investigation/bulk-review-export.mjs +0 -63
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { ACQUISITION_DECISIONS, ACQUISITION_MANUAL_CHECKS, ACQUISITION_DECISION_PACKET_SCHEMA, ACQUISITION_DECISION_PACKET_VERSION, } 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
|
-
import { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION, } from "../../packages/investigation
|
|
4
|
+
import { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION, SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS, } from "../../packages/contracts/investigation-portability.mjs";
|
|
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";
|
|
8
|
+
import { BULK_SORT_KEYS } from "../../packages/workspace/bulk-sort.mjs";
|
|
9
|
+
import { normalizeExplicitIsoTimestamp } from "../../packages/evidence/observation.mjs";
|
|
5
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";
|
|
6
11
|
import { LOOKUP_CLAIM_IDS, LOOKUP_CLAIM_READINESS_VERSION, LOOKUP_CLAIM_REQUIREMENT_IDS, } from "../../packages/investigation/lookup-claim-readiness.mjs";
|
|
7
12
|
import { normalizeDomainControlManifestDocument } from "../../packages/evidence/domain-control-runtime.mjs";
|
|
8
|
-
import { DOMAIN_CHANGE_PACKET_SCHEMA,
|
|
13
|
+
import { DOMAIN_CHANGE_PACKET_SCHEMA, DOMAIN_CHANGE_PACKET_REVIEW_VERSIONS, } from "../../lib/domain-change-packet.mjs";
|
|
14
|
+
import { DOMAIN_CHANGE_REVIEW_SCHEMA } from "../../lib/domain-change-review.mjs";
|
|
9
15
|
import { DOMAIN_CONTROL_MANIFEST_SCHEMA } from "../../packages/contracts/domain-control-manifest.mjs";
|
|
10
|
-
import { INVESTIGATION_MANIFEST_SCHEMA,
|
|
11
|
-
import { array, boolean, domain,
|
|
16
|
+
import { INVESTIGATION_MANIFEST_SCHEMA, validateInvestigationManifest, } from "../investigation-manifest.mjs";
|
|
17
|
+
import { array, boolean, domain, enumeration, exact, fail, integer, iso, optionalText, sameValues, strings, text, validateIntegrity, } from "./structure-primitives.mjs";
|
|
12
18
|
const CLAIM_PASSPORT_SOURCE_STATES = ['complete', 'not_found', 'partial', 'skipped', 'unavailable', 'unknown', 'unsupported'];
|
|
13
19
|
const COMPARISON_STATES = ['conflicting', 'different', 'equal', 'missing', 'not_recorded', 'unavailable'];
|
|
14
20
|
const SOURCE_STATES = ['complete', 'error', 'not_found', 'not_recorded', 'partial', 'skipped', 'unavailable', 'unsupported'];
|
|
@@ -209,11 +215,17 @@ function validateDomainComparison(value) {
|
|
|
209
215
|
strings(comparison.limitations, 'Domain comparison limitations', 12, 600);
|
|
210
216
|
validateIntegrity(root.integrity, 'Domain comparison integrity', root.version, BULK_DOMAIN_COMPARISON_EXPORT_VERSION);
|
|
211
217
|
}
|
|
212
|
-
function
|
|
213
|
-
|
|
218
|
+
function validateNullableObservationTime(value, label) {
|
|
219
|
+
if (value !== null && normalizeExplicitIsoTimestamp(value) !== value)
|
|
220
|
+
fail(label);
|
|
221
|
+
}
|
|
222
|
+
function validateSourceCoverage(value, label, observedTime = false) {
|
|
223
|
+
const source = exact(value, ['source', 'state', ...(observedTime ? ['observedAt'] : [])], label);
|
|
214
224
|
if (typeof source.source !== 'string' || !/^[a-z][a-z0-9_-]{0,39}$/u.test(source.source))
|
|
215
225
|
fail(label);
|
|
216
226
|
enumeration(source.state, SOURCE_STATES.filter((state) => state !== 'not_recorded'), label);
|
|
227
|
+
if (observedTime)
|
|
228
|
+
validateNullableObservationTime(source.observedAt, `${label} observedAt`);
|
|
217
229
|
}
|
|
218
230
|
function validateProfileContext(value, label) {
|
|
219
231
|
const context = exact(value, ['sourceState', 'activeProfileId', 'profileUpdatedAt', 'limitation'], label);
|
|
@@ -281,29 +293,40 @@ function validateBulkView(value, label) {
|
|
|
281
293
|
text(view.caseDispositionFilter, label, 60, true);
|
|
282
294
|
enumeration(view.reviewStateFilter, ['', 'unreviewed', 'reviewing', 'reviewed', 'deferred'], label);
|
|
283
295
|
text(view.groupBy, label, 60, true);
|
|
284
|
-
enumeration(view.sortKey,
|
|
296
|
+
enumeration(view.sortKey, BULK_SORT_KEYS, label);
|
|
285
297
|
if (view.sortDirection !== 1 && view.sortDirection !== -1)
|
|
286
298
|
fail(label);
|
|
287
299
|
}
|
|
288
300
|
function validateBulkReviewManifest(value) {
|
|
289
301
|
const root = exact(value, ['schema', 'version', 'generatedAt', 'observedAt', 'lookupProfile', 'selection', 'view', 'rows', 'limitations', 'integrity'], 'Bulk review manifest');
|
|
302
|
+
const version = SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS.find((candidate) => candidate === root.version);
|
|
303
|
+
if (version === undefined)
|
|
304
|
+
fail('Bulk review manifest version');
|
|
305
|
+
const sourceTimes = version === BULK_REVIEW_MANIFEST_VERSION;
|
|
306
|
+
const maximumRows = sourceTimes ? MAX_BULK_SESSION_ROWS : 2_000;
|
|
307
|
+
const maximumSources = sourceTimes ? MAX_BULK_SESSION_SOURCES : 12;
|
|
290
308
|
iso(root.generatedAt, 'Bulk review manifest generatedAt');
|
|
291
|
-
|
|
309
|
+
if (sourceTimes)
|
|
310
|
+
validateNullableObservationTime(root.observedAt, 'Bulk review manifest observedAt');
|
|
311
|
+
else
|
|
312
|
+
iso(root.observedAt, 'Bulk review manifest observedAt');
|
|
292
313
|
enumeration(root.lookupProfile, ['deep', 'fast'], 'Bulk review manifest lookup profile');
|
|
293
314
|
const selection = exact(root.selection, ['count', 'domains'], 'Bulk review manifest selection');
|
|
294
|
-
const domains = strings(selection.domains, 'Bulk review manifest selected domains',
|
|
315
|
+
const domains = strings(selection.domains, 'Bulk review manifest selected domains', maximumRows, 253);
|
|
295
316
|
domains.forEach((item) => domain(item, 'Bulk review manifest selected domain'));
|
|
296
|
-
if (integer(selection.count, 'Bulk review manifest selection count', 0,
|
|
317
|
+
if (integer(selection.count, 'Bulk review manifest selection count', 0, maximumRows) !== domains.length)
|
|
297
318
|
fail('Bulk review manifest selection');
|
|
298
319
|
validateBulkView(root.view, 'Bulk review manifest view');
|
|
299
|
-
const rows = array(root.rows, 'Bulk review manifest rows',
|
|
320
|
+
const rows = array(root.rows, 'Bulk review manifest rows', maximumRows);
|
|
300
321
|
for (const [index, candidate] of rows.entries()) {
|
|
301
|
-
const row = exact(candidate, ['domain', 'reviewState', 'resultState', 'scanDepth', 'sourceCoverage', 'profileContext'], `Bulk review manifest row ${index + 1}`);
|
|
322
|
+
const row = exact(candidate, ['domain', 'reviewState', 'resultState', 'scanDepth', ...(sourceTimes ? ['observedAt'] : []), 'sourceCoverage', 'profileContext'], `Bulk review manifest row ${index + 1}`);
|
|
302
323
|
domain(row.domain, 'Bulk review manifest row domain');
|
|
303
324
|
enumeration(row.reviewState, ['unreviewed', 'reviewing', 'reviewed', 'deferred'], 'Bulk review manifest review state');
|
|
304
325
|
enumeration(row.resultState, ['complete', 'error'], 'Bulk review manifest result state');
|
|
305
326
|
enumeration(row.scanDepth, ['deep', 'fast'], 'Bulk review manifest scan depth');
|
|
306
|
-
|
|
327
|
+
if (sourceTimes)
|
|
328
|
+
validateNullableObservationTime(row.observedAt, 'Bulk review manifest row observedAt');
|
|
329
|
+
array(row.sourceCoverage, 'Bulk review manifest source coverage', maximumSources).forEach((item, sourceIndex) => validateSourceCoverage(item, `Bulk review manifest source ${sourceIndex + 1}`, sourceTimes));
|
|
307
330
|
validateProfileContext(row.profileContext, 'Bulk review manifest profile context');
|
|
308
331
|
if (domains[index] !== row.domain)
|
|
309
332
|
fail('Bulk review manifest selection');
|
|
@@ -311,59 +334,81 @@ function validateBulkReviewManifest(value) {
|
|
|
311
334
|
if (rows.length !== domains.length)
|
|
312
335
|
fail('Bulk review manifest selection');
|
|
313
336
|
strings(root.limitations, 'Bulk review manifest limitations', 8, 600);
|
|
314
|
-
validateIntegrity(root.integrity, 'Bulk review manifest integrity', root.version,
|
|
337
|
+
validateIntegrity(root.integrity, 'Bulk review manifest integrity', root.version, version);
|
|
315
338
|
}
|
|
316
|
-
function
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|| integer(summary.totalBytes, 'Investigation manifest total bytes', 1, MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES) !== totalBytes)
|
|
349
|
-
fail('Investigation manifest summary');
|
|
350
|
-
strings(root.limitations, 'Investigation manifest limitations', 8, 600);
|
|
351
|
-
validateIntegrity(root.integrity, 'Investigation manifest integrity', root.version, INVESTIGATION_MANIFEST_VERSION);
|
|
339
|
+
function validateSourceObservations(value, label, queryCoverage) {
|
|
340
|
+
const names = new Set();
|
|
341
|
+
let queryCount = 0;
|
|
342
|
+
return array(value, label, 16).map((candidate) => {
|
|
343
|
+
const source = exact(candidate, ['label', 'source', 'state', 'observedAt', ...(queryCoverage ? ['queries'] : [])], label);
|
|
344
|
+
const name = text(source.label, `${label} label`, 120).toLowerCase();
|
|
345
|
+
if (names.has(name))
|
|
346
|
+
fail(`${label} duplicate label`);
|
|
347
|
+
names.add(name);
|
|
348
|
+
text(source.source, `${label} source`, 240);
|
|
349
|
+
enumeration(source.state, ['observed', 'partial', 'unavailable'], `${label} state`);
|
|
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
|
+
}
|
|
369
|
+
return source;
|
|
370
|
+
});
|
|
352
371
|
}
|
|
353
|
-
function validateReviewMatrix(value, label) {
|
|
372
|
+
function validateReviewMatrix(value, label, sources, queryCoverage = false) {
|
|
373
|
+
const rowKeys = new Set();
|
|
354
374
|
for (const [rowIndex, candidate] of array(value, label, 500).entries()) {
|
|
355
375
|
const row = exact(candidate, ['owner', 'type', 'state', 'observations'], `${label} row ${rowIndex + 1}`);
|
|
356
376
|
text(row.owner, `${label} owner`, 253);
|
|
357
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);
|
|
358
382
|
const observations = array(row.observations, `${label} observations`, 16);
|
|
383
|
+
if (sources && observations.length !== sources.length)
|
|
384
|
+
fail(`${label} source coverage`);
|
|
359
385
|
let complete = 0;
|
|
360
386
|
const signatures = new Set();
|
|
387
|
+
const names = new Set();
|
|
361
388
|
for (const observationCandidate of observations) {
|
|
362
|
-
const observation = exact(observationCandidate, ['label', 'source', 'state', 'values', 'ttlRange'], `${label} observation`);
|
|
389
|
+
const observation = exact(observationCandidate, ['label', 'source', 'state', 'values', 'ttlRange', ...(sources ? ['observedAt'] : [])], `${label} observation`);
|
|
363
390
|
text(observation.label, `${label} observation label`, 120);
|
|
364
391
|
text(observation.source, `${label} observation source`, 240);
|
|
365
|
-
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`);
|
|
366
393
|
const values = strings(observation.values, `${label} observation values`, 500, 16_384);
|
|
394
|
+
if (sources) {
|
|
395
|
+
const name = String(observation.label).toLowerCase();
|
|
396
|
+
if (names.has(name))
|
|
397
|
+
fail(`${label} duplicate observation`);
|
|
398
|
+
names.add(name);
|
|
399
|
+
const source = sources.find((item) => item.label === observation.label);
|
|
400
|
+
const provenance = queryCoverage ? ['label', 'source', 'observedAt'] : ['label', 'source', 'state', 'observedAt'];
|
|
401
|
+
if (!source || provenance.some((key) => observation[key] !== source[key]))
|
|
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
|
+
}
|
|
410
|
+
iso(observation.observedAt, `${label} observation time`);
|
|
411
|
+
}
|
|
367
412
|
if (state === 'observed') {
|
|
368
413
|
complete += 1;
|
|
369
414
|
signatures.add(JSON.stringify(values));
|
|
@@ -376,20 +421,28 @@ function validateReviewMatrix(value, label) {
|
|
|
376
421
|
fail(`${label} TTL range`);
|
|
377
422
|
}
|
|
378
423
|
}
|
|
379
|
-
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';
|
|
380
425
|
if (row.state !== expected)
|
|
381
426
|
fail(`${label} state`);
|
|
382
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
|
+
}
|
|
383
433
|
}
|
|
384
|
-
function validateDomainChangeReview(value, label) {
|
|
385
|
-
const
|
|
386
|
-
|
|
434
|
+
function validateDomainChangeReview(value, label, version) {
|
|
435
|
+
const retainedTimes = version >= 2;
|
|
436
|
+
const queryCoverage = version >= 3;
|
|
437
|
+
const review = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'state', 'authoritativeRecordMatrix', 'resolverDivergenceMatrix', 'dnssecAutomation', 'acmeDependencies', 'certificate', 'services', 'hsts', 'gate', 'limitations', ...(retainedTimes ? ['sourceObservations'] : [])], label);
|
|
438
|
+
if (review.schema !== DOMAIN_CHANGE_REVIEW_SCHEMA || review.version !== version)
|
|
387
439
|
fail(label);
|
|
388
440
|
iso(review.generatedAt, `${label} generatedAt`);
|
|
389
441
|
domain(review.domain, `${label} domain`);
|
|
390
442
|
enumeration(review.state, ['ready', 'review'], `${label} state`);
|
|
391
|
-
|
|
392
|
-
validateReviewMatrix(review.
|
|
443
|
+
const sources = retainedTimes ? exact(review.sourceObservations, ['authorities', 'resolvers'], `${label} source observations`) : null;
|
|
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);
|
|
393
446
|
const automation = exact(review.dnssecAutomation, ['state', 'cdsObserved', 'cdnskeyObserved', 'csyncObserved', 'conflictingTypes', 'detail'], `${label} DNSSEC automation`);
|
|
394
447
|
enumeration(automation.state, ['not_observed', 'conflict', 'partial', 'review_ready'], `${label} DNSSEC automation`);
|
|
395
448
|
boolean(automation.cdsObserved, `${label} CDS observed`);
|
|
@@ -405,8 +458,13 @@ function validateDomainChangeReview(value, label) {
|
|
|
405
458
|
optionalText(dependency.provider, `${label} ACME provider`, 120);
|
|
406
459
|
enumeration(dependency.state, ['confirmed', 'partial', 'unknown'], `${label} ACME state`);
|
|
407
460
|
}
|
|
408
|
-
const certificate = exact(review.certificate, ['state', 'continuity', 'findings'], `${label} certificate`);
|
|
461
|
+
const certificate = exact(review.certificate, ['state', 'continuity', 'findings', ...(retainedTimes ? ['observedAt'] : [])], `${label} certificate`);
|
|
409
462
|
enumeration(certificate.state, ['not_supplied', 'observed', 'partial', 'unavailable'], `${label} certificate state`);
|
|
463
|
+
if (retainedTimes) {
|
|
464
|
+
iso(certificate.observedAt, `${label} certificate observedAt`, true);
|
|
465
|
+
if ((certificate.state === 'not_supplied') !== (certificate.observedAt === null))
|
|
466
|
+
fail(`${label} certificate observation time`);
|
|
467
|
+
}
|
|
410
468
|
enumeration(certificate.continuity, ['unknown', 'retained', 'changes'], `${label} certificate continuity`);
|
|
411
469
|
strings(certificate.findings, `${label} certificate findings`, 8, 600);
|
|
412
470
|
for (const candidate of array(review.services, `${label} services`, 1_000)) {
|
|
@@ -496,7 +554,7 @@ function validatePlannedAssurance(value, label) {
|
|
|
496
554
|
strings(assurance.limitations, `${label} limitations`, 8, 600);
|
|
497
555
|
return assurance;
|
|
498
556
|
}
|
|
499
|
-
function expectedDomainChangeSummary(before, after) {
|
|
557
|
+
function expectedDomainChangeSummary(before, after, queryCoverage) {
|
|
500
558
|
const rows = (review) => new Map(review.authoritativeRecordMatrix.map((row) => [`${row.owner}\u0000${row.type}`, row]));
|
|
501
559
|
const beforeRows = rows(before);
|
|
502
560
|
const afterRows = rows(after);
|
|
@@ -508,7 +566,7 @@ function expectedDomainChangeSummary(before, after) {
|
|
|
508
566
|
};
|
|
509
567
|
const rowEvidence = (review, row) => {
|
|
510
568
|
if (!row)
|
|
511
|
-
return { state: collectionComplete(review) ? 'complete' : 'unavailable', values: [] };
|
|
569
|
+
return { state: !queryCoverage && collectionComplete(review) ? 'complete' : 'unavailable', values: [] };
|
|
512
570
|
const observations = row.observations;
|
|
513
571
|
if (observations.some((item) => item.state !== 'observed'))
|
|
514
572
|
return { state: 'partial', values: [] };
|
|
@@ -519,21 +577,28 @@ function expectedDomainChangeSummary(before, after) {
|
|
|
519
577
|
return { state: 'complete', values: [...new Set(observations.flatMap((item) => item.values))].sort() };
|
|
520
578
|
};
|
|
521
579
|
const changed = [];
|
|
580
|
+
const reasons = queryCoverage && keys.length > 500 ? ['The comparison exceeds 500 owner and record-type pairs.'] : [];
|
|
522
581
|
for (const key of keys.slice(0, 500)) {
|
|
523
582
|
const left = rowEvidence(before, beforeRows.get(key));
|
|
524
583
|
const right = rowEvidence(after, afterRows.get(key));
|
|
525
584
|
const [owner, type] = key.split('\u0000');
|
|
526
585
|
if (left.state !== 'complete' || right.state !== 'complete') {
|
|
586
|
+
if (queryCoverage)
|
|
587
|
+
reasons.push(`Comparison evidence is incomplete for ${owner} ${type}.`);
|
|
527
588
|
continue;
|
|
528
589
|
}
|
|
529
590
|
else if (!sameValues(left.values, right.values)) {
|
|
530
591
|
changed.push({ owner: owner ?? '', type: type ?? '', beforeValues: left.values, afterValues: right.values });
|
|
531
592
|
}
|
|
532
593
|
}
|
|
533
|
-
return changed;
|
|
594
|
+
return { changed, reasons };
|
|
534
595
|
}
|
|
535
596
|
function validateDomainChangePacket(value) {
|
|
536
597
|
const root = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'reference', 'state', 'gate', 'summary', 'evidence', 'limitations', 'integrity'], 'Domain change packet');
|
|
598
|
+
const version = integer(root.version, 'Domain change packet version', 1, 1_000);
|
|
599
|
+
const reviewVersion = DOMAIN_CHANGE_PACKET_REVIEW_VERSIONS[version];
|
|
600
|
+
if (reviewVersion === undefined)
|
|
601
|
+
fail('Domain change packet version');
|
|
537
602
|
iso(root.generatedAt, 'Domain change packet generatedAt');
|
|
538
603
|
domain(root.domain, 'Domain change packet domain');
|
|
539
604
|
text(root.reference, 'Domain change packet reference', 200);
|
|
@@ -556,8 +621,8 @@ function validateDomainChangePacket(value) {
|
|
|
556
621
|
enumeration(summary.postChangeState, ['ready', 'review'], 'Domain change packet post-change state');
|
|
557
622
|
enumeration(summary.assuranceState, ['incomplete', 'needs_review', 'ready'], 'Domain change packet assurance state');
|
|
558
623
|
const evidence = exact(root.evidence, ['preChange', 'postChange', 'assurance'], 'Domain change packet evidence');
|
|
559
|
-
const pre = validateDomainChangeReview(evidence.preChange, 'Domain change packet pre-change review');
|
|
560
|
-
const post = validateDomainChangeReview(evidence.postChange, 'Domain change packet post-change review');
|
|
624
|
+
const pre = validateDomainChangeReview(evidence.preChange, 'Domain change packet pre-change review', reviewVersion);
|
|
625
|
+
const post = validateDomainChangeReview(evidence.postChange, 'Domain change packet post-change review', reviewVersion);
|
|
561
626
|
const assurance = validatePlannedAssurance(evidence.assurance, 'Domain change packet assurance');
|
|
562
627
|
const assuranceResult = assurance.result;
|
|
563
628
|
if (pre.generatedAt !== root.generatedAt || post.generatedAt !== root.generatedAt || assurance.generatedAt !== root.generatedAt
|
|
@@ -568,13 +633,14 @@ function validateDomainChangePacket(value) {
|
|
|
568
633
|
const expectedReasons = [
|
|
569
634
|
...pre.gate.reasons,
|
|
570
635
|
].map((reason) => `Pre-change evidence: ${reason}`);
|
|
571
|
-
|
|
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);
|
|
572
638
|
const boundedReasons = expectedReasons.slice(0, 100);
|
|
573
639
|
if (!sameValues(gateReasons, boundedReasons)
|
|
574
640
|
|| gate.pass !== (boundedReasons.length === 0)
|
|
575
641
|
|| root.state !== (boundedReasons.length === 0 ? 'ready' : 'review'))
|
|
576
642
|
fail('Domain change packet gate');
|
|
577
|
-
const expectedChanges =
|
|
643
|
+
const expectedChanges = comparison.changed;
|
|
578
644
|
if (changed.length !== expectedChanges.length || changed.some((candidate, index) => {
|
|
579
645
|
const item = candidate;
|
|
580
646
|
const expected = expectedChanges[index];
|
|
@@ -584,13 +650,15 @@ function validateDomainChangePacket(value) {
|
|
|
584
650
|
}))
|
|
585
651
|
fail('Domain change packet summary');
|
|
586
652
|
strings(root.limitations, 'Domain change packet limitations', 8, 600);
|
|
587
|
-
validateIntegrity(root.integrity, 'Domain change packet integrity', root.version,
|
|
653
|
+
validateIntegrity(root.integrity, 'Domain change packet integrity', root.version, version);
|
|
588
654
|
}
|
|
589
655
|
export function validateSignedDigestArtifactStructure(schema, value) {
|
|
590
656
|
if (value.schema !== schema)
|
|
591
657
|
fail('Signed review artefact');
|
|
592
658
|
if (schema === ACQUISITION_DECISION_PACKET_SCHEMA)
|
|
593
659
|
validateAcquisition(value);
|
|
660
|
+
else if (schema === MANAGED_INDICATOR_SET_SCHEMA)
|
|
661
|
+
validateManagedIndicatorSet(value);
|
|
594
662
|
else if (schema === LOOKUP_CLAIM_PASSPORT_SCHEMA)
|
|
595
663
|
validateLookupClaimPassport(value);
|
|
596
664
|
else if (schema === BULK_DOMAIN_COMPARISON_SCHEMA)
|
|
@@ -1,105 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const DIGEST_RE = /^sha256:[a-f0-9]{64}$/u;
|
|
3
|
-
const DOMAIN_RE = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u;
|
|
4
|
-
export const HEX_DIGEST_RE = /^[a-f0-9]{64}$/u;
|
|
5
|
-
export const SEMANTIC_VERSION_RE = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
|
|
6
|
-
export function fail(label) {
|
|
7
|
-
throw new TypeError(`${label} has an unsupported or malformed structure.`);
|
|
8
|
-
}
|
|
9
|
-
export function record(value, label) {
|
|
10
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
11
|
-
fail(label);
|
|
12
|
-
return value;
|
|
13
|
-
}
|
|
14
|
-
export function exact(value, keys, label) {
|
|
15
|
-
const source = record(value, label);
|
|
16
|
-
const actual = Object.keys(source);
|
|
17
|
-
if (actual.length !== keys.length || actual.some((key) => !keys.includes(key)))
|
|
18
|
-
fail(label);
|
|
19
|
-
return source;
|
|
20
|
-
}
|
|
21
|
-
export function exactOptional(value, required, optional, label) {
|
|
22
|
-
const source = record(value, label);
|
|
23
|
-
const actual = Object.keys(source);
|
|
24
|
-
if (required.some((key) => !actual.includes(key))
|
|
25
|
-
|| actual.some((key) => !required.includes(key) && !optional.includes(key)))
|
|
26
|
-
fail(label);
|
|
27
|
-
return source;
|
|
28
|
-
}
|
|
29
|
-
export function text(value, label, maximum = 2_000, allowEmpty = false) {
|
|
30
|
-
if (typeof value !== 'string' || value.length > maximum || CONTROL_RE.test(value) || (!allowEmpty && !value))
|
|
31
|
-
fail(label);
|
|
32
|
-
return value;
|
|
33
|
-
}
|
|
34
|
-
export function optionalText(value, label, maximum = 2_000) {
|
|
35
|
-
if (value !== null)
|
|
36
|
-
text(value, label, maximum);
|
|
37
|
-
}
|
|
38
|
-
export function iso(value, label, nullable = false) {
|
|
39
|
-
if (nullable && value === null)
|
|
40
|
-
return;
|
|
41
|
-
const candidate = text(value, label, 64);
|
|
42
|
-
const parsed = Date.parse(candidate);
|
|
43
|
-
if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== candidate)
|
|
44
|
-
fail(label);
|
|
45
|
-
}
|
|
46
|
-
export function integer(value, label, minimum = 0, maximum = Number.MAX_SAFE_INTEGER) {
|
|
47
|
-
if (!Number.isSafeInteger(value) || Number(value) < minimum || Number(value) > maximum)
|
|
48
|
-
fail(label);
|
|
49
|
-
return Number(value);
|
|
50
|
-
}
|
|
51
|
-
export function boolean(value, label) {
|
|
52
|
-
if (typeof value !== 'boolean')
|
|
53
|
-
fail(label);
|
|
54
|
-
}
|
|
55
|
-
export function enumeration(value, allowed, label) {
|
|
56
|
-
if (typeof value !== 'string' || !allowed.includes(value))
|
|
57
|
-
fail(label);
|
|
58
|
-
return value;
|
|
59
|
-
}
|
|
60
|
-
export function array(value, label, maximum, minimum = 0) {
|
|
61
|
-
if (!Array.isArray(value) || value.length < minimum || value.length > maximum)
|
|
62
|
-
fail(label);
|
|
63
|
-
return value;
|
|
64
|
-
}
|
|
65
|
-
export function strings(value, label, maximum, textMaximum = 2_000) {
|
|
66
|
-
const values = array(value, label, maximum);
|
|
67
|
-
for (const item of values)
|
|
68
|
-
text(item, label, textMaximum);
|
|
69
|
-
return values;
|
|
70
|
-
}
|
|
71
|
-
export function digest(value, label) {
|
|
72
|
-
if (typeof value !== 'string' || !DIGEST_RE.test(value))
|
|
73
|
-
fail(label);
|
|
74
|
-
}
|
|
75
|
-
export function sameValues(left, right) {
|
|
76
|
-
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
77
|
-
}
|
|
78
|
-
export function domain(value, label) {
|
|
79
|
-
if (typeof value !== 'string' || !DOMAIN_RE.test(value))
|
|
80
|
-
fail(label);
|
|
81
|
-
}
|
|
82
|
-
export function absoluteUrl(value, label, protocols) {
|
|
83
|
-
const candidate = text(value, label, 2_048);
|
|
84
|
-
try {
|
|
85
|
-
const parsed = new URL(candidate);
|
|
86
|
-
if (!protocols.includes(parsed.protocol) || parsed.username || parsed.password || parsed.search || parsed.hash)
|
|
87
|
-
fail(label);
|
|
88
|
-
return parsed;
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return fail(label);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
export function nullableRecord(value, label) {
|
|
95
|
-
if (value !== null)
|
|
96
|
-
record(value, label);
|
|
97
|
-
}
|
|
98
|
-
export function validateIntegrity(value, label, version, currentVersion) {
|
|
99
|
-
if (version !== currentVersion)
|
|
100
|
-
fail(label);
|
|
101
|
-
const integrity = exact(value, ['algorithm', 'canonicalization', 'digestSha256'], label);
|
|
102
|
-
if (integrity.algorithm !== 'SHA-256' || integrity.canonicalization !== 'sorted-json-v2')
|
|
103
|
-
fail(label);
|
|
104
|
-
digest(integrity.digestSha256, label);
|
|
105
|
-
}
|
|
1
|
+
export * from "../../packages/evidence/artifact-structure.mjs";
|