@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/security-txt.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Risk. Published contact and policy values are normalized into a small,
|
|
5
5
|
// bounded record; the response body itself is discarded after parsing.
|
|
6
6
|
import { safeFetchDetailed, readTextCapped } from "./safe-fetch.mjs";
|
|
7
|
-
import { createObservation } from "../packages/evidence/observation.mjs";
|
|
7
|
+
import { createObservation, normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
|
|
8
8
|
const SECURITY_TXT_VERSION = 1;
|
|
9
9
|
const SECURITY_TXT_PATH = '/.well-known/security.txt';
|
|
10
10
|
const SECURITY_TXT_TIMEOUT_MS = 6_000;
|
|
@@ -18,7 +18,6 @@ const CONTROL_CHARACTER_RE = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
|
|
|
18
18
|
const URI_CONTROL_CHARACTER_RE = /[\u0000-\u001f\u007f]/u;
|
|
19
19
|
const FIELD_RE = /^([A-Za-z][A-Za-z-]*):[ \t]*(.*)$/u;
|
|
20
20
|
const LANGUAGE_RE = /^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/iu;
|
|
21
|
-
const RFC3339_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/u;
|
|
22
21
|
const CONTACT_PROTOCOLS = new Set(['https:', 'mailto:', 'tel:']);
|
|
23
22
|
const HTTPS_PROTOCOLS = new Set(['https:']);
|
|
24
23
|
const ENCRYPTION_PROTOCOLS = new Set(['https:', 'dns:', 'openpgp4fpr:']);
|
|
@@ -222,8 +221,9 @@ function parseSecurityTxt(text, options = {}) {
|
|
|
222
221
|
malformedCount += languages.length - valid.length;
|
|
223
222
|
}
|
|
224
223
|
else if (field === 'expires') {
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
const expiry = normalizeExplicitIsoTimestamp(value);
|
|
225
|
+
if (expiry)
|
|
226
|
+
expires.push(expiry);
|
|
227
227
|
else
|
|
228
228
|
malformedCount += 1;
|
|
229
229
|
}
|
|
@@ -235,15 +235,18 @@ function parseSecurityTxt(text, options = {}) {
|
|
|
235
235
|
const boundedPolicies = uniqueBounded(policies);
|
|
236
236
|
const boundedEncryption = uniqueBounded(encryption);
|
|
237
237
|
const boundedCanonical = uniqueBounded(canonical);
|
|
238
|
+
const boundedCanonicalComparison = uniqueBounded(canonicalComparison);
|
|
239
|
+
const canonicalComparisonTruncated = valuesTruncated || boundedCanonicalComparison.truncated;
|
|
238
240
|
const boundedLanguages = uniqueBounded(preferredLanguages);
|
|
239
|
-
valuesTruncated ||= [boundedContacts, boundedPolicies, boundedEncryption, boundedCanonical, boundedLanguages]
|
|
241
|
+
valuesTruncated ||= [boundedContacts, boundedPolicies, boundedEncryption, boundedCanonical, boundedCanonicalComparison, boundedLanguages]
|
|
240
242
|
.some((entry) => entry.truncated);
|
|
241
243
|
const expiresAt = expires.length === 1 ? expires[0] : null;
|
|
242
244
|
const finalUrl = options.finalUrl ? normalizedHttpsUrl(options.finalUrl) : null;
|
|
243
245
|
const finalComparisonUrl = options.finalUrl ? normalizedCanonicalComparisonUrl(options.finalUrl) : null;
|
|
244
246
|
const canonicalMatches = boundedCanonical.values.length && finalUrl && finalComparisonUrl
|
|
245
|
-
?
|
|
246
|
-
|
|
247
|
+
? boundedCanonicalComparison.values.includes(finalComparisonUrl)
|
|
248
|
+
? true
|
|
249
|
+
: canonicalComparisonTruncated ? null : false
|
|
247
250
|
: null;
|
|
248
251
|
const stale = Boolean(expiresAt && Date.parse(expiresAt) <= (options.now ?? Date.now()));
|
|
249
252
|
const requiredMalformed = boundedContacts.values.length === 0 || expires.length !== 1;
|
package/lib/semantic-version.mjs
CHANGED
|
@@ -1,54 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// errors retain their local context without reimplementing SemVer parsing.
|
|
4
|
-
export const MAX_SEMANTIC_VERSION_LENGTH = 128;
|
|
5
|
-
function fail(label, detail) {
|
|
6
|
-
throw new TypeError(`${label} ${detail}`);
|
|
7
|
-
}
|
|
8
|
-
function validateIdentifierList(value, label, kind, forbidNumericLeadingZero) {
|
|
9
|
-
const identifiers = value.split('.');
|
|
10
|
-
if (identifiers.some((identifier) => identifier.length === 0)) {
|
|
11
|
-
fail(label, `${kind} contains an empty identifier.`);
|
|
12
|
-
}
|
|
13
|
-
for (const identifier of identifiers) {
|
|
14
|
-
if (!/^[0-9A-Za-z-]+$/u.test(identifier)) {
|
|
15
|
-
fail(label, `${kind} contains an invalid identifier.`);
|
|
16
|
-
}
|
|
17
|
-
if (forbidNumericLeadingZero && /^[0-9]+$/u.test(identifier) && identifier.length > 1 && identifier.startsWith('0')) {
|
|
18
|
-
fail(label, `${kind} numeric identifiers must not contain leading zeroes.`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export function normalizeBoundedSemanticVersion(value, label = 'Semantic') {
|
|
23
|
-
if (typeof value !== 'string'
|
|
24
|
-
|| value.length === 0
|
|
25
|
-
|| value.length > MAX_SEMANTIC_VERSION_LENGTH
|
|
26
|
-
|| value.trim() !== value) {
|
|
27
|
-
fail(label, 'version must be a bounded semantic-version string.');
|
|
28
|
-
}
|
|
29
|
-
const buildParts = value.split('+');
|
|
30
|
-
if (buildParts.length > 2)
|
|
31
|
-
fail(label, 'version contains more than one build-metadata separator.');
|
|
32
|
-
const [precedence, buildMetadata] = buildParts;
|
|
33
|
-
if (!precedence)
|
|
34
|
-
fail(label, 'version must include numeric precedence.');
|
|
35
|
-
const prereleaseSeparator = precedence.indexOf('-');
|
|
36
|
-
const core = prereleaseSeparator === -1 ? precedence : precedence.slice(0, prereleaseSeparator);
|
|
37
|
-
const prerelease = prereleaseSeparator === -1 ? undefined : precedence.slice(prereleaseSeparator + 1);
|
|
38
|
-
const coreParts = core.split('.');
|
|
39
|
-
if (coreParts.length !== 3 || coreParts.some((part) => !/^(?:0|[1-9][0-9]*)$/u.test(part))) {
|
|
40
|
-
fail(label, 'version must contain major, minor, and patch numbers without leading zeroes.');
|
|
41
|
-
}
|
|
42
|
-
if (prerelease !== undefined)
|
|
43
|
-
validateIdentifierList(prerelease, label, 'prerelease', true);
|
|
44
|
-
if (buildMetadata !== undefined)
|
|
45
|
-
validateIdentifierList(buildMetadata, label, 'build metadata', false);
|
|
46
|
-
return value;
|
|
47
|
-
}
|
|
48
|
-
export function normalizeBoundedStableSemanticVersion(value, label = 'Stable semantic') {
|
|
49
|
-
const version = normalizeBoundedSemanticVersion(value, label);
|
|
50
|
-
if (version.includes('-') || version.includes('+')) {
|
|
51
|
-
fail(label, 'version must not include prerelease or build identifiers.');
|
|
52
|
-
}
|
|
53
|
-
return version;
|
|
54
|
-
}
|
|
1
|
+
// Retained public import; the runtime-neutral implementation lives in the domain layer.
|
|
2
|
+
export * from "../packages/analysis/semantic-version.mjs";
|
|
@@ -376,6 +376,8 @@ function parseServiceBindingDnsResponse(messageValue, expected) {
|
|
|
376
376
|
throw new ServiceBindingDnsError('DNS response question does not match the query');
|
|
377
377
|
}
|
|
378
378
|
const records = [];
|
|
379
|
+
const aliases = new Map();
|
|
380
|
+
const answerOwners = new Set();
|
|
379
381
|
let truncated = false;
|
|
380
382
|
for (let index = 0; index < recordCount; index += 1) {
|
|
381
383
|
const ownerResult = decodeDnsName(message, offset);
|
|
@@ -388,9 +390,18 @@ function parseServiceBindingDnsResponse(messageValue, expected) {
|
|
|
388
390
|
offset += 10;
|
|
389
391
|
const rdataEnd = offset + rdataLength;
|
|
390
392
|
assertRange(message, offset, rdataLength);
|
|
393
|
+
if (index < answerCount && rrClass === DNS_CLASS_IN && rrType === 5) {
|
|
394
|
+
const alias = decodeDnsName(message, offset);
|
|
395
|
+
if (alias.nextOffset !== rdataEnd || alias.name === '.'
|
|
396
|
+
|| (aliases.has(ownerResult.name) && aliases.get(ownerResult.name) !== alias.name)) {
|
|
397
|
+
throw new ServiceBindingDnsError('DNS answer contains an invalid or ambiguous alias');
|
|
398
|
+
}
|
|
399
|
+
aliases.set(ownerResult.name, alias.name);
|
|
400
|
+
}
|
|
391
401
|
if (index < answerCount
|
|
392
402
|
&& rrType === recordTypeCode(expected.type)
|
|
393
403
|
&& rrClass === DNS_CLASS_IN) {
|
|
404
|
+
answerOwners.add(ownerResult.name);
|
|
394
405
|
const parsed = parseServiceBindingRecord(message, ownerResult.name, expected.type, ttl, offset, rdataEnd);
|
|
395
406
|
truncated = parsed.truncated || truncated;
|
|
396
407
|
if (records.length < MAX_SERVICE_BINDING_RECORDS)
|
|
@@ -402,6 +413,22 @@ function parseServiceBindingDnsResponse(messageValue, expected) {
|
|
|
402
413
|
}
|
|
403
414
|
if (offset !== message.length)
|
|
404
415
|
throw new ServiceBindingDnsError('DNS response has trailing bytes');
|
|
416
|
+
const allowedOwners = new Set();
|
|
417
|
+
let owner = question.name;
|
|
418
|
+
while (true) {
|
|
419
|
+
if (allowedOwners.has(owner))
|
|
420
|
+
throw new ServiceBindingDnsError('DNS answer contains a cyclic alias chain');
|
|
421
|
+
allowedOwners.add(owner);
|
|
422
|
+
const next = aliases.get(owner);
|
|
423
|
+
if (!next)
|
|
424
|
+
break;
|
|
425
|
+
if (answerOwners.has(owner))
|
|
426
|
+
throw new ServiceBindingDnsError('DNS alias owner also contains service-binding records');
|
|
427
|
+
owner = next;
|
|
428
|
+
}
|
|
429
|
+
if ([...answerOwners].some(value => !allowedOwners.has(value))) {
|
|
430
|
+
throw new ServiceBindingDnsError('DNS service-binding answer owner is not bound to the query');
|
|
431
|
+
}
|
|
405
432
|
const unique = new Map();
|
|
406
433
|
for (const record of records)
|
|
407
434
|
unique.set(JSON.stringify(record), record);
|
|
@@ -442,6 +469,7 @@ function parseResolverEndpoint(value) {
|
|
|
442
469
|
}
|
|
443
470
|
function defaultTcpExchange(query, resolver, options) {
|
|
444
471
|
return new Promise((resolve, reject) => {
|
|
472
|
+
options.signal?.throwIfAborted();
|
|
445
473
|
const socket = createConnection({
|
|
446
474
|
host: resolver.address,
|
|
447
475
|
port: resolver.port,
|
|
@@ -457,12 +485,19 @@ function defaultTcpExchange(query, resolver, options) {
|
|
|
457
485
|
settled = true;
|
|
458
486
|
if (hardDeadline)
|
|
459
487
|
clearTimeout(hardDeadline);
|
|
488
|
+
options.signal?.removeEventListener('abort', abort);
|
|
460
489
|
socket.destroy();
|
|
461
490
|
if (error)
|
|
462
491
|
reject(error);
|
|
463
492
|
else
|
|
464
493
|
resolve(response);
|
|
465
494
|
};
|
|
495
|
+
const abort = () => finish(options.signal?.reason ?? new Error('DNS TCP query cancelled'));
|
|
496
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
497
|
+
if (options.signal?.aborted) {
|
|
498
|
+
abort();
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
466
501
|
hardDeadline = setTimeout(() => finish(new ServiceBindingDnsError('DNS TCP query timed out', 'ETIMEOUT')), options.timeoutMs);
|
|
467
502
|
hardDeadline.unref();
|
|
468
503
|
socket.setTimeout(options.timeoutMs, () => finish(new ServiceBindingDnsError('DNS TCP query timed out', 'ETIMEOUT')));
|
|
@@ -25,6 +25,7 @@ const MAX_SMTP_RESPONSE_BYTES = 16 * 1024;
|
|
|
25
25
|
const MAX_SMTP_REPLY_LINES = 64;
|
|
26
26
|
const MAX_SMTP_LINE_BYTES = 1_000;
|
|
27
27
|
const MAX_SMTP_CAPABILITIES = 32;
|
|
28
|
+
const SMTP_CAPABILITY_OMISSION = 'The retained EHLO capability inventory is incomplete; additional names were omitted. STARTTLS detection used the full bounded reply.';
|
|
28
29
|
const MAX_SMTP_CERTIFICATE_BYTES = 256 * 1024;
|
|
29
30
|
const SMTP_ENDPOINT_TIMEOUT_MS = 8_000;
|
|
30
31
|
const MAIL_TRANSPORT_TOTAL_TIMEOUT_MS = 30_000;
|
|
@@ -138,19 +139,27 @@ function normalizeSmtpReply(linesValue) {
|
|
|
138
139
|
const sha256 = crypto.createHash('sha256').update(`${lines.join('\r\n')}\r\n`, 'ascii').digest('hex');
|
|
139
140
|
return Object.freeze({ code, lines: Object.freeze(lines), bytes, sha256 });
|
|
140
141
|
}
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
function smtpCapabilityInventory(reply) {
|
|
143
|
+
// The reply has already passed line and byte admission. The display cap must
|
|
144
|
+
// not decide which protocol extensions the endpoint advertised.
|
|
145
|
+
const seen = new Set();
|
|
144
146
|
const output = [];
|
|
145
|
-
for (const line of reply.lines.slice(1)) {
|
|
147
|
+
for (const line of reply?.code === 250 ? reply.lines.slice(1) : []) {
|
|
146
148
|
const capability = line.slice(4).trim().split(/\s+/u)[0]?.toUpperCase() || '';
|
|
147
|
-
if (!/^[A-Z0-9][A-Z0-9-]{0,31}$/u.test(capability) ||
|
|
149
|
+
if (!/^[A-Z0-9][A-Z0-9-]{0,31}$/u.test(capability) || seen.has(capability))
|
|
148
150
|
continue;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
seen.add(capability);
|
|
152
|
+
if (output.length < MAX_SMTP_CAPABILITIES)
|
|
153
|
+
output.push(capability);
|
|
152
154
|
}
|
|
153
|
-
return Object.freeze(
|
|
155
|
+
return Object.freeze({
|
|
156
|
+
capabilities: Object.freeze(output.sort()),
|
|
157
|
+
starttlsAdvertised: seen.has('STARTTLS'),
|
|
158
|
+
truncated: seen.size > output.length,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function smtpCapabilities(reply) {
|
|
162
|
+
return smtpCapabilityInventory(reply).capabilities;
|
|
154
163
|
}
|
|
155
164
|
async function runSmtpConversation(hostname, connection) {
|
|
156
165
|
try {
|
|
@@ -162,8 +171,7 @@ async function runSmtpConversation(hostname, connection) {
|
|
|
162
171
|
connection.assertIdle();
|
|
163
172
|
await connection.write(EHLO_COMMAND);
|
|
164
173
|
const ehlo = await connection.readReply();
|
|
165
|
-
const capabilities =
|
|
166
|
-
const starttlsAdvertised = capabilities.includes('STARTTLS');
|
|
174
|
+
const { capabilities, starttlsAdvertised } = smtpCapabilityInventory(ehlo);
|
|
167
175
|
if (!starttlsAdvertised) {
|
|
168
176
|
const diagnostics = connection.diagnostics();
|
|
169
177
|
return Object.freeze({ connectedAddress: connection.remoteAddress, greeting, ehlo, capabilities, starttlsAdvertised, starttlsReply: null, starttlsState: 'not_advertised', tls: null, ...diagnostics });
|
|
@@ -197,12 +205,17 @@ class SocketReplyReader {
|
|
|
197
205
|
#endedError = null;
|
|
198
206
|
bytesRead = 0;
|
|
199
207
|
lineCount = 0;
|
|
208
|
+
#onData = (chunk) => this.#receive(chunk);
|
|
209
|
+
#onError = (error) => this.#fail(error);
|
|
210
|
+
#onEnd = () => this.#fail(new Error('SMTP server closed the connection before completing a reply.'));
|
|
211
|
+
#onTimeout = () => this.#fail(new Error('SMTP transport timed out.'));
|
|
200
212
|
constructor(socket) {
|
|
201
213
|
this.#socket = socket;
|
|
202
|
-
socket.on('data',
|
|
203
|
-
socket.once('error',
|
|
204
|
-
socket.once('end',
|
|
205
|
-
socket.once('
|
|
214
|
+
socket.on('data', this.#onData);
|
|
215
|
+
socket.once('error', this.#onError);
|
|
216
|
+
socket.once('end', this.#onEnd);
|
|
217
|
+
socket.once('close', this.#onEnd);
|
|
218
|
+
socket.once('timeout', this.#onTimeout);
|
|
206
219
|
}
|
|
207
220
|
#receive(chunk) {
|
|
208
221
|
if (this.#endedError)
|
|
@@ -273,10 +286,14 @@ class SocketReplyReader {
|
|
|
273
286
|
}
|
|
274
287
|
release() {
|
|
275
288
|
this.assertIdle();
|
|
276
|
-
this
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.#socket.
|
|
289
|
+
this.detach();
|
|
290
|
+
}
|
|
291
|
+
detach() {
|
|
292
|
+
this.#socket.removeListener('data', this.#onData);
|
|
293
|
+
this.#socket.removeListener('error', this.#onError);
|
|
294
|
+
this.#socket.removeListener('end', this.#onEnd);
|
|
295
|
+
this.#socket.removeListener('close', this.#onEnd);
|
|
296
|
+
this.#socket.removeListener('timeout', this.#onTimeout);
|
|
280
297
|
}
|
|
281
298
|
}
|
|
282
299
|
function smtpStartTlsOptions(socket, hostname) {
|
|
@@ -289,10 +306,18 @@ function smtpStartTlsOptions(socket, hostname) {
|
|
|
289
306
|
checkServerIdentity: () => undefined,
|
|
290
307
|
};
|
|
291
308
|
}
|
|
292
|
-
async function defaultSmtpProbe(options
|
|
293
|
-
|
|
309
|
+
async function defaultSmtpProbe(options, transport = {
|
|
310
|
+
connect: options => net.createConnection(options),
|
|
311
|
+
startTls: options => tls.connect(options),
|
|
312
|
+
}) {
|
|
313
|
+
options.signal.throwIfAborted();
|
|
314
|
+
const socket = transport.connect({ host: options.address, port: SMTP_PORT, family: options.family });
|
|
315
|
+
let activeSocket = socket;
|
|
316
|
+
let reader = null;
|
|
294
317
|
socket.setTimeout(options.timeoutMs);
|
|
295
|
-
|
|
318
|
+
// Closing the active phase rejects its pending read/handshake. Do not emit
|
|
319
|
+
// an unhandled error on the raw socket after ownership has moved to TLS.
|
|
320
|
+
const aborted = () => { activeSocket.destroy(); };
|
|
296
321
|
options.signal.addEventListener('abort', aborted, { once: true });
|
|
297
322
|
try {
|
|
298
323
|
await new Promise((resolve, reject) => {
|
|
@@ -300,38 +325,48 @@ async function defaultSmtpProbe(options) {
|
|
|
300
325
|
socket.removeListener('connect', connected);
|
|
301
326
|
socket.removeListener('error', failed);
|
|
302
327
|
socket.removeListener('timeout', timedOut);
|
|
328
|
+
socket.removeListener('close', closed);
|
|
303
329
|
};
|
|
304
330
|
const connected = () => { cleanup(); resolve(); };
|
|
305
331
|
const failed = (error) => { cleanup(); socket.destroy(); reject(error); };
|
|
306
332
|
const timedOut = () => { cleanup(); socket.destroy(); reject(new Error('SMTP connection timed out.')); };
|
|
333
|
+
const closed = () => failed(new Error('SMTP connection closed before connecting.'));
|
|
307
334
|
socket.once('connect', connected);
|
|
308
335
|
socket.once('error', failed);
|
|
309
336
|
socket.once('timeout', timedOut);
|
|
337
|
+
socket.once('close', closed);
|
|
310
338
|
if (options.signal.aborted)
|
|
311
339
|
aborted();
|
|
312
340
|
});
|
|
313
|
-
const
|
|
341
|
+
const replyReader = new SocketReplyReader(socket);
|
|
342
|
+
reader = replyReader;
|
|
314
343
|
const connection = {
|
|
315
344
|
remoteAddress: socket.remoteAddress || null,
|
|
316
|
-
readReply: () =>
|
|
317
|
-
assertIdle: () =>
|
|
345
|
+
readReply: () => replyReader.readReply(),
|
|
346
|
+
assertIdle: () => replyReader.assertIdle(),
|
|
318
347
|
write: (command) => new Promise((resolve, reject) => socket.write(command, (error) => error ? reject(error) : resolve())),
|
|
319
348
|
startTls: async (hostname) => {
|
|
320
|
-
|
|
321
|
-
const tlsSocket =
|
|
349
|
+
replyReader.release();
|
|
350
|
+
const tlsSocket = transport.startTls(smtpStartTlsOptions(socket, hostname));
|
|
351
|
+
activeSocket = tlsSocket;
|
|
322
352
|
tlsSocket.setTimeout(options.timeoutMs);
|
|
323
353
|
await new Promise((resolve, reject) => {
|
|
324
354
|
const cleanup = () => {
|
|
325
355
|
tlsSocket.removeListener('secureConnect', secured);
|
|
326
356
|
tlsSocket.removeListener('error', failed);
|
|
327
357
|
tlsSocket.removeListener('timeout', timedOut);
|
|
358
|
+
tlsSocket.removeListener('close', closed);
|
|
328
359
|
};
|
|
329
360
|
const secured = () => { cleanup(); resolve(); };
|
|
330
361
|
const failed = (error) => { cleanup(); tlsSocket.destroy(); reject(error); };
|
|
331
362
|
const timedOut = () => { cleanup(); tlsSocket.destroy(); reject(new Error('SMTP STARTTLS handshake timed out.')); };
|
|
363
|
+
const closed = () => failed(new Error('SMTP STARTTLS connection closed before completing the handshake.'));
|
|
332
364
|
tlsSocket.once('secureConnect', secured);
|
|
333
365
|
tlsSocket.once('error', failed);
|
|
334
366
|
tlsSocket.once('timeout', timedOut);
|
|
367
|
+
tlsSocket.once('close', closed);
|
|
368
|
+
if (options.signal.aborted)
|
|
369
|
+
aborted();
|
|
335
370
|
});
|
|
336
371
|
const cipher = tlsSocket.getCipher();
|
|
337
372
|
return Object.freeze({
|
|
@@ -343,13 +378,15 @@ async function defaultSmtpProbe(options) {
|
|
|
343
378
|
remoteAddress: tlsSocket.remoteAddress || null,
|
|
344
379
|
});
|
|
345
380
|
},
|
|
346
|
-
diagnostics: () => ({ bytesRead:
|
|
347
|
-
destroy: () => socket.destroy(),
|
|
381
|
+
diagnostics: () => ({ bytesRead: replyReader.bytesRead, lineCount: replyReader.lineCount }),
|
|
382
|
+
destroy: () => { activeSocket.destroy(); socket.destroy(); },
|
|
348
383
|
};
|
|
349
384
|
return await runSmtpConversation(options.hostname, connection);
|
|
350
385
|
}
|
|
351
386
|
finally {
|
|
352
387
|
options.signal.removeEventListener('abort', aborted);
|
|
388
|
+
reader?.detach();
|
|
389
|
+
activeSocket.destroy();
|
|
353
390
|
socket.destroy();
|
|
354
391
|
}
|
|
355
392
|
}
|
|
@@ -486,11 +523,12 @@ function addressAuthentication(selected) {
|
|
|
486
523
|
})
|
|
487
524
|
: Object.freeze({ state: 'unavailable', detail: 'No address candidate was retained for cryptographic authentication review.' });
|
|
488
525
|
}
|
|
489
|
-
function endpointLimitations(addressState) {
|
|
526
|
+
function endpointLimitations(addressState, capabilitiesTruncated = false) {
|
|
490
527
|
const addressLimitation = addressState === 'connected'
|
|
491
528
|
? 'The endpoint observation is one point-in-time connection to one revalidated and pinned public address. Other addresses or later observations may differ.'
|
|
492
529
|
: 'The endpoint did not produce confirmed connection evidence. Its address field retains only the highest completed public selection or revalidation stage, if any.';
|
|
493
530
|
return Object.freeze([
|
|
531
|
+
...(capabilitiesTruncated ? [SMTP_CAPABILITY_OMISSION] : []),
|
|
494
532
|
addressLimitation,
|
|
495
533
|
'Public-address validation, fresh revalidation, and connection pinning do not cryptographically authenticate the selected A or AAAA RRset or any CNAME chain.',
|
|
496
534
|
'Only the SMTP greeting, EHLO capability names, and optional STARTTLS negotiation were observed. No authentication, recipient, mailbox, catch-all, relay, or message command was attempted.',
|
|
@@ -587,10 +625,12 @@ async function collectEndpoint(options) {
|
|
|
587
625
|
const tlsaComplete = !['indeterminate', 'timed_out', 'unavailable'].includes(tlsa.state)
|
|
588
626
|
&& (tlsa.state !== 'validated' || !tlsa.dane || !['partial', 'unavailable', 'invalid'].includes(tlsa.dane.state));
|
|
589
627
|
const certificateComplete = smtp.starttlsState !== 'negotiated' || certificate.observation.state === 'observed';
|
|
628
|
+
const capabilitiesTruncated = smtpCapabilityInventory(smtp.ehlo).truncated;
|
|
590
629
|
const complete = smtpState === 'observed'
|
|
591
630
|
&& dnssec.report.completeness === 'complete'
|
|
592
631
|
&& tlsaComplete
|
|
593
|
-
&& certificateComplete
|
|
632
|
+
&& certificateComplete
|
|
633
|
+
&& !capabilitiesTruncated;
|
|
594
634
|
return Object.freeze({
|
|
595
635
|
host: options.host,
|
|
596
636
|
observedAt: options.observedAt,
|
|
@@ -604,7 +644,7 @@ async function collectEndpoint(options) {
|
|
|
604
644
|
dnssec: dnssec.report,
|
|
605
645
|
tlsa,
|
|
606
646
|
failure: null,
|
|
607
|
-
limitations: endpointLimitations(addressState),
|
|
647
|
+
limitations: endpointLimitations(addressState, capabilitiesTruncated),
|
|
608
648
|
});
|
|
609
649
|
}
|
|
610
650
|
catch (error) {
|
|
@@ -627,7 +667,7 @@ async function collectEndpoint(options) {
|
|
|
627
667
|
dnssec: dnssec?.report ?? null,
|
|
628
668
|
tlsa: unavailableTlsa('unavailable', 'TLSA and DANE comparison was unavailable because endpoint collection did not complete.'),
|
|
629
669
|
failure: Object.freeze({ stage, detail }),
|
|
630
|
-
limitations: endpointLimitations(addressState),
|
|
670
|
+
limitations: endpointLimitations(addressState, smtpCapabilityInventory(connectedSmtp?.ehlo ?? null).truncated),
|
|
631
671
|
});
|
|
632
672
|
}
|
|
633
673
|
}
|
|
@@ -748,6 +788,7 @@ async function collectMailTransportReview(inputValue, options, dependencies = {}
|
|
|
748
788
|
retries: 0,
|
|
749
789
|
}),
|
|
750
790
|
limitations: Object.freeze([
|
|
791
|
+
...(endpoints.some((endpoint) => endpoint.limitations.includes(SMTP_CAPABILITY_OMISSION)) ? [SMTP_CAPABILITY_OMISSION] : []),
|
|
751
792
|
'This command performs active network collection only for the selected MX hosts in this explicitly authorised run. It is not invoked by Lookup, Bulk, monitoring, or automatic recipes.',
|
|
752
793
|
'DNSSEC, TLSA or DANE, PKIX, certificate identity, STARTTLS, SMTP transport, MTA-STS context, and TLS-RPT context retain separate states and provenance. One family never supplies or upgrades another family.',
|
|
753
794
|
ACTIVE_DANE_TA_LIMITATION,
|
|
@@ -784,4 +825,4 @@ function formatMailTransportReview(review) {
|
|
|
784
825
|
lines.push(` - ${limitation}`);
|
|
785
826
|
return `${lines.join('\n')}\n`;
|
|
786
827
|
}
|
|
787
|
-
export { EHLO_COMMAND, MAIL_TRANSPORT_INPUT_SCHEMA, MAIL_TRANSPORT_INPUT_VERSION, MAIL_TRANSPORT_REVIEW_SCHEMA, MAIL_TRANSPORT_REVIEW_VERSION, MAIL_TRANSPORT_TOTAL_TIMEOUT_MS, MAX_MAIL_TRANSPORT_CONCURRENCY, MAX_MAIL_TRANSPORT_ADDRESS_CANDIDATES, MAX_MAIL_TRANSPORT_DNS_ALIASES, MAX_MAIL_TRANSPORT_INPUT_BYTES, MAX_MAIL_TRANSPORT_TARGETS, MAX_SMTP_CAPABILITIES, MAX_SMTP_LINE_BYTES, MAX_SMTP_REPLY_LINES, MAX_SMTP_RESPONSE_BYTES, SMTP_ENDPOINT_TIMEOUT_MS, STARTTLS_COMMAND, collectMailTransportReview, certificateObservation, formatMailTransportReview, normalizeSmtpReply, parseMailTransportInput, resolveSelectedHost, runSmtpConversation, smtpStartTlsOptions, smtpCapabilities, };
|
|
828
|
+
export { EHLO_COMMAND, MAIL_TRANSPORT_INPUT_SCHEMA, MAIL_TRANSPORT_INPUT_VERSION, MAIL_TRANSPORT_REVIEW_SCHEMA, MAIL_TRANSPORT_REVIEW_VERSION, MAIL_TRANSPORT_TOTAL_TIMEOUT_MS, MAX_MAIL_TRANSPORT_CONCURRENCY, MAX_MAIL_TRANSPORT_ADDRESS_CANDIDATES, MAX_MAIL_TRANSPORT_DNS_ALIASES, MAX_MAIL_TRANSPORT_INPUT_BYTES, MAX_MAIL_TRANSPORT_TARGETS, MAX_SMTP_CAPABILITIES, MAX_SMTP_LINE_BYTES, MAX_SMTP_REPLY_LINES, MAX_SMTP_RESPONSE_BYTES, SMTP_ENDPOINT_TIMEOUT_MS, STARTTLS_COMMAND, collectMailTransportReview, certificateObservation, formatMailTransportReview, normalizeSmtpReply, parseMailTransportInput, resolveSelectedHost, runSmtpConversation, smtpStartTlsOptions, smtpCapabilities, SocketReplyReader, defaultSmtpProbe, };
|