@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
|
@@ -39,7 +39,13 @@ function canonicalArtifactJsonV2(value) {
|
|
|
39
39
|
}
|
|
40
40
|
async function sha256ArtifactDigestV2(value) {
|
|
41
41
|
const bytes = new TextEncoder().encode(canonicalArtifactJsonV2(value));
|
|
42
|
-
|
|
42
|
+
return sha256ArtifactBytes(bytes);
|
|
43
|
+
}
|
|
44
|
+
async function sha256ArtifactBytes(bytes) {
|
|
45
|
+
const buffer = bytes.buffer;
|
|
46
|
+
if (!(buffer instanceof ArrayBuffer))
|
|
47
|
+
throw new TypeError('Artefact digests require non-shared bytes.');
|
|
48
|
+
const digest = await globalThis.crypto.subtle.digest('SHA-256', new Uint8Array(buffer, bytes.byteOffset, bytes.byteLength));
|
|
43
49
|
return `sha256:${[...new Uint8Array(digest)]
|
|
44
50
|
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
45
51
|
.join('')}`;
|
|
@@ -62,4 +68,4 @@ function sha256ArtifactDigestFor(value, canonicalization) {
|
|
|
62
68
|
? sha256ArtifactDigest(value)
|
|
63
69
|
: sha256ArtifactDigestV2(value);
|
|
64
70
|
}
|
|
65
|
-
export { SORTED_JSON_V1, SORTED_JSON_V2, canonicalArtifactJson, canonicalArtifactJsonFor, canonicalArtifactJsonV2, resolveArtifactCanonicalization, sha256ArtifactDigest, sha256ArtifactDigestFor, sha256ArtifactDigestV2, };
|
|
71
|
+
export { SORTED_JSON_V1, SORTED_JSON_V2, canonicalArtifactJson, canonicalArtifactJsonFor, canonicalArtifactJsonV2, resolveArtifactCanonicalization, sha256ArtifactDigest, sha256ArtifactDigestFor, sha256ArtifactDigestV2, sha256ArtifactBytes, };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
|
|
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
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const STATES = new Set(['complete', 'partial', 'unavailable', 'not_collected', 'unknown']);
|
|
2
|
+
export function isWebCollectionQuality(value) {
|
|
3
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
4
|
+
return false;
|
|
5
|
+
const prototype = Object.getPrototypeOf(value);
|
|
6
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
7
|
+
return false;
|
|
8
|
+
const record = value;
|
|
9
|
+
const keys = Object.keys(record);
|
|
10
|
+
if (keys.length !== 4 || keys.some(key => !['version', 'page', 'favicon', 'combined'].includes(key)
|
|
11
|
+
|| !Object.hasOwn(Object.getOwnPropertyDescriptor(record, key) ?? {}, 'value')))
|
|
12
|
+
return false;
|
|
13
|
+
return record.version === 1
|
|
14
|
+
&& typeof record.page === 'string' && STATES.has(record.page)
|
|
15
|
+
&& typeof record.favicon === 'string' && STATES.has(record.favicon)
|
|
16
|
+
&& typeof record.combined === 'string' && STATES.has(record.combined)
|
|
17
|
+
&& (record.combined !== 'complete' || (record.page === 'complete' && record.favicon === 'complete'));
|
|
18
|
+
}
|
|
19
|
+
export function normalizeWebCollectionQuality(value, depth) {
|
|
20
|
+
if (value === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (!isWebCollectionQuality(value))
|
|
23
|
+
throw new TypeError('Unsupported or invalid web collection quality.');
|
|
24
|
+
if (depth === 'fast')
|
|
25
|
+
return capturedWebCollectionQuality('not_collected', 'not_collected');
|
|
26
|
+
return { version: 1, page: value.page, favicon: value.favicon, combined: value.combined };
|
|
27
|
+
}
|
|
28
|
+
export function capturedWebCollectionQuality(page, favicon) {
|
|
29
|
+
const combined = page === 'complete' && favicon === 'complete' ? 'complete'
|
|
30
|
+
: page === favicon ? page : 'partial';
|
|
31
|
+
return { version: 1, page, favicon, combined };
|
|
32
|
+
}
|
|
33
|
+
export function webCollectionQualityForCapture(value, depth) {
|
|
34
|
+
if (depth !== 'deep')
|
|
35
|
+
return capturedWebCollectionQuality('not_collected', 'not_collected');
|
|
36
|
+
return normalizeWebCollectionQuality(value) ?? capturedWebCollectionQuality('unknown', 'unknown');
|
|
37
|
+
}
|
|
38
|
+
const PAGE_FIELDS = new Set(['activityStatus', 'pageTitle', 'hasPasswordField', 'hasExternalFormAction', 'phishingLanguageMatch', 'pageBaselineMatch']);
|
|
39
|
+
const FAVICON_FIELDS = new Set(['faviconHash', 'faviconPHash', 'faviconMatch', 'faviconNearMatch']);
|
|
40
|
+
const COMBINED_FIELDS = new Set(['riskScore', 'riskFactors', 'riskModelVersion', 'opportunityScore', 'opportunityFactors', 'reusesOfficialAssets']);
|
|
41
|
+
/** Missing historical quality never proves that a false/null value was observed. */
|
|
42
|
+
export function webCollectionAllowsComparison(field, quality, depth) {
|
|
43
|
+
if (PAGE_FIELDS.has(field))
|
|
44
|
+
return quality?.page === 'complete';
|
|
45
|
+
if (FAVICON_FIELDS.has(field))
|
|
46
|
+
return quality?.favicon === 'complete';
|
|
47
|
+
if (COMBINED_FIELDS.has(field) && depth === 'deep')
|
|
48
|
+
return quality?.combined === 'complete';
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
/** Retain the measured score while qualifying incomplete inputs at presentation. */
|
|
52
|
+
export function webCollectionScoreLimitation(quality, depth) {
|
|
53
|
+
if (depth === 'unknown')
|
|
54
|
+
return 'Collection depth is unknown; score is not comparable.';
|
|
55
|
+
return webCollectionAllowsComparison('riskScore', quality, depth)
|
|
56
|
+
? null : 'Incomplete or unknown web collection; score is not comparable.';
|
|
57
|
+
}
|
|
58
|
+
/** An incomplete current attempt cannot erase the last comparable baseline. */
|
|
59
|
+
export function mergeWebCollectionQuality(previous, current) {
|
|
60
|
+
if (!previous && !current)
|
|
61
|
+
return undefined;
|
|
62
|
+
return { version: 1,
|
|
63
|
+
page: current?.page === 'complete' ? 'complete' : previous?.page ?? current?.page ?? 'unknown',
|
|
64
|
+
favicon: current?.favicon === 'complete' ? 'complete' : previous?.favicon ?? current?.favicon ?? 'unknown',
|
|
65
|
+
combined: current?.combined === 'complete' ? 'complete' : previous?.combined ?? current?.combined ?? 'unknown',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -352,7 +352,10 @@ function boundedDataArray(value, maximum, label) {
|
|
|
352
352
|
|| Number(length) < 0) {
|
|
353
353
|
throw new TypeError(`${label} must have a stable non-negative length.`);
|
|
354
354
|
}
|
|
355
|
-
|
|
355
|
+
if (Number(length) > maximum) {
|
|
356
|
+
throw new TypeError(`${label} exceeds its ${maximum}-item admission bound; no partial fact was retained.`);
|
|
357
|
+
}
|
|
358
|
+
const retainedLength = Number(length);
|
|
356
359
|
const output = [];
|
|
357
360
|
for (let index = 0; index < retainedLength; index += 1) {
|
|
358
361
|
const descriptor = stableOwnDescriptor(value, String(index), label);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { MAX_LOOKUP_DNS_RECORDS_PER_TYPE } from "../analysis/lookup-network-evidence-bounds.mjs";
|
|
2
|
+
import { addressValue, canonicalIpAddress } from "../contracts/ip-address.mjs";
|
|
3
|
+
/** Fixed values retained in Observation v1's existing diagnostic detail slot. */
|
|
4
|
+
export const DNS_QUERY_OUTCOMES = Object.freeze({
|
|
5
|
+
records: { status: 'success', label: 'Records returned' },
|
|
6
|
+
empty_answer: { status: 'not_found', label: 'No records returned' },
|
|
7
|
+
no_data: { status: 'not_found', label: 'No data for this record type (NODATA)' },
|
|
8
|
+
name_not_found: { status: 'not_found', label: 'Name not found by resolver' },
|
|
9
|
+
timeout: { status: 'error', label: 'DNS query timed out' },
|
|
10
|
+
server_failure: { status: 'error', label: 'DNS server failed to answer (SERVFAIL)' },
|
|
11
|
+
refused: { status: 'error', label: 'DNS query refused' },
|
|
12
|
+
invalid_response: { status: 'error', label: 'DNS answer could not be read completely' },
|
|
13
|
+
error: { status: 'error', label: 'DNS query failed' },
|
|
14
|
+
skipped: { status: 'skipped', label: 'Not queried' },
|
|
15
|
+
});
|
|
16
|
+
function record(value) {
|
|
17
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
18
|
+
? value : {};
|
|
19
|
+
}
|
|
20
|
+
/** Legacy not_found diagnostics do not identify which negative answer occurred. */
|
|
21
|
+
export function readDnsQueryOutcome(value) {
|
|
22
|
+
const diagnostic = record(value);
|
|
23
|
+
const detail = diagnostic.detail;
|
|
24
|
+
return typeof detail === 'string' && Object.hasOwn(DNS_QUERY_OUTCOMES, detail)
|
|
25
|
+
&& DNS_QUERY_OUTCOMES[detail].status === diagnostic.status
|
|
26
|
+
? detail : null;
|
|
27
|
+
}
|
|
28
|
+
export function dnsQueryFailureOutcome(code) {
|
|
29
|
+
switch (code) {
|
|
30
|
+
case 'ENODATA': return 'no_data';
|
|
31
|
+
case 'ENOTFOUND': return 'name_not_found';
|
|
32
|
+
case 'ETIMEOUT': return 'timeout';
|
|
33
|
+
case 'ESERVFAIL': return 'server_failure';
|
|
34
|
+
case 'EREFUSED': return 'refused';
|
|
35
|
+
case 'EBADRESP': return 'invalid_response';
|
|
36
|
+
default: return 'error';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** One family, one source state. Unrelated mail/delegation failures cannot erase it. */
|
|
40
|
+
export function dnsAddressFamilyEvidence(dnsValue, family) {
|
|
41
|
+
const dns = record(dnsValue);
|
|
42
|
+
const diagnostic = record(record(dns.diagnostics)[family]);
|
|
43
|
+
const raw = record(dns.records)[family];
|
|
44
|
+
const candidates = Array.isArray(raw) ? raw.slice(0, MAX_LOOKUP_DNS_RECORDS_PER_TYPE) : [];
|
|
45
|
+
const addresses = candidates.flatMap(value => {
|
|
46
|
+
const address = canonicalIpAddress(value);
|
|
47
|
+
return address && addressValue(address)?.family === (family === 'a' ? 4 : 6) ? [address] : [];
|
|
48
|
+
});
|
|
49
|
+
const malformed = raw !== undefined && !Array.isArray(raw)
|
|
50
|
+
|| candidates.length !== addresses.length;
|
|
51
|
+
const truncated = diagnostic.truncated === true
|
|
52
|
+
|| typeof diagnostic.discarded === 'number' && diagnostic.discarded > 0
|
|
53
|
+
|| Array.isArray(raw) && raw.length > candidates.length;
|
|
54
|
+
const outcome = readDnsQueryOutcome(diagnostic);
|
|
55
|
+
const inconsistent = outcome === 'records' && !addresses.length
|
|
56
|
+
|| outcome !== null && outcome !== 'records' && addresses.length > 0;
|
|
57
|
+
const complete = !malformed && !truncated && !inconsistent && Array.isArray(raw)
|
|
58
|
+
&& outcome !== null && DNS_QUERY_OUTCOMES[outcome].status !== 'error' && outcome !== 'skipped';
|
|
59
|
+
const state = malformed || inconsistent ? 'error'
|
|
60
|
+
: truncated ? 'partial'
|
|
61
|
+
: outcome ? DNS_QUERY_OUTCOMES[outcome].status
|
|
62
|
+
: diagnostic.status === 'error' ? 'error'
|
|
63
|
+
: diagnostic.status === 'skipped' || dns.status === 'skipped' ? 'skipped' : 'unknown';
|
|
64
|
+
const value = addresses.length ? [...new Set(addresses)].sort().join(' · ')
|
|
65
|
+
: malformed || inconsistent ? DNS_QUERY_OUTCOMES.invalid_response.label
|
|
66
|
+
: truncated ? 'DNS answer incomplete'
|
|
67
|
+
: outcome ? DNS_QUERY_OUTCOMES[outcome].label
|
|
68
|
+
: state === 'error' ? DNS_QUERY_OUTCOMES.error.label
|
|
69
|
+
: state === 'skipped' ? DNS_QUERY_OUTCOMES.skipped.label
|
|
70
|
+
: diagnostic.status === 'not_found' ? 'Negative DNS outcome not recorded' : null;
|
|
71
|
+
return { outcome, value, state, complete, truncated };
|
|
72
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { analystReviewAgeAt, ANALYST_REVIEW_STALE_AFTER_DAYS } from "./review-age.mjs";
|
|
1
2
|
import { canonicalArtifactJsonFor, resolveArtifactCanonicalization, } from "./artifact-integrity.mjs";
|
|
2
3
|
import { normalizeDomain } from "./domain-name.mjs";
|
|
3
4
|
import { normalizeExplicitIsoTimestamp, normalizeLegacyIsoTimestamp } from "./observation.mjs";
|
|
4
|
-
import {
|
|
5
|
+
import { MAX_POSTURE_CHECK_RECORDS } from "./domain-posture-context.mjs";
|
|
6
|
+
import { DOMAIN_CONTROL_CAA_RECORD_KEYS, DOMAIN_CONTROL_DIGEST_SHA256_HEX_LENGTH, DOMAIN_CONTROL_DS_RECORD_KEYS, DOMAIN_CONTROL_MANIFEST_ENTRY_KEYS, DOMAIN_CONTROL_MANIFEST_INPUT_SCHEMA as DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA, DOMAIN_CONTROL_MANIFEST_INPUT_KEYS, DOMAIN_CONTROL_MANIFEST_INPUT_VERSION, DOMAIN_CONTROL_MANIFEST_CANONICALIZATION_ROUTES, DOMAIN_CONTROL_MANIFEST_INTEGRITY_KEYS, DOMAIN_CONTROL_MANIFEST_LIMITATIONS, DOMAIN_CONTROL_MANIFEST_ROOT_KEYS, DOMAIN_CONTROL_MANIFEST_SCHEMA as DOMAIN_CONTROL_PASSPORT_SCHEMA, DOMAIN_CONTROL_MANIFEST_VERSION, DOMAIN_CONTROL_MX_RECORD_KEYS, DOMAIN_CONTROL_RECORD_LIST_FIELDS, DOMAIN_CONTROL_RECORD_MODE_OPTIONS, DOMAIN_CONTROL_SPKI_SHA256_HEX_LENGTH, DOMAIN_CONTROL_TEXT_INPUT_BOUND_FACTOR, MAX_CANONICAL_DOMAIN_CONTROL_RECORDS, MAX_CURRENT_DOMAIN_CONTROL_RECORDS, MAX_DOMAIN_CONTROL_CAA_FLAGS, MAX_DOMAIN_CONTROL_CAA_PRESENTATION_LENGTH, MAX_DOMAIN_CONTROL_CAA_TAG_LENGTH, MAX_DOMAIN_CONTROL_CAA_VALUE_LENGTH, MAX_DOMAIN_CONTROL_DOMAIN_LENGTH, MAX_DOMAIN_CONTROL_DS_ALGORITHM, MAX_DOMAIN_CONTROL_DS_DIGEST_LENGTH, MAX_DOMAIN_CONTROL_DS_DIGEST_TYPE, MAX_DOMAIN_CONTROL_DS_KEY_TAG, MAX_DOMAIN_CONTROL_DS_PRESENTATION_LENGTH, MAX_DOMAIN_CONTROL_INPUT_RECORDS, MAX_DOMAIN_CONTROL_JSON_DEPTH, MAX_DOMAIN_CONTROL_JSON_VALUES, MAX_DOMAIN_CONTROL_MANIFEST_BYTES, MAX_DOMAIN_CONTROL_MANIFEST_ENTRIES, MAX_DOMAIN_CONTROL_MX_PRIORITY, MAX_DOMAIN_CONTROL_MX_TEXT_LENGTH, MAX_DOMAIN_CONTROL_NAME_INPUT_LENGTH, MAX_DOMAIN_CONTROL_NOTE_LENGTH, MAX_DOMAIN_CONTROL_RECORD_INTEGER_TEXT_LENGTH, MAX_DOMAIN_CONTROL_TEXT_LENGTH, MAX_DOMAIN_CONTROL_TIMESTAMP_LENGTH, MIN_DOMAIN_CONTROL_DS_DIGEST_LENGTH, MIN_DOMAIN_CONTROL_MANIFEST_ENTRIES, MIN_DOMAIN_CONTROL_RECORDS, PUBLIC_DOMAIN_CONTROL_MANIFEST_ENTRY_KEYS, PUBLIC_DOMAIN_CONTROL_MANIFEST_INPUT_VERSION, PUBLIC_DOMAIN_CONTROL_MANIFEST_LIMITATIONS, PUBLIC_DOMAIN_CONTROL_MANIFEST_VERSION, SUPPORTED_DOMAIN_CONTROL_MANIFEST_INPUT_VERSIONS, } from "../contracts/domain-control-manifest.mjs";
|
|
5
7
|
const DOMAIN_CONTROL_PASSPORT_VERSION = DOMAIN_CONTROL_MANIFEST_INPUT_VERSION;
|
|
6
8
|
export { DOMAIN_CONTROL_MANIFEST_VERSION, DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA, DOMAIN_CONTROL_PASSPORT_SCHEMA, DOMAIN_CONTROL_PASSPORT_VERSION, MAX_CANONICAL_DOMAIN_CONTROL_RECORDS, MAX_DOMAIN_CONTROL_MANIFEST_BYTES, };
|
|
7
9
|
export const MAX_DOMAIN_CONTROL_PASSPORT_BYTES = MAX_DOMAIN_CONTROL_MANIFEST_BYTES;
|
|
@@ -10,6 +12,8 @@ export const DOMAIN_CONTROL_PASSPORT_LIMITATIONS = DOMAIN_CONTROL_MANIFEST_LIMIT
|
|
|
10
12
|
const INPUT_KEYS = new Set(DOMAIN_CONTROL_MANIFEST_INPUT_KEYS);
|
|
11
13
|
const MANIFEST_KEYS = new Set(DOMAIN_CONTROL_MANIFEST_ROOT_KEYS);
|
|
12
14
|
const ENTRY_KEYS = new Set(DOMAIN_CONTROL_MANIFEST_ENTRY_KEYS);
|
|
15
|
+
const PUBLIC_ENTRY_KEYS = new Set(PUBLIC_DOMAIN_CONTROL_MANIFEST_ENTRY_KEYS);
|
|
16
|
+
const RECORD_MODE_KEYS = new Set(DOMAIN_CONTROL_RECORD_LIST_FIELDS);
|
|
13
17
|
const INTEGRITY_KEYS = new Set(DOMAIN_CONTROL_MANIFEST_INTEGRITY_KEYS);
|
|
14
18
|
const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
|
|
15
19
|
const RECORD_LIST_FIELDS = DOMAIN_CONTROL_RECORD_LIST_FIELDS;
|
|
@@ -82,6 +86,7 @@ function boundedDataArray(value, label, minimum, maximum, maximumMessage) {
|
|
|
82
86
|
return result;
|
|
83
87
|
}
|
|
84
88
|
const MX_RECORD_KEYS = new Set(DOMAIN_CONTROL_MX_RECORD_KEYS);
|
|
89
|
+
const RESOLVER_MX_RECORD_KEYS = new Set([...MX_RECORD_KEYS, 'type']);
|
|
85
90
|
const CAA_RECORD_KEYS = new Set(DOMAIN_CONTROL_CAA_RECORD_KEYS);
|
|
86
91
|
const DS_RECORD_KEYS = new Set(DOMAIN_CONTROL_DS_RECORD_KEYS);
|
|
87
92
|
const DS_DIGEST_PATTERN = new RegExp(`^[a-f0-9]{${MIN_DOMAIN_CONTROL_DS_DIGEST_LENGTH},${MAX_DOMAIN_CONTROL_DS_DIGEST_LENGTH}}$`, 'u');
|
|
@@ -155,21 +160,48 @@ function canonicalIntegerAlias(source, keys, maximum, label, fallback = null) {
|
|
|
155
160
|
}
|
|
156
161
|
return normalised[0] ?? null;
|
|
157
162
|
}
|
|
158
|
-
|
|
159
|
-
const item = structuredRecord(value, MX_RECORD_KEYS, 'Domain control MX record');
|
|
163
|
+
function mxRecordParts(value, resolverRecord = false) {
|
|
164
|
+
const item = structuredRecord(value, resolverRecord ? RESOLVER_MX_RECORD_KEYS : MX_RECORD_KEYS, 'Domain control MX record');
|
|
160
165
|
if (item) {
|
|
161
|
-
|
|
166
|
+
if (resolverRecord && Object.hasOwn(item, 'type') && item.type !== 'MX')
|
|
167
|
+
return null;
|
|
168
|
+
const host = canonicalStringAlias(item, ['exchange', 'host', 'value'], (value) => value === '' ? '.' : exchange(value), 'Domain control MX host');
|
|
162
169
|
const priority = canonicalIntegerAlias(item, ['priority', 'preference'], MAX_DOMAIN_CONTROL_MX_PRIORITY, 'Domain control MX priority');
|
|
163
|
-
return host && priority !== null ?
|
|
170
|
+
return host && priority !== null ? { priority, exchange: host } : null;
|
|
164
171
|
}
|
|
165
172
|
const candidate = recordText(value, MAX_DOMAIN_CONTROL_MX_TEXT_LENGTH).toLowerCase();
|
|
166
173
|
const match = /^(\d{1,5})\s+(.+)$/u.exec(candidate);
|
|
167
174
|
if (match) {
|
|
168
175
|
const priority = recordInteger(match[1], MAX_DOMAIN_CONTROL_MX_PRIORITY);
|
|
169
176
|
const host = exchange(match[2]);
|
|
170
|
-
return priority !== null && host ?
|
|
177
|
+
return priority !== null && host ? { priority, exchange: host } : null;
|
|
178
|
+
}
|
|
179
|
+
const host = exchange(candidate);
|
|
180
|
+
return host ? { priority: null, exchange: host } : null;
|
|
181
|
+
}
|
|
182
|
+
export function canonicalMxRecord(value) {
|
|
183
|
+
const parts = mxRecordParts(value);
|
|
184
|
+
return !parts ? '' : parts.priority === null ? parts.exchange : `${parts.priority} ${parts.exchange}`;
|
|
185
|
+
}
|
|
186
|
+
function completeMxRecord(value, resolverRecord) {
|
|
187
|
+
try {
|
|
188
|
+
const parts = mxRecordParts(value, resolverRecord);
|
|
189
|
+
return parts && parts.priority !== null
|
|
190
|
+
? { priority: parts.priority, exchange: parts.exchange } : null;
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (!(error instanceof TypeError))
|
|
194
|
+
throw error;
|
|
195
|
+
return null;
|
|
171
196
|
}
|
|
172
|
-
|
|
197
|
+
}
|
|
198
|
+
/** Complete canonical records require a preference and retain the root exchange as '.'. */
|
|
199
|
+
export function normalizeMxRecord(value) {
|
|
200
|
+
return completeMxRecord(value, false);
|
|
201
|
+
}
|
|
202
|
+
/** Accept the native resolver's optional record type without broadening import shapes. */
|
|
203
|
+
export function normalizeResolverMxRecord(value) {
|
|
204
|
+
return completeMxRecord(value, true);
|
|
173
205
|
}
|
|
174
206
|
function caaValue(value, tag) {
|
|
175
207
|
const candidate = recordText(value, MAX_DOMAIN_CONTROL_CAA_VALUE_LENGTH)
|
|
@@ -265,6 +297,88 @@ export function canonicalDomainControlRecordList(value, kind) {
|
|
|
265
297
|
.sort(compareCodeUnits)
|
|
266
298
|
.slice(0, MAX_CANONICAL_DOMAIN_CONTROL_RECORDS);
|
|
267
299
|
}
|
|
300
|
+
/** Complete field values for posture review; an invalid value never becomes an absent record. */
|
|
301
|
+
export function canonicalPostureRecords(checkId, values) {
|
|
302
|
+
if (values.length > MAX_POSTURE_CHECK_RECORDS)
|
|
303
|
+
return null;
|
|
304
|
+
const normalise = checkId === 'nameservers' ? domainControlName
|
|
305
|
+
: checkId === 'mx' ? canonicalMxRecord : checkId === 'caa' ? canonicalCaaRecord
|
|
306
|
+
: checkId === 'registration_lock'
|
|
307
|
+
? (value) => typeof value === 'string' && value.length <= 120 && !/[\u0000-\u001f\u007f]/u.test(value)
|
|
308
|
+
? value.trim().toLowerCase().replace(/[\s_-]+/gu, '') : ''
|
|
309
|
+
: null;
|
|
310
|
+
if (!normalise)
|
|
311
|
+
return null;
|
|
312
|
+
try {
|
|
313
|
+
const records = values.map(normalise);
|
|
314
|
+
if (records.some((value) => !value))
|
|
315
|
+
return null;
|
|
316
|
+
return [...new Set(records)].sort(compareCodeUnits);
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/** Complete current record inputs are rejected, not shortened, when invalid or over capacity. */
|
|
323
|
+
export function canonicalDomainControlRecords(value, field) {
|
|
324
|
+
if (value === undefined)
|
|
325
|
+
return [];
|
|
326
|
+
const rows = boundedDataArray(value, `Domain control ${field}`, 0, MAX_DOMAIN_CONTROL_INPUT_RECORDS);
|
|
327
|
+
const normalise = field === 'nameservers' ? domainControlName : field === 'ds' ? canonicalDsRecord
|
|
328
|
+
: field === 'mx' ? canonicalMxRecord : canonicalCaaRecord;
|
|
329
|
+
const values = rows.map(normalise);
|
|
330
|
+
if (values.some((item) => !item))
|
|
331
|
+
throw new TypeError(`Domain control ${field} contains an invalid record.`);
|
|
332
|
+
const records = [...new Set(values)].sort(compareCodeUnits);
|
|
333
|
+
if (records.length > MAX_CURRENT_DOMAIN_CONTROL_RECORDS)
|
|
334
|
+
throw new TypeError(`Domain control ${field} exceeds ${MAX_CURRENT_DOMAIN_CONTROL_RECORDS} records.`);
|
|
335
|
+
return records;
|
|
336
|
+
}
|
|
337
|
+
export function domainControlRecordMode(entry, field) {
|
|
338
|
+
return entry.recordModes?.[field] ?? (entry[field].length ? 'expect_records' : 'unconfigured');
|
|
339
|
+
}
|
|
340
|
+
export function normalizeDomainControlRecordModes(value, records) {
|
|
341
|
+
const source = normalizeDeclaredDomainControlRecordModes(value);
|
|
342
|
+
return Object.freeze(Object.fromEntries(DOMAIN_CONTROL_RECORD_LIST_FIELDS.map((field) => {
|
|
343
|
+
const mode = Object.hasOwn(source, field) ? source[field] : (records[field].length ? 'expect_records' : 'unconfigured');
|
|
344
|
+
if ((mode === 'expect_records') !== (records[field].length > 0)) {
|
|
345
|
+
throw new TypeError(`Domain control ${field} requires records only in the specified-records mode.`);
|
|
346
|
+
}
|
|
347
|
+
return [field, mode];
|
|
348
|
+
})));
|
|
349
|
+
}
|
|
350
|
+
export function normalizeDeclaredDomainControlRecordModes(value) {
|
|
351
|
+
const source = value === undefined ? {} : boundedDataRecord(value, RECORD_MODE_KEYS, 'Domain control record modes');
|
|
352
|
+
if (!source)
|
|
353
|
+
throw new TypeError('Domain control record modes must be an ordinary object.');
|
|
354
|
+
const modes = {};
|
|
355
|
+
for (const field of DOMAIN_CONTROL_RECORD_LIST_FIELDS) {
|
|
356
|
+
if (!Object.hasOwn(source, field))
|
|
357
|
+
continue;
|
|
358
|
+
const mode = source[field];
|
|
359
|
+
if (!DOMAIN_CONTROL_RECORD_MODE_OPTIONS.some((option) => option.value === mode))
|
|
360
|
+
throw new TypeError(`Domain control ${field} has an unsupported record mode.`);
|
|
361
|
+
modes[field] = mode;
|
|
362
|
+
}
|
|
363
|
+
return Object.freeze(modes);
|
|
364
|
+
}
|
|
365
|
+
export function normalizeDomainControlRecordSettings(value) {
|
|
366
|
+
const records = {
|
|
367
|
+
nameservers: canonicalDomainControlRecords(value.nameservers, 'nameservers'),
|
|
368
|
+
ds: canonicalDomainControlRecords(value.ds, 'ds'),
|
|
369
|
+
mx: canonicalDomainControlRecords(value.mx, 'mx'),
|
|
370
|
+
caa: canonicalDomainControlRecords(value.caa, 'caa'),
|
|
371
|
+
};
|
|
372
|
+
return { ...records, recordModes: normalizeDomainControlRecordModes(value.recordModes, records) };
|
|
373
|
+
}
|
|
374
|
+
export function domainControlEvidenceAdmission(observedAt, now) {
|
|
375
|
+
const age = analystReviewAgeAt(observedAt, now);
|
|
376
|
+
if (age === 'unknown')
|
|
377
|
+
return 'A valid source observation time at or before the review is required.';
|
|
378
|
+
if (age === 'stale')
|
|
379
|
+
return `The source observation is more than ${ANALYST_REVIEW_STALE_AFTER_DAYS} days old; collect a current observation before comparing expected settings.`;
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
268
382
|
class DomainControlJsonBudgetError extends Error {
|
|
269
383
|
}
|
|
270
384
|
class DomainControlJsonStructureError extends Error {
|
|
@@ -397,10 +511,10 @@ export function domainControlPassportSerialisedBytes(value, label = 'Domain cont
|
|
|
397
511
|
export function assertDomainControlPassportByteBudget(value, label = 'Domain control manifest') {
|
|
398
512
|
domainControlPassportSerialisedBytes(value, label);
|
|
399
513
|
}
|
|
400
|
-
function boundedDomainControlPassportEntries(value, label) {
|
|
514
|
+
function boundedDomainControlPassportEntries(value, label, legacy = false) {
|
|
401
515
|
const entries = boundedDataArray(value, label, MIN_DOMAIN_CONTROL_MANIFEST_ENTRIES, MAX_DOMAIN_CONTROL_MANIFEST_ENTRIES);
|
|
402
516
|
return entries.map((entry) => {
|
|
403
|
-
const source = boundedDataRecord(entry, ENTRY_KEYS, `${label} entry`);
|
|
517
|
+
const source = boundedDataRecord(entry, legacy ? PUBLIC_ENTRY_KEYS : ENTRY_KEYS, `${label} entry`);
|
|
404
518
|
if (!source)
|
|
405
519
|
return entry;
|
|
406
520
|
for (const field of RECORD_LIST_FIELDS) {
|
|
@@ -408,6 +522,11 @@ function boundedDomainControlPassportEntries(value, label) {
|
|
|
408
522
|
continue;
|
|
409
523
|
source[field] = boundedDataArray(source[field], `${label} entry ${field}`, MIN_DOMAIN_CONTROL_RECORDS, MAX_DOMAIN_CONTROL_INPUT_RECORDS, `${label} entry ${field} exceeds ${MAX_DOMAIN_CONTROL_INPUT_RECORDS} input records.`);
|
|
410
524
|
}
|
|
525
|
+
if (Object.hasOwn(source, 'recordModes')) {
|
|
526
|
+
source.recordModes = boundedDataRecord(source.recordModes, RECORD_MODE_KEYS, `${label} entry record modes`);
|
|
527
|
+
if (!source.recordModes)
|
|
528
|
+
throw new TypeError(`${label} entry record modes must be an ordinary object.`);
|
|
529
|
+
}
|
|
411
530
|
return source;
|
|
412
531
|
});
|
|
413
532
|
}
|
|
@@ -442,7 +561,7 @@ function digest(value) {
|
|
|
442
561
|
const candidate = value.trim().toLowerCase();
|
|
443
562
|
return SPKI_DIGEST_PATTERN.test(candidate) ? candidate : null;
|
|
444
563
|
}
|
|
445
|
-
function normalizeEntry(value) {
|
|
564
|
+
function normalizeEntry(value, legacyRecords, includeModes) {
|
|
446
565
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
447
566
|
return null;
|
|
448
567
|
const source = value;
|
|
@@ -455,38 +574,44 @@ function normalizeEntry(value) {
|
|
|
455
574
|
&& !tlsSpkiSha256) {
|
|
456
575
|
throw new TypeError('Domain control TLS public-key fingerprint must be exactly 64 hexadecimal characters.');
|
|
457
576
|
}
|
|
577
|
+
const records = {
|
|
578
|
+
nameservers: Object.freeze(legacyRecords ? hostnames(source.nameservers) : canonicalDomainControlRecords(source.nameservers, 'nameservers')),
|
|
579
|
+
ds: Object.freeze(legacyRecords ? canonicalDomainControlRecordList(source.ds, 'ds') : canonicalDomainControlRecords(source.ds, 'ds')),
|
|
580
|
+
mx: Object.freeze(legacyRecords ? canonicalDomainControlRecordList(source.mx, 'mx') : canonicalDomainControlRecords(source.mx, 'mx')),
|
|
581
|
+
caa: Object.freeze(legacyRecords ? canonicalDomainControlRecordList(source.caa, 'caa') : canonicalDomainControlRecords(source.caa, 'caa')),
|
|
582
|
+
};
|
|
458
583
|
return Object.freeze({
|
|
459
584
|
domain,
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
mx: Object.freeze(canonicalDomainControlRecordList(source.mx, 'mx')),
|
|
463
|
-
caa: Object.freeze(canonicalDomainControlRecordList(source.caa, 'caa')),
|
|
585
|
+
...records,
|
|
586
|
+
...(includeModes ? { recordModes: normalizeDomainControlRecordModes(source.recordModes, records) } : {}),
|
|
464
587
|
tlsIssuer: text(source.tlsIssuer, MAX_DOMAIN_CONTROL_TEXT_LENGTH)?.toLowerCase() ?? null,
|
|
465
588
|
tlsSpkiSha256,
|
|
466
589
|
registrarLock: source.registrarLock === 'required' || source.registrarLock === 'not_required'
|
|
467
590
|
? source.registrarLock
|
|
468
591
|
: null,
|
|
469
|
-
renewalReviewAt: timestamp(source.renewalReviewAt,
|
|
592
|
+
renewalReviewAt: timestamp(source.renewalReviewAt, legacyRecords),
|
|
470
593
|
note: text(source.note, MAX_DOMAIN_CONTROL_NOTE_LENGTH),
|
|
471
594
|
});
|
|
472
595
|
}
|
|
473
596
|
function buildUnsignedDomainControlPassportInternal(input, generatedAtValue, options = {}) {
|
|
474
597
|
const source = boundedDataRecord(input, INPUT_KEYS, 'Domain control manifest input');
|
|
475
|
-
if (!source || source.schema !== DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA ||
|
|
476
|
-
throw new TypeError(`Domain control manifest input must use ${DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA} version ${
|
|
598
|
+
if (!source || source.schema !== DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA || !SUPPORTED_DOMAIN_CONTROL_MANIFEST_INPUT_VERSIONS.some((version) => version === source.version)) {
|
|
599
|
+
throw new TypeError(`Domain control manifest input must use ${DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA} version ${SUPPORTED_DOMAIN_CONTROL_MANIFEST_INPUT_VERSIONS.join(' or ')}.`);
|
|
477
600
|
}
|
|
478
|
-
const
|
|
601
|
+
const legacy = source.version === PUBLIC_DOMAIN_CONTROL_MANIFEST_INPUT_VERSION;
|
|
602
|
+
const version = options.manifestVersion ?? DOMAIN_CONTROL_MANIFEST_VERSION;
|
|
603
|
+
const inputEntries = boundedDomainControlPassportEntries(source.entries, 'Domain control manifest input', legacy);
|
|
479
604
|
source.entries = inputEntries;
|
|
480
605
|
assertDomainControlPassportByteBudget(source, 'Domain control manifest input');
|
|
481
606
|
const generatedAt = timestamp(generatedAtValue, options.legacyGeneratedAt === true);
|
|
482
|
-
const expiresAt = timestamp(source.expiresAt,
|
|
607
|
+
const expiresAt = timestamp(source.expiresAt, legacy);
|
|
483
608
|
if (!generatedAt) {
|
|
484
609
|
throw new TypeError('Domain control manifest generation time must be valid and include an explicit timezone.');
|
|
485
610
|
}
|
|
486
611
|
if (!expiresAt || Date.parse(expiresAt) <= Date.parse(generatedAt)) {
|
|
487
612
|
throw new TypeError('Domain control manifest expiry must be a valid time after generation.');
|
|
488
613
|
}
|
|
489
|
-
const entries = inputEntries.map(normalizeEntry);
|
|
614
|
+
const entries = inputEntries.map((entry) => normalizeEntry(entry, legacy, version === DOMAIN_CONTROL_MANIFEST_VERSION));
|
|
490
615
|
if (entries.some((entry) => entry === null))
|
|
491
616
|
throw new TypeError('Domain control manifest contains an invalid entry.');
|
|
492
617
|
const normalizedEntries = entries;
|
|
@@ -498,11 +623,11 @@ function buildUnsignedDomainControlPassportInternal(input, generatedAtValue, opt
|
|
|
498
623
|
: [...normalizedEntries].sort((left, right) => compareCodeUnits(left.domain, right.domain));
|
|
499
624
|
return Object.freeze({
|
|
500
625
|
schema: DOMAIN_CONTROL_PASSPORT_SCHEMA,
|
|
501
|
-
version
|
|
626
|
+
version,
|
|
502
627
|
generatedAt,
|
|
503
628
|
expiresAt,
|
|
504
629
|
entries: Object.freeze(orderedEntries),
|
|
505
|
-
limitations: DOMAIN_CONTROL_PASSPORT_LIMITATIONS,
|
|
630
|
+
limitations: version === PUBLIC_DOMAIN_CONTROL_MANIFEST_VERSION ? PUBLIC_DOMAIN_CONTROL_MANIFEST_LIMITATIONS : DOMAIN_CONTROL_PASSPORT_LIMITATIONS,
|
|
506
631
|
});
|
|
507
632
|
}
|
|
508
633
|
export function buildUnsignedDomainControlPassport(input, generatedAtValue, options = {}) {
|
|
@@ -519,29 +644,32 @@ function normalizeDomainControlPassportDocumentInternal(value) {
|
|
|
519
644
|
|| !MANIFEST_DIGEST_PATTERN.test(integrity.digestSha256)) {
|
|
520
645
|
throw new TypeError('Domain control manifest has an unsupported or malformed structure.');
|
|
521
646
|
}
|
|
647
|
+
const canonicalization = resolveArtifactCanonicalization(source.version, integrity.canonicalization, DOMAIN_CONTROL_MANIFEST_CANONICALIZATION_ROUTES, 'Domain control manifest');
|
|
648
|
+
const legacy = source.version === PUBLIC_DOMAIN_CONTROL_MANIFEST_VERSION;
|
|
649
|
+
const expectedLimitations = legacy ? PUBLIC_DOMAIN_CONTROL_MANIFEST_LIMITATIONS : DOMAIN_CONTROL_PASSPORT_LIMITATIONS;
|
|
522
650
|
let limitations;
|
|
523
651
|
try {
|
|
524
|
-
limitations = boundedDataArray(source.limitations, 'Domain control manifest limitations',
|
|
652
|
+
limitations = boundedDataArray(source.limitations, 'Domain control manifest limitations', expectedLimitations.length, expectedLimitations.length);
|
|
525
653
|
}
|
|
526
654
|
catch {
|
|
527
655
|
throw new TypeError('Domain control manifest has an unsupported or malformed structure.');
|
|
528
656
|
}
|
|
529
|
-
if (limitations.some((item, index) => item !==
|
|
657
|
+
if (limitations.some((item, index) => item !== expectedLimitations[index])) {
|
|
530
658
|
throw new TypeError('Domain control manifest has an unsupported or malformed structure.');
|
|
531
659
|
}
|
|
532
660
|
source.limitations = limitations;
|
|
533
|
-
const manifestEntries = boundedDomainControlPassportEntries(source.entries, 'Domain control manifest');
|
|
661
|
+
const manifestEntries = boundedDomainControlPassportEntries(source.entries, 'Domain control manifest', legacy);
|
|
534
662
|
source.entries = manifestEntries;
|
|
535
663
|
source.integrity = integrity;
|
|
536
664
|
assertDomainControlPassportByteBudget(source);
|
|
537
|
-
const canonicalization = resolveArtifactCanonicalization(source.version, integrity.canonicalization, DOMAIN_CONTROL_MANIFEST_CANONICALIZATION_ROUTES, 'Domain control manifest');
|
|
538
665
|
const baseUnsigned = buildUnsignedDomainControlPassportInternal({
|
|
539
666
|
schema: DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA,
|
|
540
|
-
version: DOMAIN_CONTROL_MANIFEST_INPUT_VERSION,
|
|
667
|
+
version: legacy ? PUBLIC_DOMAIN_CONTROL_MANIFEST_INPUT_VERSION : DOMAIN_CONTROL_MANIFEST_INPUT_VERSION,
|
|
541
668
|
expiresAt: source.expiresAt,
|
|
542
669
|
entries: manifestEntries,
|
|
543
670
|
}, source.generatedAt, {
|
|
544
671
|
preserveEntryOrder: true,
|
|
672
|
+
manifestVersion: legacy ? PUBLIC_DOMAIN_CONTROL_MANIFEST_VERSION : DOMAIN_CONTROL_MANIFEST_VERSION,
|
|
545
673
|
});
|
|
546
674
|
const unsigned = baseUnsigned;
|
|
547
675
|
const { integrity: _integrity, ...suppliedUnsigned } = source;
|