@slicedearth/whoisleuth-cli 2.4.0 → 2.6.0
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/DISCLOSURE +1 -1
- package/NOTICE +2 -2
- package/cli/argument-values.mjs +55 -0
- package/cli/arguments.mjs +24 -470
- package/cli/artifact-validation/case-response.mjs +67 -9
- package/cli/artifact-validation/lookup-evidence.mjs +18 -4
- package/cli/artifact-validation/signed-review.mjs +61 -64
- package/cli/artifact-verify.mjs +11 -2
- package/cli/assurance-arguments.mjs +56 -0
- package/cli/assurance-command-definitions.mjs +179 -0
- package/cli/assurance-command-runner.mjs +1 -1
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +1 -1
- package/cli/bulk-command-runner.mjs +6 -8
- package/cli/bulk.mjs +2 -3
- package/cli/case-command.mjs +99 -6
- package/cli/case-pack.mjs +19 -4
- 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 -210
- package/cli/command-definition.mjs +330 -0
- package/cli/command-reference.mjs +55 -1388
- package/cli/command-seeds.mjs +18 -0
- package/cli/compare.mjs +1 -1
- package/cli/config-profile.mjs +1 -1
- package/cli/ct-event-intake.mjs +4 -4
- package/cli/discover.mjs +2 -2
- package/cli/discovery-observation-snapshot.mjs +2 -2
- package/cli/discovery-scan-command-runner.mjs +7 -9
- package/cli/discovery-scan.mjs +1 -1
- package/cli/discovery-snapshot.mjs +2 -2
- package/cli/discovery-workflow.mjs +4 -4
- package/cli/domain-control-monitor.mjs +6 -6
- package/cli/domain-control-observations.mjs +2 -1
- package/cli/domain-feed.mjs +61 -0
- package/cli/errors.mjs +5 -6
- package/cli/evidence-arguments.mjs +46 -0
- package/cli/evidence-command-definitions.mjs +69 -0
- package/cli/evidence-command-runner.mjs +2 -3
- package/cli/evidence-signing.mjs +3 -2
- package/cli/external-observation-mapping.mjs +2 -2
- package/cli/formatters/json.mjs +1 -13
- package/cli/formatters/terminal-command-formats.mjs +6 -0
- package/cli/formatters/terminal-lookup.mjs +6 -0
- package/cli/history-arguments.mjs +39 -0
- package/cli/history-command-definitions.mjs +88 -0
- package/cli/indicator-set-command.mjs +32 -0
- package/cli/inline-command-families.mjs +6 -47
- package/cli/inline-command-runner.mjs +4 -31
- package/cli/input.mjs +2 -2
- package/cli/intake-command.mjs +147 -0
- package/cli/interchange-report.mjs +4 -2
- package/cli/investigation-artifacts.mjs +3 -4
- package/cli/investigation-package-review.mjs +2 -0
- package/cli/investigation-plan.mjs +32 -154
- package/cli/investigation-recipes.mjs +177 -0
- package/cli/investigation-run.mjs +78 -12
- package/cli/local-mmdb-review.mjs +220 -17
- package/cli/lookalike-calibration.mjs +1 -1
- package/cli/lookup-brief.mjs +2 -2
- package/cli/lookup-command-runner.mjs +19 -35
- package/cli/lookup-diff.mjs +10 -9
- package/cli/lookup-plan.mjs +10 -14
- package/cli/lookup-reconcile.mjs +6 -5
- package/cli/lookup-timeline.mjs +7 -6
- package/cli/mail-header-review.mjs +24 -247
- package/cli/mail-review.mjs +9 -7
- package/cli/network-arguments.mjs +51 -0
- package/cli/network-command-definitions.mjs +130 -0
- package/cli/network-command-runner.mjs +43 -31
- package/cli/offline-evidence-review.mjs +85 -6
- package/cli/posture.mjs +1 -1
- package/cli/registry-fixture-scaffold.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +8 -7
- package/cli/review-arguments.mjs +132 -0
- package/cli/review-command-definitions.mjs +279 -0
- package/cli/review-command-runner.mjs +72 -159
- package/cli/risk-calibration.mjs +14 -12
- package/cli/runner.mjs +20 -32
- package/cli/saved-lookup.mjs +2 -2
- package/cli/selected-input-worker.mjs +73 -0
- package/cli/sharing-review.mjs +5 -64
- package/cli/source-reliability.mjs +2 -2
- package/cli/support-arguments.mjs +49 -0
- package/cli/support-command-definitions.mjs +209 -0
- package/cli/support-command-runner.mjs +4 -4
- package/cli/watchlist-review.mjs +43 -0
- package/cli/workflow-arguments.mjs +100 -0
- package/cli/workflow-command-definitions.mjs +103 -0
- package/cli/workflow-command-runner.mjs +40 -1
- package/docs/cli-reference.md +27 -0
- package/docs/cli.md +322 -4
- package/docs/contextual-reviews.md +178 -0
- package/docs/infrastructure-observations.md +126 -0
- package/docs/offline-intake-context.md +213 -0
- package/lib/analyst-taxonomy.mjs +2 -40
- package/lib/application-version.mjs +1 -1
- package/lib/availability.mjs +13 -789
- package/lib/bounded-contract-normalizers.mjs +4 -0
- package/lib/bounded-json.mjs +2 -330
- package/lib/browser-library-profile.mjs +62 -84
- package/lib/candidate-provenance-bounds.mjs +2 -0
- package/lib/classify.mjs +1 -1
- package/lib/credential-form-attribution.mjs +136 -0
- package/lib/credential-surface-profile.mjs +6 -2
- package/lib/ct-query.mjs +2 -27
- package/lib/ct-response-bounds.mjs +2 -9
- package/lib/ct-search.mjs +83 -47
- package/lib/dmarc-discovery.mjs +62 -0
- package/lib/dns-convergence-review.mjs +21 -8
- package/lib/dns-delegation-health.mjs +35 -15
- package/lib/dns-inheritance-review.mjs +53 -63
- package/lib/dns-intelligence.mjs +27 -11
- package/lib/dns-query-coverage.mjs +40 -0
- package/lib/domain-assurance.mjs +1 -1
- package/lib/domain-change-packet.mjs +12 -13
- package/lib/domain-change-review.mjs +32 -14
- package/lib/domain-control-flight-recorder.mjs +103 -27
- package/lib/domain-control-manifest.mjs +25 -17
- package/lib/domain-posture-analysis.mjs +75 -7
- package/lib/domain-posture-parsers.mjs +9 -0
- package/lib/domain-posture.mjs +45 -25
- package/lib/domain-registration.mjs +392 -0
- package/lib/evidence-export-privacy.mjs +27 -17
- package/lib/evidence-export.mjs +3 -3
- package/lib/evidence-report.mjs +5 -1
- package/lib/external-intelligence-risk.mjs +2 -124
- package/lib/favicon.mjs +13 -4
- package/lib/generated/cisa-kev-catalog.mjs +41 -3
- package/lib/generated/registrar-standing-catalogue.mjs +18 -10
- package/lib/generated/retire-browser-catalog.mjs +332 -19
- package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
- package/lib/html-signals.mjs +13 -2
- package/lib/http-evidence-bounds.mjs +2 -8
- package/lib/http-intelligence.mjs +13 -1
- 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-child-profile-contract.mjs +28 -11
- package/lib/lookup-contract-primitives.mjs +1 -1
- package/lib/lookup-diagnostics.mjs +1 -23
- package/lib/lookup-network-evidence-bounds.mjs +1 -10
- package/lib/lookup-progress.mjs +50 -1
- package/lib/lookup-response-contract.mjs +70 -40
- package/lib/lookup-response.mjs +5 -15
- package/lib/lookup-source-progress.mjs +8 -22
- package/lib/lookup.mjs +1 -1
- package/lib/operation-budget.mjs +4 -0
- package/lib/opportunity-scoring.mjs +2 -107
- package/lib/outbound-identity.mjs +1 -1
- package/lib/page-fingerprints.mjs +18 -1
- package/lib/perceptual-hash-comparison.mjs +2 -34
- package/lib/perceptual-hash.mjs +36 -10
- package/lib/portable-generator.mjs +2 -29
- package/lib/project-metadata.mjs +2 -8
- package/lib/rdap-client.mjs +11 -7
- package/lib/registrable-domain.mjs +2 -56
- package/lib/registrar-standing-contract.mjs +1 -1
- package/lib/registrar-standing.mjs +9 -13
- package/lib/registry-capabilities.mjs +2 -2
- package/lib/registry-capability-catalogue.mjs +27 -19
- package/lib/risk-calibration-summary.mjs +2 -352
- package/lib/risk-scoring.mjs +2 -345
- package/lib/scoring-evidence-quality.mjs +2 -107
- package/lib/semantic-version.mjs +2 -54
- package/lib/service-binding-dns.mjs +46 -2
- package/lib/smtp-transport-review.mjs +48 -19
- package/lib/sslbl-certificates.generated.mjs +142 -139
- package/lib/static-html-analysis.mjs +32 -4
- package/lib/structured-data-identity.mjs +1 -1
- package/lib/technology-evidence-role.mjs +1 -51
- package/lib/threat-intelligence-manifests.mjs +1 -1
- package/lib/threat-intelligence-runtime.mjs +1 -1
- package/lib/threat-intelligence-targets.mjs +1 -1
- package/lib/threat-intelligence-types.mjs +2 -75
- package/lib/tls-intelligence.mjs +30 -5
- package/lib/typosquat-generator.mjs +11 -2
- package/lib/website-enrichment.mjs +404 -0
- package/lib/whois-authority.mjs +43 -5
- package/lib/whois-chain.mjs +12 -3
- package/lib/whois-common-formats.mjs +3 -1
- package/lib/whois-contacts.mjs +13 -2
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-parser.mjs +5 -3
- package/package.json +15 -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 +348 -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-containment-handoff.mjs +195 -0
- package/packages/cases/case-evidence-links.mjs +151 -0
- package/packages/cases/case-evidence-model.mjs +89 -12
- package/packages/cases/case-evidence-quality.mjs +5 -0
- package/packages/cases/case-export-input.mjs +7 -5
- package/packages/cases/case-incident-context.mjs +52 -0
- package/packages/cases/case-markdown.mjs +11 -0
- package/packages/cases/case-migration-model.mjs +40 -24
- package/packages/cases/case-object-coverage.mjs +33 -0
- package/packages/cases/case-packet-correction.mjs +106 -0
- package/packages/cases/case-recheck-model.mjs +124 -36
- package/packages/cases/case-record-contracts.mjs +1 -1
- package/packages/cases/case-record-core.mjs +1 -1
- package/packages/cases/case-record-operations.mjs +111 -118
- package/packages/cases/case-record-projection.mjs +23 -4
- package/packages/cases/case-record-version-input.mjs +41 -0
- package/packages/cases/case-report.mjs +93 -27
- package/packages/cases/case-requested-evidence.mjs +146 -0
- package/packages/cases/case-response-actions.mjs +159 -1
- package/packages/cases/case-response-model.mjs +16 -1
- package/packages/cases/case-response-object.mjs +119 -0
- package/packages/cases/case-response-outcomes.mjs +108 -20
- package/packages/cases/case-response-packet-render.mjs +35 -12
- package/packages/cases/case-response-packet.mjs +55 -13
- package/packages/cases/case-response-records.mjs +0 -1
- package/packages/cases/case-response-review-inputs.mjs +60 -8
- package/packages/cases/case-storage-model.mjs +1 -1
- package/packages/cases/case-workflow-metadata.mjs +217 -0
- package/packages/cases/case-workflow-migration.mjs +43 -0
- package/packages/cases/http-summary.mjs +6 -1
- package/packages/comparison/favicon-similarity.mjs +1 -1
- package/packages/comparison/page-similarity.mjs +1 -1
- package/packages/comparison/relationship-evidence.mjs +21 -9
- package/packages/comparison/relationship-provenance.mjs +7 -1
- package/packages/contracts/analyst-interchange.mjs +35 -15
- package/packages/contracts/candidate-watch-review.mjs +54 -0
- package/packages/contracts/case-portability.mjs +360 -49
- package/packages/contracts/cli-command-semantics.mjs +5 -0
- package/packages/contracts/cli-grammar.mjs +6 -0
- package/packages/contracts/cli-lookup-brief.mjs +2 -0
- package/packages/contracts/context-review.mjs +25 -0
- package/packages/contracts/document-intake.mjs +11 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +30 -14
- package/packages/contracts/domain-control-manifest.mjs +1 -1
- package/packages/contracts/domain-control-monitor.mjs +13 -2
- package/packages/contracts/domain-control-review.mjs +36 -15
- package/packages/contracts/external-observation-interchange.mjs +96 -28
- package/packages/contracts/har-review.mjs +2 -0
- package/packages/contracts/identity-events.mjs +4 -0
- package/packages/contracts/investigation-run.mjs +2 -0
- package/packages/contracts/ip-address.mjs +10 -0
- package/packages/contracts/lookup-evidence.mjs +3 -2
- package/packages/contracts/mail-authentication.mjs +5 -0
- package/packages/contracts/message-intake.mjs +37 -0
- package/packages/contracts/offline-comparison.mjs +4 -1
- package/packages/contracts/passive-mail.mjs +10 -0
- package/packages/contracts/risk-calibration.mjs +44 -29
- package/packages/contracts/schema-lifecycle.mjs +3 -1
- package/packages/contracts/web-capture.mjs +2 -2
- package/packages/contracts/workspace-portability.mjs +144 -28
- package/packages/evidence/collection-quality.mjs +67 -0
- package/packages/evidence/credential-form-attribution.mjs +60 -0
- package/packages/evidence/dns-query-outcome.mjs +72 -0
- package/packages/evidence/email-recipient.mjs +18 -0
- package/packages/evidence/image-regions.mjs +15 -0
- package/packages/evidence/lookup-target.mjs +5 -1
- package/packages/evidence/observation.mjs +2 -0
- package/packages/evidence/png-pixels.mjs +117 -0
- package/packages/evidence/public-address-policy.mjs +83 -0
- package/packages/evidence/record-identity.mjs +60 -16
- package/packages/evidence/technology-evidence-role.mjs +51 -0
- package/packages/interchange/defensive-indicator-export.mjs +264 -0
- package/packages/interchange/external-findings-import.mjs +36 -29
- package/packages/interchange/json-output.mjs +14 -0
- package/packages/interchange/managed-indicator-export.mjs +36 -0
- package/packages/interchange/managed-indicator-set.mjs +223 -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 +22 -15
- package/packages/investigation/acquisition-decision-packet.mjs +79 -1
- package/packages/investigation/bulk-mail-exposure.mjs +7 -13
- 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 +87 -0
- package/packages/investigation/docx-intake.mjs +133 -0
- package/packages/investigation/domain-history-review.mjs +132 -0
- package/packages/investigation/domain-transition-review.mjs +49 -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/infrastructure-observation.mjs +234 -0
- package/packages/investigation/intake-context.mjs +284 -0
- package/packages/investigation/intake-indicators.mjs +116 -0
- package/packages/investigation/intake-phones.mjs +155 -0
- package/packages/investigation/intake-report.mjs +20 -0
- package/packages/investigation/investigation-manifest.mjs +35 -9
- package/packages/investigation/link-intake.mjs +209 -0
- package/packages/investigation/lookup-asset-graph.mjs +1 -1
- package/packages/investigation/mail-authentication-review.mjs +222 -0
- package/packages/investigation/mail-header-identity.mjs +209 -0
- package/packages/investigation/message-intake.mjs +235 -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/domain-feed.mjs +321 -0
- package/packages/workspace/brand-candidate-workflow.mjs +351 -0
- package/packages/workspace/brand-keyword-campaign.mjs +231 -0
- package/packages/workspace/brand-profile-model.mjs +48 -4
- package/packages/workspace/bulk-review-model.mjs +28 -26
- package/packages/workspace/bulk-session-model.mjs +27 -13
- package/packages/workspace/campaign-model.mjs +1 -1
- package/packages/workspace/candidate-watch-handoff.mjs +195 -0
- package/packages/workspace/detection-rule-model.mjs +11 -3
- package/packages/workspace/hostile-input.mjs +1 -1
- package/packages/workspace/investigation-guide.mjs +2 -2
- package/packages/workspace/investigation-template-model.mjs +26 -2
- package/packages/workspace/page-baseline.mjs +2 -2
- package/packages/workspace/relationship-observation-model.mjs +17 -13
- package/packages/workspace/shortlist-model.mjs +24 -7
- package/packages/workspace/watchlist-history.mjs +59 -16
- package/packages/workspace/watchlist-store.mjs +152 -11
- package/packages/workspace/website-snapshot-model.mjs +28 -3
- package/packages/workspace/workspace-archive-crypto.mjs +1 -1
- package/packages/workspace/workspace-archive.mjs +5 -4
- package/third-party-notices.txt +549 -16
- package/lib/hostname.mjs +0 -2
|
@@ -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,32 @@
|
|
|
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 { safeTerminalValue } from "./formatters/terminal.mjs";
|
|
5
|
+
import EXIT_CODES from "./exit-codes.mjs";
|
|
6
|
+
export async function runIndicatorSetCommand(args, dependencies, context) {
|
|
7
|
+
context.setFailureLabel('Indicator revision');
|
|
8
|
+
const raw = dependencies.readArtifactInput ? await dependencies.readArtifactInput(args.source)
|
|
9
|
+
: await context.readInput(args.source, args.operation === 'revise' ? MAX_MANAGED_INDICATOR_PLAN_BYTES : MAX_MANAGED_INDICATOR_SET_BYTES, 'Indicator input');
|
|
10
|
+
const input = parseManagedIndicatorJson(raw, args.operation === 'revise');
|
|
11
|
+
const now = context.now();
|
|
12
|
+
const revision = args.operation === 'revise' ? await buildManagedIndicatorRevision(input, now) : null;
|
|
13
|
+
const manifest = revision?.manifest ?? await readManagedIndicatorSet(input);
|
|
14
|
+
dependencies.signal?.throwIfAborted();
|
|
15
|
+
if (args.quiet)
|
|
16
|
+
return EXIT_CODES.SUCCESS;
|
|
17
|
+
if (args.operation === 'stix' || args.operation === 'misp')
|
|
18
|
+
context.writeStdout((await exportManagedIndicators(manifest, args.operation)).content);
|
|
19
|
+
else if (args.output === 'json')
|
|
20
|
+
context.writeStdout((await exportManagedIndicators(manifest, 'manifest')).content);
|
|
21
|
+
else {
|
|
22
|
+
const lines = [`${safeTerminalValue(manifest.name)} · revision ${manifest.revision}`, `Set ${manifest.id}`, `Content digest ${manifest.integrity.digestSha256}`,
|
|
23
|
+
'Content verified; authorship and earlier revisions are not authenticated.'];
|
|
24
|
+
if (revision)
|
|
25
|
+
lines.push(`Changes: ${revision.changes.length} · unchanged: ${revision.unchanged} · excluded candidates: ${revision.exclusions.length}`);
|
|
26
|
+
for (const entry of manifest.entries)
|
|
27
|
+
lines.push(`${entry.domain} · ${managedIndicatorState(entry, now)} · ${entry.id}`, ` Review expires ${entry.expiresAt} · source observed ${entry.observation.observedAt ?? 'time unavailable'}`, ` ${safeTerminalValue(entry.reviewBasis)}`);
|
|
28
|
+
lines.push('Save the manifest for the next revision. Nothing was submitted or applied.');
|
|
29
|
+
context.writeStdout(context.terminal(`${lines.join('\n')}\n`, args.color));
|
|
30
|
+
}
|
|
31
|
+
return EXIT_CODES.SUCCESS;
|
|
32
|
+
}
|
|
@@ -1,47 +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',
|
|
24
|
-
'case-pack',
|
|
25
|
-
]);
|
|
26
|
-
const ASSURANCE_INLINE_COMMANDS = Object.freeze([
|
|
27
|
-
'manifest',
|
|
28
|
-
'map-observations',
|
|
29
|
-
'oam-export',
|
|
30
|
-
'domain-control',
|
|
31
|
-
'assurance',
|
|
32
|
-
'change-packet',
|
|
33
|
-
'sharing-review',
|
|
34
|
-
'ct-intake',
|
|
35
|
-
]);
|
|
36
|
-
const WORKFLOW_INLINE_COMMANDS = Object.freeze([
|
|
37
|
-
'monitor-once',
|
|
38
|
-
'workflow-plan',
|
|
39
|
-
'workflow-run',
|
|
40
|
-
]);
|
|
41
|
-
const HISTORY_INLINE_COMMANDS = Object.freeze([
|
|
42
|
-
'diff',
|
|
43
|
-
'reconcile',
|
|
44
|
-
'timeline',
|
|
45
|
-
'export',
|
|
46
|
-
]);
|
|
47
|
-
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();
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { readBoundedRegularFile } from "../lib/bounded-file.mjs";
|
|
2
|
+
import { MAX_MESSAGE_INTAKE_BYTES, MESSAGE_INTAKE_INPUTS } from "../packages/contracts/message-intake.mjs";
|
|
3
|
+
import { reviewSelectedInputInWorker } from "./selected-input-worker.mjs";
|
|
4
|
+
import { reviewIdentityIncident } from "../packages/investigation/identity-incident-review.mjs";
|
|
5
|
+
import { intakeIndicators, intakeIndicatorSource } from "../packages/investigation/intake-indicators.mjs";
|
|
6
|
+
import { MAX_INTAKE_CONTEXT_BYTES, applyIntakeContextInput } from "../packages/investigation/intake-context.mjs";
|
|
7
|
+
import { selectReceiverTrust, authenticationHeaderLabel } from "../packages/investigation/mail-authentication-review.mjs";
|
|
8
|
+
import { CliUsageError } from "./errors.mjs";
|
|
9
|
+
import { formatJsonDocument } from "./formatters/json.mjs";
|
|
10
|
+
import EXIT_CODES from "./exit-codes.mjs";
|
|
11
|
+
export function formatMessageIntake(report) {
|
|
12
|
+
const lines = [`Offline ${report.source.kind} intake`, `Review: ${report.coverage.state} · Extracted links: ${report.links.length} · Reviewed parts: ${report.coverage.reviewedParts}`,
|
|
13
|
+
`Source: ${report.source.digestSha256} · ${report.source.byteLength} bytes`];
|
|
14
|
+
for (const identity of report.identities)
|
|
15
|
+
lines.push(`Part ${identity.part} ${identity.role.replaceAll('_', ' ')}: ${identity.domain}`);
|
|
16
|
+
for (const header of report.authenticationReview.headers) {
|
|
17
|
+
lines.push(`${authenticationHeaderLabel(header)} · ${header.state} · receiver trust: ${header.receiverTrust.replaceAll('_', ' ')}`);
|
|
18
|
+
for (const claim of header.claims)
|
|
19
|
+
lines.push(` ${claim.method}/${claim.methodVersion}: ${claim.result} (${claim.state})${claim.domains.map(value => ` · ${value.property}=${value.domain}`).join('')}${claim.duplicateProperties.length ? ` · ambiguous properties: ${claim.duplicateProperties.join(', ')}` : ''}`);
|
|
20
|
+
if (header.duplicateOf)
|
|
21
|
+
lines.push(` Duplicate of header ${header.duplicateOf} in this part.`);
|
|
22
|
+
lines.push(...header.issues.map(issue => ` ${issue}`));
|
|
23
|
+
}
|
|
24
|
+
for (const link of report.links) {
|
|
25
|
+
lines.push(`${link.id}: ${link.origin} · ${link.source.replaceAll('_', ' ')}${link.parentId ? ` · supplied inside ${link.parentId}` : ''}`);
|
|
26
|
+
if (link.displayedHostname)
|
|
27
|
+
lines.push(` Displayed: ${link.displayedHostname} · ${link.displayedDestination.replaceAll('_', ' ')}`);
|
|
28
|
+
if (link.authorisation) {
|
|
29
|
+
lines.push(` Authorisation: ${link.authorisation.kind.replaceAll('_', ' ')} · client ${link.authorisation.clientId ?? 'not retained or ambiguous'}`);
|
|
30
|
+
if (link.authorisation.scopes.length)
|
|
31
|
+
lines.push(` Requested scopes: ${link.authorisation.scopes.join(', ')}`);
|
|
32
|
+
if (link.authorisation.redirectOrigin)
|
|
33
|
+
lines.push(` Supplied return origin: ${link.authorisation.redirectOrigin}`);
|
|
34
|
+
if (link.authorisation.duplicateParameters.length)
|
|
35
|
+
lines.push(` Ambiguous parameters: ${link.authorisation.duplicateParameters.join(', ')}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const indicator of intakeIndicators(report))
|
|
39
|
+
lines.push(`${indicator.id}: ${indicator.kind.toUpperCase()} ${indicator.value} · literal selected text · ${indicator.location.partId}${indicator.location.page ? ` page ${indicator.location.page}` : ''} · source ${intakeIndicatorSource(report, indicator)}`);
|
|
40
|
+
if (report.schemaVersion === 2) {
|
|
41
|
+
lines.push(`Indicator coverage: ${report.indicatorCoverage.state.replaceAll('_', ' ')}. Hashes require explicit algorithm labels; no indicator was resolved or reputation-checked.`);
|
|
42
|
+
const supplied = report.distributionContext;
|
|
43
|
+
if (supplied)
|
|
44
|
+
lines.push(`Analyst-declared distribution: ${supplied.channel} · ${supplied.sourceLabel} · observed ${supplied.observedAt ?? 'unknown'}`, ` Reference ${supplied.reference ?? 'not supplied'} · observer ${supplied.observerLabel ?? 'unknown'} · vantage ${supplied.vantageLabel ?? 'unknown'}`, 'Distribution, observation time and vantage are supplied claims, not verified capture conditions or independent collection.');
|
|
45
|
+
const selected = report.selectedEvidence;
|
|
46
|
+
if (selected) {
|
|
47
|
+
lines.push(`Deliberately selected phone groups: ${selected.phones.length}. Original spans use zero-based, end-exclusive UTF-16 code units.`);
|
|
48
|
+
lines.push(`Phone extraction coverage: ${selected.phoneCoverage.state.replaceAll('_', ' ')}; ${selected.phoneCoverage.candidatesShown} transient candidates shown, ${selected.phoneCoverage.candidatesReviewed} candidate runs reviewed.`);
|
|
49
|
+
for (const phone of selected.phones) {
|
|
50
|
+
lines.push(` ${phone.canonical ?? 'No canonical number'} · ${phone.state.replaceAll('_', ' ')} · declared ${phone.declaration.role.replaceAll('_', ' ')} · ${phone.declaration.basis.replaceAll('_', ' ')} · country context ${phone.declaration.countryCallingCode ?? 'unknown'}`, ` Source ${phone.declaration.sourceLabel}; declared observation ${phone.declaration.observedAt ?? 'unknown'}; input ${selected.sourceDigestSha256}`);
|
|
51
|
+
for (const occurrence of phone.occurrences)
|
|
52
|
+
lines.push(` ${JSON.stringify(occurrence.original)} · [${occurrence.start}, ${occurrence.end})${phone.extension ? ` · separate extension ${phone.extension}` : ''}`);
|
|
53
|
+
}
|
|
54
|
+
const pair = selected.destinationPair;
|
|
55
|
+
if (pair)
|
|
56
|
+
lines.push(`Supplied destination pair: ${pair.state.replaceAll('_', ' ')} · displayed ${pair.displayed.hostname ?? pair.displayed.state} · destination ${pair.destination.hostname ?? pair.destination.state}`, ` Displayed source ${pair.displayedDeclaration.sourceLabel}; declared ${pair.displayedDeclaration.observedAt ?? 'unknown'}; ${pair.displayedDeclaration.basis}`, ` Destination role ${pair.destinationDeclaration.role}; source ${pair.destinationDeclaration.sourceLabel}; declared ${pair.destinationDeclaration.observedAt ?? 'unknown'}; ${pair.destinationDeclaration.basis}`);
|
|
57
|
+
lines.push(...selected.limitations);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (report.actionHints.length)
|
|
61
|
+
lines.push(`Review wording: ${report.actionHints.map(value => value.replaceAll('_', ' ')).join(', ')}`);
|
|
62
|
+
if (report.documentReview) {
|
|
63
|
+
lines.push(`Document coverage: ${report.documentReview.state} · pages ${report.documentReview.reviewedPages}/${report.documentReview.pageCount ?? 'not a paginated review'}`);
|
|
64
|
+
for (const part of report.documentReview.parts)
|
|
65
|
+
lines.push(` ${part.id}${part.page ? ` · page ${part.page}` : ''} · ${part.kind} · ${part.identity} · ${part.digestSha256}`);
|
|
66
|
+
lines.push(...report.documentReview.notes);
|
|
67
|
+
}
|
|
68
|
+
if (report.harReview) {
|
|
69
|
+
lines.push('Recorded HTTP sequence (not replayed)');
|
|
70
|
+
for (const entry of report.harReview.entries)
|
|
71
|
+
lines.push(` ${entry.sequence}. ${entry.startedAt ?? 'time unavailable'} · ${entry.method} ${entry.origin ?? 'origin unavailable'} · status ${entry.status ?? 'unavailable'} · ${entry.mimeCategory} · ${entry.durationMs === null ? 'duration unavailable' : `${entry.durationMs} ms`}`);
|
|
72
|
+
if (report.harReview.invalidEntries)
|
|
73
|
+
lines.push(`Invalid request records omitted: ${report.harReview.invalidEntries}`);
|
|
74
|
+
}
|
|
75
|
+
if (report.identityEventReview) {
|
|
76
|
+
const review = report.identityEventReview;
|
|
77
|
+
const matches = new Map(review.comparison?.events.map(event => [event.sequence, event]));
|
|
78
|
+
lines.push(`Selected identity events: ${review.provider} · ${review.events.length} records · ${review.invalidEvents} invalid records omitted`);
|
|
79
|
+
for (const event of review.events) {
|
|
80
|
+
const match = matches.get(event.sequence);
|
|
81
|
+
lines.push(` ${event.sequence}. ${event.occurredAt ?? 'time unavailable'} · ${event.actorLabel ?? 'actor unavailable'} · ${event.kind} · ${event.result}`, ` applications ${event.applicationIds.join(', ') || 'unavailable'} · resource tenant ${event.tenantId ?? 'unavailable'} · protocol ${event.protocol}`);
|
|
82
|
+
if (match)
|
|
83
|
+
lines.push(` comparison ${match.state}: application ${match.application}, tenant ${match.tenant}, actor ${match.actor}, time ${match.time}`);
|
|
84
|
+
}
|
|
85
|
+
lines.push('Actor labels are file-local. Matching supplied fields does not establish account compromise.');
|
|
86
|
+
}
|
|
87
|
+
for (const step of report.identityRecovery.nextSteps)
|
|
88
|
+
lines.push(`Account response — ${step.title}: ${step.detail}`);
|
|
89
|
+
if (report.coverage.unreviewedAttachments)
|
|
90
|
+
lines.push(`Unreviewed attachments: ${report.coverage.unreviewedAttachments}`);
|
|
91
|
+
if (report.coverage.rejectedLinks)
|
|
92
|
+
lines.push(`Unsupported link values: ${report.coverage.rejectedLinks}`);
|
|
93
|
+
if (report.coverage.boundsReached.length)
|
|
94
|
+
lines.push(`Partial analysis: ${report.coverage.boundsReached.join('; ')}`);
|
|
95
|
+
if (report.source.kind === 'qr' && !report.coverage.reviewedParts)
|
|
96
|
+
lines.push('No QR text was decoded. This does not establish that the image contains no QR symbol.');
|
|
97
|
+
lines.push('Exact paths, queries, fragments and surrounding message content are not included. Only deliberately selected phone substrings can be retained. Link relationships and authorisation parameters are supplied claims, not proof of a completed redirect or account compromise.');
|
|
98
|
+
return `${lines.join('\n')}\n`;
|
|
99
|
+
}
|
|
100
|
+
export async function runIntakeCommand(args, dependencies, context) {
|
|
101
|
+
context.setFailureLabel('Message intake');
|
|
102
|
+
if (MESSAGE_INTAKE_INPUTS[args.kind].binary && (!args.source || args.source === '-'))
|
|
103
|
+
throw new CliUsageError('Binary intake requires a selected file; binary stdin is not accepted.');
|
|
104
|
+
let bytes;
|
|
105
|
+
if (dependencies.readBinaryArtifactInput && args.source && args.source !== '-')
|
|
106
|
+
bytes = await dependencies.readBinaryArtifactInput(args.source);
|
|
107
|
+
else if (args.source && args.source !== '-')
|
|
108
|
+
bytes = await readBoundedRegularFile(args.source, { maximumBytes: MAX_MESSAGE_INTAKE_BYTES, minimumBytes: 1, label: 'Selected input', ...(dependencies.signal ? { signal: dependencies.signal } : {}) });
|
|
109
|
+
else
|
|
110
|
+
bytes = new TextEncoder().encode(await context.readInput(args.source, MAX_MESSAGE_INTAKE_BYTES, 'Selected input'));
|
|
111
|
+
try {
|
|
112
|
+
const result = await reviewSelectedInputInWorker(bytes, args.kind, context.now(), dependencies.signal);
|
|
113
|
+
let authenticationReview;
|
|
114
|
+
try {
|
|
115
|
+
authenticationReview = selectReceiverTrust(result.report.authenticationReview, args.trustedAuthHeaders ?? []);
|
|
116
|
+
}
|
|
117
|
+
catch (cause) {
|
|
118
|
+
if (cause instanceof TypeError)
|
|
119
|
+
throw new CliUsageError(cause.message);
|
|
120
|
+
throw cause;
|
|
121
|
+
}
|
|
122
|
+
let report = { ...result.report, authenticationReview, identityRecovery: reviewIdentityIncident({ reportedActions: args.reportedActions }) };
|
|
123
|
+
if (args.intakeContextSource) {
|
|
124
|
+
if (args.intakeContextSource === '-')
|
|
125
|
+
throw new CliUsageError('Distribution context requires a separate selected file, not stdin.');
|
|
126
|
+
report = applyIntakeContextInput({ ...result, report }, await context.readInput(args.intakeContextSource, MAX_INTAKE_CONTEXT_BYTES, 'Intake context'));
|
|
127
|
+
}
|
|
128
|
+
dependencies.signal?.throwIfAborted();
|
|
129
|
+
if (!args.quiet && result.phoneReview && (result.phoneReview.candidates.length || result.phoneReview.state === 'partial')) {
|
|
130
|
+
const phones = result.phoneReview;
|
|
131
|
+
context.writeStderr([
|
|
132
|
+
`Transient phone candidates: ${phones.candidates.length}; coverage ${phones.state}. Values are omitted until explicitly selected.`,
|
|
133
|
+
`Full source digest: ${phones.sourceDigestSha256}`,
|
|
134
|
+
...phones.candidates.map(candidate => `${candidate.id}: UTF-16 span [${candidate.start}, ${candidate.end}) · ${candidate.state.replaceAll('_', ' ')}`),
|
|
135
|
+
'Review these spans in the original plaintext. Use --intake-context with version 2, review.sourceDigestSha256, and review.phones entries containing start, end and an explicit declaration. Offsets are zero-based UTF-16 code units, end exclusive; retain BOMs and supplementary characters when counting.',
|
|
136
|
+
'Only selected values enter the private report. Public/trusted Case summaries and STIX/MISP mappings omit phone values.',
|
|
137
|
+
'',
|
|
138
|
+
].join('\n'));
|
|
139
|
+
}
|
|
140
|
+
if (!args.quiet)
|
|
141
|
+
context.writeStdout(args.output === 'json' ? formatJsonDocument(report) : context.terminal(formatMessageIntake(report), args.color));
|
|
142
|
+
return args.strictExit && (report.coverage.state === 'partial' || result.phoneReview?.state === 'partial') ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
bytes.fill(0);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -89,6 +89,7 @@ export async function buildInterchangeFidelityReport(raw, options = {}) {
|
|
|
89
89
|
}),
|
|
90
90
|
limitations: Object.freeze([
|
|
91
91
|
'The file did not match a registered interchange contract. Its supplied schema text and contents are not echoed.',
|
|
92
|
+
'This is an import/export compatibility catalogue, not the complete verifier inventory. verify-artifact may support this file independently.',
|
|
92
93
|
'This report makes no request and does not establish that evidence is accurate, current, complete, or safe to share.',
|
|
93
94
|
]),
|
|
94
95
|
});
|
|
@@ -185,6 +186,7 @@ export async function buildInterchangeFidelityReport(raw, options = {}) {
|
|
|
185
186
|
blockedSectionCount, prunedRecordCount,
|
|
186
187
|
}),
|
|
187
188
|
limitations: Object.freeze([
|
|
189
|
+
...(contract.id === 'lookup_evidence' ? ['CLI read support means offline verification and packaging. brief, export, compare, registry-doctor and source-report require a saved CLI Lookup, not this browser evidence export.'] : []),
|
|
188
190
|
'Compatibility describes declared field groups and supported operations, not byte-for-byte equality or evidence truth.',
|
|
189
191
|
'The report emits only registered metadata, counts, and fixed field-group identifiers. Targets, notes, contacts, passphrases, and evidence values are not included.',
|
|
190
192
|
'A verified integrity envelope detects file changes but does not establish author identity, source accuracy, currency, completeness, or sharing authority.',
|
|
@@ -195,7 +197,7 @@ export function formatInterchangeFidelityReport(report) {
|
|
|
195
197
|
const browser = report.compatibility.browser;
|
|
196
198
|
const cli = report.compatibility.cli;
|
|
197
199
|
const nextAction = !report.recognised
|
|
198
|
-
? '
|
|
200
|
+
? 'Run verify-artifact for independent verification support. Import/export compatibility is not registered here.'
|
|
199
201
|
: !report.artifact.versionSupported
|
|
200
202
|
? 'Use a WHOISleuth release that supports this exact version, or re-export it with its last supported release.'
|
|
201
203
|
: !report.verification.assuranceSatisfied
|
|
@@ -209,7 +211,7 @@ export function formatInterchangeFidelityReport(report) {
|
|
|
209
211
|
: 'Retain the verification result and use only the operations declared above.';
|
|
210
212
|
return [
|
|
211
213
|
'Interchange fidelity report',
|
|
212
|
-
`
|
|
214
|
+
`Interchange contract ${report.recognised ? 'registered' : 'not registered'}`,
|
|
213
215
|
`Artifact ${report.artifact.id ?? 'unrecognised'}`,
|
|
214
216
|
`Version ${report.artifact.version ?? 'unavailable'}${report.artifact.versionSupported ? '' : ' (unsupported)'}`,
|
|
215
217
|
`Verification ${report.verification.state}`,
|
|
@@ -9,8 +9,8 @@ import { OFFLINE_ARTIFACT_VERIFICATION_SCHEMA, OFFLINE_ARTIFACT_VERIFICATION_VER
|
|
|
9
9
|
import { validateLookupEvidenceArtifactStructure } from "./artifact-validation/lookup-evidence.mjs";
|
|
10
10
|
import { CLI_DISCOVERY_SCAN_SCHEMA, CLI_DISCOVERY_SCAN_VERSION } from "./discovery-scan.mjs";
|
|
11
11
|
import { CLI_DISCOVER_SCHEMA, CLI_DISCOVER_SCHEMA_VERSION, CLI_POSTURE_SCHEMA, CLI_POSTURE_SCHEMA_VERSION, CLI_CT_SEARCH_SCHEMA, CLI_CT_SEARCH_SCHEMA_VERSION, CLI_COMPARE_SCHEMA, CLI_COMPARE_SCHEMA_VERSION, } from "./formatters/json.mjs";
|
|
12
|
-
import { CLI_LOOKUP_BRIEF_SCHEMA, CLI_LOOKUP_BRIEF_VERSION } from "
|
|
13
|
-
import { EXTERNAL_FINDINGS_SCHEMA,
|
|
12
|
+
import { CLI_LOOKUP_BRIEF_SCHEMA, CLI_LOOKUP_BRIEF_VERSION } from "../packages/contracts/cli-lookup-brief.mjs";
|
|
13
|
+
import { EXTERNAL_FINDINGS_SCHEMA, parseExternalFindingsDocument } from "../packages/interchange/external-findings-import.mjs";
|
|
14
14
|
import { SOURCE_RELIABILITY_REPORT_SCHEMA, SOURCE_RELIABILITY_REPORT_VERSION } from "./source-reliability.mjs";
|
|
15
15
|
import { CLI_CASE_PACK_SCHEMA, CLI_CASE_PACK_VERSION, verifyCliCasePack } from "./case-pack.mjs";
|
|
16
16
|
import { SHARING_REVIEW_SCHEMA, SHARING_REVIEW_VERSION } from "./sharing-review.mjs";
|
|
@@ -59,8 +59,7 @@ export function validateWorkflowResult(step, result, retained) {
|
|
|
59
59
|
break;
|
|
60
60
|
case 'ct-intake':
|
|
61
61
|
schema = EXTERNAL_FINDINGS_SCHEMA;
|
|
62
|
-
version =
|
|
63
|
-
parseExternalFindingsDocument(value);
|
|
62
|
+
version = parseExternalFindingsDocument(value).schemaVersion;
|
|
64
63
|
break;
|
|
65
64
|
case 'brief':
|
|
66
65
|
schema = CLI_LOOKUP_BRIEF_SCHEMA;
|
|
@@ -74,6 +74,7 @@ export async function verifyOfflineInvestigationPackage(input, passphrase) {
|
|
|
74
74
|
}
|
|
75
75
|
entries.push({ id: reviewed.entry.id, byteLength: reviewed.entry.byteLength,
|
|
76
76
|
mediaType: 'mediaType' in reviewed.entry ? reviewed.entry.mediaType : 'application/json',
|
|
77
|
+
imageDerivation: 'imageDerivation' in reviewed.entry ? reviewed.entry.imageDerivation : null,
|
|
77
78
|
state, identity: reviewed.state === 'identity_verified' ? 'verified' : 'failed', verification, issue });
|
|
78
79
|
}
|
|
79
80
|
const complete = entries.every((entry) => entry.state === 'admitted' || entry.state === 'opaque')
|
|
@@ -97,6 +98,7 @@ export async function verifyOfflineInvestigationPackage(input, passphrase) {
|
|
|
97
98
|
'Capture attachment checks bind selected bytes to the capture manifest declaration, not to a trusted publisher or real website observation.',
|
|
98
99
|
'Case-file checks match original references to other verified package bytes by length and digest, not by filename or claimed source. Missing originals make the package partial; no file is fetched or imported.',
|
|
99
100
|
'Source declarations and packaging times are not authenticated. Signatures and timestamp tokens require their own verification and trust decisions.',
|
|
101
|
+
'Image derivation declares the immediate parent fingerprint and editing operations, not proof of transformation or complete redaction. Parent bytes are optional; missing declarations leave editing history unknown.',
|
|
100
102
|
'This command does not import, overwrite or collect evidence. Unchanged bytes and valid source structure do not establish factual accuracy, currentness or safe sharing.',
|
|
101
103
|
]),
|
|
102
104
|
});
|