@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
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
// proof of common ownership, coordination, intent, or maliciousness. This
|
|
4
4
|
// module deliberately produces no aggregate score and is never a persistence
|
|
5
5
|
// boundary.
|
|
6
|
-
import { normalizeDomain } from "../
|
|
6
|
+
import { normalizeDomain } from "../evidence/domain-name.mjs";
|
|
7
|
+
import { canonicalPublicIpAddress } from "../evidence/public-address-policy.mjs";
|
|
7
8
|
import { groupBySimilarFavicon } from "./favicon-similarity.mjs";
|
|
8
|
-
import {
|
|
9
|
-
|
|
9
|
+
import { qualifyRelationshipSources, relationshipSourceEvidence, normalizeRelationshipSourceProjection, FAVICON_RELATIONSHIP_DESCRIPTION, unknownRelationshipSource, MAX_RELATIONSHIP_SOURCES_PER_DOMAIN, } from "./relationship-provenance.mjs";
|
|
10
|
+
import { RELATIONSHIP_EVIDENCE_SCHEMA, RELATIONSHIP_EVIDENCE_VERSION, SUPPORTED_RELATIONSHIP_EVIDENCE_VERSIONS, RELATIONSHIP_SOURCE_PROVENANCE_VERSION, SUPPORTED_TLS_RELATIONSHIP_PROFILE_VERSIONS, TLS_RELATIONSHIP_PROFILE_VERSION, } from "../contracts/offline-comparison.mjs";
|
|
11
|
+
export { RELATIONSHIP_EVIDENCE_SCHEMA, RELATIONSHIP_EVIDENCE_VERSION, SUPPORTED_RELATIONSHIP_EVIDENCE_VERSIONS, SUPPORTED_TLS_RELATIONSHIP_PROFILE_VERSIONS, TLS_RELATIONSHIP_PROFILE_VERSION, };
|
|
10
12
|
export const MAX_RELATIONSHIP_ROWS = 2000;
|
|
11
13
|
export const MAX_RELATIONSHIP_GROUPS = 100;
|
|
12
14
|
export const MAX_RELATIONSHIP_DOMAINS = 50;
|
|
@@ -16,6 +18,12 @@ export const MAX_TRACKING_IDS_PER_ROW = 30;
|
|
|
16
18
|
export const MAX_OFFICIAL_ASSET_HOSTS_PER_ROW = 30;
|
|
17
19
|
export const MAX_OFFICIAL_DOMAINS = 200;
|
|
18
20
|
export const MAX_FAVICON_ROWS = 250;
|
|
21
|
+
const ARRAY_RELATIONSHIP_FIELDS = {
|
|
22
|
+
nameserver_set: { field: 'nameservers', maximum: MAX_NAMESERVERS_PER_ROW },
|
|
23
|
+
ip_address: { field: 'ipAddresses', maximum: MAX_IPS_PER_ROW },
|
|
24
|
+
tracking_identifier: { field: 'trackingIdentifiers', maximum: MAX_TRACKING_IDS_PER_ROW },
|
|
25
|
+
official_asset: { field: 'officialAssetHosts', maximum: MAX_OFFICIAL_ASSET_HOSTS_PER_ROW },
|
|
26
|
+
};
|
|
19
27
|
const CONTROL_RE = /[\x00-\x1f\x7f]/;
|
|
20
28
|
const FAVICON_SHA_RE = /^[a-f0-9]{64}$/i;
|
|
21
29
|
const FAVICON_PHASH_RE = /^[a-f0-9]{16}$/i;
|
|
@@ -92,7 +100,7 @@ function tlsCertificateFingerprint(availability) {
|
|
|
92
100
|
const certificate = record(tls?.certificate);
|
|
93
101
|
const fingerprint = certificate?.fingerprintSha256;
|
|
94
102
|
const status = tls?.status;
|
|
95
|
-
if (tls?.source !== 'tls' || !SUPPORTED_TLS_RELATIONSHIP_PROFILE_VERSIONS.some((version) => version ===
|
|
103
|
+
if (tls?.source !== 'tls' || !SUPPORTED_TLS_RELATIONSHIP_PROFILE_VERSIONS.some((version) => version === tls?.profileVersion)
|
|
96
104
|
|| typeof status !== 'string' || !['success', 'partial'].includes(status) || typeof fingerprint !== 'string'
|
|
97
105
|
|| fingerprint.length !== 64 || !CERTIFICATE_SHA_RE.test(fingerprint))
|
|
98
106
|
return null;
|
|
@@ -131,6 +139,36 @@ export function relationshipObservation(rawAvailability, rawOfficialDomains = []
|
|
|
131
139
|
? availability.faviconHash.toLowerCase() : null;
|
|
132
140
|
const faviconPHash = typeof availability.faviconPHash === 'string' && FAVICON_PHASH_RE.test(availability.faviconPHash)
|
|
133
141
|
? availability.faviconPHash.toLowerCase() : null;
|
|
142
|
+
const certificateFingerprint = tlsCertificateFingerprint(availability);
|
|
143
|
+
const dnsSource = relationshipSourceEvidence('dns', availability.dns);
|
|
144
|
+
const httpSource = relationshipSourceEvidence('http', availability.http);
|
|
145
|
+
const projectedSource = (source, truncated) => truncated
|
|
146
|
+
? { ...source, complete: false, truncated: true } : source;
|
|
147
|
+
const sourceEvidence = {};
|
|
148
|
+
if (nameservers.values.length)
|
|
149
|
+
sourceEvidence.nameserver_set = [
|
|
150
|
+
// The compact availability nameserver list can be registration data or a
|
|
151
|
+
// DNS fallback. Its source is not distinguishable from this projection.
|
|
152
|
+
...(Array.isArray(availability.nameservers) && availability.nameservers.length
|
|
153
|
+
? [availability.source === 'dns' ? projectedSource(dnsSource, nameservers.truncated)
|
|
154
|
+
: unknownRelationshipSource('registration_or_dns')] : []),
|
|
155
|
+
...(Array.isArray(records.ns) && records.ns.length ? [projectedSource(dnsSource, nameservers.truncated)] : []),
|
|
156
|
+
];
|
|
157
|
+
if (ipAddresses.values.length)
|
|
158
|
+
sourceEvidence.ip_address = [projectedSource(dnsSource, ipAddresses.truncated)];
|
|
159
|
+
if (certificateFingerprint)
|
|
160
|
+
sourceEvidence.certificate = [relationshipSourceEvidence('tls', availability.tls)];
|
|
161
|
+
if (trackingIdentifiers.values.length)
|
|
162
|
+
sourceEvidence.tracking_identifier = [
|
|
163
|
+
projectedSource(httpSource, trackingIdentifiers.truncated),
|
|
164
|
+
relationshipSourceEvidence('page_identity', availability.pageIdentity),
|
|
165
|
+
];
|
|
166
|
+
if (officialAssetHosts.length)
|
|
167
|
+
sourceEvidence.official_asset = [projectedSource(httpSource, assets.truncated
|
|
168
|
+
|| officialDomainSource.length > MAX_OFFICIAL_DOMAINS)];
|
|
169
|
+
// A homepage timestamp does not date the independently fetched favicon.
|
|
170
|
+
if (faviconHash || faviconPHash)
|
|
171
|
+
sourceEvidence.favicon = [unknownRelationshipSource('favicon')];
|
|
134
172
|
return {
|
|
135
173
|
version: RELATIONSHIP_EVIDENCE_VERSION,
|
|
136
174
|
nameservers: nameservers.values,
|
|
@@ -139,7 +177,8 @@ export function relationshipObservation(rawAvailability, rawOfficialDomains = []
|
|
|
139
177
|
officialAssetHosts,
|
|
140
178
|
faviconHash,
|
|
141
179
|
faviconPHash,
|
|
142
|
-
certificateFingerprint
|
|
180
|
+
certificateFingerprint,
|
|
181
|
+
sourceEvidence,
|
|
143
182
|
truncated: nameservers.truncated || ipAddresses.truncated || trackingIdentifiers.truncated || assets.truncated
|
|
144
183
|
|| officialDomainSource.length > MAX_OFFICIAL_DOMAINS,
|
|
145
184
|
};
|
|
@@ -152,7 +191,22 @@ function addBucket(buckets, value, domain) {
|
|
|
152
191
|
buckets.get(value)?.add(domain);
|
|
153
192
|
}
|
|
154
193
|
function group(type, label, method, value, domains, description, normalizedValue = value) {
|
|
155
|
-
return { type, label, method, value, normalizedValue, domains, description };
|
|
194
|
+
return { type, label, method, value, normalizedValue, domains, description, ...qualifyRelationshipSources([], domains) };
|
|
195
|
+
}
|
|
196
|
+
function contributesToGroup(observation, item, domain) {
|
|
197
|
+
if (item.type === 'certificate')
|
|
198
|
+
return typeof observation.certificateFingerprint === 'string'
|
|
199
|
+
&& observation.certificateFingerprint.toLowerCase() === item.normalizedValue;
|
|
200
|
+
if (item.type === 'nameserver_set')
|
|
201
|
+
return Array.isArray(observation.nameservers)
|
|
202
|
+
&& [...new Set(observation.nameservers.slice(0, MAX_NAMESERVERS_PER_ROW).map(hostname).filter(Boolean))].sort().join(' · ') === item.normalizedValue;
|
|
203
|
+
const field = ARRAY_RELATIONSHIP_FIELDS[item.type];
|
|
204
|
+
if (field)
|
|
205
|
+
return Array.isArray(observation[field.field]) && observation[field.field].slice(0, field.maximum).some((value) => (item.type === 'ip_address' ? ipAddress(value) : item.type === 'official_asset' ? hostname(value) : value) === item.normalizedValue);
|
|
206
|
+
return item.type === 'favicon' && item.normalizedValue.split('|').includes(`${domain}=${[
|
|
207
|
+
typeof observation.faviconHash === 'string' && FAVICON_SHA_RE.test(observation.faviconHash) ? `sha256:${observation.faviconHash.toLowerCase()}` : '',
|
|
208
|
+
typeof observation.faviconPHash === 'string' && FAVICON_PHASH_RE.test(observation.faviconPHash) ? `dhash:${observation.faviconPHash.toLowerCase()}` : '',
|
|
209
|
+
].filter(Boolean).join(',')}`);
|
|
156
210
|
}
|
|
157
211
|
/**
|
|
158
212
|
* @param {Array<{domain?:unknown,trusted?:unknown,relationship?:unknown}>} rawRows
|
|
@@ -164,7 +218,7 @@ export function buildScanRelationships(rawRows) {
|
|
|
164
218
|
for (const raw of input.slice(0, MAX_RELATIONSHIP_ROWS)) {
|
|
165
219
|
const domain = normalizeDomain(raw?.domain);
|
|
166
220
|
const observation = record(raw?.relationship);
|
|
167
|
-
if (!domain || raw?.trusted || !observation ||
|
|
221
|
+
if (!domain || raw?.trusted || !observation || !SUPPORTED_RELATIONSHIP_EVIDENCE_VERSIONS.some((version) => version === observation.version))
|
|
168
222
|
continue;
|
|
169
223
|
rows.push({ domain, observation });
|
|
170
224
|
if (observation.truncated === true)
|
|
@@ -175,6 +229,7 @@ export function buildScanRelationships(rawRows) {
|
|
|
175
229
|
const identifiers = new Map();
|
|
176
230
|
const certificates = new Map();
|
|
177
231
|
const officialAssets = new Map();
|
|
232
|
+
let excludedNonPublicAddresses = 0;
|
|
178
233
|
for (const { domain, observation } of rows) {
|
|
179
234
|
const nameserverSource = Array.isArray(observation.nameservers) ? observation.nameservers : [];
|
|
180
235
|
const addressSource = Array.isArray(observation.ipAddresses) ? observation.ipAddresses : [];
|
|
@@ -186,8 +241,16 @@ export function buildScanRelationships(rawRows) {
|
|
|
186
241
|
const nameservers = nameserverSource.slice(0, MAX_NAMESERVERS_PER_ROW).map(hostname).filter(Boolean);
|
|
187
242
|
if (nameservers.length)
|
|
188
243
|
addBucket(nameserverSets, [...new Set(nameservers)].sort().join(' · '), domain);
|
|
189
|
-
for (const value of addressSource.slice(0, MAX_IPS_PER_ROW))
|
|
190
|
-
|
|
244
|
+
for (const value of addressSource.slice(0, MAX_IPS_PER_ROW)) {
|
|
245
|
+
const address = ipAddress(value);
|
|
246
|
+
if (!address)
|
|
247
|
+
continue;
|
|
248
|
+
const publicAddress = canonicalPublicIpAddress(address);
|
|
249
|
+
if (publicAddress)
|
|
250
|
+
addBucket(addresses, publicAddress, domain);
|
|
251
|
+
else
|
|
252
|
+
excludedNonPublicAddresses += 1;
|
|
253
|
+
}
|
|
191
254
|
for (const value of identifierSource.slice(0, MAX_TRACKING_IDS_PER_ROW)) {
|
|
192
255
|
if (typeof value === 'string' && /^[a-z-]{1,40}:[A-Z0-9-]{1,64}$/.test(value)) {
|
|
193
256
|
addBucket(identifiers, value, domain);
|
|
@@ -235,7 +298,7 @@ export function buildScanRelationships(rawRows) {
|
|
|
235
298
|
evidence?.faviconPHash ? `dhash:${evidence.faviconPHash}` : '',
|
|
236
299
|
].filter(Boolean).join(',')}`;
|
|
237
300
|
}).join('|');
|
|
238
|
-
output.push(group('favicon', '
|
|
301
|
+
output.push(group('favicon', 'Connected favicon matches', 'Exact SHA-256 or perceptual dHash distance ≤ 6', '', distinctDomains, FAVICON_RELATIONSHIP_DESCRIPTION, normalizedValue));
|
|
239
302
|
}
|
|
240
303
|
}
|
|
241
304
|
for (const [value, domains] of officialAssets)
|
|
@@ -245,17 +308,43 @@ export function buildScanRelationships(rawRows) {
|
|
|
245
308
|
output.sort((left, right) => (Number(order.get(left.type)) - Number(order.get(right.type))) || left.value.localeCompare(right.value) || left.domains.join('|').localeCompare(right.domains.join('|')));
|
|
246
309
|
if (output.length > MAX_RELATIONSHIP_GROUPS)
|
|
247
310
|
truncated = true;
|
|
311
|
+
const rowsByDomain = new Map();
|
|
312
|
+
for (const row of rows) {
|
|
313
|
+
const members = rowsByDomain.get(row.domain) ?? [];
|
|
314
|
+
members.push(row);
|
|
315
|
+
rowsByDomain.set(row.domain, members);
|
|
316
|
+
}
|
|
248
317
|
const groups = output.slice(0, MAX_RELATIONSHIP_GROUPS).map((item) => {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
318
|
+
const domains = item.domains.slice(0, MAX_RELATIONSHIP_DOMAINS);
|
|
319
|
+
let groupTruncated = item.domains.length > domains.length || input.length > MAX_RELATIONSHIP_ROWS;
|
|
320
|
+
if (groupTruncated)
|
|
321
|
+
truncated = true;
|
|
322
|
+
const contributors = [];
|
|
323
|
+
for (const domain of domains)
|
|
324
|
+
for (const { observation } of rowsByDomain.get(domain) ?? []) {
|
|
325
|
+
if (!contributesToGroup(observation, item, domain))
|
|
326
|
+
continue;
|
|
327
|
+
const field = ARRAY_RELATIONSHIP_FIELDS[item.type];
|
|
328
|
+
if (field && Array.isArray(observation[field.field]) && observation[field.field].length > field.maximum)
|
|
329
|
+
groupTruncated = true;
|
|
330
|
+
const projection = Number(observation.version) >= RELATIONSHIP_SOURCE_PROVENANCE_VERSION
|
|
331
|
+
? normalizeRelationshipSourceProjection(observation.sourceEvidence) : {};
|
|
332
|
+
for (const source of projection[item.type] ?? [unknownRelationshipSource()]) {
|
|
333
|
+
if (contributors.length >= MAX_RELATIONSHIP_DOMAINS * MAX_RELATIONSHIP_SOURCES_PER_DOMAIN)
|
|
334
|
+
groupTruncated = true;
|
|
335
|
+
else
|
|
336
|
+
contributors.push({ domain, ...source });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return { ...item, domains, ...qualifyRelationshipSources(contributors, domains, { truncated: groupTruncated, type: item.type }) };
|
|
253
340
|
});
|
|
254
341
|
return {
|
|
255
342
|
version: RELATIONSHIP_EVIDENCE_VERSION,
|
|
256
343
|
groups,
|
|
344
|
+
excludedNonPublicAddresses,
|
|
257
345
|
truncated,
|
|
258
346
|
limitations: [
|
|
347
|
+
...(excludedNonPublicAddresses ? [`${excludedNonPublicAddresses} non-public address observations were excluded from public-infrastructure grouping; their DNS answers remain in the scan evidence.`] : []),
|
|
259
348
|
'Shared observations are investigation pivots, not proof of common ownership, coordination, intent, or maliciousness.',
|
|
260
349
|
'Certificate relationships use exact native TLS leaf-certificate SHA-256 values only. Certificate Transparency counts and hostnames are never treated as certificate reuse.',
|
|
261
350
|
'A shared certificate can reflect a multi-domain certificate, shared hosting, CDN, or managed platform and does not establish common control.',
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Only the source metadata needed to qualify an observed relationship crosses
|
|
2
|
+
// this boundary. Collection and retention times are never source-time fallbacks.
|
|
3
|
+
import { normalizeDomain } from "../evidence/domain-name.mjs";
|
|
4
|
+
import { canonicalPublicIpAddress } from "../evidence/public-address-policy.mjs";
|
|
5
|
+
import { normalizeExplicitIsoTimestamp, readObservationEnvelope } from "../evidence/observation.mjs";
|
|
6
|
+
export const RELATIONSHIP_TYPES = Object.freeze([
|
|
7
|
+
'nameserver_set', 'ip_address', 'certificate', 'tracking_identifier', 'favicon', 'official_asset',
|
|
8
|
+
]);
|
|
9
|
+
export const NON_PUBLIC_RELATIONSHIP_LIMITATION = 'This is an equal non-public DNS answer, not evidence of shared public hosting. Private, loopback, reserved and sinkhole-style answers can recur independently.';
|
|
10
|
+
export const FAVICON_RELATIONSHIP_DESCRIPTION = 'Each domain is connected by exact or bounded perceptual favicon matches, directly or through another member. Not every pair necessarily matches.';
|
|
11
|
+
export function isNonPublicAddressRelationship(type, value) {
|
|
12
|
+
return type === 'ip_address' && !canonicalPublicIpAddress(value);
|
|
13
|
+
}
|
|
14
|
+
const SOURCES = ['dns', 'tls', 'http', 'page_identity', 'favicon', 'registration_or_dns', 'unknown'];
|
|
15
|
+
const SOURCES_BY_TYPE = {
|
|
16
|
+
nameserver_set: ['dns', 'registration_or_dns'], ip_address: ['dns'], certificate: ['tls'],
|
|
17
|
+
tracking_identifier: ['http', 'page_identity'], favicon: ['favicon'], official_asset: ['http'],
|
|
18
|
+
};
|
|
19
|
+
const STATUSES = ['success', 'partial', 'not_found', 'skipped', 'error', 'unsupported', 'not_applicable', 'unknown'];
|
|
20
|
+
export const MAX_RELATIONSHIP_SOURCES_PER_DOMAIN = 3;
|
|
21
|
+
export const MAX_RELATIONSHIP_SOURCE_DOMAINS = 50;
|
|
22
|
+
function record(value) {
|
|
23
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
24
|
+
}
|
|
25
|
+
export function unknownRelationshipSource(source = 'unknown') {
|
|
26
|
+
return { source, status: 'unknown', observedAt: null, complete: false, truncated: null };
|
|
27
|
+
}
|
|
28
|
+
export function relationshipSourceEvidence(source, raw) {
|
|
29
|
+
const result = readObservationEnvelope(raw);
|
|
30
|
+
if (result.state !== 'supported' || result.observation.source !== source) {
|
|
31
|
+
return { ...unknownRelationshipSource(source), status: result.state === 'unsupported' ? 'unsupported' : 'unknown' };
|
|
32
|
+
}
|
|
33
|
+
return normalizeSource({ ...result.observation, truncated: record(raw).truncated });
|
|
34
|
+
}
|
|
35
|
+
function normalizeSource(raw, type) {
|
|
36
|
+
const value = record(raw);
|
|
37
|
+
const source = SOURCES.find((candidate) => candidate === value.source) ?? 'unknown';
|
|
38
|
+
if (type && source !== 'unknown' && !SOURCES_BY_TYPE[type].includes(source))
|
|
39
|
+
return unknownRelationshipSource();
|
|
40
|
+
const status = STATUSES.find((candidate) => candidate === value.status) ?? 'unknown';
|
|
41
|
+
const observedAt = normalizeExplicitIsoTimestamp(value.observedAt);
|
|
42
|
+
const truncated = typeof value.truncated === 'boolean' ? value.truncated : null;
|
|
43
|
+
return {
|
|
44
|
+
source, status, observedAt, truncated,
|
|
45
|
+
complete: source !== 'unknown' && source !== 'registration_or_dns' && status === 'success'
|
|
46
|
+
&& value.complete === true && truncated === false && observedAt !== null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function normalizeRelationshipSourceProjection(raw) {
|
|
50
|
+
const value = record(raw);
|
|
51
|
+
const output = {};
|
|
52
|
+
for (const type of RELATIONSHIP_TYPES) {
|
|
53
|
+
if (!Array.isArray(value[type]))
|
|
54
|
+
continue;
|
|
55
|
+
const candidates = value[type];
|
|
56
|
+
output[type] = candidates.slice(0, MAX_RELATIONSHIP_SOURCES_PER_DOMAIN).map((candidate) => {
|
|
57
|
+
const source = normalizeSource(candidate, type);
|
|
58
|
+
return candidates.length > MAX_RELATIONSHIP_SOURCES_PER_DOMAIN ? { ...source, complete: false, truncated: true } : source;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return output;
|
|
62
|
+
}
|
|
63
|
+
/** Missing, malformed or omitted contributors cannot make a group complete. */
|
|
64
|
+
export function qualifyRelationshipSources(raw, rawDomains, options = {}) {
|
|
65
|
+
const domains = [...new Set(rawDomains.slice(0, MAX_RELATIONSHIP_SOURCE_DOMAINS * 2).map(normalizeDomain).filter(Boolean))].slice(0, MAX_RELATIONSHIP_SOURCE_DOMAINS);
|
|
66
|
+
const allowed = new Set(domains);
|
|
67
|
+
const candidates = Array.isArray(raw) ? raw : [];
|
|
68
|
+
const maximum = MAX_RELATIONSHIP_SOURCE_DOMAINS * MAX_RELATIONSHIP_SOURCES_PER_DOMAIN;
|
|
69
|
+
const byDomain = new Map();
|
|
70
|
+
let omitted = options.truncated === true || rawDomains.length > MAX_RELATIONSHIP_SOURCE_DOMAINS || candidates.length > maximum;
|
|
71
|
+
for (const candidate of candidates.slice(0, maximum)) {
|
|
72
|
+
const value = record(candidate);
|
|
73
|
+
const domain = normalizeDomain(value.domain);
|
|
74
|
+
if (!domain || !allowed.has(domain)) {
|
|
75
|
+
omitted = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const contribution = { domain, ...normalizeSource(value, options.type) };
|
|
79
|
+
const entries = byDomain.get(domain) ?? new Map();
|
|
80
|
+
const key = JSON.stringify(contribution);
|
|
81
|
+
if (!entries.has(key) && entries.size >= MAX_RELATIONSHIP_SOURCES_PER_DOMAIN)
|
|
82
|
+
omitted = true;
|
|
83
|
+
else
|
|
84
|
+
entries.set(key, contribution);
|
|
85
|
+
byDomain.set(domain, entries);
|
|
86
|
+
}
|
|
87
|
+
const sourceEvidence = domains.sort().flatMap((domain) => {
|
|
88
|
+
const entries = byDomain.get(domain);
|
|
89
|
+
return entries?.size ? [...entries.values()].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)))
|
|
90
|
+
: [{ domain, ...unknownRelationshipSource() }];
|
|
91
|
+
});
|
|
92
|
+
const observedAt = sourceEvidence.length && sourceEvidence.every((entry) => entry.observedAt !== null)
|
|
93
|
+
? sourceEvidence.map((entry) => entry.observedAt).sort().at(-1) ?? null : null;
|
|
94
|
+
return {
|
|
95
|
+
sourceEvidence, observedAt,
|
|
96
|
+
complete: sourceEvidence.length > 0 && !omitted && sourceEvidence.every((entry) => entry.complete),
|
|
97
|
+
truncated: omitted || sourceEvidence.some((entry) => entry.truncated === true),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { defineSchemaCompatibility } from "./schema-compatibility.mjs";
|
|
2
|
+
import { buildExtractedLifecycleFamily } from "./extracted-domain-lifecycle.mjs";
|
|
3
|
+
import { defineSchemaLifecycleFamily } from "./schema-lifecycle.mjs";
|
|
4
|
+
export const ANALYST_INTERCHANGE_CONTRACT_OWNER = 'packages/contracts/analyst-interchange.mts';
|
|
5
|
+
export const INVESTIGATION_CACAO_SPEC_VERSION = 'cacao-2.0';
|
|
6
|
+
export const INVESTIGATION_CACAO_PROFILE_VERSION = 3;
|
|
7
|
+
export const INVESTIGATION_CACAO_PROFILE_SEMVER = '3.0.0';
|
|
8
|
+
export const INVESTIGATION_CACAO_SUPPORTED_PROFILE_VERSIONS = [2, INVESTIGATION_CACAO_PROFILE_VERSION];
|
|
9
|
+
export const MAX_INVESTIGATION_CACAO_IMPORT_BYTES = 384 * 1024;
|
|
10
|
+
export const BRAND_PROTECTION_OPERATIONS_REPORT_SCHEMA = 'whoisleuth.brand-protection-operations-report';
|
|
11
|
+
export const BRAND_PROTECTION_OPERATIONS_REPORT_VERSION = 2;
|
|
12
|
+
export const MAX_OPERATIONS_REPORT_BYTES = 64 * 1024;
|
|
13
|
+
export const DEFENSIVE_INDICATOR_LIFECYCLE_SCHEMA = 'whoisleuth.internal.defensive-indicators';
|
|
14
|
+
export const DEFENSIVE_INDICATOR_EXPORT_VERSION = 2;
|
|
15
|
+
export const DEFENSIVE_INDICATOR_MANIFEST_SCHEMA = 'whoisleuth.defensive-indicator-manifest';
|
|
16
|
+
export const DEFENSIVE_INDICATOR_ROLLBACK_SCHEMA = 'whoisleuth.defensive-indicator-rollback';
|
|
17
|
+
export const STIX_INDICATOR_LIFECYCLE_SCHEMA = 'whoisleuth.internal.stix-indicators';
|
|
18
|
+
export const STIX_INDICATOR_EXPORT_VERSION = 2;
|
|
19
|
+
export const MISP_INDICATOR_LIFECYCLE_SCHEMA = 'whoisleuth.internal.misp-indicators';
|
|
20
|
+
export const MISP_INDICATOR_EXPORT_VERSION = 2;
|
|
21
|
+
export const MANAGED_INDICATOR_SET_SCHEMA = 'whoisleuth.managed-indicator-set';
|
|
22
|
+
export const MANAGED_INDICATOR_SET_VERSION = 1;
|
|
23
|
+
export const MAX_MANAGED_INDICATOR_SET_BYTES = 4 * 1024 * 1024;
|
|
24
|
+
export const MAX_MANAGED_INDICATOR_PLAN_BYTES = 16 * 1024 * 1024;
|
|
25
|
+
export const DNS_CHANGE_REHEARSAL_VERSION = 2;
|
|
26
|
+
export const DNS_CHANGE_REHEARSAL_EXPORT_SCHEMA = 'whoisleuth.dns-change-rehearsal';
|
|
27
|
+
export const MAIL_REPORT_SCHEMA = 'whoisleuth.mail-report-review';
|
|
28
|
+
export const MAIL_REPORT_VERSION = 3;
|
|
29
|
+
export const MAIL_REPORT_CANONICALIZATION_ROUTES = Object.freeze([
|
|
30
|
+
Object.freeze({ version: 2, canonicalization: 'sorted-json-v1', explicit: false }),
|
|
31
|
+
Object.freeze({ version: MAIL_REPORT_VERSION, canonicalization: 'sorted-json-v2', explicit: true }),
|
|
32
|
+
]);
|
|
33
|
+
export const REGISTRATION_DISCLOSURE_PLAN_SCHEMA = 'whoisleuth.registration-disclosure-plan';
|
|
34
|
+
export const REGISTRATION_DISCLOSURE_PLAN_VERSION = 2;
|
|
35
|
+
export const STATIC_PAGE_PATTERN_PACK_SCHEMA = 'whoisleuth.static-page-pattern-pack';
|
|
36
|
+
export const STATIC_PAGE_PATTERN_PACK_VERSION = 2;
|
|
37
|
+
export const MAX_STATIC_PAGE_PATTERN_PACK_BYTES = 256 * 1024;
|
|
38
|
+
export const WEB_CAPTURE_SUMMARY_SCHEMA = 'whoisleuth.web-capture-summary';
|
|
39
|
+
export const WEB_CAPTURE_SUMMARY_VERSION = 1;
|
|
40
|
+
export const WEB_CAPTURE_MANIFEST_SCHEMA = 'whoisleuth.web-capture-manifest';
|
|
41
|
+
export const WEB_CAPTURE_MANIFEST_VERSION = 3;
|
|
42
|
+
export const WEB_CAPTURE_MANIFEST_SUPPORTED_VERSIONS = [2, WEB_CAPTURE_MANIFEST_VERSION];
|
|
43
|
+
export const MAX_WEB_CAPTURE_MANIFEST_BYTES = 1024 * 1024;
|
|
44
|
+
const CACAO_COMPATIBILITY = defineSchemaCompatibility({
|
|
45
|
+
id: 'export.investigation-cacao-profile', kind: 'export', schema: INVESTIGATION_CACAO_SPEC_VERSION,
|
|
46
|
+
currentVersion: INVESTIGATION_CACAO_PROFILE_VERSION, supportedVersions: INVESTIGATION_CACAO_SUPPORTED_PROFILE_VERSIONS,
|
|
47
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'normalize_to_current',
|
|
48
|
+
writeSemantics: 'non_destructive_merge', byteBudget: MAX_INVESTIGATION_CACAO_IMPORT_BYTES,
|
|
49
|
+
owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
50
|
+
note: 'Restricted CACAO 2.0 manual profile. Version 3 retains optional lesson-revision provenance; public profile 2 remains readable. Executable commands, branches, targets and credentials remain rejected.',
|
|
51
|
+
});
|
|
52
|
+
const OPERATIONS_REPORT_COMPATIBILITY = defineSchemaCompatibility({
|
|
53
|
+
id: 'export.brand-protection-operations-report', kind: 'export', schema: BRAND_PROTECTION_OPERATIONS_REPORT_SCHEMA,
|
|
54
|
+
currentVersion: BRAND_PROTECTION_OPERATIONS_REPORT_VERSION, supportedVersions: [BRAND_PROTECTION_OPERATIONS_REPORT_VERSION],
|
|
55
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'not_applicable', migration: 'read_only',
|
|
56
|
+
writeSemantics: 'read_only', byteBudget: MAX_OPERATIONS_REPORT_BYTES, owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
57
|
+
note: 'Version 2 aggregates typed action and independent-effect events with explicit denominators, omissions, time-window basis, and limitations. It excludes case identities and is not a service level or provider ranking.',
|
|
58
|
+
});
|
|
59
|
+
const DEFENSIVE_INDICATORS_COMPATIBILITY = defineSchemaCompatibility({
|
|
60
|
+
id: 'export.defensive-indicators', kind: 'export', schema: null,
|
|
61
|
+
currentVersion: DEFENSIVE_INDICATOR_EXPORT_VERSION, supportedVersions: [DEFENSIVE_INDICATOR_EXPORT_VERSION],
|
|
62
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'not_applicable', migration: 'read_only',
|
|
63
|
+
writeSemantics: 'read_only', byteBudget: null, owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
64
|
+
note: 'Review-only indicator, provenance-manifest, and rollback formats; never submitted or applied automatically.',
|
|
65
|
+
});
|
|
66
|
+
const STIX_INDICATORS_COMPATIBILITY = defineSchemaCompatibility({
|
|
67
|
+
id: 'export.stix-indicators', kind: 'export', schema: null,
|
|
68
|
+
currentVersion: STIX_INDICATOR_EXPORT_VERSION, supportedVersions: [1, STIX_INDICATOR_EXPORT_VERSION],
|
|
69
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'not_applicable', migration: 'read_only',
|
|
70
|
+
writeSemantics: 'read_only', byteBudget: null, owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
71
|
+
note: 'STIX 2.1 bundle with observations separated from heuristic indicators. Version 2 uses a Note when observation time is unknown; Indicator validity starts at creation, not at a substituted sighting time.',
|
|
72
|
+
});
|
|
73
|
+
const MISP_INDICATORS_COMPATIBILITY = defineSchemaCompatibility({
|
|
74
|
+
id: 'export.misp-indicators', kind: 'export', schema: null,
|
|
75
|
+
currentVersion: MISP_INDICATOR_EXPORT_VERSION, supportedVersions: [1, MISP_INDICATOR_EXPORT_VERSION],
|
|
76
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'not_applicable', migration: 'read_only',
|
|
77
|
+
writeSemantics: 'read_only', byteBudget: null, owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
78
|
+
note: 'Unpublished, non-IDS, non-correlating event for reviewed import. Version 2 omits optional seen times when the source observation time is unknown.',
|
|
79
|
+
});
|
|
80
|
+
export const MANAGED_INDICATOR_SET_COMPATIBILITY = defineSchemaCompatibility({
|
|
81
|
+
id: 'export.managed-indicator-set', kind: 'export', schema: MANAGED_INDICATOR_SET_SCHEMA,
|
|
82
|
+
currentVersion: MANAGED_INDICATOR_SET_VERSION, supportedVersions: [MANAGED_INDICATOR_SET_VERSION],
|
|
83
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'exact_current_only',
|
|
84
|
+
writeSemantics: 'read_only', byteBudget: MAX_MANAGED_INDICATOR_SET_BYTES, owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
85
|
+
note: 'File-backed managed indicator revisions retain original observations, stable identities, explicit renewals and permanent withdrawals. Known predecessors prevent history replacement; content digests do not authenticate authorship or an unavailable earlier chain.',
|
|
86
|
+
});
|
|
87
|
+
const WEB_CAPTURE_SUMMARY_COMPATIBILITY = defineSchemaCompatibility({
|
|
88
|
+
id: 'export.web-capture-summary', kind: 'export', schema: WEB_CAPTURE_SUMMARY_SCHEMA,
|
|
89
|
+
currentVersion: WEB_CAPTURE_SUMMARY_VERSION, supportedVersions: [WEB_CAPTURE_SUMMARY_VERSION],
|
|
90
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'exact_current_only',
|
|
91
|
+
writeSemantics: 'read_only', byteBudget: null, owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
92
|
+
note: 'Sanitised metadata-only capture summary accepted through the bounded external-finding preview.',
|
|
93
|
+
});
|
|
94
|
+
const WEB_CAPTURE_MANIFEST_COMPATIBILITY = defineSchemaCompatibility({
|
|
95
|
+
id: 'export.web-capture-manifest', kind: 'export', schema: WEB_CAPTURE_MANIFEST_SCHEMA,
|
|
96
|
+
currentVersion: WEB_CAPTURE_MANIFEST_VERSION, supportedVersions: WEB_CAPTURE_MANIFEST_SUPPORTED_VERSIONS,
|
|
97
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'read_only',
|
|
98
|
+
writeSemantics: 'read_only', byteBudget: MAX_WEB_CAPTURE_MANIFEST_BYTES,
|
|
99
|
+
owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
100
|
+
note: 'Metadata-only capture manifest. Version 3 adds bounded page and dependency observations; version 2 remains readable with those observations unavailable.',
|
|
101
|
+
});
|
|
102
|
+
const DEFENSIVE_MANIFEST_COMPATIBILITY = defineSchemaCompatibility({
|
|
103
|
+
id: 'export.defensive-indicator-manifest', kind: 'export', schema: DEFENSIVE_INDICATOR_MANIFEST_SCHEMA,
|
|
104
|
+
currentVersion: DEFENSIVE_INDICATOR_EXPORT_VERSION, supportedVersions: [DEFENSIVE_INDICATOR_EXPORT_VERSION],
|
|
105
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'not_applicable', migration: 'read_only',
|
|
106
|
+
writeSemantics: 'read_only', byteBudget: null, owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
107
|
+
note: 'Output-only manifest for explicitly selected defensive indicators; historical version 1 is not claimed without a retained reader.',
|
|
108
|
+
});
|
|
109
|
+
const DEFENSIVE_ROLLBACK_COMPATIBILITY = defineSchemaCompatibility({
|
|
110
|
+
id: 'export.defensive-indicator-rollback', kind: 'export', schema: DEFENSIVE_INDICATOR_ROLLBACK_SCHEMA,
|
|
111
|
+
currentVersion: DEFENSIVE_INDICATOR_EXPORT_VERSION, supportedVersions: [DEFENSIVE_INDICATOR_EXPORT_VERSION],
|
|
112
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'not_applicable', migration: 'read_only',
|
|
113
|
+
writeSemantics: 'read_only', byteBudget: null, owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
114
|
+
note: 'Output-only rollback companion for the same bounded defensive-indicator selection.',
|
|
115
|
+
});
|
|
116
|
+
const DNS_REHEARSAL_COMPATIBILITY = defineSchemaCompatibility({
|
|
117
|
+
id: 'export.dns-change-rehearsal', kind: 'export', schema: DNS_CHANGE_REHEARSAL_EXPORT_SCHEMA,
|
|
118
|
+
currentVersion: DNS_CHANGE_REHEARSAL_VERSION, supportedVersions: [DNS_CHANGE_REHEARSAL_VERSION],
|
|
119
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'not_applicable', migration: 'read_only',
|
|
120
|
+
writeSemantics: 'read_only', byteBudget: null, owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
121
|
+
note: 'Output-only DNS change rehearsal document; version 2 is the only producer contract represented by this tree.',
|
|
122
|
+
});
|
|
123
|
+
const MAIL_REPORT_COMPATIBILITY = defineSchemaCompatibility({
|
|
124
|
+
id: 'export.mail-report-review', kind: 'export', schema: MAIL_REPORT_SCHEMA,
|
|
125
|
+
currentVersion: MAIL_REPORT_VERSION, supportedVersions: [1, 2, MAIL_REPORT_VERSION], acceptsUnversionedLegacy: false,
|
|
126
|
+
futureVersionBehavior: 'not_applicable', migration: 'read_only', writeSemantics: 'read_only', byteBudget: null,
|
|
127
|
+
owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
128
|
+
note: 'Output-only aggregate mail review: v3 declares sorted-json-v2 integrity; v2 retains its undeclared v1 digest. Admission coverage is separate from pagination. Historical v1 has no reader; input and output byte bounds differ.',
|
|
129
|
+
});
|
|
130
|
+
const REGISTRATION_DISCLOSURE_COMPATIBILITY = defineSchemaCompatibility({
|
|
131
|
+
id: 'export.registration-disclosure-plan', kind: 'export', schema: REGISTRATION_DISCLOSURE_PLAN_SCHEMA,
|
|
132
|
+
currentVersion: REGISTRATION_DISCLOSURE_PLAN_VERSION, supportedVersions: [REGISTRATION_DISCLOSURE_PLAN_VERSION],
|
|
133
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'not_applicable', migration: 'read_only',
|
|
134
|
+
writeSemantics: 'read_only', byteBudget: null, owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
135
|
+
note: 'Output-only bounded registration-disclosure planning document; version 2 is the only current producer contract.',
|
|
136
|
+
});
|
|
137
|
+
const STATIC_PATTERN_PACK_COMPATIBILITY = defineSchemaCompatibility({
|
|
138
|
+
id: 'export.static-page-pattern-pack', kind: 'export', schema: STATIC_PAGE_PATTERN_PACK_SCHEMA,
|
|
139
|
+
currentVersion: STATIC_PAGE_PATTERN_PACK_VERSION, supportedVersions: [STATIC_PAGE_PATTERN_PACK_VERSION],
|
|
140
|
+
acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'exact_current_only',
|
|
141
|
+
writeSemantics: 'non_destructive_merge', byteBudget: MAX_STATIC_PAGE_PATTERN_PACK_BYTES,
|
|
142
|
+
owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
143
|
+
note: 'Exact-current bounded page-pattern pack imported by explicit analyst action and merged without executing code or starting collection.',
|
|
144
|
+
});
|
|
145
|
+
export function serialiseAnalystInterchangeJson(value) {
|
|
146
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
147
|
+
}
|
|
148
|
+
const F = 'test/fixtures/extracted-domain-lifecycle/';
|
|
149
|
+
export const ANALYST_INTERCHANGE_LIFECYCLE_FAMILY = defineSchemaLifecycleFamily(buildExtractedLifecycleFamily({
|
|
150
|
+
id: 'analyst-interchange', owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
|
|
151
|
+
serializerExportName: 'serialiseAnalystInterchangeJson', plane: 'shared', projection: 'browser_export',
|
|
152
|
+
retention: 'operator_controlled_output',
|
|
153
|
+
includedCategories: ['reviewed-indicators', 'source-identity', 'provenance', 'completeness', 'omitted-counts', 'limitations'],
|
|
154
|
+
excludedCategories: ['credentials', 'cookies', 'message-content', 'raw-upstream-responses', 'expanded-contacts'],
|
|
155
|
+
formats: [
|
|
156
|
+
{ descriptor: MANAGED_INDICATOR_SET_COMPATIBILITY, lifecycleSchema: MANAGED_INDICATOR_SET_SCHEMA,
|
|
157
|
+
requiredKeys: ['schema', 'version', 'id', 'producerId', 'revisionId', 'name', 'revision', 'createdAt', 'modifiedAt', 'previous', 'entries', 'integrity'], optionalKeys: [],
|
|
158
|
+
hook: { module: 'packages/interchange/managed-indicator-set.mts', exportName: 'validateManagedIndicatorSet', role: 'structure_validator', runtime: 'shared' },
|
|
159
|
+
fixtures: [{ id: 'managed-indicator-set-v1', path: `${F}managed-indicator-set-v1.json`, bytes: 1_193, sha256: '3a4ffb7256e696d1f1b3bae14743bb8c03ddeb0922186868e935f5a489ba3985', version: MANAGED_INDICATOR_SET_VERSION }] },
|
|
160
|
+
{ descriptor: CACAO_COMPATIBILITY, lifecycleSchema: INVESTIGATION_CACAO_SPEC_VERSION,
|
|
161
|
+
requiredKeys: ['type', 'spec_version', 'id', 'name', 'created', 'modified', 'workflow_start', 'workflow', 'extension_definitions'],
|
|
162
|
+
optionalKeys: ['description', 'playbook_types', 'playbook_activities', 'created_by', 'playbook_extensions', 'agent_definitions', 'x_whoisleuth_profile_version'],
|
|
163
|
+
hook: { module: 'packages/interchange/investigation-playbook-interchange.mts', exportName: 'parseCacaoInvestigationPlaybook', role: 'normaliser', runtime: 'shared' },
|
|
164
|
+
fixtures: [
|
|
165
|
+
{ id: 'investigation-cacao-profile-v2', path: `${F}investigation-cacao-profile-v2.json`, bytes: 4_091, sha256: '7e9a3de6e12b83477690c0c58e03e6efbbb8b0c11fd98dc8470bdb6784d42358', version: 2 },
|
|
166
|
+
{ id: 'investigation-cacao-profile-v3', path: `${F}investigation-cacao-profile-v3.json`, bytes: 4_067, sha256: '608cbb7875c3448ca11f6f8cb2f96ae098e18316532a8f651bbe540ca7f25a2d', version: INVESTIGATION_CACAO_PROFILE_VERSION },
|
|
167
|
+
] },
|
|
168
|
+
{ descriptor: OPERATIONS_REPORT_COMPATIBILITY, lifecycleSchema: BRAND_PROTECTION_OPERATIONS_REPORT_SCHEMA,
|
|
169
|
+
requiredKeys: ['schema', 'version', 'generatedAt', 'window', 'limitations'], optionalKeys: ['sourceState', 'summary', 'counts', 'states', 'actionTypes', 'durations', 'denominators', 'omissions'],
|
|
170
|
+
hook: { module: 'packages/interchange/brand-protection-operations-report.mts', exportName: 'serializeBrandProtectionOperationsReport', role: 'builder', runtime: 'shared' },
|
|
171
|
+
fixtures: [
|
|
172
|
+
{ id: 'brand-protection-operations-report-v2', path: `${F}brand-protection-operations-report-v2.json`, bytes: 187, sha256: 'd38bf9b2c5cfc9c83df267ff2303ea2c3cdb398dd12c6607fbedb265b880a324', version: BRAND_PROTECTION_OPERATIONS_REPORT_VERSION },
|
|
173
|
+
] },
|
|
174
|
+
{ descriptor: DEFENSIVE_INDICATORS_COMPATIBILITY, lifecycleSchema: DEFENSIVE_INDICATOR_LIFECYCLE_SCHEMA,
|
|
175
|
+
requiredKeys: ['version', 'format'], optionalKeys: ['generatedAt', 'expiresAt', 'domains', 'entries', 'indicators', 'exclusions', 'explicitSelection', 'includeWildcards', 'truncated', 'filename', 'manifestFilename', 'rollbackFilename', 'mimeType', 'content', 'manifestContent', 'rollbackContent', 'limitations'],
|
|
176
|
+
hook: { module: 'packages/interchange/defensive-indicator-export.mts', exportName: 'buildDefensiveIndicatorExport', role: 'builder', runtime: 'shared' },
|
|
177
|
+
fixtures: [
|
|
178
|
+
{ id: 'defensive-indicators-v2', path: `${F}defensive-indicators-v2.json`, bytes: 66, sha256: '597794782fbb2291acbcd854ced48bb2b8142f175fd9dd8ab99d53dd5532701e', version: DEFENSIVE_INDICATOR_EXPORT_VERSION },
|
|
179
|
+
] },
|
|
180
|
+
{ descriptor: STIX_INDICATORS_COMPATIBILITY, lifecycleSchema: STIX_INDICATOR_LIFECYCLE_SCHEMA,
|
|
181
|
+
requiredKeys: ['type', 'id', 'objects'], optionalKeys: ['x_whoisleuth_export_version'],
|
|
182
|
+
hook: { module: 'packages/interchange/stix-indicator-export.mts', exportName: 'buildStixIndicatorExport', role: 'builder', runtime: 'shared' },
|
|
183
|
+
fixtures: [
|
|
184
|
+
{ id: 'stix-indicators-v1', path: `${F}stix-indicators-v1.json`, bytes: 115, sha256: '517fe4aec65bbdb5252945d5f0f0a393c5c72a2eb8f48a1c8147ae5c6e681f8e', version: 1 },
|
|
185
|
+
{ id: 'stix-indicators-v2', path: `${F}stix-indicators-v2.json`, bytes: 5302, sha256: '09b3e53413944efbad81458b0042cc76e4d64f0895d5c1c2dbace3d52af0a6a4', version: STIX_INDICATOR_EXPORT_VERSION },
|
|
186
|
+
] },
|
|
187
|
+
{ descriptor: MISP_INDICATORS_COMPATIBILITY, lifecycleSchema: MISP_INDICATOR_LIFECYCLE_SCHEMA,
|
|
188
|
+
requiredKeys: ['Event'], optionalKeys: [],
|
|
189
|
+
hook: { module: 'packages/interchange/misp-indicator-export.mts', exportName: 'buildMispIndicatorExport', role: 'builder', runtime: 'shared' },
|
|
190
|
+
fixtures: [
|
|
191
|
+
{ id: 'misp-indicators-v1', path: `${F}misp-indicators-v1.json`, bytes: 167, sha256: '043102cfb145ead16f6eba9b4ebe723aed12cc02f5899c918a0d172674e74997', version: 1 },
|
|
192
|
+
{ id: 'misp-indicators-v2', path: `${F}misp-indicators-v2.json`, bytes: 1611, sha256: '9bcf624c2bcc6da9e1299ef470b288ca2f2da51c65665f0729f332c513a3d558', version: MISP_INDICATOR_EXPORT_VERSION },
|
|
193
|
+
] },
|
|
194
|
+
{ descriptor: WEB_CAPTURE_SUMMARY_COMPATIBILITY, lifecycleSchema: WEB_CAPTURE_SUMMARY_SCHEMA,
|
|
195
|
+
requiredKeys: ['schema', 'schemaVersion', 'source', 'captures'], optionalKeys: [],
|
|
196
|
+
hook: { module: 'packages/interchange/web-capture-import.mts', exportName: 'parseWebCaptureSummary', role: 'normaliser', runtime: 'shared' },
|
|
197
|
+
fixtures: [{ id: 'web-capture-summary-v1', path: `${F}web-capture-summary-v1.json`, bytes: 341, sha256: '70fba8a784f61f473ba6cde71a7d9dd915cfca46fbb3469e3bb57767adf973d9', version: WEB_CAPTURE_SUMMARY_VERSION }] },
|
|
198
|
+
{ descriptor: WEB_CAPTURE_MANIFEST_COMPATIBILITY, lifecycleSchema: WEB_CAPTURE_MANIFEST_SCHEMA,
|
|
199
|
+
requiredKeys: ['schema', 'schemaVersion', 'source', 'captures'], optionalKeys: [],
|
|
200
|
+
hook: { module: 'packages/interchange/web-capture-import.mts', exportName: 'parseWebCaptureManifest', role: 'normaliser', runtime: 'shared' },
|
|
201
|
+
fixtures: [
|
|
202
|
+
{ id: 'web-capture-manifest-v2', path: `${F}web-capture-manifest-v2.json`, bytes: 494, sha256: 'a2208dc956a832d70e37cd9a5929db81d29148856c485e4fd6ce5b123de5b8e6', version: 2 },
|
|
203
|
+
{ id: 'web-capture-manifest-v3', path: `${F}web-capture-manifest-v3.json`, bytes: 868, sha256: 'bca4e8cb539ac6035dd44586fa7e27774766a14dadbc4fc73cad2b71dace2ac8', version: WEB_CAPTURE_MANIFEST_VERSION },
|
|
204
|
+
] },
|
|
205
|
+
{ descriptor: DEFENSIVE_MANIFEST_COMPATIBILITY, lifecycleSchema: DEFENSIVE_INDICATOR_MANIFEST_SCHEMA,
|
|
206
|
+
requiredKeys: ['schema', 'version', 'generatedAt'], optionalKeys: ['expiresAt', 'reviewRequired', 'explicitSelection', 'includeWildcards', 'entries', 'indicators', 'exclusions', 'limitations'],
|
|
207
|
+
hook: { module: 'packages/interchange/defensive-indicator-export.mts', exportName: 'buildDefensiveIndicatorExport', role: 'builder', runtime: 'shared' },
|
|
208
|
+
fixtures: [{ id: 'defensive-indicator-manifest-v2', path: `${F}defensive-indicator-manifest-v2.json`, bytes: 139, sha256: '8a2475b1be1810bcc10b6285630e7056986f961320c884fd8cfc72951eb79db7', version: DEFENSIVE_INDICATOR_EXPORT_VERSION }] },
|
|
209
|
+
{ descriptor: DEFENSIVE_ROLLBACK_COMPATIBILITY, lifecycleSchema: DEFENSIVE_INDICATOR_ROLLBACK_SCHEMA,
|
|
210
|
+
requiredKeys: ['schema', 'version', 'generatedAt'], optionalKeys: ['removes', 'indicators', 'limitations'],
|
|
211
|
+
hook: { module: 'packages/interchange/defensive-indicator-export.mts', exportName: 'buildDefensiveIndicatorExport', role: 'builder', runtime: 'shared' },
|
|
212
|
+
fixtures: [{ id: 'defensive-indicator-rollback-v2', path: `${F}defensive-indicator-rollback-v2.json`, bytes: 139, sha256: '1af3d32d3e808327fadc39066256d441abb5957d1ae696b834a61a37658103e6', version: DEFENSIVE_INDICATOR_EXPORT_VERSION }] },
|
|
213
|
+
{ descriptor: DNS_REHEARSAL_COMPATIBILITY, lifecycleSchema: DNS_CHANGE_REHEARSAL_EXPORT_SCHEMA,
|
|
214
|
+
requiredKeys: ['schema', 'version', 'generatedAt'], optionalKeys: ['domain', 'changes', 'findings', 'limitations'],
|
|
215
|
+
hook: { module: 'packages/interchange/dns-change-rehearsal.mts', exportName: 'buildDnsChangeRehearsalExport', role: 'builder', runtime: 'shared' },
|
|
216
|
+
fixtures: [{ id: 'dns-change-rehearsal-v2', path: `${F}dns-change-rehearsal-v2.json`, bytes: 169, sha256: '31b2e5d8d3774356362c4f5fc1741506ce7060953b49bb3cd3588adbd3bb96d3', version: DNS_CHANGE_REHEARSAL_VERSION }] },
|
|
217
|
+
{ descriptor: MAIL_REPORT_COMPATIBILITY, lifecycleSchema: MAIL_REPORT_SCHEMA,
|
|
218
|
+
requiredKeys: ['schema', 'version', 'generatedAt'], optionalKeys: ['source', 'summary', 'findings', 'limitations', 'reports', 'profileScope', 'integrity'],
|
|
219
|
+
hook: { module: 'packages/interchange/mail-report-workbench.mts', exportName: 'buildMailReportReview', role: 'builder', runtime: 'shared' },
|
|
220
|
+
fixtures: [
|
|
221
|
+
{ id: 'mail-report-review-v1', path: `${F}mail-report-review-v1.json`, bytes: 152, sha256: '804ffda72784ee2f0b34985f47f78075238841b4fbcc484b971efcd17f2b0c17', version: 1 },
|
|
222
|
+
{ id: 'mail-report-review-v2', path: `${F}mail-report-review-v2.json`, bytes: 2188, sha256: '42bb3b3a05605b0ffdda3f369a52e0490dcf1541405f2db9cee900ddf0477d97', version: 2 },
|
|
223
|
+
{ id: 'mail-report-review-v3', path: `${F}mail-report-review-v3.json`, bytes: 1627, sha256: '32d71697a08eae70cc070b981953cb18829efee139283f6163edecd126614048', version: MAIL_REPORT_VERSION },
|
|
224
|
+
] },
|
|
225
|
+
{ descriptor: REGISTRATION_DISCLOSURE_COMPATIBILITY, lifecycleSchema: REGISTRATION_DISCLOSURE_PLAN_SCHEMA,
|
|
226
|
+
requiredKeys: ['schema', 'version', 'generatedAt'], optionalKeys: ['domain', 'requests', 'limitations'],
|
|
227
|
+
hook: { module: 'packages/interchange/registration-disclosure-plan.mts', exportName: 'buildRegistrationDisclosurePlan', role: 'builder', runtime: 'shared' },
|
|
228
|
+
fixtures: [{ id: 'registration-disclosure-plan-v2', path: `${F}registration-disclosure-plan-v2.json`, bytes: 164, sha256: '74e516dc369a2c62fc5794134512bff8ec251faa85b30e78172f670b58db9037', version: REGISTRATION_DISCLOSURE_PLAN_VERSION }] },
|
|
229
|
+
{ descriptor: STATIC_PATTERN_PACK_COMPATIBILITY, lifecycleSchema: STATIC_PAGE_PATTERN_PACK_SCHEMA,
|
|
230
|
+
requiredKeys: ['schema', 'version', 'id', 'label', 'description', 'evidenceBoundary', 'relationship', 'confidence', 'rules'], optionalKeys: [],
|
|
231
|
+
hook: { module: 'packages/interchange/static-page-pattern-packs.mts', exportName: 'validateStaticPagePatternPack', role: 'structure_validator', runtime: 'shared' },
|
|
232
|
+
fixtures: [{ id: 'static-page-pattern-pack-v2', path: `${F}static-page-pattern-pack-v2.json`, bytes: 1_094, sha256: 'c409e527850bcb924f5be0708a58c5e2c32d98947c42150f6a79765991c0eb6f', version: STATIC_PAGE_PATTERN_PACK_VERSION }] },
|
|
233
|
+
],
|
|
234
|
+
}));
|
|
@@ -53,6 +53,17 @@ export const ANALYST_REVIEW_EVIDENCE_FAMILIES = Object.freeze([
|
|
|
53
53
|
'packet',
|
|
54
54
|
]);
|
|
55
55
|
export const ANALYST_REVIEW_DISPOSITIONS = Object.freeze(['open', 'expected', 'suppressed', 'resolved']);
|
|
56
|
+
export const ANALYST_REVIEW_PRIORITIES = ['urgent', 'high', 'normal'];
|
|
57
|
+
export const ANALYST_REVIEW_AGES = ['current', 'aging', 'stale', 'unknown'];
|
|
58
|
+
export const ANALYST_REVIEW_NEXT_ACTIONS = ['review', 'refresh', 'follow_up', 'resume'];
|
|
59
|
+
export const ANALYST_REVIEW_LIFECYCLE_STATES = ['open', 'expected', 'suppressed', 'resolved', 'expired', 'invalidated', 'recurred', 'orphaned'];
|
|
60
|
+
export const ANALYST_REVIEW_QUEUE_OPTIONS = [
|
|
61
|
+
{ value: 'needs_action', label: 'Needs action' },
|
|
62
|
+
{ value: 'waiting', label: 'Waiting / follow-up' },
|
|
63
|
+
{ value: 'changed', label: 'Changed since review' },
|
|
64
|
+
{ value: 'reviewed', label: 'Reviewed' },
|
|
65
|
+
{ value: 'all', label: 'Everything' },
|
|
66
|
+
];
|
|
56
67
|
export const ANALYST_REVIEW_DISPOSITION_OPTIONS = Object.freeze([
|
|
57
68
|
{ value: 'open', label: 'Open' },
|
|
58
69
|
{ value: 'expected', label: 'Expected' },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineSchemaCompatibility } from "./schema-compatibility.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { buildExtractedLifecycleFamily } from "./extracted-domain-lifecycle.mjs";
|
|
3
3
|
import { defineSchemaLifecycleFamily } from "./schema-lifecycle.mjs";
|
|
4
4
|
import { ANALYST_REVIEW_STATE_SCHEMA, ANALYST_REVIEW_STATE_SCHEMA_VERSION, ANALYST_REVIEW_STATE_SUPPORTED_VERSIONS, MAX_ANALYST_REVIEW_STATE_BYTES, } from "./analyst-review-state-contract.mjs";
|
|
5
5
|
export * from "./analyst-review-state-contract.mjs";
|
|
@@ -18,14 +18,15 @@ export const ANALYST_REVIEW_STATE_COMPATIBILITY = defineSchemaCompatibility({
|
|
|
18
18
|
owner: ANALYST_REVIEW_STATE_CONTRACT_OWNER,
|
|
19
19
|
note: 'Bounded analyst-authored lifecycle overlay. Material evidence changes, expiry, and incomplete evidence reopen review without rewriting source evidence or losing earlier rationale.',
|
|
20
20
|
});
|
|
21
|
-
export const ANALYST_REVIEW_STATE_LIFECYCLE_FAMILY = defineSchemaLifecycleFamily(
|
|
21
|
+
export const ANALYST_REVIEW_STATE_LIFECYCLE_FAMILY = defineSchemaLifecycleFamily(buildExtractedLifecycleFamily({
|
|
22
22
|
id: 'analyst-review-state',
|
|
23
23
|
owner: ANALYST_REVIEW_STATE_CONTRACT_OWNER,
|
|
24
24
|
serializerModule: 'packages/monitoring/analyst-review-state.mts',
|
|
25
25
|
serializerExportName: 'serialiseAnalystReviewStateJson',
|
|
26
26
|
plane: 'browser',
|
|
27
|
-
projection: '
|
|
28
|
-
retention: '
|
|
27
|
+
projection: 'browser_import',
|
|
28
|
+
retention: 'browser_indexeddb',
|
|
29
|
+
notePolicy: 'allowed_bounded',
|
|
29
30
|
includedCategories: ['subject-identity', 'material-fingerprint', 'analyst-disposition', 'rationale', 'review-times', 'case-and-campaign-references'],
|
|
30
31
|
excludedCategories: ['raw-upstream-responses', 'expanded-contacts', 'complete-query-urls', 'credentials', 'cookies', 'certificate-transparency-history'],
|
|
31
32
|
formats: [{
|