@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
package/lib/domain-posture.mjs
CHANGED
|
@@ -7,6 +7,10 @@ import { nonEmptyErrorMessage } from "./error-detail.mjs";
|
|
|
7
7
|
import { safeFetch, readTextCapped } from "./safe-fetch.mjs";
|
|
8
8
|
import { whoisleuthRequestHeaders } from "./outbound-identity.mjs";
|
|
9
9
|
import { classifyMxRecords } from "./dns-mx.mjs";
|
|
10
|
+
import { collectDnsInheritanceReview } from "./dns-inheritance-review.mjs";
|
|
11
|
+
import { normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
|
|
12
|
+
import { DOMAIN_POSTURE_COMPARISON_VERSION, MAX_POSTURE_CHECK_RECORDS, POSTURE_CHECK_SOURCES } from "../packages/evidence/domain-posture-context.mjs";
|
|
13
|
+
import { canonicalPostureRecords } from "../packages/evidence/domain-control-runtime.mjs";
|
|
10
14
|
import { parseSpfRecords, parseDmarcRecords, parseMtaStsDnsRecords, parseMtaStsPolicy, parseTlsRptRecords, parseBimiRecords, parseDkimRecords, } from "./domain-posture-parsers.mjs";
|
|
11
15
|
import { buildExternalDependencies, expandSpfPolicy, validateDmarcExternalReporting, } from "./domain-posture-analysis.mjs";
|
|
12
16
|
const DNS_TIMEOUT_MS = 6000;
|
|
@@ -88,14 +92,23 @@ function withTimeout(promise, label, timeoutMs = DNS_TIMEOUT_MS, timers = {
|
|
|
88
92
|
});
|
|
89
93
|
}
|
|
90
94
|
async function resolveDns(label, factory, timeoutMs = DNS_TIMEOUT_MS, timers) {
|
|
95
|
+
const completed = (result) => {
|
|
96
|
+
let observedAt = null;
|
|
97
|
+
try {
|
|
98
|
+
const at = timers?.now?.();
|
|
99
|
+
observedAt = at instanceof Date && Number.isFinite(at.getTime()) ? at.toISOString() : null;
|
|
100
|
+
}
|
|
101
|
+
catch { /* Unknown source time remains explicit. */ }
|
|
102
|
+
return { ...result, observedAt };
|
|
103
|
+
};
|
|
91
104
|
try {
|
|
92
|
-
return { records: await withTimeout(factory(), label, timeoutMs, timers), error: null };
|
|
105
|
+
return completed({ records: await withTimeout(factory(), label, timeoutMs, timers), error: null });
|
|
93
106
|
}
|
|
94
107
|
catch (err) {
|
|
95
108
|
const error = errorRecord(err);
|
|
96
109
|
if (typeof error.code === 'string' && MISSING_DNS_CODES.has(error.code))
|
|
97
|
-
return { records: [], error: null };
|
|
98
|
-
return { records: [], error: nonEmptyErrorMessage(err, String(err)) };
|
|
110
|
+
return completed({ records: [], error: null });
|
|
111
|
+
return completed({ records: [], error: nonEmptyErrorMessage(err, String(err)) });
|
|
99
112
|
}
|
|
100
113
|
}
|
|
101
114
|
function check(id, label, status, summary, { detail = '', records = [], remediation = '' } = {}) {
|
|
@@ -189,11 +202,18 @@ function dmarcCheck(query, authorizations = []) {
|
|
|
189
202
|
parsed.failureReporting ? 'Failure reporting is configured.' : 'No failure reporting destination is configured.',
|
|
190
203
|
...parsed.issues,
|
|
191
204
|
];
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
205
|
+
const unresolvedExternal = authorizations.filter((authorization) => !['self', 'authorized'].includes(authorization.state));
|
|
206
|
+
const intendedDestinations = parsed.aggregateDestinations.length + parsed.failureDestinations.length;
|
|
207
|
+
const omittedDestinations = Math.max(0, intendedDestinations - authorizations.length);
|
|
208
|
+
if (omittedDestinations) {
|
|
209
|
+
details.push(`Reporting authorisation coverage: ${authorizations.length} of ${intendedDestinations} destinations reviewed; ${omittedDestinations} not checked within the bounded collection. Omitted destinations are not assumed authorised.`);
|
|
210
|
+
}
|
|
211
|
+
if (authorizations.length > 0) {
|
|
212
|
+
details.push(`${authorizations.length} reporting destination${authorizations.length === 1 ? '' : 's'} reviewed; `
|
|
213
|
+
+ `${unresolvedExternal.length} need further evidence.`);
|
|
214
|
+
}
|
|
215
|
+
if (unresolvedExternal.some(item => item.state === 'unavailable')) {
|
|
216
|
+
details.push('An unknown organisational boundary or unavailable query does not establish a missing authorisation requirement.');
|
|
197
217
|
}
|
|
198
218
|
if (parsed.testMode) {
|
|
199
219
|
return check('dmarc', 'DMARC', 'warning', 'Policy is in test mode (t=y)', {
|
|
@@ -213,11 +233,11 @@ function dmarcCheck(query, authorizations = []) {
|
|
|
213
233
|
remediation: 'Set sp and np to quarantine or reject unless weaker subdomain treatment is intentional.',
|
|
214
234
|
});
|
|
215
235
|
}
|
|
216
|
-
if (unresolvedExternal.length > 0) {
|
|
217
|
-
return check('dmarc', 'DMARC', 'warning', `Enforced at p=${parsed.policy};
|
|
236
|
+
if (unresolvedExternal.length > 0 || omittedDestinations > 0) {
|
|
237
|
+
return check('dmarc', 'DMARC', 'warning', `Enforced at p=${parsed.policy}; reporting authorisation is incomplete`, {
|
|
218
238
|
detail: `${details.join(' ')} ${unresolvedExternal.map((authorization) => `${authorization.destination}: ${authorization.state}.`).join(' ')}`,
|
|
219
239
|
records: parsed.records,
|
|
220
|
-
remediation: '
|
|
240
|
+
remediation: 'Review unresolved destinations and their organisational boundaries, then confirm any required external reporting authorisation before relying on delivery.',
|
|
221
241
|
});
|
|
222
242
|
}
|
|
223
243
|
if (!parsed.aggregateReporting) {
|
|
@@ -332,7 +352,11 @@ function matchesMtaPattern(host, pattern) {
|
|
|
332
352
|
const normalizedPattern = String(pattern || '').toLowerCase().replace(/\.+$/, '');
|
|
333
353
|
if (normalizedPattern.startsWith('*.')) {
|
|
334
354
|
const suffix = normalizedPattern.slice(1);
|
|
335
|
-
|
|
355
|
+
if (!normalizedHost.endsWith(suffix))
|
|
356
|
+
return false;
|
|
357
|
+
const label = normalizedHost.slice(0, -suffix.length);
|
|
358
|
+
// MTA-STS permits a wildcard for exactly one leftmost DNS label.
|
|
359
|
+
return /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(label);
|
|
336
360
|
}
|
|
337
361
|
return normalizedHost === normalizedPattern;
|
|
338
362
|
}
|
|
@@ -565,10 +589,11 @@ function defensiveMailProfileCheck(profile, input) {
|
|
|
565
589
|
function registrationLockCheck(registry) {
|
|
566
590
|
if (registry.error)
|
|
567
591
|
return queryFailureCheck('registration_lock', 'Registration controls', registry.error);
|
|
568
|
-
const statuses = registry.statuses
|
|
592
|
+
const statuses = canonicalPostureRecords('registration_lock', registry.statuses) ?? [];
|
|
569
593
|
if (statuses.length === 0) {
|
|
570
594
|
return check('registration_lock', 'Registration controls', 'info', 'Registry lock state is unavailable', {
|
|
571
595
|
detail: 'No normalised EPP status was returned. This does not describe registrar account security.',
|
|
596
|
+
records: registry.statuses,
|
|
572
597
|
});
|
|
573
598
|
}
|
|
574
599
|
const transferLocks = statuses.filter((status) => ['clienttransferprohibited', 'servertransferprohibited'].includes(status));
|
|
@@ -635,11 +660,36 @@ function buildPostureReport(domain, input) {
|
|
|
635
660
|
...(input.registry && input.nameservers ? [nameserverCheck(input.nameservers, input.registry)] : []),
|
|
636
661
|
];
|
|
637
662
|
const summary = { pass: 0, warning: 0, danger: 0, info: 0 };
|
|
638
|
-
for (const item of checks)
|
|
663
|
+
for (const item of checks) {
|
|
639
664
|
summary[item.status] += 1;
|
|
665
|
+
const source = POSTURE_CHECK_SOURCES[item.id];
|
|
666
|
+
const query = item.id === 'nameservers' ? input.nameservers : item.id === 'mx' ? input.mx : item.id === 'caa' ? input.caa : undefined;
|
|
667
|
+
const registry = item.id === 'registration_lock' ? input.registry : undefined;
|
|
668
|
+
if (!source || (!query && !registry))
|
|
669
|
+
continue;
|
|
670
|
+
const omittedRecords = Math.max(0, item.records.length - MAX_POSTURE_CHECK_RECORDS);
|
|
671
|
+
const records = item.records.slice(0, MAX_POSTURE_CHECK_RECORDS);
|
|
672
|
+
const error = query?.error ?? registry?.error;
|
|
673
|
+
const retained = canonicalPostureRecords(item.id, records);
|
|
674
|
+
const rawRecords = query?.records ?? registry?.statuses ?? [];
|
|
675
|
+
const raw = item.id === 'caa'
|
|
676
|
+
? canonicalPostureRecords(item.id, rawRecords.map((record) => record && typeof record === 'object' ? caaDisplay(record) : record))
|
|
677
|
+
: canonicalPostureRecords(item.id, rawRecords);
|
|
678
|
+
const complete = !error && omittedRecords === 0 && retained !== null && raw !== null
|
|
679
|
+
&& retained.length === raw.length && retained.every((value, index) => value === raw[index])
|
|
680
|
+
&& (!registry || (registry.statusesComplete === true && records.length > 0));
|
|
681
|
+
item.sourceContext = {
|
|
682
|
+
version: DOMAIN_POSTURE_COMPARISON_VERSION, source,
|
|
683
|
+
observedAt: normalizeExplicitIsoTimestamp(query?.observedAt ?? registry?.observedAt),
|
|
684
|
+
state: error ? 'unavailable' : complete ? 'complete' : 'partial',
|
|
685
|
+
omittedRecords: complete ? 0 : omittedRecords || null,
|
|
686
|
+
};
|
|
687
|
+
item.records = records;
|
|
688
|
+
}
|
|
640
689
|
return { domain, summary, checks };
|
|
641
690
|
}
|
|
642
|
-
async function fetchMtaStsPolicy(domain, fetcher = safeFetch) {
|
|
691
|
+
async function fetchMtaStsPolicy(domain, fetcher = safeFetch, signal) {
|
|
692
|
+
signal?.throwIfAborted();
|
|
643
693
|
const controller = new AbortController();
|
|
644
694
|
const timeout = setTimeout(() => controller.abort(), POLICY_TIMEOUT_MS);
|
|
645
695
|
try {
|
|
@@ -648,7 +698,7 @@ async function fetchMtaStsPolicy(domain, fetcher = safeFetch) {
|
|
|
648
698
|
// following HTTP redirects for policy discovery. A zero-hop safe fetch
|
|
649
699
|
// also keeps the returned body bound to the required policy host/path.
|
|
650
700
|
const res = await fetcher(url, {
|
|
651
|
-
signal: controller.signal,
|
|
701
|
+
signal: signal ? AbortSignal.any([signal, controller.signal]) : controller.signal,
|
|
652
702
|
headers: whoisleuthRequestHeaders({ Accept: 'text/plain' }),
|
|
653
703
|
}, 0);
|
|
654
704
|
if (res.status !== 200) {
|
|
@@ -659,11 +709,13 @@ async function fetchMtaStsPolicy(domain, fetcher = safeFetch) {
|
|
|
659
709
|
return { text: '', contentType: res.headers.get('content-type'), error: `Policy endpoint returned HTTP ${res.status}.` };
|
|
660
710
|
}
|
|
661
711
|
const body = await readTextCapped(res, MAX_POLICY_BYTES);
|
|
712
|
+
signal?.throwIfAborted();
|
|
662
713
|
if (body.truncated)
|
|
663
714
|
return { text: '', contentType: res.headers.get('content-type'), error: `Policy exceeds ${MAX_POLICY_BYTES} bytes.` };
|
|
664
715
|
return { text: body.text, contentType: res.headers.get('content-type'), error: null };
|
|
665
716
|
}
|
|
666
717
|
catch (err) {
|
|
718
|
+
signal?.throwIfAborted();
|
|
667
719
|
return {
|
|
668
720
|
text: '',
|
|
669
721
|
contentType: null,
|
|
@@ -676,17 +728,8 @@ async function fetchMtaStsPolicy(domain, fetcher = safeFetch) {
|
|
|
676
728
|
clearTimeout(timeout);
|
|
677
729
|
}
|
|
678
730
|
}
|
|
679
|
-
async function
|
|
680
|
-
|
|
681
|
-
resolveMx: (name) => dns.resolveMx(name),
|
|
682
|
-
resolveNs: (name) => dns.resolveNs(name),
|
|
683
|
-
resolveCaa: (name) => dns.resolveCaa(name),
|
|
684
|
-
fetchRdapRecord,
|
|
685
|
-
fetchMtaStsPolicy: (name) => fetchMtaStsPolicy(name),
|
|
686
|
-
now: () => new Date(),
|
|
687
|
-
setTimer: (callback, milliseconds) => setTimeout(callback, milliseconds),
|
|
688
|
-
clearTimer: (handle) => clearTimeout(handle),
|
|
689
|
-
}) {
|
|
731
|
+
async function collectDomainPosture(domain, { dkimSelectors = [], retiredDkimSelectors = [], mailProtectionProfile = 'standard', includeInheritedDns, signal, }, dependencies) {
|
|
732
|
+
signal?.throwIfAborted();
|
|
690
733
|
const normalizedDomain = normalizeAuditDomain(domain);
|
|
691
734
|
if (!normalizedDomain)
|
|
692
735
|
throw new Error('Invalid domain name for posture audit.');
|
|
@@ -717,10 +760,11 @@ async function checkDomainPosture(domain, { dkimSelectors = [], retiredDkimSelec
|
|
|
717
760
|
...await resolveDns(`TXT ${selector}._domainkey.${domain}`, () => dependencies.resolveTxt(`${selector}._domainkey.${domain}`), DNS_TIMEOUT_MS, dependencies),
|
|
718
761
|
})),
|
|
719
762
|
]),
|
|
720
|
-
dependencies.fetchRdapRecord('domain', domain).catch((err) => ({
|
|
763
|
+
dependencies.fetchRdapRecord('domain', domain, signal ? { signal } : {}).catch((err) => ({
|
|
721
764
|
error: nonEmptyErrorMessage(err, String(err)),
|
|
722
765
|
})),
|
|
723
766
|
]);
|
|
767
|
+
signal?.throwIfAborted();
|
|
724
768
|
const parsedMtaDns = mtaStsDns.error ? null : parseMtaStsDnsRecords(mtaStsDns.records);
|
|
725
769
|
const enrichmentStartedAt = dependencies.now();
|
|
726
770
|
if (!(enrichmentStartedAt instanceof Date) || !Number.isFinite(enrichmentStartedAt.getTime())) {
|
|
@@ -728,6 +772,7 @@ async function checkDomainPosture(domain, { dkimSelectors = [], retiredDkimSelec
|
|
|
728
772
|
}
|
|
729
773
|
const enrichmentDeadline = enrichmentStartedAt.getTime() + POSTURE_ENRICHMENT_DEADLINE_MS;
|
|
730
774
|
const resolveEnrichmentTxt = (name) => {
|
|
775
|
+
signal?.throwIfAborted();
|
|
731
776
|
const observedAt = dependencies.now();
|
|
732
777
|
if (!(observedAt instanceof Date) || !Number.isFinite(observedAt.getTime())) {
|
|
733
778
|
return Promise.resolve({ records: [], error: 'The bounded posture-enrichment clock was unavailable.' });
|
|
@@ -737,11 +782,22 @@ async function checkDomainPosture(domain, { dkimSelectors = [], retiredDkimSelec
|
|
|
737
782
|
return Promise.resolve({ records: [], error: 'The bounded posture-enrichment deadline was reached.' });
|
|
738
783
|
return resolveDns(`TXT ${name}`, () => dependencies.resolveTxt(name), Math.min(DNS_TIMEOUT_MS, remaining), dependencies);
|
|
739
784
|
};
|
|
740
|
-
const
|
|
741
|
-
parsedMtaDns?.valid ? dependencies.fetchMtaStsPolicy(domain) : Promise.resolve(null),
|
|
785
|
+
const enrichment = [
|
|
786
|
+
parsedMtaDns?.valid ? dependencies.fetchMtaStsPolicy(domain, signal) : Promise.resolve(null),
|
|
742
787
|
expandSpfPolicy(domain, spf, resolveEnrichmentTxt),
|
|
743
|
-
|
|
744
|
-
|
|
788
|
+
includeInheritedDns === true
|
|
789
|
+
? (dependencies.collectDnsInheritanceReview
|
|
790
|
+
? dependencies.collectDnsInheritanceReview(domain, dmarc, nameservers, signal ? { signal } : {})
|
|
791
|
+
: Promise.reject(new TypeError('The explicit DNS review collector is unavailable.')))
|
|
792
|
+
: validateDmarcExternalReporting(domain, dmarc, resolveEnrichmentTxt)
|
|
793
|
+
.then(dmarcAuthorizations => ({ checks: [], dmarcAuthorizations })),
|
|
794
|
+
];
|
|
795
|
+
// A rejected policy request must not release the operation's capacity while
|
|
796
|
+
// other started enrichment collectors are still settling.
|
|
797
|
+
const [mtaStsPolicy, spfExpansion, dnsReview] = await Promise.all(enrichment)
|
|
798
|
+
.finally(() => Promise.allSettled(enrichment));
|
|
799
|
+
const { dmarcAuthorizations, checks: inheritanceChecks } = dnsReview;
|
|
800
|
+
signal?.throwIfAborted();
|
|
745
801
|
const dnssec = !rdap
|
|
746
802
|
? { value: null, error: 'RDAP did not return a domain record.' }
|
|
747
803
|
: 'error' in rdap
|
|
@@ -758,6 +814,8 @@ async function checkDomainPosture(domain, { dkimSelectors = [], retiredDkimSelec
|
|
|
758
814
|
dsRecordCount: parsedDomain.dsData.length,
|
|
759
815
|
dsDataTruncated: parsedDomain.dsDataTruncated,
|
|
760
816
|
error: null,
|
|
817
|
+
observedAt: normalizeExplicitIsoTimestamp(rdap && !('error' in rdap) ? rdap.fetchedAt : null),
|
|
818
|
+
statusesComplete: parsedDomain.statusesTruncated === false && parsedDomain.serverTruncated === false,
|
|
761
819
|
}
|
|
762
820
|
: {
|
|
763
821
|
statuses: [],
|
|
@@ -794,6 +852,10 @@ async function checkDomainPosture(domain, { dkimSelectors = [], retiredDkimSelec
|
|
|
794
852
|
if (!(checkedAt instanceof Date) || !Number.isFinite(checkedAt.getTime())) {
|
|
795
853
|
throw new TypeError('Domain-posture completion time must be valid.');
|
|
796
854
|
}
|
|
855
|
+
for (const item of inheritanceChecks) {
|
|
856
|
+
report.checks.push(item);
|
|
857
|
+
report.summary[item.status] += 1;
|
|
858
|
+
}
|
|
797
859
|
return {
|
|
798
860
|
...report,
|
|
799
861
|
checkedAt: checkedAt.toISOString(),
|
|
@@ -805,4 +867,30 @@ async function checkDomainPosture(domain, { dkimSelectors = [], retiredDkimSelec
|
|
|
805
867
|
externalDependencies,
|
|
806
868
|
};
|
|
807
869
|
}
|
|
870
|
+
async function checkDomainPosture(domain, options = {}, dependencies) {
|
|
871
|
+
options.signal?.throwIfAborted();
|
|
872
|
+
// Cancelling one audit must never cancel another audit's resolver.
|
|
873
|
+
const resolver = dependencies ? null : new dns.Resolver();
|
|
874
|
+
const cancel = () => resolver?.cancel();
|
|
875
|
+
options.signal?.addEventListener('abort', cancel, { once: true });
|
|
876
|
+
try {
|
|
877
|
+
options.signal?.throwIfAborted();
|
|
878
|
+
return await collectDomainPosture(domain, options, dependencies ?? {
|
|
879
|
+
resolveTxt: name => resolver.resolveTxt(name),
|
|
880
|
+
resolveMx: name => resolver.resolveMx(name),
|
|
881
|
+
resolveNs: name => resolver.resolveNs(name),
|
|
882
|
+
resolveCaa: name => resolver.resolveCaa(name),
|
|
883
|
+
fetchRdapRecord,
|
|
884
|
+
fetchMtaStsPolicy: (name, signal) => fetchMtaStsPolicy(name, safeFetch, signal),
|
|
885
|
+
now: () => new Date(),
|
|
886
|
+
setTimer: (callback, milliseconds) => setTimeout(callback, milliseconds),
|
|
887
|
+
clearTimer: handle => clearTimeout(handle),
|
|
888
|
+
collectDnsInheritanceReview,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
finally {
|
|
892
|
+
options.signal?.removeEventListener('abort', cancel);
|
|
893
|
+
cancel();
|
|
894
|
+
}
|
|
895
|
+
}
|
|
808
896
|
export { normalizeAuditDomain, normalizeDkimSelectors, normalizeMailProtectionProfile, matchesMtaPattern, fetchMtaStsPolicy, buildPostureReport, checkDomainPosture, };
|