@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/cli/sharing-review.mjs
CHANGED
|
@@ -1,97 +1,25 @@
|
|
|
1
|
-
import { hasVerifiedWholeArtifactIntegrity, UnsupportedOfflineArtifactError, verifyOfflineArtifact, } from "./artifact-verify.mjs";
|
|
2
|
-
import {
|
|
1
|
+
import { hasVerifiedWholeArtifactIntegrity, MAX_OFFLINE_ARTIFACT_BYTES, parseOfflineArtifactJson, UnsupportedOfflineArtifactError, verifyOfflineArtifact, } from "./artifact-verify.mjs";
|
|
2
|
+
import { TLP_MARKINGS, RECIPIENT_SCOPES, expectedScope, tlpLabel, scanSharingMetadata as scanArtifact } from "../packages/interchange/sharing-policy.mjs";
|
|
3
3
|
const SHARING_REVIEW_SCHEMA = 'whoisleuth.cli.sharing-review';
|
|
4
4
|
const SHARING_REVIEW_VERSION = 2;
|
|
5
|
-
const MAX_SHARING_REVIEW_BYTES =
|
|
6
|
-
const TLP_MARKINGS = ['clear', 'green', 'amber', 'amber-strict', 'red'];
|
|
7
|
-
const RECIPIENT_SCOPES = ['public', 'community', 'organization', 'named-recipients'];
|
|
5
|
+
const MAX_SHARING_REVIEW_BYTES = MAX_OFFLINE_ARTIFACT_BYTES;
|
|
8
6
|
const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
|
|
9
|
-
const RISKY_KEYS = new Set([
|
|
10
|
-
'authorization', 'cookie', 'cookies', 'credential', 'credentials', 'email', 'emails',
|
|
11
|
-
'entities', 'password', 'phone', 'raw', 'rawrdap', 'rawwhois', 'registrant', 'session', 'token',
|
|
12
|
-
]);
|
|
13
|
-
const MARKING_KEYS = new Set([
|
|
14
|
-
'informationmarking', 'marking', 'markings', 'sharingmarking',
|
|
15
|
-
'tlp', 'tlplabel', 'tlpmarking', 'trafficlightprotocol',
|
|
16
|
-
]);
|
|
17
7
|
function record(value) {
|
|
18
8
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
19
9
|
}
|
|
20
|
-
function normalizedKey(value) {
|
|
21
|
-
return value.toLowerCase().replace(/[^a-z0-9]/gu, '');
|
|
22
|
-
}
|
|
23
10
|
function boundedMetadataText(value) {
|
|
24
11
|
if (typeof value !== 'string' || value.length > 120 || CONTROL_RE.test(value))
|
|
25
12
|
return null;
|
|
26
13
|
const trimmed = value.trim();
|
|
27
14
|
return trimmed || null;
|
|
28
15
|
}
|
|
29
|
-
function markingFromText(value) {
|
|
30
|
-
const normalized = value.toUpperCase().replace(/\s+/gu, '').replace('TLP:', '').replace('+STRICT', '-STRICT').toLowerCase();
|
|
31
|
-
return TLP_MARKINGS.includes(normalized) ? normalized : null;
|
|
32
|
-
}
|
|
33
|
-
function scanArtifact(root) {
|
|
34
|
-
const stack = [{ value: root, depth: 0, key: null }];
|
|
35
|
-
const markings = new Set();
|
|
36
|
-
let riskyKeyCount = 0;
|
|
37
|
-
let visited = 0;
|
|
38
|
-
let truncated = false;
|
|
39
|
-
while (stack.length && visited < 20_000) {
|
|
40
|
-
const next = stack.pop();
|
|
41
|
-
visited += 1;
|
|
42
|
-
if (next.key && RISKY_KEYS.has(normalizedKey(next.key)))
|
|
43
|
-
riskyKeyCount += 1;
|
|
44
|
-
if (typeof next.value === 'string') {
|
|
45
|
-
const marking = markingFromText(next.value);
|
|
46
|
-
if (marking && (next.key === null || MARKING_KEYS.has(normalizedKey(next.key))))
|
|
47
|
-
markings.add(marking);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (next.depth >= 12) {
|
|
51
|
-
if (next.value && typeof next.value === 'object')
|
|
52
|
-
truncated = true;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if (Array.isArray(next.value)) {
|
|
56
|
-
for (const item of next.value.slice(0, 500))
|
|
57
|
-
stack.push({ value: item, depth: next.depth + 1, key: next.key });
|
|
58
|
-
if (next.value.length > 500)
|
|
59
|
-
truncated = true;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const valueRecord = record(next.value);
|
|
63
|
-
if (!valueRecord)
|
|
64
|
-
continue;
|
|
65
|
-
const entries = Object.entries(valueRecord);
|
|
66
|
-
for (const [key, value] of entries.slice(0, 500))
|
|
67
|
-
stack.push({ value, depth: next.depth + 1, key });
|
|
68
|
-
if (entries.length > 500)
|
|
69
|
-
truncated = true;
|
|
70
|
-
}
|
|
71
|
-
if (stack.length)
|
|
72
|
-
truncated = true;
|
|
73
|
-
return { importedMarkings: [...markings], riskyKeyCount, truncated };
|
|
74
|
-
}
|
|
75
|
-
function expectedScope(marking) {
|
|
76
|
-
if (marking === 'clear')
|
|
77
|
-
return 'public';
|
|
78
|
-
if (marking === 'green')
|
|
79
|
-
return 'community';
|
|
80
|
-
if (marking === 'red')
|
|
81
|
-
return 'named-recipients';
|
|
82
|
-
return 'organization';
|
|
83
|
-
}
|
|
84
|
-
function tlpLabel(marking) {
|
|
85
|
-
return `TLP:${marking === 'amber-strict' ? 'AMBER+STRICT' : marking.toUpperCase()}`;
|
|
86
|
-
}
|
|
87
16
|
async function buildSharingReview(raw, options, generatedAt = new Date().toISOString()) {
|
|
88
17
|
if (Buffer.byteLength(raw, 'utf8') > MAX_SHARING_REVIEW_BYTES) {
|
|
89
18
|
throw new TypeError(`Sharing review input is limited to ${MAX_SHARING_REVIEW_BYTES} bytes.`);
|
|
90
19
|
}
|
|
91
20
|
let artifactValue;
|
|
92
21
|
try {
|
|
93
|
-
|
|
94
|
-
artifactValue = JSON.parse(raw);
|
|
22
|
+
artifactValue = parseOfflineArtifactJson(raw);
|
|
95
23
|
}
|
|
96
24
|
catch {
|
|
97
25
|
throw new TypeError('Sharing review input must be valid bounded JSON without duplicate keys.');
|
|
@@ -106,8 +34,10 @@ async function buildSharingReview(raw, options, generatedAt = new Date().toISOSt
|
|
|
106
34
|
const importedRank = strictestImported === null ? -1 : TLP_MARKINGS.indexOf(strictestImported);
|
|
107
35
|
const effective = importedRank > requestedRank ? strictestImported : options.marking;
|
|
108
36
|
let integrity = 'unsupported';
|
|
37
|
+
let verifiedIdentity = null;
|
|
109
38
|
try {
|
|
110
39
|
const verification = await verifyOfflineArtifact(raw);
|
|
40
|
+
verifiedIdentity = verification.artifact;
|
|
111
41
|
integrity = verification.state === 'verified' && hasVerifiedWholeArtifactIntegrity(verification)
|
|
112
42
|
? 'verified'
|
|
113
43
|
: verification.state === 'integrity_valid'
|
|
@@ -147,15 +77,13 @@ async function buildSharingReview(raw, options, generatedAt = new Date().toISOSt
|
|
|
147
77
|
add('recipient-scope', options.recipientScope === expectedScope(effective) ? 'pass' : 'block', 'Recipient scope', options.recipientScope === expectedScope(effective)
|
|
148
78
|
? `The recipient scope matches the effective ${tlpLabel(effective)} sharing boundary.`
|
|
149
79
|
: `${tlpLabel(effective)} requires the ${expectedScope(effective)} recipient scope in this local policy.`);
|
|
150
|
-
if (scan.truncated)
|
|
151
|
-
add('scan-bounds', 'caution', 'Bounded content scan', 'The defensive key and marking scan reached a traversal bound; review the artefact manually.');
|
|
152
80
|
const counts = {
|
|
153
81
|
block: findings.filter((finding) => finding.state === 'block').length,
|
|
154
82
|
caution: findings.filter((finding) => finding.state === 'caution').length,
|
|
155
83
|
pass: findings.filter((finding) => finding.state === 'pass').length,
|
|
156
84
|
};
|
|
157
|
-
const artifactSchema = boundedMetadataText(artifact.schema);
|
|
158
|
-
const rawArtifactVersion = artifact.version ?? artifact.schemaVersion;
|
|
85
|
+
const artifactSchema = boundedMetadataText(verifiedIdentity ? verifiedIdentity.schema : artifact.schema);
|
|
86
|
+
const rawArtifactVersion = verifiedIdentity ? verifiedIdentity.version : artifact.version ?? artifact.schemaVersion;
|
|
159
87
|
const artifactVersion = Number.isSafeInteger(rawArtifactVersion)
|
|
160
88
|
&& Number(rawArtifactVersion) >= 1
|
|
161
89
|
&& Number(rawArtifactVersion) <= 10_000
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { parseBoundedJsonObject } from "./bounded-json.mjs";
|
|
3
|
+
import { normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
|
|
4
|
+
import { formatEvidenceSignatureVerification } from "./evidence-signing.mjs";
|
|
5
|
+
export const SIGNER_TRUST_STORE_SCHEMA = 'whoisleuth.evidence-signer-trust-store';
|
|
6
|
+
export const SIGNER_TRUST_STORE_VERSION = 1;
|
|
7
|
+
export const SIGNER_TRUST_REPORT_SCHEMA = 'whoisleuth.evidence-signer-trust-report';
|
|
8
|
+
export const SIGNER_TRUST_REPORT_VERSION = 1;
|
|
9
|
+
export const MAX_SIGNER_TRUST_STORE_BYTES = 4 * 1024 * 1024;
|
|
10
|
+
export const MAX_SIGNER_TRUST_ENTRIES = 1_024;
|
|
11
|
+
export const MAX_SIGNER_TRUST_LABEL_LENGTH = 160;
|
|
12
|
+
export const MAX_SIGNER_TRUST_NOTE_LENGTH = 2_048;
|
|
13
|
+
const FINGERPRINT = /^[a-f0-9]{64}$/u;
|
|
14
|
+
const UNSAFE_TEXT = /[\x00-\x1f\x7f-\x9f\u2028\u2029]|\p{Default_Ignorable_Code_Point}/u;
|
|
15
|
+
const PRIVATE_KEY_HEADER = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----/u;
|
|
16
|
+
function object(value) {
|
|
17
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
18
|
+
}
|
|
19
|
+
function text(value, maximum, label, allowEmpty = false) {
|
|
20
|
+
if (typeof value !== 'string' || value.length > maximum || (!allowEmpty && !value.trim()) || UNSAFE_TEXT.test(value) || PRIVATE_KEY_HEADER.test(value)) {
|
|
21
|
+
throw new TypeError(`Signer trust ${label} must be single-line text of at most ${maximum} characters without hidden controls.`);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
export function parseSignerTrustStore(raw) {
|
|
26
|
+
const value = parseBoundedJsonObject(raw, { label: 'Signer trust store', maximumBytes: MAX_SIGNER_TRUST_STORE_BYTES });
|
|
27
|
+
if (value.schema !== SIGNER_TRUST_STORE_SCHEMA || value.version !== SIGNER_TRUST_STORE_VERSION
|
|
28
|
+
|| Object.keys(value).some((key) => !['schema', 'version', 'entries'].includes(key))
|
|
29
|
+
|| !Array.isArray(value.entries) || value.entries.length > MAX_SIGNER_TRUST_ENTRIES) {
|
|
30
|
+
throw new TypeError(`Signer trust store must use ${SIGNER_TRUST_STORE_SCHEMA} version ${SIGNER_TRUST_STORE_VERSION} with at most ${MAX_SIGNER_TRUST_ENTRIES} entries.`);
|
|
31
|
+
}
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
const entries = value.entries.map((candidate) => {
|
|
34
|
+
const entry = object(candidate);
|
|
35
|
+
if (!entry || Object.keys(entry).some((key) => !['keyIdSha256', 'label', 'status', 'updatedAt', 'note', 'successorKeyIdSha256'].includes(key))
|
|
36
|
+
|| typeof entry.keyIdSha256 !== 'string' || !FINGERPRINT.test(entry.keyIdSha256)
|
|
37
|
+
|| typeof entry.status !== 'string' || !['trusted', 'retired', 'revoked'].includes(entry.status)) {
|
|
38
|
+
throw new TypeError('Signer trust entry requires a SHA-256 key fingerprint and trusted, retired or revoked status.');
|
|
39
|
+
}
|
|
40
|
+
if (seen.has(entry.keyIdSha256))
|
|
41
|
+
throw new TypeError('Signer trust store contains duplicate key fingerprints.');
|
|
42
|
+
seen.add(entry.keyIdSha256);
|
|
43
|
+
const updatedAt = normalizeExplicitIsoTimestamp(entry.updatedAt);
|
|
44
|
+
if (!updatedAt)
|
|
45
|
+
throw new TypeError('Signer trust entry updatedAt requires a valid timestamp with an explicit timezone.');
|
|
46
|
+
const successor = entry.successorKeyIdSha256 ?? null;
|
|
47
|
+
if (successor !== null && (typeof successor !== 'string' || !FINGERPRINT.test(successor) || successor === entry.keyIdSha256)) {
|
|
48
|
+
throw new TypeError('Signer trust successor must be a different SHA-256 fingerprint or null.');
|
|
49
|
+
}
|
|
50
|
+
return Object.freeze({
|
|
51
|
+
keyIdSha256: entry.keyIdSha256,
|
|
52
|
+
label: text(entry.label, MAX_SIGNER_TRUST_LABEL_LENGTH, 'label'),
|
|
53
|
+
status: entry.status,
|
|
54
|
+
updatedAt,
|
|
55
|
+
note: text(entry.note === undefined ? '' : entry.note, MAX_SIGNER_TRUST_NOTE_LENGTH, 'note', true),
|
|
56
|
+
successorKeyIdSha256: successor,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
return Object.freeze({ schema: SIGNER_TRUST_STORE_SCHEMA, version: SIGNER_TRUST_STORE_VERSION, entries: Object.freeze(entries) });
|
|
60
|
+
}
|
|
61
|
+
export function assessEvidenceSignerTrust(verification, trustStoreRaw, checkedAt = new Date().toISOString()) {
|
|
62
|
+
const now = normalizeExplicitIsoTimestamp(checkedAt);
|
|
63
|
+
if (!now)
|
|
64
|
+
throw new TypeError('Signer trust evaluation requires a valid timestamp with an explicit timezone.');
|
|
65
|
+
const store = parseSignerTrustStore(trustStoreRaw);
|
|
66
|
+
const entry = store.entries.find((candidate) => candidate.keyIdSha256 === verification.signature.keyIdSha256) ?? null;
|
|
67
|
+
// Signing time is self-asserted. It cannot bypass the current revocation policy,
|
|
68
|
+
// and a successor reference never makes another fingerprint trusted.
|
|
69
|
+
const state = !entry ? 'unknown' : entry.status !== 'trusted' ? entry.status
|
|
70
|
+
: Date.parse(entry.updatedAt) > Date.parse(now) ? 'review_required' : 'trusted';
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
schema: SIGNER_TRUST_REPORT_SCHEMA,
|
|
73
|
+
version: SIGNER_TRUST_REPORT_VERSION,
|
|
74
|
+
state: state === 'trusted' ? 'trusted' : 'not_trusted',
|
|
75
|
+
checkedAt: now,
|
|
76
|
+
verification,
|
|
77
|
+
trust: Object.freeze({ state, storeDigestSha256: createHash('sha256').update(trustStoreRaw, 'utf8').digest('hex'), entry }),
|
|
78
|
+
limitations: Object.freeze([
|
|
79
|
+
'Trust is the current policy in the explicitly selected local file. Obtain fingerprints and revocation updates through an authenticated channel; this command does not discover or refresh them.',
|
|
80
|
+
'A label or successor reference does not authenticate a person, organisation or replacement key. A trusted fingerprint does not establish evidence accuracy, authority or collection time.',
|
|
81
|
+
'Signing time is supplied by the signer, not a trusted timestamp. Retired and revoked fingerprints remain untrusted regardless of that time.',
|
|
82
|
+
'The report includes only the matching entry and a SHA-256 digest of the exact trust-file bytes, not the file path, other entries or key files. Do not put sensitive material in labels or notes.',
|
|
83
|
+
]),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
export function formatSignerTrustReport(report) {
|
|
87
|
+
const entry = report.trust.entry;
|
|
88
|
+
return [
|
|
89
|
+
`WHOISleuth signer trust: ${report.trust.state}`,
|
|
90
|
+
`Checked: ${report.checkedAt}`,
|
|
91
|
+
`Trust-file SHA-256: ${report.trust.storeDigestSha256}`,
|
|
92
|
+
...(entry ? [
|
|
93
|
+
`Local label: ${entry.label}`,
|
|
94
|
+
`Entry reviewed: ${entry.updatedAt}`,
|
|
95
|
+
...(entry.note ? [`Local note: ${entry.note}`] : []),
|
|
96
|
+
...(entry.successorKeyIdSha256 ? [`Successor reference: sha256:${entry.successorKeyIdSha256} (not a trust grant)`] : []),
|
|
97
|
+
] : ['No matching fingerprint in the selected trust file.']),
|
|
98
|
+
'',
|
|
99
|
+
'Signature checks (separate from current trust policy)',
|
|
100
|
+
formatEvidenceSignatureVerification(report.verification, false).trimEnd(),
|
|
101
|
+
...report.limitations.map((limitation) => `Trust limitation: ${limitation}`),
|
|
102
|
+
'',
|
|
103
|
+
].join('\n');
|
|
104
|
+
}
|
|
@@ -1,35 +1,23 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { canonicalArtifactJson } from "../packages/evidence/artifact-integrity.mjs";
|
|
3
|
-
import { scanBoundedJson } from "../
|
|
3
|
+
import { scanBoundedJson } from "../packages/analysis/bounded-json.mjs";
|
|
4
|
+
import { LOOKUP_SOURCE_STATES, lookupDiagnosticStates } from "../lib/lookup-diagnostics.mjs";
|
|
4
5
|
import { normalizeExplicitIsoTimestamp, normalizeLegacyIsoTimestamp, } from "../packages/evidence/observation.mjs";
|
|
5
6
|
import { CLI_LOOKUP_SCHEMA, MAX_CLI_LOOKUP_BYTES, } from "../packages/contracts/cli-lookup.mjs";
|
|
6
7
|
import { normalizeCliLookupDocument } from "./saved-lookup.mjs";
|
|
8
|
+
import { CLI_BULK_SCHEMA, CLI_BULK_ITEM_SCHEMA, CLI_BULK_SCHEMA_VERSION } from "./formatters/json.mjs";
|
|
9
|
+
import { LOOKUP_THREAT_INTELLIGENCE_PROVIDERS } from "../lib/lookup-threat-provider-inventory.mjs";
|
|
10
|
+
import { MAX_THREAT_INTELLIGENCE_PROVIDERS } from "../lib/lookup-response-contract.mjs";
|
|
11
|
+
import { THREAT_INTELLIGENCE_SCHEMA, THREAT_INTELLIGENCE_CONTRACT_VERSION } from "../packages/analysis/threat-intelligence-types.mjs";
|
|
7
12
|
export const SOURCE_RELIABILITY_REPORT_SCHEMA = 'whoisleuth.source-reliability-report';
|
|
8
13
|
export const SOURCE_RELIABILITY_REPORT_VERSION = 1;
|
|
9
14
|
export const MAX_SOURCE_RELIABILITY_INPUT_BYTES = 12 * 1024 * 1024;
|
|
10
15
|
export const MAX_SOURCE_RELIABILITY_DOCUMENTS = 100;
|
|
11
16
|
export const MAX_SOURCE_RELIABILITY_SOURCES = 64;
|
|
12
|
-
export const MAX_SOURCE_RELIABILITY_TRAVERSAL_NODES = 25_000;
|
|
13
17
|
const SOURCE_RE = /^[a-z][a-z0-9_-]{0,39}$/u;
|
|
14
|
-
const STATES = new Set(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'not_found',
|
|
18
|
-
'skipped',
|
|
19
|
-
'error',
|
|
20
|
-
'unsupported',
|
|
21
|
-
'not_applicable',
|
|
22
|
-
'unavailable',
|
|
23
|
-
'rate_limited',
|
|
24
|
-
'complete',
|
|
25
|
-
'disabled',
|
|
26
|
-
'stale',
|
|
27
|
-
]);
|
|
28
|
-
const LOOKUP_SCHEMAS = new Set([
|
|
29
|
-
CLI_LOOKUP_SCHEMA,
|
|
30
|
-
'whoisleuth.cli.bulk',
|
|
31
|
-
'whoisleuth.cli.bulk.item',
|
|
32
|
-
]);
|
|
18
|
+
const STATES = new Set(LOOKUP_SOURCE_STATES);
|
|
19
|
+
const BULK_HEALTH_VERSIONS = new Set([1, 2, CLI_BULK_SCHEMA_VERSION]);
|
|
20
|
+
const PROVIDER_IDS = new Set(LOOKUP_THREAT_INTELLIGENCE_PROVIDERS.map((provider) => provider.id));
|
|
33
21
|
const REPORT_KEYS = new Set([
|
|
34
22
|
'schema',
|
|
35
23
|
'version',
|
|
@@ -175,9 +163,9 @@ function parseLookupDocuments(values) {
|
|
|
175
163
|
throw new TypeError('Source reliability input requires a supported bounded CLI Lookup document.');
|
|
176
164
|
}
|
|
177
165
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
throw new TypeError('Source reliability input requires CLI Lookup
|
|
166
|
+
if ((schema !== CLI_BULK_SCHEMA && schema !== CLI_BULK_ITEM_SCHEMA)
|
|
167
|
+
|| typeof document.version !== 'number' || !BULK_HEALTH_VERSIONS.has(document.version)) {
|
|
168
|
+
throw new TypeError('Source reliability input requires supported CLI Lookup, Bulk or Bulk-item documents, or only source reliability reports.');
|
|
181
169
|
}
|
|
182
170
|
return document;
|
|
183
171
|
});
|
|
@@ -232,35 +220,21 @@ function collectTiming(document, store) {
|
|
|
232
220
|
}
|
|
233
221
|
}
|
|
234
222
|
function collectDiagnostics(document, store) {
|
|
235
|
-
const
|
|
236
|
-
if (!diagnostics)
|
|
237
|
-
return;
|
|
238
|
-
const fixed = [
|
|
239
|
-
['rdap', record(diagnostics.rdap)?.status],
|
|
240
|
-
['registrar_rdap', record(record(diagnostics.rdap)?.registrar)?.status],
|
|
241
|
-
['whois', record(diagnostics.whois)?.status],
|
|
242
|
-
['availability', record(diagnostics.availability)?.status],
|
|
243
|
-
['reverse_dns', record(diagnostics.reverseDns)?.status],
|
|
244
|
-
['network_context', record(diagnostics.network)?.status],
|
|
245
|
-
['security_txt', record(diagnostics.securityTxt)?.status],
|
|
246
|
-
['sslbl', record(diagnostics.sslbl)?.status],
|
|
247
|
-
];
|
|
248
|
-
for (const [source, value] of fixed) {
|
|
249
|
-
const state = safeState(value);
|
|
223
|
+
for (const [source, state] of Object.entries(lookupDiagnosticStates(document.diagnostics))) {
|
|
250
224
|
if (state)
|
|
251
225
|
addState(store, source, state);
|
|
252
226
|
}
|
|
253
227
|
}
|
|
254
|
-
function collectObservation(
|
|
255
|
-
|
|
228
|
+
function collectObservation(raw, source, store) {
|
|
229
|
+
const value = record(raw);
|
|
230
|
+
if (!value || value.version !== 1 || value.source !== source
|
|
256
231
|
|| typeof value.observedAt !== 'string'
|
|
257
232
|
|| typeof value.complete !== 'boolean'
|
|
258
233
|
|| typeof value.truncated !== 'boolean'
|
|
259
234
|
|| !Array.isArray(value.limitations))
|
|
260
235
|
return;
|
|
261
|
-
const source = safeSource(value.source);
|
|
262
236
|
const state = safeState(value.status);
|
|
263
|
-
if (!
|
|
237
|
+
if (!state)
|
|
264
238
|
return;
|
|
265
239
|
const item = accumulator(store, source);
|
|
266
240
|
item.observationSamples += 1;
|
|
@@ -271,48 +245,65 @@ function collectObservation(value, store) {
|
|
|
271
245
|
if (value.truncated === true)
|
|
272
246
|
item.truncated += 1;
|
|
273
247
|
}
|
|
274
|
-
function
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
248
|
+
function collectLookupHealth(document, store) {
|
|
249
|
+
collectDiagnostics(document, store);
|
|
250
|
+
collectTiming(document, store);
|
|
251
|
+
const availability = record(document.availability);
|
|
252
|
+
const dns = record(availability?.dns);
|
|
253
|
+
const technology = record(availability?.technologyProfile);
|
|
254
|
+
// Only collector-owned envelopes have health semantics. Raw publications,
|
|
255
|
+
// parsed registration extensions and nested provider findings remain opaque.
|
|
256
|
+
const observations = [
|
|
257
|
+
['dns', dns], ['dns', dns?.caaPolicy], ['dns_delegation', dns?.delegation],
|
|
258
|
+
['http', availability?.http], ['tls', availability?.tls],
|
|
259
|
+
['html', availability?.pageIdentity],
|
|
260
|
+
];
|
|
261
|
+
const fullLookup = document.schema === CLI_LOOKUP_SCHEMA;
|
|
262
|
+
if (fullLookup)
|
|
263
|
+
observations.push(['html', availability?.credentialSurfaceProfile], ['html', availability?.structuredDataIdentity], ['derived', technology], ['derived', technology?.browserLibraryProfile], ['derived', availability?.pageRoleProfile], ['derived', availability?.clientBehaviorProfile], ['derived', availability?.securityPosture], ['reverse_dns', document.reverseDns], ['ip_rdap', document.networkContext], ['security_txt', document.securityTxt], ['sslbl', document.sslbl]);
|
|
264
|
+
for (const [source, observation] of observations)
|
|
265
|
+
collectObservation(observation, source, store);
|
|
266
|
+
if (!fullLookup)
|
|
267
|
+
return;
|
|
268
|
+
const providers = record(document.threatIntelligence)?.providers;
|
|
269
|
+
if (providers === undefined)
|
|
270
|
+
return;
|
|
271
|
+
if (!Array.isArray(providers) || providers.length > MAX_THREAT_INTELLIGENCE_PROVIDERS) {
|
|
272
|
+
throw new TypeError(`Source reliability input exceeds the ${MAX_THREAT_INTELLIGENCE_PROVIDERS}-provider health boundary.`);
|
|
273
|
+
}
|
|
274
|
+
const seen = new Set();
|
|
275
|
+
for (const rawProvider of providers) {
|
|
276
|
+
const provider = record(rawProvider);
|
|
277
|
+
const source = record(provider?.provider)?.id;
|
|
278
|
+
if (provider?.schema !== THREAT_INTELLIGENCE_SCHEMA || provider.version !== THREAT_INTELLIGENCE_CONTRACT_VERSION
|
|
279
|
+
|| typeof source !== 'string' || !PROVIDER_IDS.has(source)
|
|
280
|
+
|| record(provider.observation)?.source !== source)
|
|
280
281
|
continue;
|
|
281
|
-
if (
|
|
282
|
-
throw new TypeError('Source reliability input
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (child && typeof child === 'object')
|
|
304
|
-
stack.push({ value: child, depth: current.depth + 1 });
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
else if (Array.isArray(current.value)) {
|
|
308
|
-
if (current.value.length > 1_000) {
|
|
309
|
-
throw new TypeError('Source reliability input contains an array with more than 1000 items.');
|
|
310
|
-
}
|
|
311
|
-
for (const child of current.value) {
|
|
312
|
-
if (child && typeof child === 'object')
|
|
313
|
-
stack.push({ value: child, depth: current.depth + 1 });
|
|
314
|
-
}
|
|
282
|
+
if (seen.has(source))
|
|
283
|
+
throw new TypeError('Source reliability input contains duplicate provider health identities.');
|
|
284
|
+
seen.add(source);
|
|
285
|
+
collectObservation(provider?.observation, source, store);
|
|
286
|
+
if (provider?.state === 'rate_limited')
|
|
287
|
+
addState(store, source, 'rate_limited');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function collectDocumentHealth(document, store) {
|
|
291
|
+
if (document.schema === CLI_LOOKUP_SCHEMA) {
|
|
292
|
+
collectLookupHealth(document, store);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const items = document.schema === CLI_BULK_SCHEMA ? document.results : [document];
|
|
296
|
+
if (!Array.isArray(items) || items.length > 1_000) {
|
|
297
|
+
throw new TypeError('Source reliability Bulk input requires at most 1000 result items.');
|
|
298
|
+
}
|
|
299
|
+
for (const rawItem of items) {
|
|
300
|
+
const item = record(rawItem);
|
|
301
|
+
if (!item || item.schema !== CLI_BULK_ITEM_SCHEMA || item.version !== document.version
|
|
302
|
+
|| typeof item.ok !== 'boolean') {
|
|
303
|
+
throw new TypeError('Source reliability Bulk input contains an invalid result envelope.');
|
|
315
304
|
}
|
|
305
|
+
if (item.ok)
|
|
306
|
+
collectLookupHealth(item, store);
|
|
316
307
|
}
|
|
317
308
|
}
|
|
318
309
|
function percentile(sorted, ratio) {
|
|
@@ -663,7 +654,7 @@ export function buildSourceReliabilityReport(raw, generatedAt = new Date().toISO
|
|
|
663
654
|
const sampleDate = lookupDocumentTimestamp(document);
|
|
664
655
|
if (sampleDate)
|
|
665
656
|
sampleDates.push(sampleDate);
|
|
666
|
-
|
|
657
|
+
collectDocumentHealth(document, store);
|
|
667
658
|
}
|
|
668
659
|
}
|
|
669
660
|
sampleDates.sort();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {} from "./command-argument-grammar.mjs";
|
|
2
|
+
import {} from "./command-reference.mjs";
|
|
3
|
+
import { terminalOptions, jsonOutput, singleInputAction } from "./argument-values.mjs";
|
|
4
|
+
export const SUPPORT_ARGUMENT_PARSERS = Object.freeze({
|
|
5
|
+
completion: (parsed) => ({
|
|
6
|
+
action: 'completion',
|
|
7
|
+
shell: parsed.positionalValue('shell'),
|
|
8
|
+
}),
|
|
9
|
+
doctor: (parsed) => ({
|
|
10
|
+
action: 'doctor',
|
|
11
|
+
network: parsed.hasOption('--network'),
|
|
12
|
+
output: jsonOutput(parsed),
|
|
13
|
+
...terminalOptions(parsed),
|
|
14
|
+
}),
|
|
15
|
+
commands: (parsed) => ({
|
|
16
|
+
action: 'commands',
|
|
17
|
+
output: jsonOutput(parsed),
|
|
18
|
+
common: parsed.hasOption('--common'),
|
|
19
|
+
group: parsed.optionValue('--group'),
|
|
20
|
+
mode: parsed.optionValue('--mode'),
|
|
21
|
+
...terminalOptions(parsed),
|
|
22
|
+
}),
|
|
23
|
+
manual: () => ({ action: 'manual' }),
|
|
24
|
+
'registry-support': (parsed) => ({
|
|
25
|
+
action: 'registry-support',
|
|
26
|
+
target: parsed.positionalValue('domain-or-suffix'),
|
|
27
|
+
output: jsonOutput(parsed),
|
|
28
|
+
...terminalOptions(parsed),
|
|
29
|
+
}),
|
|
30
|
+
'registry-doctor': (parsed) => singleInputAction('registry-doctor', parsed),
|
|
31
|
+
'registry-cohort': (parsed) => singleInputAction('registry-cohort', parsed),
|
|
32
|
+
'registry-scaffold': (parsed) => ({
|
|
33
|
+
action: 'registry-scaffold',
|
|
34
|
+
profile: parsed.optionValue('--profile'),
|
|
35
|
+
suffix: parsed.optionValue('--suffix'),
|
|
36
|
+
scenario: parsed.optionValue('--scenario'),
|
|
37
|
+
}),
|
|
38
|
+
'risk-calibrate': (parsed) => ({
|
|
39
|
+
action: 'risk-calibrate',
|
|
40
|
+
source: parsed.positionalValue('source'),
|
|
41
|
+
output: parsed.hasOption('--json')
|
|
42
|
+
? 'json'
|
|
43
|
+
: parsed.hasOption('--summary-json')
|
|
44
|
+
? 'summary_json'
|
|
45
|
+
: 'terminal',
|
|
46
|
+
...terminalOptions(parsed),
|
|
47
|
+
}),
|
|
48
|
+
'lookalike-calibrate': (parsed) => singleInputAction('lookalike-calibrate', parsed),
|
|
49
|
+
});
|