@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { REGISTRY_CAPABILITIES_VERSION, registryCapabilityFor } from "../lib/registry-capabilities.mjs";
|
|
2
|
-
import { buildRiskCalibrationSummaryReport } from "../
|
|
3
|
-
import { explainRiskScore,
|
|
2
|
+
import { buildRiskCalibrationSummaryReport } from "../packages/analysis/risk-calibration-summary.mjs";
|
|
3
|
+
import { explainRiskScore, explainRiskScoreV8, RISK_MODEL_VERSION, RISK_REVIEW_THRESHOLD } from "../packages/analysis/risk-scoring.mjs";
|
|
4
4
|
import { buildCliCommandCatalogue, formatCliCommandCatalogue, selectCliCommands } from "./command-catalogue.mjs";
|
|
5
5
|
import { CLI_COMMAND_REGISTRY, CLI_COMMANDS, COMMAND_COLLECTION, COMMAND_DETAILS, COMMAND_USAGE, } from "./command-reference.mjs";
|
|
6
6
|
import { buildShellCompletion } from "./completion.mjs";
|
|
@@ -169,8 +169,8 @@ async function runRiskCalibrationCommand(args, dependencies, context) {
|
|
|
169
169
|
modelVersion: dependencies.riskModelVersion || RISK_MODEL_VERSION,
|
|
170
170
|
reviewThreshold: dependencies.riskReviewThreshold || RISK_REVIEW_THRESHOLD,
|
|
171
171
|
...(!dependencies.explainRiskScore ? {
|
|
172
|
-
previousModelVersion:
|
|
173
|
-
explainPreviousRiskScore:
|
|
172
|
+
previousModelVersion: 8,
|
|
173
|
+
explainPreviousRiskScore: explainRiskScoreV8,
|
|
174
174
|
} : {}),
|
|
175
175
|
});
|
|
176
176
|
if (!args.quiet) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { parseBoundedJson } from "../packages/analysis/bounded-json.mjs";
|
|
2
|
+
import { applyCandidateWatchHandoff, planCandidateWatchHandoff, } from "../packages/workspace/candidate-watch-handoff.mjs";
|
|
3
|
+
import { buildWatchlistExport, mergeWatchlistStores, } from "../packages/workspace/watchlist-store.mjs";
|
|
4
|
+
import { MAX_WATCHLIST_IMPORT_BYTES } from "../packages/contracts/workspace-portability.mjs";
|
|
5
|
+
import { ordinaryWorkspaceRecord } from "../packages/workspace/hostile-input.mjs";
|
|
6
|
+
import { CANDIDATE_WATCH_INPUT_SCHEMA, CANDIDATE_WATCH_INPUT_VERSION, } from "../packages/contracts/candidate-watch-review.mjs";
|
|
7
|
+
export function parseCandidateWatchInput(input) {
|
|
8
|
+
const value = typeof input === 'string'
|
|
9
|
+
? parseBoundedJson(input, {
|
|
10
|
+
maximumBytes: MAX_WATCHLIST_IMPORT_BYTES,
|
|
11
|
+
label: 'Candidate watch selection',
|
|
12
|
+
})
|
|
13
|
+
: input;
|
|
14
|
+
const raw = ordinaryWorkspaceRecord(value, 'Candidate watch selection');
|
|
15
|
+
if (!raw ||
|
|
16
|
+
raw.schema !== CANDIDATE_WATCH_INPUT_SCHEMA ||
|
|
17
|
+
raw.version !== CANDIDATE_WATCH_INPUT_VERSION ||
|
|
18
|
+
Object.keys(raw).some((key) => !['schema', 'version', 'watchlists', 'selection'].includes(key)))
|
|
19
|
+
throw new TypeError('Use candidate-watch-input schema 1 with watchlists and an explicit selection.');
|
|
20
|
+
const selection = ordinaryWorkspaceRecord(raw.selection, 'Candidate watch selection');
|
|
21
|
+
if (!selection ||
|
|
22
|
+
Object.keys(selection).some((key) => ![
|
|
23
|
+
'name',
|
|
24
|
+
'candidates',
|
|
25
|
+
'brandProfileId',
|
|
26
|
+
'priority',
|
|
27
|
+
'reason',
|
|
28
|
+
'reviewDueAt',
|
|
29
|
+
'replaceExistingContext',
|
|
30
|
+
].includes(key)))
|
|
31
|
+
throw new TypeError('The candidate selection contains undeclared fields.');
|
|
32
|
+
const local = raw.watchlists === null ? {} : mergeWatchlistStores({}, raw.watchlists).watchlists;
|
|
33
|
+
planCandidateWatchHandoff(local, selection);
|
|
34
|
+
return raw;
|
|
35
|
+
}
|
|
36
|
+
export function reviewCandidateWatchInput(input, operation, now) {
|
|
37
|
+
const raw = parseCandidateWatchInput(input), selection = raw.selection;
|
|
38
|
+
const local = raw.watchlists === null ? {} : mergeWatchlistStores({}, raw.watchlists).watchlists;
|
|
39
|
+
if (operation === 'plan')
|
|
40
|
+
return planCandidateWatchHandoff(local, selection);
|
|
41
|
+
const result = applyCandidateWatchHandoff(local, selection, now);
|
|
42
|
+
return buildWatchlistExport(result.watchlists, now);
|
|
43
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {} from "./command-argument-grammar.mjs";
|
|
2
|
+
import { CliUsageError } from "./errors.mjs";
|
|
3
|
+
import {} from "./fail-policy.mjs";
|
|
4
|
+
import { commandDefaultNumber } from "./command-reference.mjs";
|
|
5
|
+
import { WORKFLOW_COMMAND_DEFINITIONS } from "./workflow-command-definitions.mjs";
|
|
6
|
+
import { parseDomainFeedSelectors } from "./domain-feed.mjs";
|
|
7
|
+
import { terminalOptions, jsonOutput, failPolicies, parseOutput, } from "./argument-values.mjs";
|
|
8
|
+
function parseDomainFeedArguments(parsed) {
|
|
9
|
+
const operation = parsed.positionalValue('operation');
|
|
10
|
+
const contextSource = parsed.positionalValue('context');
|
|
11
|
+
if ((operation === 'watch-input') !== Boolean(contextSource))
|
|
12
|
+
throw new CliUsageError('watch-input requires one existing candidate-watch-input context file; review does not accept a context file.');
|
|
13
|
+
return { action: 'domain-feed', operation, feedId: parsed.positionalValue('feed'), source: parsed.positionalValue('source'), contextSource,
|
|
14
|
+
selection: parseDomainFeedSelectors(parsed.optionValues('--select')), output: jsonOutput(parsed), ...terminalOptions(parsed) };
|
|
15
|
+
}
|
|
16
|
+
function parseWatchlistReviewArguments(parsed) {
|
|
17
|
+
return { action: 'watchlist-review', operation: parsed.positionalValue('operation'), source: parsed.positionalValue('source'), output: jsonOutput(parsed), ...terminalOptions(parsed) };
|
|
18
|
+
}
|
|
19
|
+
function parseMonitorOnceArguments(parsed) {
|
|
20
|
+
const selectedFailPolicies = failPolicies(parsed, 'monitor-once');
|
|
21
|
+
return {
|
|
22
|
+
action: 'monitor-once',
|
|
23
|
+
source: parsed.positionalValue('source'),
|
|
24
|
+
previousSource: parsed.optionValue('--previous'),
|
|
25
|
+
output: parseOutput(parsed, WORKFLOW_COMMAND_DEFINITIONS['monitor-once']),
|
|
26
|
+
limit: parsed.integerOption('--limit') ?? commandDefaultNumber('monitor-once', '--limit'),
|
|
27
|
+
concurrency: parsed.integerOption('--concurrency') ??
|
|
28
|
+
commandDefaultNumber('monitor-once', '--concurrency'),
|
|
29
|
+
...terminalOptions(parsed),
|
|
30
|
+
...(selectedFailPolicies ? { failOn: selectedFailPolicies } : {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function parseWorkflowPlanArguments(parsed) {
|
|
34
|
+
const options = { output: jsonOutput(parsed), ...terminalOptions(parsed) };
|
|
35
|
+
if (parsed.hasOption('--list')) {
|
|
36
|
+
if (parsed.allPositionals.length > 0)
|
|
37
|
+
throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
|
|
38
|
+
return { action: 'workflow-plan', discovery: 'list', ...options };
|
|
39
|
+
}
|
|
40
|
+
if (parsed.hasOption('--explain')) {
|
|
41
|
+
if (parsed.allPositionals.length > 0)
|
|
42
|
+
throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
|
|
43
|
+
return {
|
|
44
|
+
action: 'workflow-plan',
|
|
45
|
+
discovery: 'explain',
|
|
46
|
+
recipe: parsed.optionValue('--explain'),
|
|
47
|
+
...options,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (parsed.allPositionals.length !== 2) {
|
|
51
|
+
throw new CliUsageError('workflow-plan requires one fixed recipe and one subject, --list, or --explain <recipe>.');
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
action: 'workflow-plan',
|
|
55
|
+
recipe: parsed.positionalValue('recipe'),
|
|
56
|
+
subject: parsed.positionalValue('subject'),
|
|
57
|
+
...options,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function parseWorkflowRunArguments(parsed) {
|
|
61
|
+
const artifactBindings = parsed.optionValues('--use-artifact').map((value) => {
|
|
62
|
+
const match = /^([a-z0-9]+(?:-[a-z0-9]+)*):([1-9][0-9]?)=([a-z0-9]+(?:-[a-z0-9]+)*)$/u.exec(value);
|
|
63
|
+
if (!match)
|
|
64
|
+
throw new CliUsageError('--use-artifact requires <step-id>:<input-number>=<earlier-step-id>.');
|
|
65
|
+
return Object.freeze({ stepId: match[1], input: Number(match[2]), sourceStepId: match[3] });
|
|
66
|
+
});
|
|
67
|
+
const selections = parsed.optionValues('--select').map((value) => {
|
|
68
|
+
const separator = value.indexOf('=');
|
|
69
|
+
const stepId = separator === -1 ? '' : value.slice(0, separator);
|
|
70
|
+
const selection = separator === -1 ? '' : value.slice(separator + 1);
|
|
71
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(stepId) || !selection.trim()) {
|
|
72
|
+
throw new CliUsageError('--select requires <step-id>=<path-or-value>.');
|
|
73
|
+
}
|
|
74
|
+
if (selection.startsWith('-')) {
|
|
75
|
+
throw new CliUsageError('--select values cannot start with a hyphen; prefix a file path with ./ when needed.');
|
|
76
|
+
}
|
|
77
|
+
return Object.freeze({ stepId, value: selection });
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
action: 'workflow-run',
|
|
81
|
+
recipe: parsed.positionalValue('recipe'),
|
|
82
|
+
subject: parsed.positionalValue('subject'),
|
|
83
|
+
resumeSource: parsed.optionValue('--resume'),
|
|
84
|
+
selections: Object.freeze(selections),
|
|
85
|
+
artifactBindings: Object.freeze(artifactBindings),
|
|
86
|
+
confirmedReviews: Object.freeze(parsed.optionValues('--confirm-review')),
|
|
87
|
+
approveNetwork: parsed.hasOption('--approve-network'),
|
|
88
|
+
interactive: parsed.hasOption('--interactive'),
|
|
89
|
+
...(parsed.hasOption('--preview') ? { preview: true } : {}),
|
|
90
|
+
output: jsonOutput(parsed),
|
|
91
|
+
...terminalOptions(parsed),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export const WORKFLOW_ARGUMENT_PARSERS = Object.freeze({
|
|
95
|
+
'domain-feed': parseDomainFeedArguments,
|
|
96
|
+
'watchlist-review': parseWatchlistReviewArguments,
|
|
97
|
+
'monitor-once': parseMonitorOnceArguments,
|
|
98
|
+
'workflow-plan': parseWorkflowPlanArguments,
|
|
99
|
+
'workflow-run': parseWorkflowRunArguments,
|
|
100
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { INVESTIGATION_PLAN_RECIPES, RUNNABLE_INVESTIGATION_PLAN_RECIPES } from "./investigation-recipes.mjs";
|
|
2
|
+
import { positional, OPTIONAL_FILE_POSITIONAL, constraint, commandSeed, integer, BASE_INTEGER_RANGE, file, optionDefinition } from "./command-definition.mjs";
|
|
3
|
+
import { DOMAIN_FEED_CATALOGUE } from "../packages/monitoring/domain-feed.mjs";
|
|
4
|
+
export const WORKFLOW_COMMAND_DEFINITIONS = Object.freeze({
|
|
5
|
+
'domain-feed': commandSeed({
|
|
6
|
+
reference: { description: 'Review exact hosts or literal terms in a selected local plain-domain feed.',
|
|
7
|
+
example: 'whoisleuth domain-feed review tif-mini feed.txt --select host:candidate.example --json',
|
|
8
|
+
boundary: 'Always available offline. Reads one explicit local regular file; never downloads a feed, performs a Lookup, changes scores, retains candidates or enables a schedule. Streams at most 256 MiB and 10 million physical rows with 1,024-byte lines and a ten-minute deadline; retains at most 200 matched hosts. Repeat --select host:<hostname>, term:<literal> or exclude:<literal> for at most 200 exact hosts, 20 positive and 20 negative literal terms of 3–80 characters. A negative match vetoes even an exact host before the match bound; at least one positive term or exact host is required. Matching uses canonical ASCII/punycode substrings, never token boundaries, parent equivalence, typo/confusable expansion or regular expressions. Review emits the full raw-byte digest, separate declared file/local clocks, unknown per-host clocks and omissions. watch-input also requires a candidate-watch-input v1 context file with an empty candidates array, and emits that existing format for a separate watchlist-review plan/export. Inclusion is not a maliciousness, activity, ownership or availability verdict.' },
|
|
9
|
+
collection: { scope: 'Local raw file: 256 MiB, 10 million physical rows, 1,024 bytes per line, ten minutes; 200 retained hosts, 20 positive and 20 negative literal terms, no requests.' },
|
|
10
|
+
summary: 'Review a selected local plain-domain feed', options: ['--select', '--json', '--quiet', '--no-color'],
|
|
11
|
+
optionOverrides: { '--select': optionDefinition('text', 'Select host:<exact-hostname>, term:<literal-text> or exclude:<literal-text>; repeat for separate explicit selectors.', { occurrence: 'repeatable', acceptsOptionLikeValue: true }) },
|
|
12
|
+
positionals: Object.freeze([positional('operation', 'enum', 1, 1, ['review', 'watch-input']),
|
|
13
|
+
positional('feed', 'enum', 1, 1, DOMAIN_FEED_CATALOGUE.map(feed => feed.id)), positional('source', 'file', 1, 1), positional('context', 'file', 0, 1)]),
|
|
14
|
+
constraints: Object.freeze([constraint({ kind: 'required', options: ['--select'] })]),
|
|
15
|
+
handlerOwner: 'workflow', networkEffect: 'offline', common: false,
|
|
16
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.candidate-watch-input']), primaryArtefacts: Object.freeze(['Bounded feed inclusion review', 'Explicit candidate watch selection']),
|
|
17
|
+
planSupport: true, additionalOutputFormats: Object.freeze([]), bootstrapProfile: 'allowed',
|
|
18
|
+
}),
|
|
19
|
+
'watchlist-review': commandSeed({
|
|
20
|
+
reference: { description: 'Preview or export an explicit candidate selection to a local watchlist.', example: 'whoisleuth watchlist-review plan selection.json --json',
|
|
21
|
+
boundary: 'Reads bounded local JSON only. Plan shows exact domains, duplicates, per-domain outcomes, destination and review priority with zero additional requests. Export emits Watchlist schema 5 without creating a scan or baseline. Neither mode enables collection or scheduling.' },
|
|
22
|
+
collection: { scope: 'At most 2,000 selected candidate domains; no target requests.' },
|
|
23
|
+
summary: 'Review a local candidate watchlist handoff', options: ['--json', '--quiet', '--no-color'],
|
|
24
|
+
positionals: Object.freeze([positional('operation', 'enum', 1, 1, ['plan', 'export']), positional('source', 'file', 0, 1, [], 'argv_or_stdin')]),
|
|
25
|
+
constraints: Object.freeze([]), handlerOwner: 'workflow', networkEffect: 'offline', common: false,
|
|
26
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.candidate-watch-input', 'whoisleuth.watchlists']), primaryArtefacts: Object.freeze(['Exact candidate handoff plan', 'Local watchlist export']),
|
|
27
|
+
planSupport: true, additionalOutputFormats: Object.freeze([]), bootstrapProfile: 'allowed',
|
|
28
|
+
}),
|
|
29
|
+
"monitor-once": commandSeed({
|
|
30
|
+
reference: {
|
|
31
|
+
description: 'Collect one bounded owned-domain review and compare it with an optional prior checkpoint.',
|
|
32
|
+
example: 'whoisleuth monitor-once manifest.json --previous previous.json --json --output next.json',
|
|
33
|
+
boundary: 'This is an operator-scheduled one-shot collection, not a daemon. It caps targets and concurrency, retains normalised observations, and never changes domain configuration.',
|
|
34
|
+
},
|
|
35
|
+
collection: { scope: 'Runs deep collection for at most 20 manifest domains with concurrency capped at 3.' },
|
|
36
|
+
summary: 'Run one bounded domain control review',
|
|
37
|
+
options: ['--previous', '--limit', '--concurrency', '--fail-on', '--json', '--junit', '--quiet', '--no-color'],
|
|
38
|
+
optionOverrides: {
|
|
39
|
+
'--concurrency': integer('Set the maximum number of concurrent collection tasks.', () => Object.freeze([BASE_INTEGER_RANGE(1, 3)]), () => 2),
|
|
40
|
+
},
|
|
41
|
+
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
42
|
+
constraints: Object.freeze([
|
|
43
|
+
constraint({ kind: 'mutually_exclusive', options: ['--json', '--junit'] }),
|
|
44
|
+
]),
|
|
45
|
+
handlerOwner: 'workflow',
|
|
46
|
+
networkEffect: 'always_network',
|
|
47
|
+
common: false,
|
|
48
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.cli.domain-control-monitor', 'whoisleuth.domain-control-flight-recorder.input']),
|
|
49
|
+
primaryArtefacts: Object.freeze([]),
|
|
50
|
+
planSupport: false,
|
|
51
|
+
additionalOutputFormats: Object.freeze([]),
|
|
52
|
+
bootstrapProfile: 'allowed',
|
|
53
|
+
}),
|
|
54
|
+
"workflow-plan": commandSeed({
|
|
55
|
+
reference: {
|
|
56
|
+
description: 'Build a fixed domain-investigation plan from existing bounded CLI commands.',
|
|
57
|
+
example: 'whoisleuth workflow-plan domain-triage example.test --json',
|
|
58
|
+
boundary: 'Planning is offline and plan-only. It does not execute commands, expand placeholders, read files, make requests, or submit evidence.',
|
|
59
|
+
},
|
|
60
|
+
collection: { scope: 'Builds a fixed typed recipe and executes none of its network or file steps.' },
|
|
61
|
+
summary: 'Plan a fixed investigation recipe',
|
|
62
|
+
options: ['--list', '--explain', '--json', '--quiet', '--no-color'],
|
|
63
|
+
positionals: Object.freeze([
|
|
64
|
+
positional('recipe', 'enum', 0, 1, INVESTIGATION_PLAN_RECIPES),
|
|
65
|
+
positional('subject', 'text', 0, 1),
|
|
66
|
+
]),
|
|
67
|
+
constraints: Object.freeze([
|
|
68
|
+
constraint({ kind: 'mutually_exclusive', options: ['--list', '--explain'] }),
|
|
69
|
+
]),
|
|
70
|
+
handlerOwner: 'workflow',
|
|
71
|
+
networkEffect: 'offline',
|
|
72
|
+
common: true,
|
|
73
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.cli.investigation-plan', 'whoisleuth.cli.workflow-recipe-catalogue']),
|
|
74
|
+
primaryArtefacts: Object.freeze(['Plan-only workflow document']),
|
|
75
|
+
planSupport: true,
|
|
76
|
+
additionalOutputFormats: Object.freeze([]),
|
|
77
|
+
bootstrapProfile: 'allowed',
|
|
78
|
+
}),
|
|
79
|
+
"workflow-run": commandSeed({
|
|
80
|
+
reference: {
|
|
81
|
+
description: 'Execute approved steps from a fixed investigation recipe and emit a resumable checkpoint.',
|
|
82
|
+
example: 'whoisleuth workflow-run domain-triage example.test --approve-network --json --output run.json',
|
|
83
|
+
boundary: '--preview validates the checkpoint and shows retained outputs, unresolved inputs and remaining approvals without executing or writing. It cannot be combined with approval, interactive, quiet or output-file flags. Only installed recipe commands can run. Network steps require explicit approval for each invocation. New runs connect compatible earlier outputs using the recipe defaults. Use --use-artifact <step-id>:<input-number>=<earlier-step-id> to override a connection; input numbers start at 1. Repeat --select for remaining placeholders in order, or supply every input for a step to replace its connections with files. Values stay literal and cannot start with a hyphen or invoke a shell. Optional --interactive prompts on terminal stderr for missing inputs; a blank answer pauses. It grants neither network approval nor human-review confirmation. A step declaring human review still requires --confirm-review <step-id> for that invocation. Checkpoints do not grant later approvals. Resumes preserve recorded connections. Content digests identify retained output, not authenticity or freshness. Partial collections pause for review and are not recollected on resume; failed validation or export steps remain retryable. Diagnostics go to stderr. File output holds exclusive adjacent locks and refuses concurrently changed state files.',
|
|
84
|
+
},
|
|
85
|
+
collection: { scope: 'Runs only fixed-recipe steps; network collection requires --approve-network and unresolved analyst selections pause.' },
|
|
86
|
+
summary: 'Execute approved fixed-recipe steps',
|
|
87
|
+
options: ['--select', '--use-artifact', '--confirm-review', '--approve-network', '--resume', '--interactive', '--preview', '--json', '--quiet', '--no-color'],
|
|
88
|
+
optionOverrides: { '--resume': file('Resume the selected workflow checkpoint; approvals must be supplied again.') },
|
|
89
|
+
positionals: Object.freeze([
|
|
90
|
+
positional('recipe', 'enum', 1, 1, RUNNABLE_INVESTIGATION_PLAN_RECIPES),
|
|
91
|
+
positional('subject', 'text', 1, 1),
|
|
92
|
+
]),
|
|
93
|
+
constraints: Object.freeze([constraint({ kind: 'excludes_all', option: '--preview', excludedOptions: ['--approve-network', '--confirm-review', '--interactive', '--output', '--force', '--quiet'] })]),
|
|
94
|
+
handlerOwner: 'workflow',
|
|
95
|
+
networkEffect: 'conditional_network',
|
|
96
|
+
common: false,
|
|
97
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.cli.investigation-run', 'whoisleuth.cli.investigation-preview']),
|
|
98
|
+
primaryArtefacts: Object.freeze(['Resumable workflow state', 'Offline resume preview']),
|
|
99
|
+
planSupport: false,
|
|
100
|
+
additionalOutputFormats: Object.freeze([]),
|
|
101
|
+
bootstrapProfile: 'allowed',
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
@@ -5,7 +5,7 @@ import EXIT_CODES from "./exit-codes.mjs";
|
|
|
5
5
|
import { evaluateCliFailPolicies, formatFailPolicyNotice } from "./fail-policy.mjs";
|
|
6
6
|
import { formatJsonDocument } from "./formatters/json.mjs";
|
|
7
7
|
import { buildInvestigationPlan, buildWorkflowRecipeCatalogue, formatInvestigationPlan, formatWorkflowRecipeCatalogue, } from "./investigation-plan.mjs";
|
|
8
|
-
import { MAX_INVESTIGATION_RUN_BYTES, formatInvestigationRun, investigationRunExitCode, runInvestigationRecipe, } from "./investigation-run.mjs";
|
|
8
|
+
import { MAX_INVESTIGATION_RUN_BYTES, formatInvestigationRun, investigationRunExitCode, runInvestigationRecipe, previewInvestigationRecipe, formatInvestigationPreview, } from "./investigation-run.mjs";
|
|
9
9
|
import { MAX_OFFLINE_EVIDENCE_INPUT_BYTES } from "./offline-evidence-review.mjs";
|
|
10
10
|
import { MAX_OFFLINE_ARTIFACT_BYTES } from "./artifact-verify.mjs";
|
|
11
11
|
import { MAX_RETAINED_ARTIFACT_DIFF_BYTES } from "./retained-artifact-diff.mjs";
|
|
@@ -16,7 +16,37 @@ import { MAX_SHARING_REVIEW_BYTES } from "./sharing-review.mjs";
|
|
|
16
16
|
import { createBufferedOutput } from "./output-file.mjs";
|
|
17
17
|
import { boundedInteractiveAnswer, canReadInteractiveLine, readBoundedInteractiveLine } from "./terminal-input.mjs";
|
|
18
18
|
import { WORKFLOW_INLINE_COMMANDS } from "./inline-command-families.mjs";
|
|
19
|
+
import { reviewCandidateWatchInput, parseCandidateWatchInput } from "./watchlist-review.mjs";
|
|
20
|
+
import { readDomainFeedFile } from "./domain-feed.mjs";
|
|
21
|
+
import { scanDomainFeed, buildDomainFeedWatchInput, normalizeDomainFeedSelection } from "../packages/monitoring/domain-feed.mjs";
|
|
22
|
+
import { MAX_WATCHLIST_IMPORT_BYTES } from "../packages/contracts/workspace-portability.mjs";
|
|
19
23
|
import { runDiscriminatedCommandHandler } from "./discriminated-command-handlers.mjs";
|
|
24
|
+
async function runWatchlistReviewCommand(args, dependencies, context) {
|
|
25
|
+
const raw = dependencies.readArtifactInput ? await dependencies.readArtifactInput(args.source) : await context.readInput(args.source, MAX_WATCHLIST_IMPORT_BYTES, 'Candidate watch selection');
|
|
26
|
+
const document = reviewCandidateWatchInput(raw, args.operation, context.now());
|
|
27
|
+
if (!args.quiet)
|
|
28
|
+
context.writeStdout(formatJsonDocument(document));
|
|
29
|
+
return EXIT_CODES.SUCCESS;
|
|
30
|
+
}
|
|
31
|
+
async function runDomainFeedCommand(args, dependencies, context) {
|
|
32
|
+
context.setFailureLabel('Offline domain-feed review');
|
|
33
|
+
const handoff = args.contextSource ? parseCandidateWatchInput(dependencies.readArtifactInput
|
|
34
|
+
? await dependencies.readArtifactInput(args.contextSource)
|
|
35
|
+
: await context.readInput(args.contextSource, MAX_WATCHLIST_IMPORT_BYTES, 'Candidate watch context')) : null;
|
|
36
|
+
const watchSelection = handoff?.selection;
|
|
37
|
+
if (watchSelection && watchSelection.candidates.length)
|
|
38
|
+
throw new CliUsageError('The watch-input context must have an empty candidates array; only reviewed feed matches may be nominated.');
|
|
39
|
+
const selection = normalizeDomainFeedSelection({ ...args.selection, brandProfileId: watchSelection?.brandProfileId ?? null });
|
|
40
|
+
const review = await scanDomainFeed((dependencies.readDomainFeedInput ?? readDomainFeedFile)(args.source, dependencies.signal), {
|
|
41
|
+
feedId: args.feedId, selection, importedAt: context.now(), ...(dependencies.signal ? { signal: dependencies.signal } : {}),
|
|
42
|
+
});
|
|
43
|
+
const document = watchSelection && handoff
|
|
44
|
+
? buildDomainFeedWatchInput(review, review.matches.map(match => match.domain), watchSelection, handoff.watchlists)
|
|
45
|
+
: review;
|
|
46
|
+
if (!args.quiet)
|
|
47
|
+
context.writeStdout(formatJsonDocument(document));
|
|
48
|
+
return EXIT_CODES.SUCCESS;
|
|
49
|
+
}
|
|
20
50
|
function boundWorkflowInputs(command, inputs, dependencies, context) {
|
|
21
51
|
if (!inputs.size)
|
|
22
52
|
return {};
|
|
@@ -125,6 +155,13 @@ async function runWorkflowRecipeCommand(args, dependencies, context) {
|
|
|
125
155
|
throw new CliUsageError(`Could not read investigation resume state: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
126
156
|
}
|
|
127
157
|
}
|
|
158
|
+
if (args.preview) {
|
|
159
|
+
const preview = previewInvestigationRecipe(args.recipe, args.subject, {
|
|
160
|
+
resumeInput, selections: args.selections, artifactBindings: args.artifactBindings, generatedAt: context.now(),
|
|
161
|
+
});
|
|
162
|
+
context.writeStdout(args.output === 'json' ? formatJsonDocument(preview) : context.terminal(formatInvestigationPreview(preview), args.color));
|
|
163
|
+
return EXIT_CODES.SUCCESS;
|
|
164
|
+
}
|
|
128
165
|
const document = await runInvestigationRecipe(args.recipe, args.subject, {
|
|
129
166
|
approveNetwork: args.approveNetwork,
|
|
130
167
|
selections: args.selections,
|
|
@@ -174,6 +211,8 @@ async function runWorkflowRecipeCommand(args, dependencies, context) {
|
|
|
174
211
|
return investigationRunExitCode(document);
|
|
175
212
|
}
|
|
176
213
|
const WORKFLOW_COMMAND_HANDLERS = Object.freeze({
|
|
214
|
+
'domain-feed': runDomainFeedCommand,
|
|
215
|
+
'watchlist-review': runWatchlistReviewCommand,
|
|
177
216
|
'monitor-once': runMonitorOnceCommand,
|
|
178
217
|
'workflow-plan': runWorkflowPlanCommand,
|
|
179
218
|
'workflow-run': runWorkflowRecipeCommand,
|
package/docs/cli-reference.md
CHANGED
|
@@ -10,6 +10,11 @@ This page records the common interfaces and the boundaries that matter across
|
|
|
10
10
|
commands. The generated [privacy/data-flow catalogue](https://github.com/slicedearth/whoisleuth/blob/main/docs/privacy-data-flow-catalogue.md)
|
|
11
11
|
contains the exhaustive recipient, retention and export metadata.
|
|
12
12
|
|
|
13
|
+
For an explicitly selected local IP-location database, see the
|
|
14
|
+
[MMDB query and compatibility guide](cli.md#local-ip-location-database-review).
|
|
15
|
+
Current reviews retain exact-byte identity and intrinsic metadata, and require
|
|
16
|
+
an analyst-justified freshness policy. No database is downloaded or updated.
|
|
17
|
+
|
|
13
18
|
## Installation
|
|
14
19
|
|
|
15
20
|
Use the [installation guide](cli.md#installation) for runtime requirements,
|
|
@@ -61,6 +66,26 @@ Optional browser providers are not implicit CLI actions.
|
|
|
61
66
|
The [message-header review](cli.md#message-header-review) is offline and reports
|
|
62
67
|
publisher claims, not independent DNS or cryptographic validation.
|
|
63
68
|
|
|
69
|
+
For copied-content or credential-abuse review, keep the supplied reference,
|
|
70
|
+
candidate, form destination and deliberately observed navigation separately
|
|
71
|
+
attributed. A similar page without a logo or brand name can still merit manual
|
|
72
|
+
evidence review; correspondence does not establish authorship, rights authority
|
|
73
|
+
or maliciousness. Missing optional capture or comparison evidence does not
|
|
74
|
+
prevent ordinary Case pins and conclusions. Installed command help remains the
|
|
75
|
+
authority for offline file inputs and explicit collection plans.
|
|
76
|
+
|
|
77
|
+
The [reporting guide](https://www.whoisleuth.com/resources/reporting-and-takedown-guidance)
|
|
78
|
+
uses the same reviewed official-route catalogue as the Console for social
|
|
79
|
+
content, storefronts, app listings, hosted files/forms and advertising. Verify
|
|
80
|
+
the exact object, current route, reporter authority and disclosure before any
|
|
81
|
+
manual submission. Fraud, order complaints and rights notices are different
|
|
82
|
+
processes; search or app delisting is not source-content removal. Formal domain
|
|
83
|
+
disputes and copyright procedures have their own criteria, remedies and
|
|
84
|
+
jurisdiction-specific rules, not a universal waiting period. CLI preparation
|
|
85
|
+
does not submit a report or make a legal declaration. Preserve original delivery
|
|
86
|
+
references when preparing additional evidence, and recheck each response scope
|
|
87
|
+
independently rather than treating a closed ticket as remediation.
|
|
88
|
+
|
|
64
89
|
`dnssec-validate` and `mail-transport` are isolated authorised actions. Both
|
|
65
90
|
require a selected literal public resolver, a local trust-anchor document and
|
|
66
91
|
`--owned-or-authorized`; mail transport also requires `--active-probe`. Mail
|
|
@@ -194,6 +219,8 @@ own retention policy. No automatic key discovery, key storage or trust refresh o
|
|
|
194
219
|
evidence. They do not tune the running model, change infrastructure or turn an
|
|
195
220
|
analyst label into observed truth.
|
|
196
221
|
- `workflow-plan` lists fixed installed recipes without executing them.
|
|
222
|
+
`workflow-run --preview` validates a selected checkpoint and explains remaining
|
|
223
|
+
inputs and approvals offline, without running steps or rewriting the checkpoint.
|
|
197
224
|
`workflow-run` executes only installed steps, requires approval for network
|
|
198
225
|
work and pauses at unresolved analyst selections. Repeat
|
|
199
226
|
`--select <step-id>=<path-or-value>` for remaining placeholders in order, or bind
|