@slicedearth/whoisleuth-cli 2.3.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +8 -2
- package/README.md +19 -15
- package/bin/whoisleuth.mjs +8 -1
- package/cli/archive-content-digest.mjs +14 -0
- package/cli/archive-inspect.mjs +20 -53
- package/cli/argument-values.mjs +55 -0
- package/cli/arguments.mjs +24 -408
- package/cli/artifact-validation/case-response.mjs +65 -44
- package/cli/artifact-validation/investigation-capsule.mjs +52 -31
- package/cli/artifact-validation/lookup-evidence.mjs +15 -9
- package/cli/artifact-validation/signed-review.mjs +136 -68
- package/cli/artifact-validation/structure-primitives.mjs +1 -105
- package/cli/artifact-verify.mjs +92 -24
- package/cli/assurance-arguments.mjs +56 -0
- package/cli/assurance-command-definitions.mjs +179 -0
- package/cli/assurance-command-runner.mjs +40 -5
- package/cli/bagit-folder.mjs +57 -0
- package/cli/bagit-review.mjs +23 -0
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +1 -1
- package/cli/bulk-command-runner.mjs +4 -2
- package/cli/bulk-output.mjs +29 -3
- package/cli/bulk.mjs +2 -3
- package/cli/case-command.mjs +212 -0
- package/cli/case-pack.mjs +50 -32
- package/cli/ci-report.mjs +6 -3
- package/cli/collection-arguments.mjs +123 -0
- package/cli/collection-command-definitions.mjs +330 -0
- package/cli/collection-preflight.mjs +8 -1
- package/cli/command-argument-grammar.mjs +8 -206
- package/cli/command-definition.mjs +329 -0
- package/cli/command-reference.mjs +94 -1357
- package/cli/command-seeds.mjs +18 -0
- package/cli/compare.mjs +1 -1
- package/cli/completion.mjs +298 -79
- package/cli/config-profile.mjs +8 -6
- package/cli/ct-event-intake.mjs +16 -16
- package/cli/discover.mjs +5 -4
- package/cli/discovery-observation-snapshot.mjs +46 -18
- package/cli/discovery-scan-command-runner.mjs +5 -3
- package/cli/discovery-scan.mjs +7 -4
- package/cli/discovery-snapshot.mjs +2 -2
- package/cli/discovery-workflow.mjs +5 -5
- package/cli/domain-control-monitor.mjs +50 -20
- package/cli/domain-control-observations.mjs +151 -103
- package/cli/errors.mjs +37 -6
- package/cli/evidence-arguments.mjs +46 -0
- package/cli/evidence-command-definitions.mjs +69 -0
- package/cli/evidence-command-runner.mjs +18 -8
- package/cli/evidence-signing.mjs +5 -4
- package/cli/external-observation-mapping.mjs +9 -10
- package/cli/formatters/terminal-command-formats.mjs +6 -0
- package/cli/formatters/terminal-lookup.mjs +24 -46
- package/cli/formatters/terminal-shared.mjs +5 -3
- package/cli/formatters/terminal-technology.mjs +114 -0
- package/cli/history-arguments.mjs +39 -0
- package/cli/history-command-definitions.mjs +88 -0
- package/cli/indicator-set-command.mjs +33 -0
- package/cli/inline-command-families.mjs +6 -46
- package/cli/inline-command-runner.mjs +4 -31
- package/cli/input.mjs +2 -2
- package/cli/intake-command.mjs +107 -0
- package/cli/interactive-launcher.mjs +2 -160
- package/cli/interchange-report.mjs +8 -10
- package/cli/investigation-artifacts.mjs +176 -0
- package/cli/investigation-checkpoint.mjs +10 -0
- package/cli/investigation-folder.mjs +114 -0
- package/cli/investigation-manifest.mjs +1 -112
- package/cli/investigation-package-review.mjs +103 -0
- package/cli/investigation-plan.mjs +38 -147
- package/cli/investigation-recipes.mjs +177 -0
- package/cli/investigation-run.mjs +256 -35
- package/cli/local-document-checkpoint.mjs +170 -0
- package/cli/local-mmdb-review.mjs +197 -17
- package/cli/lookalike-calibration.mjs +1 -1
- package/cli/lookup-brief.mjs +2 -2
- package/cli/lookup-browser.mjs +5 -12
- package/cli/lookup-command-runner.mjs +33 -39
- package/cli/lookup-diff.mjs +47 -12
- package/cli/lookup-plan.mjs +16 -14
- package/cli/lookup-reconcile.mjs +15 -12
- package/cli/lookup-timeline.mjs +7 -6
- package/cli/mail-header-review.mjs +24 -247
- package/cli/mail-review.mjs +75 -21
- package/cli/manual.mjs +1 -0
- package/cli/network-arguments.mjs +51 -0
- package/cli/network-command-definitions.mjs +130 -0
- package/cli/network-command-runner.mjs +45 -31
- package/cli/offline-evidence-review.mjs +39 -6
- package/cli/output-file.mjs +31 -5
- package/cli/posture.mjs +1 -1
- package/cli/registry-doctor.mjs +3 -1
- package/cli/registry-fixture-scaffold.mjs +1 -1
- package/cli/registry-support.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +10 -9
- package/cli/review-arguments.mjs +131 -0
- package/cli/review-command-definitions.mjs +273 -0
- package/cli/review-command-runner.mjs +104 -149
- package/cli/risk-calibration.mjs +8 -8
- package/cli/runner.mjs +74 -37
- package/cli/saved-lookup.mjs +2 -2
- package/cli/selected-input-worker.mjs +63 -0
- package/cli/sharing-review.mjs +8 -80
- package/cli/signer-trust.mjs +104 -0
- package/cli/source-reliability.mjs +75 -84
- package/cli/support-arguments.mjs +49 -0
- package/cli/support-command-definitions.mjs +209 -0
- package/cli/support-command-runner.mjs +2 -2
- package/cli/terminal-input.mjs +168 -0
- package/cli/workflow-arguments.mjs +86 -0
- package/cli/workflow-command-definitions.mjs +78 -0
- package/cli/workflow-command-runner.mjs +75 -13
- package/docs/cli-reference.md +103 -30
- package/docs/cli.md +510 -25
- package/docs/contextual-reviews.md +155 -0
- package/lib/abort.mjs +4 -1
- package/lib/analyst-taxonomy.mjs +2 -40
- package/lib/application-version.mjs +1 -1
- package/lib/availability.mjs +27 -754
- package/lib/bounded-contract-normalizers.mjs +4 -0
- package/lib/bounded-file.mjs +3 -1
- package/lib/bounded-html-document.mjs +125 -0
- package/lib/bounded-json.mjs +2 -290
- package/lib/browser-library-profile.mjs +99 -130
- package/lib/browser-library-worker.mjs +112 -0
- package/lib/candidate-provenance-bounds.mjs +2 -0
- package/lib/classify.mjs +1 -1
- package/lib/client-behavior-profile.mjs +3 -3
- package/lib/ct-query.mjs +2 -27
- package/lib/ct-response-bounds.mjs +2 -9
- package/lib/ct-search.mjs +89 -46
- package/lib/dmarc-discovery.mjs +62 -0
- package/lib/dns-convergence-review.mjs +21 -8
- package/lib/dns-delegation-health.mjs +36 -22
- package/lib/dns-inheritance-review.mjs +207 -0
- package/lib/dns-intelligence.mjs +44 -29
- package/lib/dns-mx.mjs +1 -1
- package/lib/dns-query-coverage.mjs +40 -0
- package/lib/dnssec-evidence-validation.mjs +11 -3
- package/lib/domain-assurance.mjs +1 -1
- package/lib/domain-change-packet.mjs +18 -14
- package/lib/domain-change-review.mjs +40 -16
- package/lib/domain-control-flight-recorder.mjs +301 -124
- package/lib/domain-control-manifest.mjs +126 -58
- package/lib/domain-posture-analysis.mjs +41 -11
- package/lib/domain-posture-parsers.mjs +88 -6
- package/lib/domain-posture.mjs +120 -32
- package/lib/domain-registration.mjs +392 -0
- package/lib/evidence-export-privacy.mjs +568 -0
- package/lib/evidence-export.mjs +12 -593
- package/lib/evidence-report.mjs +7 -2
- package/lib/external-intelligence-risk.mjs +2 -124
- package/lib/favicon.mjs +117 -41
- package/lib/generated/cisa-kev-catalog.mjs +50 -4
- package/lib/generated/registrar-standing-catalogue.mjs +18 -10
- package/lib/generated/retire-browser-catalog.mjs +2742 -1034
- package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
- package/lib/html-signals.mjs +87 -85
- package/lib/http-evidence-bounds.mjs +2 -7
- package/lib/http-intelligence.mjs +52 -27
- package/lib/idn-confusable-policy.mjs +6 -4
- package/lib/idn-confusables.mjs +1 -1
- package/lib/interchange-fidelity-registry.mjs +10 -0
- package/lib/lookup-cache.mjs +34 -11
- package/lib/lookup-child-profile-contract.mjs +24 -15
- package/lib/lookup-contract-primitives.mjs +1 -1
- package/lib/lookup-diagnostics.mjs +2 -1
- package/lib/lookup-network-evidence-bounds.mjs +1 -10
- package/lib/lookup-progress.mjs +79 -13
- package/lib/lookup-response-contract.mjs +1077 -0
- package/lib/lookup-response.mjs +11 -24
- package/lib/lookup-source-progress.mjs +24 -35
- package/lib/lookup.mjs +49 -28
- package/lib/observed-network-context.mjs +43 -27
- package/lib/opportunity-scoring.mjs +2 -107
- package/lib/outbound-identity.mjs +1 -1
- package/lib/outbound-request-bounds.mjs +4 -1
- package/lib/page-fingerprints.mjs +87 -134
- package/lib/page-role-profile.mjs +12 -8
- package/lib/perceptual-hash-comparison.mjs +2 -34
- package/lib/perceptual-hash.mjs +79 -24
- package/lib/portable-generator.mjs +2 -29
- package/lib/project-metadata.mjs +2 -8
- package/lib/rdap-bootstrap.mjs +72 -22
- package/lib/rdap-client.mjs +26 -14
- package/lib/rdap-normalization.mjs +33 -18
- package/lib/rdap-search-workbench.mjs +1 -1
- package/lib/rdap-transport.mjs +47 -20
- package/lib/rdap-types.mjs +2 -1
- package/lib/rdap.mjs +18 -3
- package/lib/registrable-domain.mjs +2 -36
- package/lib/registrar-standing-contract.mjs +2 -1
- package/lib/registrar-standing.mjs +9 -13
- package/lib/registry-capabilities.mjs +7 -3
- package/lib/registry-capability-catalogue.mjs +29 -20
- package/lib/registry-dates.mjs +2 -190
- package/lib/registry-insights.mjs +56 -18
- package/lib/response-policy.mjs +86 -48
- package/lib/risk-calibration-summary.mjs +2 -338
- package/lib/risk-scoring.mjs +2 -345
- package/lib/rpki-evidence.mjs +8 -5
- package/lib/safe-fetch.mjs +54 -47
- package/lib/scoring-evidence-quality.mjs +2 -107
- package/lib/security-txt.mjs +10 -7
- package/lib/semantic-version.mjs +2 -54
- package/lib/service-binding-dns.mjs +35 -0
- package/lib/smtp-transport-review.mjs +75 -34
- package/lib/sslbl-certificates.generated.mjs +141 -138
- package/lib/static-html-analysis.mjs +117 -146
- package/lib/structured-data-identity.mjs +2 -1
- package/lib/technology-markup.mjs +100 -0
- package/lib/threat-intelligence-manifests.mjs +11 -139
- package/lib/threat-intelligence-runtime.mjs +24 -52
- package/lib/threat-intelligence-targets.mjs +6 -34
- package/lib/threat-intelligence-types.mjs +2 -36
- package/lib/typosquat-generator.mjs +29 -16
- package/lib/urlhaus-intelligence.mjs +8 -3
- package/lib/urlscan-intelligence.mjs +1 -1
- package/lib/website-enrichment.mjs +388 -0
- package/lib/website-security-posture.mjs +30 -6
- package/lib/website-technology.mjs +111 -71
- package/lib/whois-authority.mjs +55 -5
- package/lib/whois-chain.mjs +9 -2
- package/lib/whois-common-formats.mjs +5 -5
- package/lib/whois-contracts.mjs +2 -1
- package/lib/whois-dialects.mjs +2 -2
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-parser-support.mjs +8 -1
- package/lib/whois-parser.mjs +5 -3
- package/lib/whois-transport.mjs +23 -7
- package/package.json +14 -14
- package/packages/analysis/analyst-taxonomy.mjs +40 -0
- package/packages/analysis/bounded-json.mjs +330 -0
- package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
- package/packages/analysis/cli-argument-grammar.mjs +216 -0
- package/packages/analysis/ct-query.mjs +27 -0
- package/packages/analysis/ct-response-bounds.mjs +9 -0
- package/packages/analysis/external-intelligence-risk.mjs +124 -0
- package/packages/analysis/http-evidence-bounds.mjs +8 -0
- package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
- package/packages/analysis/opportunity-scoring.mjs +107 -0
- package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
- package/packages/analysis/portable-generator.mjs +29 -0
- package/packages/analysis/project-metadata.mjs +8 -0
- package/packages/analysis/registrable-domain.mjs +56 -0
- package/packages/analysis/risk-calibration-summary.mjs +352 -0
- package/packages/analysis/risk-scoring.mjs +345 -0
- package/packages/analysis/scoring-evidence-quality.mjs +107 -0
- package/packages/analysis/semantic-version.mjs +54 -0
- package/packages/analysis/threat-intelligence-types.mjs +75 -0
- package/packages/cases/case-attachment-model.mjs +83 -0
- package/packages/cases/case-evidence-links.mjs +135 -0
- package/packages/cases/case-evidence-model.mjs +229 -69
- package/packages/cases/case-evidence-quality.mjs +5 -0
- package/packages/cases/case-export-input.mjs +55 -0
- package/packages/cases/case-incident-context.mjs +52 -0
- package/packages/cases/case-markdown.mjs +11 -0
- package/packages/cases/case-migration-model.mjs +98 -48
- package/packages/cases/case-model.mjs +1 -0
- package/packages/cases/case-recheck-model.mjs +149 -0
- package/packages/cases/case-record-contracts.mjs +1 -1
- package/packages/cases/case-record-core.mjs +4 -3
- package/packages/cases/case-record-operations.mjs +123 -133
- package/packages/cases/case-record-projection.mjs +30 -20
- package/packages/cases/case-record-version-input.mjs +19 -0
- package/packages/cases/case-report.mjs +114 -67
- package/packages/cases/case-requested-evidence.mjs +146 -0
- package/packages/cases/case-response-actions.mjs +756 -0
- package/packages/cases/case-response-model.mjs +49 -1129
- package/packages/cases/case-response-outcomes.mjs +338 -0
- package/packages/cases/case-response-packet-render.mjs +157 -0
- package/packages/cases/case-response-packet-types.mjs +1 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
- package/packages/cases/case-response-packet.mjs +58 -593
- package/packages/cases/case-response-records.mjs +93 -0
- package/packages/cases/case-response-review-inputs.mjs +382 -0
- package/packages/cases/case-response-values.mjs +85 -0
- package/packages/cases/case-review-package.mjs +45 -0
- package/packages/cases/case-selection.mjs +46 -0
- package/packages/cases/case-storage-model.mjs +26 -1
- package/packages/cases/case-workflow-metadata.mjs +225 -0
- package/packages/cases/case-workflow-migration.mjs +43 -0
- package/packages/cases/http-summary.mjs +6 -1
- package/packages/cases/response-route-freshness.mjs +16 -0
- package/packages/comparison/capture-context.mjs +61 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
- package/packages/comparison/favicon-similarity.mjs +1 -1
- package/packages/comparison/page-similarity.mjs +3 -2
- package/packages/comparison/relationship-evidence.mjs +103 -14
- package/packages/comparison/relationship-provenance.mjs +99 -0
- package/packages/contracts/analyst-interchange.mjs +234 -0
- package/packages/contracts/analyst-review-state-contract.mjs +11 -0
- package/packages/contracts/analyst-review-state.mjs +5 -4
- package/packages/contracts/case-portability.mjs +370 -114
- package/packages/contracts/case-views-contract.mjs +12 -0
- package/packages/contracts/case-views.mjs +26 -0
- package/packages/contracts/cli-command-semantics.mjs +3 -0
- package/packages/contracts/cli-grammar.mjs +6 -0
- package/packages/contracts/cli-lookup-brief.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +0 -1
- package/packages/contracts/context-review.mjs +24 -0
- package/packages/contracts/document-intake.mjs +11 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +98 -44
- package/packages/contracts/domain-control-manifest.mjs +128 -74
- package/packages/contracts/domain-control-monitor.mjs +59 -45
- package/packages/contracts/domain-control-review.mjs +200 -121
- package/packages/contracts/domain-name.mjs +21 -0
- package/packages/contracts/external-observation-interchange.mjs +2 -1
- package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
- package/packages/contracts/har-review.mjs +2 -0
- package/packages/contracts/http-url.mjs +2 -0
- package/packages/contracts/identity-events.mjs +4 -0
- package/packages/contracts/investigation-package-limits.mjs +24 -0
- package/packages/contracts/investigation-portability.mjs +56 -27
- package/packages/contracts/investigation-run.mjs +18 -0
- package/packages/contracts/ip-address.mjs +44 -0
- package/packages/contracts/lookup-evidence.mjs +35 -0
- package/packages/contracts/mail-authentication.mjs +5 -0
- package/packages/contracts/message-intake.mjs +31 -0
- package/packages/contracts/offline-comparison.mjs +4 -2
- package/packages/contracts/page-fingerprints.mjs +15 -0
- package/packages/contracts/passive-mail.mjs +10 -0
- package/packages/contracts/risk-calibration.mjs +0 -1
- package/packages/contracts/schema-compatibility.mjs +1 -1
- package/packages/contracts/schema-lifecycle.mjs +83 -153
- package/packages/contracts/selected-file-limits.mjs +6 -0
- package/packages/contracts/vulnerability-identifiers.mjs +5 -0
- package/packages/contracts/web-capture.mjs +10 -0
- package/packages/contracts/workspace-portability.mjs +219 -238
- package/packages/evidence/artifact-integrity.mjs +8 -2
- package/packages/evidence/artifact-structure.mjs +105 -0
- package/packages/evidence/collection-quality.mjs +67 -0
- package/packages/evidence/decision-fact.mjs +4 -1
- package/packages/evidence/dns-query-outcome.mjs +72 -0
- package/packages/evidence/domain-control-runtime.mjs +155 -27
- package/packages/evidence/domain-posture-context.mjs +79 -0
- package/packages/evidence/email-recipient.mjs +18 -0
- package/packages/evidence/image-regions.mjs +56 -0
- package/packages/evidence/latest-observations.mjs +28 -0
- package/packages/evidence/lookup-target.mjs +108 -0
- package/packages/evidence/observation.mjs +18 -1
- package/packages/evidence/passphrase-encryption.mjs +44 -0
- package/packages/evidence/png-pixels.mjs +117 -0
- package/packages/evidence/public-address-policy.mjs +83 -0
- package/packages/evidence/record-identity.mjs +72 -0
- package/packages/evidence/registry-dates.mjs +190 -0
- package/packages/evidence/retained-file.mjs +69 -0
- package/packages/evidence/review-age.mjs +14 -0
- package/packages/interchange/bagit.mjs +342 -0
- package/packages/interchange/bounded-zip-extraction.mjs +278 -0
- package/packages/interchange/crc32.mjs +17 -0
- package/packages/interchange/defensive-indicator-export.mjs +264 -0
- package/packages/interchange/external-findings-import.mjs +93 -61
- package/packages/interchange/managed-indicator-export.mjs +35 -0
- package/packages/interchange/managed-indicator-set.mjs +210 -0
- package/packages/interchange/misp-indicator-export.mjs +93 -0
- package/packages/interchange/sharing-policy.mjs +56 -0
- package/packages/interchange/stix-indicator-export.mjs +129 -0
- package/packages/interchange/web-capture-import.mjs +389 -0
- package/packages/investigation/acquisition-decision-packet.mjs +1 -1
- package/packages/investigation/bulk-mail-exposure.mjs +9 -14
- package/packages/investigation/capture-coverage.mjs +57 -0
- package/packages/investigation/connector-provenance-review.mjs +100 -0
- package/packages/investigation/context-review.mjs +35 -0
- package/packages/investigation/document-intake.mjs +81 -0
- package/packages/investigation/docx-intake.mjs +133 -0
- package/packages/investigation/domain-history-review.mjs +100 -0
- package/packages/investigation/domain-transition-review.mjs +47 -0
- package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
- package/packages/investigation/har-intake.mjs +68 -0
- package/packages/investigation/identity-event-intake.mjs +131 -0
- package/packages/investigation/identity-incident-review.mjs +39 -0
- package/packages/investigation/incident-sequence-review.mjs +58 -0
- package/packages/investigation/intake-report.mjs +19 -0
- package/packages/investigation/investigation-bagit.mjs +14 -0
- package/packages/investigation/investigation-capsule.mjs +33 -5
- package/packages/investigation/investigation-manifest.mjs +200 -0
- package/packages/investigation/investigation-package-crypto.mjs +98 -0
- package/packages/investigation/investigation-package.mjs +200 -0
- package/packages/investigation/link-intake.mjs +179 -0
- package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
- package/packages/investigation/lookup-asset-graph.mjs +161 -141
- package/packages/investigation/lookup-claim-readiness.mjs +18 -0
- package/packages/investigation/lookup-investigation-brief.mjs +3 -3
- package/packages/investigation/lookup-task-guidance.mjs +58 -0
- package/packages/investigation/mail-authentication-review.mjs +222 -0
- package/packages/investigation/mail-header-identity.mjs +209 -0
- package/packages/investigation/message-intake.mjs +195 -0
- package/packages/investigation/page-behaviour.mjs +113 -0
- package/packages/investigation/pdf-intake.mjs +155 -0
- package/packages/investigation/platform-continuity-review.mjs +60 -0
- package/packages/investigation/qr-intake.mjs +56 -0
- package/packages/investigation/requested-action-hints.mjs +11 -0
- package/packages/investigation/selected-input-review.mjs +17 -0
- package/packages/investigation/storefront-review.mjs +70 -0
- package/packages/monitoring/analyst-review-state.mjs +38 -90
- package/packages/workspace/brand-profile-model.mjs +160 -22
- package/packages/workspace/bulk-columns.mjs +17 -0
- package/packages/workspace/bulk-review-model.mjs +39 -31
- package/packages/workspace/bulk-session-model.mjs +112 -27
- package/packages/workspace/bulk-sort.mjs +1 -0
- package/packages/workspace/campaign-model.mjs +1 -1
- package/packages/workspace/case-views.mjs +102 -0
- package/packages/workspace/detection-rule-model.mjs +1 -1
- package/packages/workspace/hostile-input.mjs +29 -8
- package/packages/workspace/investigation-guide.mjs +2 -2
- package/packages/workspace/investigation-template-model.mjs +45 -6
- package/packages/workspace/page-baseline.mjs +20 -17
- package/packages/workspace/relationship-observation-model.mjs +54 -35
- package/packages/workspace/shortlist-model.mjs +14 -5
- package/packages/workspace/watchlist-history.mjs +30 -14
- package/packages/workspace/watchlist-store.mjs +24 -6
- package/packages/workspace/website-snapshot-model.mjs +98 -33
- package/packages/workspace/workspace-archive-crypto.mjs +41 -61
- package/packages/workspace/workspace-archive.mjs +90 -25
- package/third-party-notices.txt +577 -17
- package/lib/hostname.mjs +0 -38
- package/packages/investigation/bulk-review-export.mjs +0 -63
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { domainToASCII } from 'node:url';
|
|
2
|
-
import { exactKeys, requireBoundedString, requireIsoTimestamp, requireRecord, } from "../lib/bounded-contract-normalizers.mjs";
|
|
3
|
-
import { EXTERNAL_FINDINGS_SCHEMA, EXTERNAL_FINDINGS_VERSION, EXTERNAL_FINDING_CATEGORIES, MAX_EXTERNAL_FINDINGS, MAX_EXTERNAL_FINDINGS_PER_DOMAIN, MAX_EXTERNAL_FINDING_DOMAINS, parseExternalFindingsDocument, } from "../packages/interchange/external-findings-import.mjs";
|
|
2
|
+
import { compareCodeUnits, exactKeys, requireBoundedString, requireIsoTimestamp, requireRecord, } from "../lib/bounded-contract-normalizers.mjs";
|
|
3
|
+
import { EXTERNAL_FINDINGS_SCHEMA, EXTERNAL_FINDINGS_VERSION, EXTERNAL_FINDING_CATEGORIES, MAX_EXTERNAL_FINDINGS, MAX_EXTERNAL_FINDINGS_PER_DOMAIN, MAX_EXTERNAL_FINDING_DOMAINS, MAX_EXTERNAL_FINDING_LIMITATIONS, MAX_EXTERNAL_FINDING_LIMITATION_LENGTH, parseExternalFindingsDocument, retainExternalFindingLimitations, } from "../packages/interchange/external-findings-import.mjs";
|
|
4
4
|
export const EXTERNAL_OBSERVATION_MAPPING_SCHEMA = 'whoisleuth.external-observation-mapping';
|
|
5
5
|
export const EXTERNAL_OBSERVATION_MAPPING_VERSION = 1;
|
|
6
6
|
export const MAX_EXTERNAL_OBSERVATION_MAPPING_BYTES = 4 * 1024 * 1024;
|
|
@@ -49,9 +49,9 @@ function domain(value, label) {
|
|
|
49
49
|
function limitations(value) {
|
|
50
50
|
if (value === undefined)
|
|
51
51
|
return Object.freeze([]);
|
|
52
|
-
if (!Array.isArray(value) || value.length >
|
|
53
|
-
throw new TypeError(
|
|
54
|
-
return Object.freeze([...new Set(value.map((entry, index) => requireBoundedString(entry, `profile.limitations[${index}]`,
|
|
52
|
+
if (!Array.isArray(value) || value.length > MAX_EXTERNAL_FINDING_LIMITATIONS)
|
|
53
|
+
throw new TypeError(`profile.limitations must contain no more than ${MAX_EXTERNAL_FINDING_LIMITATIONS} entries.`);
|
|
54
|
+
return Object.freeze([...new Set(value.map((entry, index) => requireBoundedString(entry, `profile.limitations[${index}]`, MAX_EXTERNAL_FINDING_LIMITATION_LENGTH)))]);
|
|
55
55
|
}
|
|
56
56
|
export function mapExternalObservations(inputRaw) {
|
|
57
57
|
const input = requireRecord(inputRaw, 'External observation mapping');
|
|
@@ -112,7 +112,7 @@ export function mapExternalObservations(inputRaw) {
|
|
|
112
112
|
: null,
|
|
113
113
|
structuredObservation: null,
|
|
114
114
|
});
|
|
115
|
-
}).sort((left, right) => left.domain
|
|
115
|
+
}).sort((left, right) => compareCodeUnits(left.domain, right.domain) || compareCodeUnits(left.observedAt, right.observedAt) || compareCodeUnits(left.summary, right.summary));
|
|
116
116
|
const selected = [];
|
|
117
117
|
const seen = new Set();
|
|
118
118
|
const domainCounts = new Map();
|
|
@@ -140,11 +140,10 @@ export function mapExternalObservations(inputRaw) {
|
|
|
140
140
|
source,
|
|
141
141
|
findings: selected.map((finding) => ({
|
|
142
142
|
...finding,
|
|
143
|
-
limitations: [
|
|
144
|
-
...finding.limitations,
|
|
143
|
+
limitations: retainExternalFindingLimitations(finding.limitations, [
|
|
145
144
|
`Mapped locally with declarative profile ${profile.id} version ${profile.version}; the profile does not verify the upstream field semantics.`,
|
|
146
|
-
...(truncated ? [
|
|
147
|
-
]
|
|
145
|
+
...(truncated ? [`The supplied records exceeded the browser import boundary; ${selected.length} of ${seen.size} unique findings were retained in deterministic order.`] : []),
|
|
146
|
+
]),
|
|
148
147
|
})),
|
|
149
148
|
};
|
|
150
149
|
return parseExternalFindingsDocument(document);
|
|
@@ -168,6 +168,12 @@ function formatTerminalPosture(document) {
|
|
|
168
168
|
`Dependencies ${dependencies.length} observed · ${dmarcAuthorizations.length} DMARC reporting destination${dmarcAuthorizations.length === 1 ? '' : 's'} checked`,
|
|
169
169
|
'',
|
|
170
170
|
];
|
|
171
|
+
for (const value of dmarcAuthorizations.slice(0, 10)) {
|
|
172
|
+
const item = terminalRecord(value);
|
|
173
|
+
lines.push(`Reporting ${safeTerminalValue(item.destination)} · ${safeTerminalValue(item.reportType)} · ${item.state === 'self' ? 'same organisational scope' : safeTerminalValue(item.state).replaceAll('_', ' ')}`);
|
|
174
|
+
if (item.error)
|
|
175
|
+
lines.push(` Detail ${safeTerminalValue(item.error)}`);
|
|
176
|
+
}
|
|
171
177
|
for (const value of checks) {
|
|
172
178
|
const item = terminalRecord(value);
|
|
173
179
|
lines.push(`[${safeTerminalValue(item.status, 'info').toUpperCase()}] ${safeTerminalValue(item.label)}: ${safeTerminalValue(item.summary)}`);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { registryAccessProfileLabel } from "../registry-access.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { dnsAddressFamilyEvidence } from "../../packages/evidence/dns-query-outcome.mjs";
|
|
3
|
+
import { appendTechnologyLines } from "./terminal-technology.mjs";
|
|
3
4
|
import { appendDeliveryMetadataLines, appendPublicationMetadataLines } from "./terminal-metadata.mjs";
|
|
5
|
+
import { securityPostureReview, summarizeSecurityPostureReview } from "../../lib/website-security-posture.mjs";
|
|
4
6
|
import { appendSection, boundedTerminalComponent, boundedTerminalList, boundedTerminalWithSuffix, MAX_TLS_TERMINAL_ALT_NAMES, MAX_TLS_TERMINAL_PURPOSES, safeTerminalValue, terminalCount, terminalCountSummary, terminalDisplayCount, terminalRecord, titleCase, } from "./terminal-shared.mjs";
|
|
5
7
|
const MAX_LOOKUP_TERMINAL_RECORDS = 5;
|
|
6
8
|
const MAX_LOOKUP_TERMINAL_NAMES = 5;
|
|
@@ -10,13 +12,6 @@ const MAX_LOOKUP_TERMINAL_LIMITATIONS = 3;
|
|
|
10
12
|
const MAX_LOOKUP_TERMINAL_CIDRS = 5;
|
|
11
13
|
const MAX_LOOKUP_TERMINAL_FINDINGS = 5;
|
|
12
14
|
const MAX_LOOKUP_TERMINAL_ABUSE_ROUTES = 6;
|
|
13
|
-
function terminalTechnologyRoleNames(findings, role) {
|
|
14
|
-
const names = findings
|
|
15
|
-
.filter((finding) => technologyEvidenceRoles(finding).includes(role))
|
|
16
|
-
.slice(0, 6)
|
|
17
|
-
.map((finding) => safeTerminalValue(terminalRecord(finding).name, 'Unnamed indicator'));
|
|
18
|
-
return names.length ? boundedTerminalList(names, Math.max(0, findings.filter((finding) => technologyEvidenceRoles(finding).includes(role)).length - names.length)) : 'None retained';
|
|
19
|
-
}
|
|
20
15
|
function formatLookupDnsRecord(value) {
|
|
21
16
|
if (typeof value === 'string' || typeof value === 'number')
|
|
22
17
|
return safeTerminalValue(value);
|
|
@@ -244,6 +239,11 @@ function formatTerminalLookup(document, { detail = 'standard' } = {}) {
|
|
|
244
239
|
targetLines.push(`Input host ${safeTerminalValue(document.inputHostname)}`);
|
|
245
240
|
targetLines.push(`Registry query ${safeTerminalValue(document.registrableDomain)}`);
|
|
246
241
|
}
|
|
242
|
+
if (availability.observationHostname) {
|
|
243
|
+
targetLines.push(`DNS/TLS/web ${safeTerminalValue(availability.observationHostname)}`);
|
|
244
|
+
}
|
|
245
|
+
if (availability.webObservationMode === 'selected_url')
|
|
246
|
+
targetLines.push('Website scope Explicitly selected URL; query omitted from provenance');
|
|
247
247
|
if (availability.applicable) {
|
|
248
248
|
targetLines.push(`Availability ${titleCase(availability.state)}`);
|
|
249
249
|
targetLines.push(`Confidence ${titleCase(availability.confidence)}`);
|
|
@@ -320,6 +320,11 @@ function formatTerminalLookup(document, { detail = 'standard' } = {}) {
|
|
|
320
320
|
if (Object.keys(dns).length) {
|
|
321
321
|
dnsLines.push(`Evidence ${titleCase(dns.status)}`);
|
|
322
322
|
dnsLines.push(`Completeness ${dns.complete === true ? 'Complete' : 'Incomplete'}`);
|
|
323
|
+
for (const family of ['a', 'aaaa']) {
|
|
324
|
+
const evidence = dnsAddressFamilyEvidence(dns, family);
|
|
325
|
+
if (evidence.value)
|
|
326
|
+
dnsLines.push(`${family.toUpperCase().padEnd(14)} ${safeTerminalValue(evidence.value)}`);
|
|
327
|
+
}
|
|
323
328
|
const recordTypes = [
|
|
324
329
|
['a', 'A'], ['aaaa', 'AAAA'], ['cname', 'CNAME'], ['ns', 'NS'],
|
|
325
330
|
['mx', 'MX'], ['spf', 'SPF'], ['dmarc', 'DMARC'], ['caa', 'CAA'],
|
|
@@ -486,7 +491,7 @@ function formatTerminalLookup(document, { detail = 'standard' } = {}) {
|
|
|
486
491
|
const technology = terminalRecord(availability.technologyProfile);
|
|
487
492
|
const browserLibraries = terminalRecord(technology.browserLibraryProfile);
|
|
488
493
|
const posture = terminalRecord(availability.securityPosture);
|
|
489
|
-
const
|
|
494
|
+
const postureReview = summarizeSecurityPostureReview(posture.findings);
|
|
490
495
|
const pageIdentity = terminalRecord(availability.pageIdentity);
|
|
491
496
|
const pageRole = terminalRecord(availability.pageRoleProfile);
|
|
492
497
|
const clientBehavior = terminalRecord(availability.clientBehaviorProfile);
|
|
@@ -578,47 +583,20 @@ function formatTerminalLookup(document, { detail = 'standard' } = {}) {
|
|
|
578
583
|
if (detail !== 'summary' && visible.length)
|
|
579
584
|
websiteLines.push(`Declarations ${safeTerminalValue(visible.join('; '))}`);
|
|
580
585
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const item = terminalRecord(finding);
|
|
586
|
-
const qualifiers = [item.category, item.confidence ? `${item.confidence} signature strength` : null].filter(Boolean).map((value) => safeTerminalValue(value));
|
|
587
|
-
return `${safeTerminalValue(item.name, 'Unnamed indicator')}${qualifiers.length ? ` (${qualifiers.join(', ')})` : ''}`;
|
|
588
|
-
});
|
|
589
|
-
if (detail !== 'summary' && visible.length) {
|
|
590
|
-
const omitted = findings.length - visible.length;
|
|
591
|
-
websiteLines.push(`Indicators ${safeTerminalValue(`${visible.join('; ')}${omitted > 0 ? `; +${omitted} more` : ''}`)}`);
|
|
592
|
-
}
|
|
593
|
-
if (detail !== 'summary') {
|
|
594
|
-
const nameservers = Array.isArray(availability.nameservers)
|
|
595
|
-
? availability.nameservers.slice(0, MAX_LOOKUP_TERMINAL_NAMES).map((value) => safeTerminalValue(value))
|
|
596
|
-
: [];
|
|
597
|
-
websiteLines.push(`Nameservers ${nameservers.length ? boundedTerminalList(nameservers, Math.max(0, availability.nameservers.length - nameservers.length)) : 'Unavailable'} · identity does not establish operator or web-host ownership`);
|
|
598
|
-
websiteLines.push(`Observed edge ${terminalTechnologyRoleNames(findings, 'observed_edge')}`);
|
|
599
|
-
websiteLines.push(`App platform ${terminalTechnologyRoleNames(findings, 'application_platform')}`);
|
|
600
|
-
websiteLines.push(`Framework/run ${terminalTechnologyRoleNames(findings, 'framework_runtime')}`);
|
|
601
|
-
websiteLines.push(`Embedded deps ${terminalTechnologyRoleNames(findings, 'embedded_dependency')}`);
|
|
602
|
-
websiteLines.push('Origin host Not established from retained evidence');
|
|
603
|
-
}
|
|
604
|
-
if (detail !== 'summary' && (browserLibraries.profileVersion === 1 || browserLibraries.source === 'derived')) {
|
|
605
|
-
const libraries = Array.isArray(browserLibraries.findings) ? browserLibraries.findings : [];
|
|
606
|
-
const advisoryMatches = libraries.filter((finding) => terminalCount(terminalRecord(finding).advisoryCount) > 0).length;
|
|
607
|
-
websiteLines.push(`JS libraries ${titleCase(browserLibraries.status)} · ${libraries.length} apparent · `
|
|
608
|
-
+ `${advisoryMatches} with catalogue advisory match${advisoryMatches === 1 ? '' : 'es'}`);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
586
|
+
appendTechnologyLines(websiteLines, {
|
|
587
|
+
technology, libraries: browserLibraries, nameservers: availability.nameservers,
|
|
588
|
+
generatedAt: document.generatedAt, detail,
|
|
589
|
+
});
|
|
611
590
|
if (posture.status || posture.source === 'derived') {
|
|
612
591
|
websiteLines.push(`Posture ${titleCase(posture.status)}`);
|
|
613
|
-
if (detail !== 'summary' &&
|
|
614
|
-
websiteLines.push(`Posture
|
|
615
|
-
+
|
|
616
|
-
+
|
|
617
|
-
+ `${terminalCount(postureSummary.unavailable)} unavailable`);
|
|
592
|
+
if (detail !== 'summary' && Array.isArray(posture.findings))
|
|
593
|
+
websiteLines.push(`Posture checks Needs review ${postureReview.needsReview} · `
|
|
594
|
+
+ `Other findings ${postureReview.otherFindings} · `
|
|
595
|
+
+ `Could not assess ${postureReview.unavailable}`);
|
|
618
596
|
if (detail === 'verbose' && positiveSourceStatus(posture.status)) {
|
|
619
597
|
const findings = boundedFindingLabels(posture.findings, (finding) => {
|
|
620
|
-
const
|
|
621
|
-
return `${safeTerminalValue(finding.label, 'Unlabelled finding')}${
|
|
598
|
+
const review = securityPostureReview(finding);
|
|
599
|
+
return `${safeTerminalValue(finding.label, 'Unlabelled finding')}${review.label ? ` (${review.label})` : ''}`;
|
|
622
600
|
});
|
|
623
601
|
if (findings)
|
|
624
602
|
websiteLines.push(`Posture labels ${findings}`);
|
|
@@ -6,7 +6,9 @@ const MAX_TLS_TERMINAL_PURPOSES = 8;
|
|
|
6
6
|
// through soft hyphens, combining grapheme joiners, variation selectors, tag
|
|
7
7
|
// characters, bidi controls, or other zero-width formatting code points.
|
|
8
8
|
const TERMINAL_DEFAULT_IGNORABLE_RE = /\p{Default_Ignorable_Code_Point}/gu;
|
|
9
|
-
function safeTerminalValue(value, fallback = '—') {
|
|
9
|
+
function safeTerminalValue(value, fallback = '—', maximum = MAX_TERMINAL_VALUE_LENGTH) {
|
|
10
|
+
if (!Number.isSafeInteger(maximum) || maximum < 1)
|
|
11
|
+
throw new TypeError('Terminal text requires a positive finite character bound.');
|
|
10
12
|
if (value === null || value === undefined || value === '')
|
|
11
13
|
return fallback;
|
|
12
14
|
const normalized = String(value)
|
|
@@ -16,8 +18,8 @@ function safeTerminalValue(value, fallback = '—') {
|
|
|
16
18
|
.trim();
|
|
17
19
|
if (!normalized)
|
|
18
20
|
return fallback;
|
|
19
|
-
return normalized.length >
|
|
20
|
-
? `${normalized.slice(0,
|
|
21
|
+
return normalized.length > maximum
|
|
22
|
+
? `${normalized.slice(0, maximum - 1)}…`
|
|
21
23
|
: normalized;
|
|
22
24
|
}
|
|
23
25
|
function boundedTerminalList(values, omitted) {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { MAX_EVIDENCE_PER_TECHNOLOGY, MAX_LIBRARY_FINDINGS, MAX_TECHNOLOGY_FINDINGS, } from "../../lib/lookup-child-profile-contract.mjs";
|
|
2
|
+
import { TECHNOLOGY_EVIDENCE_ROLE_ORDER, technologyEvidenceRoles, } from "../../lib/technology-evidence-role.mjs";
|
|
3
|
+
import { MAX_OBSERVATION_LIMITATIONS, readObservationTime } from "../../packages/evidence/observation.mjs";
|
|
4
|
+
import { boundedTerminalList, safeTerminalValue, terminalCount, terminalRecord, titleCase } from "./terminal-shared.mjs";
|
|
5
|
+
const ROLE_LABELS = {
|
|
6
|
+
observed_edge: 'Observed edge',
|
|
7
|
+
application_platform: 'App platform',
|
|
8
|
+
framework_runtime: 'Framework/run',
|
|
9
|
+
embedded_dependency: 'Embedded deps',
|
|
10
|
+
};
|
|
11
|
+
const SUMMARY_FINDINGS = 6;
|
|
12
|
+
const SUMMARY_NAMESERVERS = 5;
|
|
13
|
+
function observationLine(label, value, generatedAt) {
|
|
14
|
+
const time = readObservationTime(value, generatedAt);
|
|
15
|
+
if (!time.observedAt)
|
|
16
|
+
return `${label.padEnd(15)}Observed time unavailable`;
|
|
17
|
+
const age = time.ageDays === null ? 'age unavailable'
|
|
18
|
+
: time.ageDays === 0 ? 'less than a day old at output generation'
|
|
19
|
+
: `${time.ageDays} day${time.ageDays === 1 ? '' : 's'} old at output generation`;
|
|
20
|
+
return `${label.padEnd(15)}${time.observedAt} · ${age}`;
|
|
21
|
+
}
|
|
22
|
+
function appendLimitations(lines, value) {
|
|
23
|
+
if (!Array.isArray(value))
|
|
24
|
+
return;
|
|
25
|
+
for (const limitation of value.slice(0, MAX_OBSERVATION_LIMITATIONS)) {
|
|
26
|
+
lines.push(` Limit ${safeTerminalValue(limitation)}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Presents admitted source records; it never collects or infers a hidden origin. */
|
|
30
|
+
export function appendTechnologyLines(lines, input) {
|
|
31
|
+
const { technology, libraries, nameservers, generatedAt, detail } = input;
|
|
32
|
+
if (!technology.status && technology.source !== 'derived')
|
|
33
|
+
return;
|
|
34
|
+
const suppliedFindings = Array.isArray(technology.findings) ? technology.findings : [];
|
|
35
|
+
const findings = suppliedFindings.slice(0, MAX_TECHNOLOGY_FINDINGS).map((value) => {
|
|
36
|
+
const finding = terminalRecord(value);
|
|
37
|
+
return { finding, roles: technologyEvidenceRoles(finding) };
|
|
38
|
+
});
|
|
39
|
+
lines.push(`Technology ${titleCase(technology.status)} · ${findings.length} indicator${findings.length === 1 ? '' : 's'}`);
|
|
40
|
+
if (suppliedFindings.length > findings.length)
|
|
41
|
+
lines.push('Technology Additional findings exceed the admitted profile limit');
|
|
42
|
+
if (detail === 'summary')
|
|
43
|
+
return;
|
|
44
|
+
lines.push(observationLine('Tech observed', technology.observedAt, generatedAt));
|
|
45
|
+
if (detail === 'standard' && findings.length) {
|
|
46
|
+
const visible = findings.slice(0, SUMMARY_FINDINGS).map(({ finding }) => {
|
|
47
|
+
const qualifiers = [finding.category, finding.confidence ? `${safeTerminalValue(finding.confidence)} signature strength` : null]
|
|
48
|
+
.filter(Boolean).map((value) => safeTerminalValue(value));
|
|
49
|
+
return `${safeTerminalValue(finding.name, 'Unnamed indicator')}${qualifiers.length ? ` (${qualifiers.join(', ')})` : ''}`;
|
|
50
|
+
});
|
|
51
|
+
lines.push(`Indicators ${boundedTerminalList(visible, findings.length - visible.length)}`);
|
|
52
|
+
}
|
|
53
|
+
const retainedNameservers = Array.isArray(nameservers) ? nameservers : [];
|
|
54
|
+
const visibleNameservers = retainedNameservers.slice(0, SUMMARY_NAMESERVERS).map((value) => safeTerminalValue(value));
|
|
55
|
+
lines.push(`Nameservers ${visibleNameservers.length ? boundedTerminalList(visibleNameservers, retainedNameservers.length - visibleNameservers.length) : 'Unavailable'} · identity does not establish operator or web-host ownership`);
|
|
56
|
+
for (const role of TECHNOLOGY_EVIDENCE_ROLE_ORDER) {
|
|
57
|
+
const matching = findings.filter((item) => item.roles.includes(role));
|
|
58
|
+
const names = matching.slice(0, SUMMARY_FINDINGS).map(({ finding }) => safeTerminalValue(finding.name, 'Unnamed indicator'));
|
|
59
|
+
lines.push(`${ROLE_LABELS[role].padEnd(15)}${names.length ? boundedTerminalList(names, matching.length - names.length) : 'None retained'}`);
|
|
60
|
+
}
|
|
61
|
+
lines.push('Origin host Not established from retained evidence');
|
|
62
|
+
if (detail === 'verbose') {
|
|
63
|
+
for (const { finding, roles } of findings) {
|
|
64
|
+
lines.push(`Indicator ${safeTerminalValue(finding.name, 'Unnamed indicator')}`);
|
|
65
|
+
lines.push(` Role ${roles.map((role) => ROLE_LABELS[role]).join(', ') || 'Not retained'}`);
|
|
66
|
+
lines.push(` Strength ${safeTerminalValue(finding.confidence, 'Unknown')} signature strength`);
|
|
67
|
+
const suppliedEvidence = Array.isArray(finding.evidence) ? finding.evidence : [];
|
|
68
|
+
const evidence = suppliedEvidence.slice(0, MAX_EVIDENCE_PER_TECHNOLOGY);
|
|
69
|
+
if (!evidence.length)
|
|
70
|
+
lines.push(' Signal No detailed signal was retained');
|
|
71
|
+
for (const value of evidence) {
|
|
72
|
+
const item = terminalRecord(value);
|
|
73
|
+
if (typeof item.source !== 'string' || !item.source.trim()
|
|
74
|
+
|| typeof item.description !== 'string' || !item.description.trim()) {
|
|
75
|
+
lines.push(' Signal Incomplete retained signal record');
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const role = TECHNOLOGY_EVIDENCE_ROLE_ORDER.find((candidate) => candidate === item.role);
|
|
79
|
+
lines.push(` Source ${safeTerminalValue(item.source, 'Not retained')} · ${role ? ROLE_LABELS[role] : 'Role not retained'}`);
|
|
80
|
+
lines.push(` Signal ${safeTerminalValue(item.description, 'Not retained')}`);
|
|
81
|
+
}
|
|
82
|
+
if (suppliedEvidence.length > evidence.length)
|
|
83
|
+
lines.push(' Limit Additional signals exceed the admitted profile limit');
|
|
84
|
+
}
|
|
85
|
+
appendLimitations(lines, technology.limitations);
|
|
86
|
+
}
|
|
87
|
+
if (libraries.profileVersion !== 1 && libraries.profileVersion !== 2 && libraries.source !== 'derived')
|
|
88
|
+
return;
|
|
89
|
+
const suppliedLibraries = Array.isArray(libraries.findings) ? libraries.findings : [];
|
|
90
|
+
const libraryFindings = suppliedLibraries.slice(0, MAX_LIBRARY_FINDINGS).map(terminalRecord);
|
|
91
|
+
const advisoryMatches = libraryFindings.filter((finding) => terminalCount(finding.advisoryCount) > 0).length;
|
|
92
|
+
lines.push(`JS libraries ${titleCase(libraries.status)} · ${libraryFindings.length} apparent · ${advisoryMatches} with catalogue advisory match${advisoryMatches === 1 ? '' : 'es'}`);
|
|
93
|
+
lines.push(observationLine('Lib observed', libraries.observedAt, generatedAt));
|
|
94
|
+
if (suppliedLibraries.length > libraryFindings.length)
|
|
95
|
+
lines.push('JS libraries Additional findings exceed the admitted profile limit');
|
|
96
|
+
if (detail !== 'verbose')
|
|
97
|
+
return;
|
|
98
|
+
const catalogue = terminalRecord(libraries.catalog);
|
|
99
|
+
lines.push(`Catalogue ${safeTerminalValue(catalogue.name, 'Component catalogue')} · ${safeTerminalValue(catalogue.version, 'Version unavailable')}`);
|
|
100
|
+
for (const library of libraryFindings) {
|
|
101
|
+
lines.push(`Library ${safeTerminalValue(library.name, 'Unnamed library')} · ${safeTerminalValue(library.apparentVersion, 'Version unavailable')}`);
|
|
102
|
+
const methods = Array.isArray(library.detectionMethods) ? library.detectionMethods.slice(0, 4).map((value) => safeTerminalValue(value)) : [];
|
|
103
|
+
lines.push(` Detected by ${methods.join(', ') || 'Not retained'}`);
|
|
104
|
+
lines.push(` Advisories ${terminalCount(library.advisoryCount)} catalogue matches`);
|
|
105
|
+
if (library.highestSeverity)
|
|
106
|
+
lines.push(` Severity ${safeTerminalValue(library.highestSeverity)}`);
|
|
107
|
+
for (const [field, label] of [['advisoryIdentifiers', 'Identifier'], ['knownExploitedIdentifiers', 'Known exploited']]) {
|
|
108
|
+
const values = Array.isArray(library[field]) ? library[field] : [];
|
|
109
|
+
for (const value of values.slice(0, 16))
|
|
110
|
+
lines.push(` ${label.padEnd(15)} ${safeTerminalValue(value)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
appendLimitations(lines, libraries.limitations);
|
|
114
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {} from "./command-argument-grammar.mjs";
|
|
2
|
+
import { CliUsageError } from "./errors.mjs";
|
|
3
|
+
import { terminalOptions, jsonOutput, parseTwoFileComparisonArguments, uniqueSources, } from "./argument-values.mjs";
|
|
4
|
+
function parseDiffArguments(parsed) {
|
|
5
|
+
const compared = parseTwoFileComparisonArguments(parsed, 'diff');
|
|
6
|
+
const leftSessionId = parsed.optionValue('--left-session');
|
|
7
|
+
const rightSessionId = parsed.optionValue('--right-session');
|
|
8
|
+
for (const [option, value] of [
|
|
9
|
+
['--left-session', leftSessionId],
|
|
10
|
+
['--right-session', rightSessionId],
|
|
11
|
+
]) {
|
|
12
|
+
if (value !== null && !/^[A-Za-z0-9_-]{1,128}$/u.test(value)) {
|
|
13
|
+
throw new CliUsageError(`${option} requires one bounded saved-session ID.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return { action: 'diff', ...compared, leftSessionId, rightSessionId };
|
|
17
|
+
}
|
|
18
|
+
export const HISTORY_ARGUMENT_PARSERS = Object.freeze({
|
|
19
|
+
diff: parseDiffArguments,
|
|
20
|
+
reconcile: (parsed) => ({
|
|
21
|
+
action: 'reconcile',
|
|
22
|
+
sources: uniqueSources(parsed, 'reconcile'),
|
|
23
|
+
output: jsonOutput(parsed),
|
|
24
|
+
...terminalOptions(parsed),
|
|
25
|
+
}),
|
|
26
|
+
timeline: (parsed) => ({
|
|
27
|
+
action: 'timeline',
|
|
28
|
+
sources: uniqueSources(parsed, 'timeline'),
|
|
29
|
+
output: jsonOutput(parsed),
|
|
30
|
+
...terminalOptions(parsed),
|
|
31
|
+
}),
|
|
32
|
+
export: (parsed) => ({
|
|
33
|
+
action: 'export',
|
|
34
|
+
source: parsed.positionalValue('source'),
|
|
35
|
+
format: parsed.hasOption('--markdown') ? 'markdown' : parsed.hasOption('--html') ? 'html' : 'json',
|
|
36
|
+
compact: parsed.hasOption('--compact'),
|
|
37
|
+
includeAttribution: !parsed.hasOption('--no-attribution'),
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { LOOKUP_EVIDENCE_SCHEMA_VERSION, V1_PUBLIC_LOOKUP_EVIDENCE_SCHEMA_VERSION } from "../packages/contracts/lookup-evidence.mjs";
|
|
2
|
+
import { PUBLISHED_V2_LOOKUP_EVIDENCE_VERSIONS, positional, OPTIONAL_FILE_POSITIONAL, constraint, EMPTY_CONSTRAINTS, commandSeed } from "./command-definition.mjs";
|
|
3
|
+
export const HISTORY_COMMAND_DEFINITIONS = Object.freeze({
|
|
4
|
+
diff: commandSeed({
|
|
5
|
+
reference: {
|
|
6
|
+
description: 'Compare an earlier and later artefact from the same retained Lookup, Bulk-session, or domain-portfolio family.',
|
|
7
|
+
example: 'whoisleuth diff earlier.json later.json --json',
|
|
8
|
+
boundary: 'Comparison is offline: the left input is earlier and the right input is later. Inputs must belong to the same supported family. Saved Lookups can describe the same or different domains; same-domain comparisons preserve observation times and collection uncertainty. For a multi-session Bulk export, --left-session selects a session from the left file and --right-session selects one from the right; missing, unavailable, equal, and different evidence remain separate states.',
|
|
9
|
+
},
|
|
10
|
+
collection: { scope: 'Reads two compatible retained artefacts capped at 8 MiB each and retains no source paths.' },
|
|
11
|
+
summary: 'Compare two compatible retained artefacts',
|
|
12
|
+
options: ['--left-session', '--right-session', '--json', '--quiet', '--no-color'],
|
|
13
|
+
positionals: Object.freeze([positional('sources', 'file', 2, 2)]),
|
|
14
|
+
constraints: EMPTY_CONSTRAINTS,
|
|
15
|
+
handlerOwner: 'history',
|
|
16
|
+
networkEffect: 'offline',
|
|
17
|
+
common: true,
|
|
18
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.cli.lookup-diff']),
|
|
19
|
+
primaryArtefacts: Object.freeze(['Retained-evidence comparison']),
|
|
20
|
+
planSupport: false,
|
|
21
|
+
additionalOutputFormats: Object.freeze([]),
|
|
22
|
+
bootstrapProfile: 'allowed',
|
|
23
|
+
}),
|
|
24
|
+
reconcile: commandSeed({
|
|
25
|
+
reference: {
|
|
26
|
+
description: 'Reconcile bounded values across independently labelled observations of one domain.',
|
|
27
|
+
example: 'whoisleuth reconcile office.json mobile.json external.json --json',
|
|
28
|
+
boundary: 'The command is offline, accepts 2 to 5 saved observations for one domain, and never treats labels as proof of network independence or majority agreement as truth.',
|
|
29
|
+
},
|
|
30
|
+
collection: { scope: 'Reads 2 to 5 saved observations for one domain, capped at 32 MiB in total.' },
|
|
31
|
+
summary: 'Reconcile independently labelled observations',
|
|
32
|
+
options: ['--json', '--quiet', '--no-color'],
|
|
33
|
+
positionals: Object.freeze([positional('sources', 'file', 2, 5)]),
|
|
34
|
+
constraints: EMPTY_CONSTRAINTS,
|
|
35
|
+
handlerOwner: 'history',
|
|
36
|
+
networkEffect: 'offline',
|
|
37
|
+
common: false,
|
|
38
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.cli.lookup-reconciliation']),
|
|
39
|
+
primaryArtefacts: Object.freeze([]),
|
|
40
|
+
planSupport: false,
|
|
41
|
+
additionalOutputFormats: Object.freeze([]),
|
|
42
|
+
bootstrapProfile: 'allowed',
|
|
43
|
+
}),
|
|
44
|
+
timeline: commandSeed({
|
|
45
|
+
reference: {
|
|
46
|
+
description: 'Build an ordered same-domain history from saved Lookup observations.',
|
|
47
|
+
example: 'whoisleuth timeline first.json second.json latest.json --json',
|
|
48
|
+
boundary: 'The command is offline, accepts 2 to 20 bounded inputs for one domain, retains no filenames or raw registry payloads, and does not treat changed collection conditions as a domain change.',
|
|
49
|
+
},
|
|
50
|
+
collection: { scope: 'Reads 2 to 20 saved observations for one domain, capped at 32 MiB in total.' },
|
|
51
|
+
summary: 'Build same-domain history from saved lookups',
|
|
52
|
+
options: ['--json', '--quiet', '--no-color'],
|
|
53
|
+
positionals: Object.freeze([positional('sources', 'file', 2, 20)]),
|
|
54
|
+
constraints: EMPTY_CONSTRAINTS,
|
|
55
|
+
handlerOwner: 'history',
|
|
56
|
+
networkEffect: 'offline',
|
|
57
|
+
common: false,
|
|
58
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.cli.lookup-timeline']),
|
|
59
|
+
primaryArtefacts: Object.freeze(['Bounded retained-observation timeline']),
|
|
60
|
+
planSupport: false,
|
|
61
|
+
additionalOutputFormats: Object.freeze([]),
|
|
62
|
+
bootstrapProfile: 'allowed',
|
|
63
|
+
}),
|
|
64
|
+
export: commandSeed({
|
|
65
|
+
reference: {
|
|
66
|
+
description: 'Convert one saved lookup into a versioned evidence report.',
|
|
67
|
+
example: 'whoisleuth export lookup.json --markdown',
|
|
68
|
+
boundary: `Saved Lookup versions 1 and 2 are capped at 8 MiB and scanned for duplicate keys, the prototype-sensitive __proto__ key, and bounded nesting, key, value, and per-container counts before parsing. Current schema-${LOOKUP_EVIDENCE_SCHEMA_VERSION} exports preserve evidence-source attribution and limitations; published v2 schemas ${PUBLISHED_V2_LOOKUP_EVIDENCE_VERSIONS} and exact v1 schema ${V1_PUBLIC_LOOKUP_EVIDENCE_SCHEMA_VERSION} remain readable, while other historical and unreleased shapes are unsupported. Markdown and HTML include a presentation-only generator footer unless --no-attribution is selected; JSON retains bounded generator provenance. Compact output intentionally omits raw registry payloads.`,
|
|
69
|
+
},
|
|
70
|
+
collection: { scope: 'Reads one saved Lookup and writes one bounded report.' },
|
|
71
|
+
summary: 'Convert a lookup to an evidence report',
|
|
72
|
+
options: ['--markdown', '--html', '--compact', '--no-attribution'],
|
|
73
|
+
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
74
|
+
constraints: Object.freeze([
|
|
75
|
+
constraint({ kind: 'mutually_exclusive', options: ['--markdown', '--html'] }),
|
|
76
|
+
constraint({ kind: 'excludes_all', option: '--compact', excludedOptions: ['--markdown', '--html'] }),
|
|
77
|
+
constraint({ kind: 'requires_any', option: '--no-attribution', requiredOptions: ['--markdown', '--html'] }),
|
|
78
|
+
]),
|
|
79
|
+
handlerOwner: 'history',
|
|
80
|
+
networkEffect: 'offline',
|
|
81
|
+
common: true,
|
|
82
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.lookup-evidence']),
|
|
83
|
+
primaryArtefacts: Object.freeze(['Portable evidence report']),
|
|
84
|
+
planSupport: false,
|
|
85
|
+
additionalOutputFormats: Object.freeze(['JSON']),
|
|
86
|
+
bootstrapProfile: 'allowed',
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { buildManagedIndicatorRevision, managedIndicatorState, parseManagedIndicatorJson, readManagedIndicatorSet } from "../packages/interchange/managed-indicator-set.mjs";
|
|
2
|
+
import { exportManagedIndicators } from "../packages/interchange/managed-indicator-export.mjs";
|
|
3
|
+
import { MAX_MANAGED_INDICATOR_PLAN_BYTES, MAX_MANAGED_INDICATOR_SET_BYTES } from "../packages/contracts/analyst-interchange.mjs";
|
|
4
|
+
import { formatJsonDocument } from "./formatters/json.mjs";
|
|
5
|
+
import { safeTerminalValue } from "./formatters/terminal.mjs";
|
|
6
|
+
import EXIT_CODES from "./exit-codes.mjs";
|
|
7
|
+
export async function runIndicatorSetCommand(args, dependencies, context) {
|
|
8
|
+
context.setFailureLabel('Indicator revision');
|
|
9
|
+
const raw = dependencies.readArtifactInput ? await dependencies.readArtifactInput(args.source)
|
|
10
|
+
: await context.readInput(args.source, args.operation === 'revise' ? MAX_MANAGED_INDICATOR_PLAN_BYTES : MAX_MANAGED_INDICATOR_SET_BYTES, 'Indicator input');
|
|
11
|
+
const input = parseManagedIndicatorJson(raw, args.operation === 'revise');
|
|
12
|
+
const now = context.now();
|
|
13
|
+
const revision = args.operation === 'revise' ? await buildManagedIndicatorRevision(input, now) : null;
|
|
14
|
+
const manifest = revision?.manifest ?? await readManagedIndicatorSet(input);
|
|
15
|
+
dependencies.signal?.throwIfAborted();
|
|
16
|
+
if (args.quiet)
|
|
17
|
+
return EXIT_CODES.SUCCESS;
|
|
18
|
+
if (args.operation === 'stix' || args.operation === 'misp')
|
|
19
|
+
context.writeStdout((await exportManagedIndicators(manifest, args.operation)).content);
|
|
20
|
+
else if (args.output === 'json')
|
|
21
|
+
context.writeStdout(formatJsonDocument(manifest));
|
|
22
|
+
else {
|
|
23
|
+
const lines = [`${safeTerminalValue(manifest.name)} · revision ${manifest.revision}`, `Set ${manifest.id}`, `Content digest ${manifest.integrity.digestSha256}`,
|
|
24
|
+
'Content verified; authorship and earlier revisions are not authenticated.'];
|
|
25
|
+
if (revision)
|
|
26
|
+
lines.push(`Changes: ${revision.changes.length} · unchanged: ${revision.unchanged} · excluded candidates: ${revision.exclusions.length}`);
|
|
27
|
+
for (const entry of manifest.entries)
|
|
28
|
+
lines.push(`${entry.domain} · ${managedIndicatorState(entry, now)} · ${entry.id}`, ` Review expires ${entry.expiresAt} · source observed ${entry.observation.observedAt ?? 'time unavailable'}`, ` ${safeTerminalValue(entry.reviewBasis)}`);
|
|
29
|
+
lines.push('Save the manifest for the next revision. Nothing was submitted or applied.');
|
|
30
|
+
context.writeStdout(context.terminal(`${lines.join('\n')}\n`, args.color));
|
|
31
|
+
}
|
|
32
|
+
return EXIT_CODES.SUCCESS;
|
|
33
|
+
}
|
|
@@ -1,46 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'registry-doctor',
|
|
8
|
-
'registry-cohort',
|
|
9
|
-
'registry-scaffold',
|
|
10
|
-
'risk-calibrate',
|
|
11
|
-
'lookalike-calibrate',
|
|
12
|
-
]);
|
|
13
|
-
const REVIEW_INLINE_COMMANDS = Object.freeze([
|
|
14
|
-
'verify-artifact',
|
|
15
|
-
'interchange-report',
|
|
16
|
-
'source-report',
|
|
17
|
-
'compare',
|
|
18
|
-
'page-compare',
|
|
19
|
-
'mail-review',
|
|
20
|
-
'mail-headers',
|
|
21
|
-
'review-evidence',
|
|
22
|
-
'brief',
|
|
23
|
-
'case-pack',
|
|
24
|
-
]);
|
|
25
|
-
const ASSURANCE_INLINE_COMMANDS = Object.freeze([
|
|
26
|
-
'manifest',
|
|
27
|
-
'map-observations',
|
|
28
|
-
'oam-export',
|
|
29
|
-
'domain-control',
|
|
30
|
-
'assurance',
|
|
31
|
-
'change-packet',
|
|
32
|
-
'sharing-review',
|
|
33
|
-
'ct-intake',
|
|
34
|
-
]);
|
|
35
|
-
const WORKFLOW_INLINE_COMMANDS = Object.freeze([
|
|
36
|
-
'monitor-once',
|
|
37
|
-
'workflow-plan',
|
|
38
|
-
'workflow-run',
|
|
39
|
-
]);
|
|
40
|
-
const HISTORY_INLINE_COMMANDS = Object.freeze([
|
|
41
|
-
'diff',
|
|
42
|
-
'reconcile',
|
|
43
|
-
'timeline',
|
|
44
|
-
'export',
|
|
45
|
-
]);
|
|
46
|
-
export { ASSURANCE_INLINE_COMMANDS, HISTORY_INLINE_COMMANDS, REVIEW_INLINE_COMMANDS, SUPPORT_INLINE_COMMANDS, WORKFLOW_INLINE_COMMANDS, };
|
|
1
|
+
import { inlineCommandsFor } from "./command-reference.mjs";
|
|
2
|
+
export const SUPPORT_INLINE_COMMANDS = inlineCommandsFor('support');
|
|
3
|
+
export const REVIEW_INLINE_COMMANDS = inlineCommandsFor('review');
|
|
4
|
+
export const ASSURANCE_INLINE_COMMANDS = inlineCommandsFor('assurance');
|
|
5
|
+
export const WORKFLOW_INLINE_COMMANDS = inlineCommandsFor('workflow');
|
|
6
|
+
export const HISTORY_INLINE_COMMANDS = inlineCommandsFor('history');
|
|
@@ -1,35 +1,8 @@
|
|
|
1
|
-
import { CLI_COMMANDS,
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
Object.freeze({ family: 'support', commands: SUPPORT_INLINE_COMMANDS }),
|
|
5
|
-
Object.freeze({ family: 'review', commands: REVIEW_INLINE_COMMANDS }),
|
|
6
|
-
Object.freeze({ family: 'assurance', commands: ASSURANCE_INLINE_COMMANDS }),
|
|
7
|
-
Object.freeze({ family: 'workflow', commands: WORKFLOW_INLINE_COMMANDS }),
|
|
8
|
-
Object.freeze({ family: 'history', commands: HISTORY_INLINE_COMMANDS }),
|
|
9
|
-
]);
|
|
10
|
-
function buildInlineCommandOwnership() {
|
|
11
|
-
const ownership = new Map();
|
|
12
|
-
for (const { family, commands } of FAMILY_COMMANDS) {
|
|
13
|
-
for (const command of commands) {
|
|
14
|
-
if (ownership.has(command))
|
|
15
|
-
throw new Error(`Inline CLI command ${command} has more than one command-family owner.`);
|
|
16
|
-
ownership.set(command, family);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return ownership;
|
|
20
|
-
}
|
|
21
|
-
const INLINE_COMMAND_OWNERSHIP = buildInlineCommandOwnership();
|
|
22
|
-
const INLINE_CLI_COMMANDS = Object.freeze(CLI_COMMANDS.filter((command) => commandDefinition(command).execution.handlerOwner === 'inline'));
|
|
23
|
-
function validateInlineCommandOwnership() {
|
|
24
|
-
const missing = INLINE_CLI_COMMANDS.filter((command) => !INLINE_COMMAND_OWNERSHIP.has(command));
|
|
25
|
-
const unexpected = [...INLINE_COMMAND_OWNERSHIP.keys()].filter((command) => !INLINE_CLI_COMMANDS.includes(command));
|
|
26
|
-
if (missing.length || unexpected.length) {
|
|
27
|
-
throw new Error(`Inline CLI command-family ownership is inconsistent (missing: ${missing.join(', ') || 'none'}; unexpected: ${unexpected.join(', ') || 'none'}).`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
validateInlineCommandOwnership();
|
|
1
|
+
import { CLI_COMMANDS, INLINE_COMMAND_FAMILIES, inlineCommandFamily, inlineCommandsFor, } from "./command-reference.mjs";
|
|
2
|
+
const FAMILY_COMMANDS = Object.freeze(INLINE_COMMAND_FAMILIES.map(family => Object.freeze({ family, commands: inlineCommandsFor(family) })));
|
|
3
|
+
const INLINE_CLI_COMMANDS = Object.freeze(CLI_COMMANDS.filter((command) => inlineCommandFamily(command) !== null));
|
|
31
4
|
async function runInlineCommand(args, dependencies, context) {
|
|
32
|
-
const family =
|
|
5
|
+
const family = inlineCommandFamily(args.action);
|
|
33
6
|
if (family === 'support') {
|
|
34
7
|
const { runSupportCommand } = await import("./support-command-runner.mjs");
|
|
35
8
|
return runSupportCommand(args, dependencies, context);
|
package/cli/input.mjs
CHANGED
|
@@ -64,7 +64,7 @@ export async function readCliTextInput(source, stdin, options) {
|
|
|
64
64
|
if (error instanceof CliUsageError)
|
|
65
65
|
throw error;
|
|
66
66
|
const message = error instanceof Error ? error.message : `${options.label} could not be read.`;
|
|
67
|
-
throw new CliUsageError(message);
|
|
67
|
+
throw new CliUsageError(message, error instanceof TypeError ? 'invalid_input' : 'input_unavailable');
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
if (!stdin || stdin.isTTY)
|
|
@@ -142,7 +142,7 @@ export async function readCliHeaderInput(source, stdin, options) {
|
|
|
142
142
|
if (error instanceof CliUsageError || options.signal?.aborted)
|
|
143
143
|
throw error;
|
|
144
144
|
const message = error instanceof Error ? error.message : `${options.label} could not be read.`;
|
|
145
|
-
throw new CliUsageError(message);
|
|
145
|
+
throw new CliUsageError(message, error instanceof TypeError ? 'invalid_input' : 'input_unavailable');
|
|
146
146
|
}
|
|
147
147
|
finally {
|
|
148
148
|
await handle?.close();
|