@slicedearth/whoisleuth-cli 1.45.0 → 2.0.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 +7 -3
- package/NOTICE +5 -4
- package/README.md +74 -124
- package/SECURITY.md +6 -3
- package/bin/whoisleuth.mjs +7 -4
- package/cli/archive-inspect.mjs +28 -10
- package/cli/arguments.mjs +450 -173
- package/cli/artifact-structure.mjs +2507 -0
- package/cli/artifact-verify.mjs +286 -64
- package/cli/bounded-json.mjs +1 -0
- package/cli/bulk-checkpoint.mjs +35 -23
- package/cli/bulk-command-runner.mjs +2 -5
- package/cli/bulk.mjs +9 -2
- package/cli/case-pack.mjs +435 -46
- package/cli/ci-report.mjs +6 -1
- package/cli/collection-preflight.mjs +2 -2
- package/cli/command-catalogue.mjs +12 -3
- package/cli/command-reference.mjs +948 -106
- package/cli/completion.mjs +504 -218
- package/cli/config-profile.mjs +13 -3
- package/cli/ct-event-intake.mjs +32 -2
- package/cli/discover.mjs +7 -1
- package/cli/discovery-observation-snapshot.mjs +24 -28
- package/cli/discovery-scan-command-runner.mjs +13 -14
- package/cli/discovery-scan.mjs +17 -3
- package/cli/discovery-snapshot.mjs +15 -7
- package/cli/discovery-workflow.mjs +2 -3
- package/cli/doctor.mjs +2 -2
- package/cli/domain-control-monitor.mjs +215 -36
- package/cli/domain-control-observations.mjs +34 -17
- package/cli/errors.mjs +8 -3
- package/cli/evidence-command-runner.mjs +17 -11
- package/cli/evidence-signing.mjs +68 -50
- package/cli/export-evidence.mjs +11 -4
- package/cli/external-observation-mapping.mjs +3 -3
- package/cli/formatters/html.mjs +3 -3
- package/cli/formatters/json.mjs +20 -32
- package/cli/formatters/terminal.mjs +586 -22
- package/cli/input.mjs +62 -0
- package/cli/interactive-launcher.mjs +226 -0
- package/cli/interchange-report.mjs +226 -0
- package/cli/investigation-manifest.mjs +16 -17
- package/cli/investigation-plan.mjs +167 -16
- package/cli/investigation-run.mjs +80 -24
- package/cli/local-mmdb-review.mjs +45 -14
- package/cli/lookalike-calibration.mjs +3 -1
- package/cli/lookup-brief.mjs +13 -8
- package/cli/lookup-browser.mjs +844 -0
- package/cli/lookup-command-runner.mjs +96 -33
- package/cli/lookup-diff.mjs +11 -2
- package/cli/lookup-plan.mjs +1 -0
- package/cli/mail-review.mjs +206 -94
- package/cli/manual.mjs +5 -5
- package/cli/network-command-runner.mjs +78 -0
- package/cli/offline-evidence-review.mjs +45 -14
- package/cli/open-asset-model-bridge.mjs +1 -1
- package/cli/output-file.mjs +70 -14
- package/cli/page-compare.mjs +18 -10
- package/cli/progress-events.mjs +2 -1
- package/cli/progress.mjs +3 -3
- package/cli/registry-cohort.mjs +343 -61
- package/cli/registry-doctor.mjs +67 -28
- package/cli/registry-support.mjs +2 -1
- package/cli/retained-artifact-diff.mjs +375 -0
- package/cli/risk-calibration.mjs +89 -40
- package/cli/runner.mjs +322 -246
- package/cli/saved-lookup.mjs +439 -49
- package/cli/sharing-review.mjs +18 -7
- package/cli/source-reliability.mjs +75 -36
- package/cli/terminal-presentation.mjs +43 -18
- package/docs/cli-reference.md +125 -1334
- package/docs/cli.md +87 -236
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -0
- package/frontend/src/lib/analysis/domain-control-records.js +3 -0
- package/lib/application-version.mjs +3 -0
- package/lib/availability.mjs +72 -52
- package/lib/bounded-contract-normalizers.mjs +2 -4
- package/lib/bounded-file.mjs +91 -4
- package/lib/bounded-json.mjs +287 -0
- package/lib/browser-library-profile.mjs +1 -1
- package/lib/certificate-extension-profile.mjs +2 -1
- package/lib/classify.mjs +56 -5
- package/lib/client-behavior-profile.mjs +1 -1
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/cryptographic-assurance.mjs +173 -0
- package/lib/ct-query.mjs +3 -3
- package/lib/ct-search.mjs +120 -64
- package/lib/distributed-operation-budget.mjs +4 -2
- package/lib/dns-delegation-health.mjs +9 -6
- package/lib/dns-intelligence.mjs +4 -3
- package/lib/dns-resolver-selection.mjs +6 -4
- package/lib/dnssec-chain-validation.mjs +1052 -0
- package/lib/dnssec-evidence-validation.mjs +23 -10
- package/lib/domain-assurance.mjs +12 -12
- package/lib/domain-change-packet.mjs +7 -5
- package/lib/domain-control-flight-recorder.mjs +442 -78
- package/lib/domain-control-manifest.mjs +314 -171
- package/lib/domain-posture-analysis.mjs +5 -2
- package/lib/domain-posture.mjs +7 -4
- package/lib/encrypted-dns-contract.mjs +2 -3
- package/lib/evidence-export.mjs +1243 -88
- package/lib/evidence-report-markdown.mjs +3 -0
- package/lib/evidence-report.mjs +5 -0
- package/lib/external-intelligence-risk.mjs +26 -10
- package/lib/favicon.mjs +8 -5
- package/lib/feature-policy.mjs +1 -1
- package/lib/homepage-metadata-contract.mjs +290 -0
- package/lib/hostname.mjs +1 -1
- package/lib/html-signals.mjs +89 -5
- package/lib/http-evidence-bounds.mjs +3 -0
- package/lib/http-intelligence.mjs +332 -6
- package/lib/interchange-fidelity-registry.mjs +124 -0
- package/lib/local-geoip-evidence.mjs +2 -1
- package/lib/lookup-cache.mjs +6 -4
- package/lib/lookup-network-evidence-bounds.mjs +9 -0
- package/lib/lookup.mjs +27 -4
- package/lib/observed-network-context.mjs +1 -1
- package/lib/outbound-identity.mjs +2 -3
- package/lib/page-fingerprints.mjs +2 -2
- package/lib/page-role-profile.mjs +1 -1
- package/lib/perceptual-hash.mjs +12 -4
- package/lib/portable-generator.mjs +29 -0
- package/lib/provider-policy-admission.mjs +11 -4
- package/lib/rdap-bootstrap.mjs +76 -16
- package/lib/rdap-client.mjs +20 -12
- package/lib/rdap-normalization.mjs +324 -80
- package/lib/rdap-registrar.mjs +5 -1
- package/lib/rdap-transport.mjs +2 -2
- package/lib/rdap-validation.mjs +65 -14
- package/lib/rdap.mjs +2 -2
- package/lib/registrable-domain.mjs +36 -0
- package/lib/registry-capabilities.mjs +3 -0
- package/lib/registry-capability-catalogue.mjs +12 -2
- package/lib/registry-comparison.mjs +28 -11
- package/lib/registry-insights.mjs +7 -0
- package/lib/response-policy.mjs +27 -4
- package/lib/risk-calibration-summary.mjs +340 -0
- package/lib/risk-scoring.mjs +4 -3
- package/lib/rpki-evidence.mjs +9 -8
- package/lib/safe-fetch.mjs +121 -50
- package/lib/security-txt.mjs +27 -4
- package/lib/service-binding-dns.mjs +6 -1
- package/lib/smtp-transport-review.mjs +787 -0
- package/lib/sslbl-certificates.generated.mjs +136 -134
- package/lib/sslbl-intelligence.mjs +6 -8
- package/lib/static-html-analysis.mjs +301 -4
- package/lib/structured-data-identity.mjs +51 -19
- package/lib/threat-intelligence-fixtures.mjs +2 -4
- package/lib/threat-intelligence-manifests.mjs +2 -7
- package/lib/threat-intelligence-runtime.mjs +5 -15
- package/lib/threat-intelligence-types.mjs +21 -1
- package/lib/threatfox-intelligence.mjs +7 -3
- package/lib/tls-intelligence.mjs +15 -9
- package/lib/tlsa-evidence.mjs +37 -7
- package/lib/urlhaus-intelligence.mjs +4 -2
- package/lib/urlscan-intelligence.mjs +6 -5
- package/lib/website-security-posture.mjs +1 -1
- package/lib/website-technology.mjs +1 -1
- package/lib/whois-authority.mjs +43 -8
- package/lib/whois-chain.mjs +25 -3
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-transport.mjs +12 -3
- package/lib/zone-intent-review.mjs +1 -1
- package/package.json +12 -4
- package/packages/cases/case-brand-profile-references.mjs +85 -0
- package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +43 -14
- package/packages/cases/case-investigation-branch-model.mjs +132 -0
- package/packages/cases/case-migration-model.mjs +476 -0
- package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
- package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -19
- package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
- package/packages/cases/case-record-model.mjs +9 -0
- package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +106 -33
- package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +143 -16
- package/packages/cases/case-response-model.mjs +1604 -0
- package/packages/cases/case-response-packet.mjs +1312 -0
- package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
- package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
- package/packages/cases/opaque-reference-id.mjs +9 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +366 -0
- package/packages/comparison/comparison-ledger-contract.mjs +241 -0
- package/packages/comparison/comparison-ledger-json.mjs +80 -0
- package/packages/comparison/comparison-ledger-serialization.mjs +52 -0
- package/packages/comparison/favicon-similarity.mjs +78 -0
- package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
- package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
- package/packages/contracts/analyst-review-state-contract.mjs +61 -0
- package/packages/contracts/analyst-review-state.mjs +50 -0
- package/packages/contracts/case-portability.mjs +1310 -0
- package/packages/contracts/cli-command-catalogue.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +394 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
- package/packages/contracts/domain-control-manifest.mjs +1027 -0
- package/packages/contracts/domain-control-monitor.mjs +310 -0
- package/packages/contracts/domain-control-review.mjs +828 -0
- package/packages/contracts/domain-name.mjs +4 -0
- package/packages/contracts/external-observation-interchange.mjs +224 -0
- package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
- package/packages/contracts/investigation-portability.mjs +331 -0
- package/packages/contracts/offline-comparison.mjs +160 -0
- package/packages/contracts/risk-calibration.mjs +1288 -0
- package/packages/contracts/schema-compatibility.mjs +71 -0
- package/packages/contracts/schema-lifecycle.mjs +1702 -0
- package/packages/contracts/sslbl-snapshot.mjs +17 -0
- package/packages/contracts/workspace-portability.mjs +1347 -0
- package/packages/evidence/artifact-integrity.mjs +65 -0
- package/packages/evidence/decision-fact.mjs +735 -0
- package/packages/evidence/domain-control-runtime.mjs +547 -0
- package/packages/evidence/domain-name.mjs +41 -0
- package/packages/evidence/observation.mjs +384 -0
- package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +86 -20
- package/packages/interchange/retained-text.mjs +8 -0
- package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +11 -8
- package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +28 -9
- package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +67 -18
- package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +10 -9
- package/packages/investigation/bulk-review-policy.mjs +1 -0
- package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +50 -18
- package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
- package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
- package/packages/investigation/lookup-claim-passport.mjs +232 -0
- package/packages/investigation/lookup-claim-readiness.mjs +287 -0
- package/packages/investigation/lookup-investigation-brief.mjs +172 -0
- package/packages/monitoring/analyst-review-state.mjs +680 -0
- package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +97 -50
- package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +15 -15
- package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +179 -39
- package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +12 -5
- package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
- package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
- package/packages/workspace/hostile-input.mjs +172 -0
- package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +51 -46
- package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +15 -15
- package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
- package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
- package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
- package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
- package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
- package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +18 -12
- package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
- package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +195 -84
- package/third-party-notices.txt +112 -5834
- package/frontend/src/lib/analysis/artifact-integrity.js +0 -21
- package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
- package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
- package/frontend/src/lib/analysis/case-migration-model.js +0 -253
- package/frontend/src/lib/analysis/case-record-model.js +0 -7
- package/frontend/src/lib/analysis/case-response-model.js +0 -669
- package/frontend/src/lib/analysis/case-response-packet.js +0 -573
- package/frontend/src/lib/analysis/lookup-decision-support.js +0 -487
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
- package/frontend/src/lib/analysis/lookup-presentation.js +0 -58
- package/frontend/src/lib/analysis/lookup-response.js +0 -2
- package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
- package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
- package/frontend/src/lib/analysis/scoring.js +0 -55
- package/frontend/src/lib/analysis/utils.js +0 -232
- package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
- package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
- package/frontend/src/lib/analysis/visualization-matrix.js +0 -88
- package/frontend/src/lib/analysis/visualization-models.js +0 -369
- package/frontend/src/lib/analysis/visualization-time-series.js +0 -144
- package/lib/bounded-json-response.mjs +0 -157
- package/lib/lookup-response-contract.mjs +0 -346
- package/lib/observation.mjs +0 -114
- /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
- /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
- /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
package/cli/arguments.mjs
CHANGED
|
@@ -1,68 +1,22 @@
|
|
|
1
1
|
import { parseInspectArchiveArguments, parseSignArtifactArguments, parseVerifySignatureArguments, } from "./evidence-command-arguments.mjs";
|
|
2
|
-
import { CliUsageError } from "./errors.mjs";
|
|
3
|
-
import { INVESTIGATION_PLAN_RECIPES, } from "./investigation-plan.mjs";
|
|
2
|
+
import { CliUsageError, hasUnsafeCliText } from "./errors.mjs";
|
|
3
|
+
import { INVESTIGATION_PLAN_RECIPES, RUNNABLE_INVESTIGATION_PLAN_RECIPES, } from "./investigation-plan.mjs";
|
|
4
4
|
import { parseCliFailPolicies } from "./fail-policy.mjs";
|
|
5
|
+
import { isDirectLookupTarget } from "../lib/classify.mjs";
|
|
6
|
+
import { CLI_COMMANDS, cliMetaActionForInvocation, isCliCommand, } from "./command-reference.mjs";
|
|
5
7
|
const MAX_CLI_ARGUMENTS = 32;
|
|
6
8
|
const MAX_CLI_ARGUMENT_LENGTH = 1024;
|
|
7
|
-
const CLI_COMMANDS = [
|
|
8
|
-
'completion',
|
|
9
|
-
'doctor',
|
|
10
|
-
'commands',
|
|
11
|
-
'manual',
|
|
12
|
-
'manifest',
|
|
13
|
-
'map-observations',
|
|
14
|
-
'oam-export',
|
|
15
|
-
'lookup',
|
|
16
|
-
'bulk',
|
|
17
|
-
'ct-search',
|
|
18
|
-
'ct-intake',
|
|
19
|
-
'discover',
|
|
20
|
-
'discover-scan',
|
|
21
|
-
'posture',
|
|
22
|
-
'http',
|
|
23
|
-
'tls',
|
|
24
|
-
'registry-support',
|
|
25
|
-
'registry-doctor',
|
|
26
|
-
'registry-cohort',
|
|
27
|
-
'registry-scaffold',
|
|
28
|
-
'risk-calibrate',
|
|
29
|
-
'lookalike-calibrate',
|
|
30
|
-
'verify-artifact',
|
|
31
|
-
'inspect-archive',
|
|
32
|
-
'sign-artifact',
|
|
33
|
-
'verify-signature',
|
|
34
|
-
'source-report',
|
|
35
|
-
'compare',
|
|
36
|
-
'page-compare',
|
|
37
|
-
'mail-review',
|
|
38
|
-
'review-evidence',
|
|
39
|
-
'brief',
|
|
40
|
-
'case-pack',
|
|
41
|
-
'domain-control',
|
|
42
|
-
'monitor-once',
|
|
43
|
-
'assurance',
|
|
44
|
-
'change-packet',
|
|
45
|
-
'sharing-review',
|
|
46
|
-
'workflow-plan',
|
|
47
|
-
'workflow-run',
|
|
48
|
-
'diff',
|
|
49
|
-
'reconcile',
|
|
50
|
-
'timeline',
|
|
51
|
-
'export',
|
|
52
|
-
];
|
|
53
9
|
function boundedArgument(value) {
|
|
54
|
-
if (typeof value !== 'string' || value.length > MAX_CLI_ARGUMENT_LENGTH ||
|
|
10
|
+
if (typeof value !== 'string' || value.length > MAX_CLI_ARGUMENT_LENGTH || hasUnsafeCliText(value)) {
|
|
55
11
|
throw new CliUsageError('Arguments must be bounded text without control characters.');
|
|
56
12
|
}
|
|
57
13
|
return value;
|
|
58
14
|
}
|
|
59
|
-
function isCliCommand(value) {
|
|
60
|
-
return CLI_COMMANDS.includes(value);
|
|
61
|
-
}
|
|
62
15
|
function extractFileOutputArguments(argv) {
|
|
63
16
|
const retained = [];
|
|
64
17
|
let destination = null;
|
|
65
18
|
let force = false;
|
|
19
|
+
let palette = null;
|
|
66
20
|
for (let index = 0; index < argv.length; index++) {
|
|
67
21
|
const argument = argv[index];
|
|
68
22
|
if (argument === undefined)
|
|
@@ -80,19 +34,32 @@ function extractFileOutputArguments(argv) {
|
|
|
80
34
|
throw new CliUsageError('--force may be supplied only once.');
|
|
81
35
|
force = true;
|
|
82
36
|
}
|
|
37
|
+
else if (index > 0 && argument === '--palette') {
|
|
38
|
+
if (palette !== null)
|
|
39
|
+
throw new CliUsageError('--palette may be supplied only once.');
|
|
40
|
+
const value = argv[++index];
|
|
41
|
+
if (value !== 'auto' && value !== 'light' && value !== 'dark') {
|
|
42
|
+
throw new CliUsageError('--palette requires auto, light, or dark.');
|
|
43
|
+
}
|
|
44
|
+
palette = value;
|
|
45
|
+
}
|
|
83
46
|
else {
|
|
84
47
|
retained.push(argument);
|
|
85
48
|
}
|
|
86
49
|
}
|
|
87
50
|
if (force && destination === null)
|
|
88
51
|
throw new CliUsageError('--force requires --output.');
|
|
89
|
-
return { argv: retained, destination, force };
|
|
52
|
+
return { argv: retained, destination, force, palette };
|
|
90
53
|
}
|
|
91
54
|
function parseCliArguments(rawArgv) {
|
|
92
55
|
if (!Array.isArray(rawArgv) || rawArgv.length > MAX_CLI_ARGUMENTS) {
|
|
93
56
|
throw new CliUsageError(`At most ${MAX_CLI_ARGUMENTS} command arguments are supported.`);
|
|
94
57
|
}
|
|
95
|
-
const
|
|
58
|
+
const boundedArguments = rawArgv.map(boundedArgument);
|
|
59
|
+
const metaAction = cliMetaActionForInvocation(boundedArguments);
|
|
60
|
+
if (metaAction)
|
|
61
|
+
return parseCliArgumentsCore(boundedArguments);
|
|
62
|
+
const extracted = extractFileOutputArguments(boundedArguments);
|
|
96
63
|
const parsed = parseCliArgumentsCore(extracted.argv);
|
|
97
64
|
if ('quiet' in parsed && parsed.quiet && extracted.destination !== null) {
|
|
98
65
|
throw new CliUsageError('--quiet cannot be combined with --output.');
|
|
@@ -100,118 +67,53 @@ function parseCliArguments(rawArgv) {
|
|
|
100
67
|
if ('events' in parsed && parsed.events && extracted.destination !== null) {
|
|
101
68
|
throw new CliUsageError('--events cannot be combined with --output because completion must describe the final output delivery.');
|
|
102
69
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
70
|
+
if ('browse' in parsed && parsed.browse === true && extracted.destination !== null) {
|
|
71
|
+
throw new CliUsageError('--browse cannot be combined with --output because it requires an interactive terminal.');
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
...parsed,
|
|
75
|
+
...(extracted.destination !== null ? { destination: extracted.destination } : {}),
|
|
76
|
+
...(extracted.force ? { force: true } : {}),
|
|
77
|
+
...(extracted.palette ? { palette: extracted.palette } : {}),
|
|
78
|
+
};
|
|
106
79
|
}
|
|
107
80
|
function parseCliArgumentsCore(argv) {
|
|
108
81
|
if (!argv.length)
|
|
109
82
|
return { action: 'help' };
|
|
110
83
|
const firstArgument = argv[0] ?? '';
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
84
|
+
const metaAction = cliMetaActionForInvocation(argv);
|
|
85
|
+
if (metaAction?.id === 'help') {
|
|
113
86
|
if (argv.length === 1)
|
|
114
87
|
return { action: 'help' };
|
|
115
|
-
if (argv.length === 2 && isCliCommand(firstArgument))
|
|
116
|
-
return { action: 'help', command: firstArgument };
|
|
88
|
+
if (argv.length === 2 && (isCliCommand(firstArgument) || isDirectLookupTarget(firstArgument))) {
|
|
89
|
+
return { action: 'help', command: isCliCommand(firstArgument) ? firstArgument : 'lookup' };
|
|
90
|
+
}
|
|
117
91
|
throw new CliUsageError('Help accepts only an optional command name.');
|
|
118
92
|
}
|
|
119
|
-
if (
|
|
93
|
+
if (metaAction?.id === 'version') {
|
|
120
94
|
if (argv.length !== 1)
|
|
121
95
|
throw new CliUsageError('--version does not accept other arguments.');
|
|
122
96
|
return { action: 'version' };
|
|
123
97
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
98
|
+
let command;
|
|
99
|
+
let directLookup = false;
|
|
100
|
+
if (isCliCommand(firstArgument))
|
|
101
|
+
command = firstArgument;
|
|
102
|
+
else if (isDirectLookupTarget(firstArgument)) {
|
|
103
|
+
command = 'lookup';
|
|
104
|
+
directLookup = true;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const displayedCommand = firstArgument.length > 80 ? `${firstArgument.slice(0, 79)}…` : firstArgument;
|
|
127
108
|
throw new CliUsageError(`Unknown command "${displayedCommand}". Run "whoisleuth commands" to list supported commands.`);
|
|
128
109
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return parseCommandsArguments(argv.slice(1));
|
|
137
|
-
if (command === 'manual')
|
|
138
|
-
return parseManualArguments(argv.slice(1));
|
|
139
|
-
if (command === 'manifest')
|
|
140
|
-
return parseManifestArguments(argv.slice(1));
|
|
141
|
-
if (command === 'map-observations')
|
|
142
|
-
return parseOfflineImportArguments('map-observations', argv.slice(1));
|
|
143
|
-
if (command === 'oam-export')
|
|
144
|
-
return parseOfflineImportArguments('oam-export', argv.slice(1));
|
|
145
|
-
if (command === 'ct-search')
|
|
146
|
-
return parseCtSearchArguments(argv.slice(1));
|
|
147
|
-
if (command === 'ct-intake')
|
|
148
|
-
return parseCtIntakeArguments(argv.slice(1));
|
|
149
|
-
if (command === 'discover')
|
|
150
|
-
return parseDiscoverArguments(argv.slice(1));
|
|
151
|
-
if (command === 'discover-scan')
|
|
152
|
-
return parseDiscoverScanArguments(argv.slice(1));
|
|
153
|
-
if (command === 'posture')
|
|
154
|
-
return parsePostureArguments(argv.slice(1));
|
|
155
|
-
if (command === 'http')
|
|
156
|
-
return parseHttpArguments(argv.slice(1));
|
|
157
|
-
if (command === 'tls')
|
|
158
|
-
return parseTlsArguments(argv.slice(1));
|
|
159
|
-
if (command === 'registry-support')
|
|
160
|
-
return parseRegistrySupportArguments(argv.slice(1));
|
|
161
|
-
if (command === 'registry-doctor')
|
|
162
|
-
return parseRegistryDoctorArguments(argv.slice(1));
|
|
163
|
-
if (command === 'registry-cohort')
|
|
164
|
-
return parseRegistryCohortArguments(argv.slice(1));
|
|
165
|
-
if (command === 'registry-scaffold')
|
|
166
|
-
return parseRegistryScaffoldArguments(argv.slice(1));
|
|
167
|
-
if (command === 'risk-calibrate')
|
|
168
|
-
return parseRiskCalibrateArguments(argv.slice(1));
|
|
169
|
-
if (command === 'lookalike-calibrate')
|
|
170
|
-
return parseLookalikeCalibrateArguments(argv.slice(1));
|
|
171
|
-
if (command === 'verify-artifact')
|
|
172
|
-
return parseVerifyArtifactArguments(argv.slice(1));
|
|
173
|
-
if (command === 'inspect-archive')
|
|
174
|
-
return parseInspectArchiveArguments(argv.slice(1));
|
|
175
|
-
if (command === 'sign-artifact')
|
|
176
|
-
return parseSignArtifactArguments(argv.slice(1));
|
|
177
|
-
if (command === 'verify-signature')
|
|
178
|
-
return parseVerifySignatureArguments(argv.slice(1));
|
|
179
|
-
if (command === 'source-report')
|
|
180
|
-
return parseSourceReportArguments(argv.slice(1));
|
|
181
|
-
if (command === 'compare')
|
|
182
|
-
return parseCompareArguments(argv.slice(1));
|
|
183
|
-
if (command === 'page-compare')
|
|
184
|
-
return parsePageCompareArguments(argv.slice(1));
|
|
185
|
-
if (command === 'mail-review')
|
|
186
|
-
return parseMailReviewArguments(argv.slice(1));
|
|
187
|
-
if (command === 'review-evidence')
|
|
188
|
-
return parseReviewEvidenceArguments(argv.slice(1));
|
|
189
|
-
if (command === 'brief')
|
|
190
|
-
return parseBriefArguments(argv.slice(1));
|
|
191
|
-
if (command === 'case-pack')
|
|
192
|
-
return parseCasePackArguments(argv.slice(1));
|
|
193
|
-
if (command === 'domain-control')
|
|
194
|
-
return parseDomainControlArguments(argv.slice(1));
|
|
195
|
-
if (command === 'monitor-once')
|
|
196
|
-
return parseMonitorOnceArguments(argv.slice(1));
|
|
197
|
-
if (command === 'assurance')
|
|
198
|
-
return parseAssuranceArguments(argv.slice(1));
|
|
199
|
-
if (command === 'change-packet')
|
|
200
|
-
return parseChangePacketArguments(argv.slice(1));
|
|
201
|
-
if (command === 'sharing-review')
|
|
202
|
-
return parseSharingReviewArguments(argv.slice(1));
|
|
203
|
-
if (command === 'workflow-plan')
|
|
204
|
-
return parseWorkflowPlanArguments(argv.slice(1));
|
|
205
|
-
if (command === 'workflow-run')
|
|
206
|
-
return parseWorkflowRunArguments(argv.slice(1));
|
|
207
|
-
if (command === 'diff')
|
|
208
|
-
return parseDiffArguments(argv.slice(1));
|
|
209
|
-
if (command === 'reconcile')
|
|
210
|
-
return parseReconcileArguments(argv.slice(1));
|
|
211
|
-
if (command === 'timeline')
|
|
212
|
-
return parseTimelineArguments(argv.slice(1));
|
|
213
|
-
if (command === 'export')
|
|
214
|
-
return parseExportArguments(argv.slice(1));
|
|
110
|
+
const parsed = CLI_PARSERS[command](directLookup ? argv : argv.slice(1));
|
|
111
|
+
if (parsed.action !== command) {
|
|
112
|
+
throw new Error(`CLI parser contract mismatch for ${command}.`);
|
|
113
|
+
}
|
|
114
|
+
return parsed;
|
|
115
|
+
}
|
|
116
|
+
function parseLookupArguments(lookupArguments) {
|
|
215
117
|
let query = null;
|
|
216
118
|
let output = 'terminal';
|
|
217
119
|
let deep = false;
|
|
@@ -223,10 +125,12 @@ function parseCliArgumentsCore(argv) {
|
|
|
223
125
|
let strictExit = false;
|
|
224
126
|
let events = false;
|
|
225
127
|
let plan = false;
|
|
128
|
+
let browse = false;
|
|
129
|
+
let includeAttribution = true;
|
|
226
130
|
let failOn = null;
|
|
227
131
|
let observerLabel = null;
|
|
228
132
|
let vantageLabel = null;
|
|
229
|
-
|
|
133
|
+
let saveLookup = null;
|
|
230
134
|
for (let index = 0; index < lookupArguments.length; index++) {
|
|
231
135
|
const argument = lookupArguments[index];
|
|
232
136
|
if (argument === undefined)
|
|
@@ -279,6 +183,24 @@ function parseCliArgumentsCore(argv) {
|
|
|
279
183
|
throw new CliUsageError('--plan may be supplied only once.');
|
|
280
184
|
plan = true;
|
|
281
185
|
}
|
|
186
|
+
else if (argument === '--browse') {
|
|
187
|
+
if (browse)
|
|
188
|
+
throw new CliUsageError('--browse may be supplied only once.');
|
|
189
|
+
browse = true;
|
|
190
|
+
}
|
|
191
|
+
else if (argument === '--save-lookup') {
|
|
192
|
+
if (saveLookup !== null)
|
|
193
|
+
throw new CliUsageError('--save-lookup may be supplied only once.');
|
|
194
|
+
const value = lookupArguments[++index];
|
|
195
|
+
if (!value || value.startsWith('-'))
|
|
196
|
+
throw new CliUsageError('--save-lookup requires one bounded file path.');
|
|
197
|
+
saveLookup = value;
|
|
198
|
+
}
|
|
199
|
+
else if (argument === '--no-attribution') {
|
|
200
|
+
if (!includeAttribution)
|
|
201
|
+
throw new CliUsageError('--no-attribution may be supplied only once.');
|
|
202
|
+
includeAttribution = false;
|
|
203
|
+
}
|
|
282
204
|
else if (argument === '--fail-on') {
|
|
283
205
|
if (failOn !== null)
|
|
284
206
|
throw new CliUsageError('--fail-on may be supplied only once.');
|
|
@@ -315,12 +237,26 @@ function parseCliArgumentsCore(argv) {
|
|
|
315
237
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
316
238
|
if (detailSet && output !== 'terminal')
|
|
317
239
|
throw new CliUsageError('--summary and --verbose apply only to terminal output.');
|
|
240
|
+
if (browse && output !== 'terminal')
|
|
241
|
+
throw new CliUsageError('--browse applies only to terminal output.');
|
|
242
|
+
if (browse && (detailSet || events || plan || quiet)) {
|
|
243
|
+
throw new CliUsageError('--browse cannot be combined with detail, event, plan, or quiet options.');
|
|
244
|
+
}
|
|
245
|
+
if (browse && query === null) {
|
|
246
|
+
throw new CliUsageError('--browse requires a positional target because interactive stdin is reserved for navigation.');
|
|
247
|
+
}
|
|
248
|
+
if (saveLookup !== null && !browse) {
|
|
249
|
+
throw new CliUsageError('--save-lookup requires --browse so the exact completed Lookup document is saved after the browser closes.');
|
|
250
|
+
}
|
|
251
|
+
if (!includeAttribution && output !== 'markdown' && output !== 'html') {
|
|
252
|
+
throw new CliUsageError('--no-attribution applies only to Markdown or HTML reports.');
|
|
253
|
+
}
|
|
318
254
|
if (plan && !['terminal', 'json'].includes(output))
|
|
319
255
|
throw new CliUsageError('--plan supports terminal or JSON output only.');
|
|
320
256
|
if (plan && (detailSet || strictExit || events || quiet || failOn !== null)) {
|
|
321
257
|
throw new CliUsageError('--plan cannot be combined with detail, strict-exit, event, or quiet options.');
|
|
322
258
|
}
|
|
323
|
-
return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, observerLabel, vantageLabel, quiet, color, ...(failOn ? { failOn } : {}) };
|
|
259
|
+
return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, includeAttribution, observerLabel, vantageLabel, quiet, color, ...(browse ? { browse: true } : {}), ...(saveLookup ? { saveLookup } : {}), ...(failOn ? { failOn } : {}) };
|
|
324
260
|
}
|
|
325
261
|
function parseCompletionArguments(argv) {
|
|
326
262
|
if (argv.length !== 1 || !['bash', 'zsh', 'fish', 'powershell'].includes(argv[0] || '')) {
|
|
@@ -413,14 +349,41 @@ function parseOfflineImportArguments(action, argv) {
|
|
|
413
349
|
}
|
|
414
350
|
function parseCommandsArguments(argv) {
|
|
415
351
|
let output = 'terminal';
|
|
352
|
+
let common = false;
|
|
353
|
+
let group = null;
|
|
354
|
+
let mode = null;
|
|
416
355
|
let quiet = false;
|
|
417
356
|
let color = true;
|
|
418
|
-
for (
|
|
357
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
358
|
+
const argument = argv[index];
|
|
419
359
|
if (argument === '--json') {
|
|
420
360
|
if (output !== 'terminal')
|
|
421
361
|
throw new CliUsageError('--json may be supplied only once.');
|
|
422
362
|
output = 'json';
|
|
423
363
|
}
|
|
364
|
+
else if (argument === '--common') {
|
|
365
|
+
if (common)
|
|
366
|
+
throw new CliUsageError('--common may be supplied only once.');
|
|
367
|
+
common = true;
|
|
368
|
+
}
|
|
369
|
+
else if (argument === '--group') {
|
|
370
|
+
if (group !== null)
|
|
371
|
+
throw new CliUsageError('--group may be supplied only once.');
|
|
372
|
+
const value = argv[++index];
|
|
373
|
+
if (!['investigate', 'respond', 'assure', 'utilities'].includes(value ?? '')) {
|
|
374
|
+
throw new CliUsageError('--group requires investigate, respond, assure, or utilities.');
|
|
375
|
+
}
|
|
376
|
+
group = value;
|
|
377
|
+
}
|
|
378
|
+
else if (argument === '--mode') {
|
|
379
|
+
if (mode !== null)
|
|
380
|
+
throw new CliUsageError('--mode may be supplied only once.');
|
|
381
|
+
const value = argv[++index];
|
|
382
|
+
if (value !== 'offline' && value !== 'network') {
|
|
383
|
+
throw new CliUsageError('--mode requires offline or network.');
|
|
384
|
+
}
|
|
385
|
+
mode = value;
|
|
386
|
+
}
|
|
424
387
|
else if (argument === '--quiet')
|
|
425
388
|
quiet = true;
|
|
426
389
|
else if (argument === '--no-color')
|
|
@@ -430,7 +393,7 @@ function parseCommandsArguments(argv) {
|
|
|
430
393
|
}
|
|
431
394
|
if (quiet && output !== 'terminal')
|
|
432
395
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
433
|
-
return { action: 'commands', output, quiet, color };
|
|
396
|
+
return { action: 'commands', output, common, group, mode, quiet, color };
|
|
434
397
|
}
|
|
435
398
|
function parseDoctorArguments(argv) {
|
|
436
399
|
let network = false;
|
|
@@ -963,6 +926,122 @@ function parseTlsArguments(argv) {
|
|
|
963
926
|
const { source: hostname, ...options } = parseSingleJsonInput(argv, 'tls accepts one hostname.');
|
|
964
927
|
return { action: 'tls', hostname, ...options };
|
|
965
928
|
}
|
|
929
|
+
function parseDnssecValidateArguments(argv) {
|
|
930
|
+
let target = null;
|
|
931
|
+
let resolver = null;
|
|
932
|
+
let trustAnchorSource = null;
|
|
933
|
+
let ownedOrAuthorized = false;
|
|
934
|
+
let output = 'terminal';
|
|
935
|
+
let quiet = false;
|
|
936
|
+
let color = true;
|
|
937
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
938
|
+
const argument = argv[index];
|
|
939
|
+
if (argument === '--resolver' || argument === '--trust-anchor') {
|
|
940
|
+
const selected = argument === '--resolver' ? resolver : trustAnchorSource;
|
|
941
|
+
if (selected !== null)
|
|
942
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
943
|
+
const value = argv[++index];
|
|
944
|
+
if (!value || value.startsWith('-'))
|
|
945
|
+
throw new CliUsageError(`${argument} requires one bounded value.`);
|
|
946
|
+
if (argument === '--resolver')
|
|
947
|
+
resolver = value;
|
|
948
|
+
else
|
|
949
|
+
trustAnchorSource = value;
|
|
950
|
+
}
|
|
951
|
+
else if (argument === '--owned-or-authorized') {
|
|
952
|
+
if (ownedOrAuthorized)
|
|
953
|
+
throw new CliUsageError('--owned-or-authorized may be supplied only once.');
|
|
954
|
+
ownedOrAuthorized = true;
|
|
955
|
+
}
|
|
956
|
+
else if (argument === '--json') {
|
|
957
|
+
if (output !== 'terminal')
|
|
958
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
959
|
+
output = 'json';
|
|
960
|
+
}
|
|
961
|
+
else if (argument === '--quiet')
|
|
962
|
+
quiet = true;
|
|
963
|
+
else if (argument === '--no-color')
|
|
964
|
+
color = false;
|
|
965
|
+
else if (argument?.startsWith('-'))
|
|
966
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
967
|
+
else if (argument && target === null)
|
|
968
|
+
target = argument;
|
|
969
|
+
else
|
|
970
|
+
throw new CliUsageError('dnssec-validate accepts one domain target.');
|
|
971
|
+
}
|
|
972
|
+
if (!target)
|
|
973
|
+
throw new CliUsageError('dnssec-validate requires one domain target.');
|
|
974
|
+
if (!resolver)
|
|
975
|
+
throw new CliUsageError('dnssec-validate requires --resolver <public-IP>.');
|
|
976
|
+
if (!trustAnchorSource)
|
|
977
|
+
throw new CliUsageError('dnssec-validate requires --trust-anchor <anchor.json>.');
|
|
978
|
+
if (!ownedOrAuthorized)
|
|
979
|
+
throw new CliUsageError('dnssec-validate requires --owned-or-authorized for every run.');
|
|
980
|
+
if (quiet && output !== 'terminal')
|
|
981
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
982
|
+
return { action: 'dnssec-validate', target, resolver, trustAnchorSource, ownedOrAuthorized: true, output, quiet, color };
|
|
983
|
+
}
|
|
984
|
+
function parseMailTransportArguments(argv) {
|
|
985
|
+
let source = null;
|
|
986
|
+
let resolver = null;
|
|
987
|
+
let trustAnchorSource = null;
|
|
988
|
+
let ownedOrAuthorized = false;
|
|
989
|
+
let activeProbeAcknowledged = false;
|
|
990
|
+
let output = 'terminal';
|
|
991
|
+
let quiet = false;
|
|
992
|
+
let color = true;
|
|
993
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
994
|
+
const argument = argv[index];
|
|
995
|
+
if (argument === '--resolver' || argument === '--trust-anchor') {
|
|
996
|
+
const selected = argument === '--resolver' ? resolver : trustAnchorSource;
|
|
997
|
+
if (selected !== null)
|
|
998
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
999
|
+
const value = argv[++index];
|
|
1000
|
+
if (!value || value.startsWith('-'))
|
|
1001
|
+
throw new CliUsageError(`${argument} requires one bounded value.`);
|
|
1002
|
+
if (argument === '--resolver')
|
|
1003
|
+
resolver = value;
|
|
1004
|
+
else
|
|
1005
|
+
trustAnchorSource = value;
|
|
1006
|
+
}
|
|
1007
|
+
else if (argument === '--owned-or-authorized') {
|
|
1008
|
+
if (ownedOrAuthorized)
|
|
1009
|
+
throw new CliUsageError('--owned-or-authorized may be supplied only once.');
|
|
1010
|
+
ownedOrAuthorized = true;
|
|
1011
|
+
}
|
|
1012
|
+
else if (argument === '--active-probe') {
|
|
1013
|
+
if (activeProbeAcknowledged)
|
|
1014
|
+
throw new CliUsageError('--active-probe may be supplied only once.');
|
|
1015
|
+
activeProbeAcknowledged = true;
|
|
1016
|
+
}
|
|
1017
|
+
else if (argument === '--json') {
|
|
1018
|
+
if (output !== 'terminal')
|
|
1019
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1020
|
+
output = 'json';
|
|
1021
|
+
}
|
|
1022
|
+
else if (argument === '--quiet')
|
|
1023
|
+
quiet = true;
|
|
1024
|
+
else if (argument === '--no-color')
|
|
1025
|
+
color = false;
|
|
1026
|
+
else if (argument?.startsWith('-'))
|
|
1027
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1028
|
+
else if (argument && source === null)
|
|
1029
|
+
source = argument;
|
|
1030
|
+
else
|
|
1031
|
+
throw new CliUsageError('mail-transport accepts one optional versioned JSON input file.');
|
|
1032
|
+
}
|
|
1033
|
+
if (!resolver)
|
|
1034
|
+
throw new CliUsageError('mail-transport requires --resolver <public-IP>.');
|
|
1035
|
+
if (!trustAnchorSource)
|
|
1036
|
+
throw new CliUsageError('mail-transport requires --trust-anchor <anchor.json>.');
|
|
1037
|
+
if (!ownedOrAuthorized)
|
|
1038
|
+
throw new CliUsageError('mail-transport requires --owned-or-authorized for every run.');
|
|
1039
|
+
if (!activeProbeAcknowledged)
|
|
1040
|
+
throw new CliUsageError('mail-transport requires --active-probe for every run.');
|
|
1041
|
+
if (quiet && output !== 'terminal')
|
|
1042
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1043
|
+
return { action: 'mail-transport', source, resolver, trustAnchorSource, ownedOrAuthorized: true, activeProbeAcknowledged: true, output, quiet, color };
|
|
1044
|
+
}
|
|
966
1045
|
function parseCompareArguments(argv) {
|
|
967
1046
|
const parsed = parseSingleJsonInput(argv, 'compare accepts one optional lookup JSON file. Otherwise pipe one lookup document on stdin.');
|
|
968
1047
|
return { action: 'compare', ...parsed };
|
|
@@ -1091,9 +1170,11 @@ function parseCasePackArguments(argv) {
|
|
|
1091
1170
|
}
|
|
1092
1171
|
if (audience === null)
|
|
1093
1172
|
throw new CliUsageError('case-pack requires --audience internal, trusted, or public.');
|
|
1173
|
+
if (!reviewed)
|
|
1174
|
+
throw new CliUsageError('case-pack requires --reviewed to confirm deliberate analyst review.');
|
|
1094
1175
|
if (quiet && output !== 'terminal')
|
|
1095
1176
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1096
|
-
return { action: 'case-pack', source, output, audience, reviewed, quiet, color };
|
|
1177
|
+
return { action: 'case-pack', source, output, audience, reviewed: true, quiet, color };
|
|
1097
1178
|
}
|
|
1098
1179
|
function parseMonitorOnceArguments(argv) {
|
|
1099
1180
|
let source = null;
|
|
@@ -1101,6 +1182,8 @@ function parseMonitorOnceArguments(argv) {
|
|
|
1101
1182
|
let output = 'terminal';
|
|
1102
1183
|
let limit = 20;
|
|
1103
1184
|
let concurrency = 2;
|
|
1185
|
+
let limitSupplied = false;
|
|
1186
|
+
let concurrencySupplied = false;
|
|
1104
1187
|
let failOn = null;
|
|
1105
1188
|
let quiet = false;
|
|
1106
1189
|
let color = true;
|
|
@@ -1127,14 +1210,21 @@ function parseMonitorOnceArguments(argv) {
|
|
|
1127
1210
|
previousSource = value;
|
|
1128
1211
|
}
|
|
1129
1212
|
else if (argument === '--limit' || argument === '--concurrency') {
|
|
1213
|
+
if (argument === '--limit' ? limitSupplied : concurrencySupplied) {
|
|
1214
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
1215
|
+
}
|
|
1130
1216
|
const value = Number(argv[++index]);
|
|
1131
1217
|
const maximum = argument === '--limit' ? 20 : 3;
|
|
1132
1218
|
if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
|
|
1133
1219
|
throw new CliUsageError(`${argument} requires an integer from 1 to ${maximum}.`);
|
|
1134
|
-
if (argument === '--limit')
|
|
1220
|
+
if (argument === '--limit') {
|
|
1135
1221
|
limit = value;
|
|
1136
|
-
|
|
1222
|
+
limitSupplied = true;
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1137
1225
|
concurrency = value;
|
|
1226
|
+
concurrencySupplied = true;
|
|
1227
|
+
}
|
|
1138
1228
|
}
|
|
1139
1229
|
else if (argument === '--fail-on') {
|
|
1140
1230
|
if (failOn !== null)
|
|
@@ -1246,15 +1336,33 @@ function parseSharingReviewArguments(argv) {
|
|
|
1246
1336
|
}
|
|
1247
1337
|
function parseWorkflowPlanArguments(argv) {
|
|
1248
1338
|
const positional = [];
|
|
1339
|
+
let discovery = null;
|
|
1340
|
+
let explainedRecipe = null;
|
|
1249
1341
|
let output = 'terminal';
|
|
1250
1342
|
let quiet = false;
|
|
1251
1343
|
let color = true;
|
|
1252
|
-
for (
|
|
1344
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1345
|
+
const argument = argv[index];
|
|
1253
1346
|
if (argument === '--json') {
|
|
1254
1347
|
if (output !== 'terminal')
|
|
1255
1348
|
throw new CliUsageError('--json may be supplied only once.');
|
|
1256
1349
|
output = 'json';
|
|
1257
1350
|
}
|
|
1351
|
+
else if (argument === '--list') {
|
|
1352
|
+
if (discovery !== null)
|
|
1353
|
+
throw new CliUsageError('--list and --explain are mutually exclusive and may be supplied only once.');
|
|
1354
|
+
discovery = 'list';
|
|
1355
|
+
}
|
|
1356
|
+
else if (argument === '--explain') {
|
|
1357
|
+
if (discovery !== null)
|
|
1358
|
+
throw new CliUsageError('--list and --explain are mutually exclusive and may be supplied only once.');
|
|
1359
|
+
const value = argv[++index];
|
|
1360
|
+
if (!INVESTIGATION_PLAN_RECIPES.includes(value)) {
|
|
1361
|
+
throw new CliUsageError(`--explain recipe must be one of: ${INVESTIGATION_PLAN_RECIPES.join(', ')}.`);
|
|
1362
|
+
}
|
|
1363
|
+
discovery = 'explain';
|
|
1364
|
+
explainedRecipe = value;
|
|
1365
|
+
}
|
|
1258
1366
|
else if (argument === '--quiet')
|
|
1259
1367
|
quiet = true;
|
|
1260
1368
|
else if (argument === '--no-color')
|
|
@@ -1264,14 +1372,21 @@ function parseWorkflowPlanArguments(argv) {
|
|
|
1264
1372
|
else
|
|
1265
1373
|
positional.push(argument);
|
|
1266
1374
|
}
|
|
1375
|
+
if (quiet && output !== 'terminal')
|
|
1376
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1377
|
+
if (discovery !== null) {
|
|
1378
|
+
if (positional.length !== 0)
|
|
1379
|
+
throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
|
|
1380
|
+
if (discovery === 'list')
|
|
1381
|
+
return { action: 'workflow-plan', discovery, output, quiet, color };
|
|
1382
|
+
return { action: 'workflow-plan', discovery, recipe: explainedRecipe, output, quiet, color };
|
|
1383
|
+
}
|
|
1267
1384
|
if (positional.length !== 2)
|
|
1268
|
-
throw new CliUsageError('workflow-plan requires one fixed recipe and one subject
|
|
1385
|
+
throw new CliUsageError('workflow-plan requires one fixed recipe and one subject, --list, or --explain <recipe>.');
|
|
1269
1386
|
const recipe = positional[0];
|
|
1270
1387
|
if (!INVESTIGATION_PLAN_RECIPES.includes(recipe)) {
|
|
1271
1388
|
throw new CliUsageError(`workflow-plan recipe must be one of: ${INVESTIGATION_PLAN_RECIPES.join(', ')}.`);
|
|
1272
1389
|
}
|
|
1273
|
-
if (quiet && output !== 'terminal')
|
|
1274
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1275
1390
|
return { action: 'workflow-plan', recipe: recipe, subject: positional[1], output, quiet, color };
|
|
1276
1391
|
}
|
|
1277
1392
|
function parseWorkflowRunArguments(argv) {
|
|
@@ -1315,8 +1430,8 @@ function parseWorkflowRunArguments(argv) {
|
|
|
1315
1430
|
if (positional.length !== 2)
|
|
1316
1431
|
throw new CliUsageError('workflow-run requires one fixed recipe and one subject.');
|
|
1317
1432
|
const recipe = positional[0];
|
|
1318
|
-
if (!
|
|
1319
|
-
throw new CliUsageError(`workflow-run recipe must be one of: ${
|
|
1433
|
+
if (!RUNNABLE_INVESTIGATION_PLAN_RECIPES.includes(recipe)) {
|
|
1434
|
+
throw new CliUsageError(`workflow-run recipe must be one of: ${RUNNABLE_INVESTIGATION_PLAN_RECIPES.join(', ')}.`);
|
|
1320
1435
|
}
|
|
1321
1436
|
if (quiet && output !== 'terminal')
|
|
1322
1437
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
@@ -1325,15 +1440,32 @@ function parseWorkflowRunArguments(argv) {
|
|
|
1325
1440
|
function parseDiffArguments(argv) {
|
|
1326
1441
|
let leftSource = null;
|
|
1327
1442
|
let rightSource = null;
|
|
1443
|
+
let leftSessionId = null;
|
|
1444
|
+
let rightSessionId = null;
|
|
1328
1445
|
let output = 'terminal';
|
|
1329
1446
|
let quiet = false;
|
|
1330
1447
|
let color = true;
|
|
1331
|
-
for (
|
|
1448
|
+
for (let index = 0; index < argv.length; index++) {
|
|
1449
|
+
const argument = argv[index];
|
|
1450
|
+
if (argument === undefined)
|
|
1451
|
+
break;
|
|
1332
1452
|
if (argument === '--json') {
|
|
1333
1453
|
if (output !== 'terminal')
|
|
1334
1454
|
throw new CliUsageError('--json may be supplied only once.');
|
|
1335
1455
|
output = 'json';
|
|
1336
1456
|
}
|
|
1457
|
+
else if (argument === '--left-session' || argument === '--right-session') {
|
|
1458
|
+
const current = argument === '--left-session' ? leftSessionId : rightSessionId;
|
|
1459
|
+
if (current !== null)
|
|
1460
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
1461
|
+
const value = argv[++index];
|
|
1462
|
+
if (!value || !/^[A-Za-z0-9_-]{1,128}$/u.test(value))
|
|
1463
|
+
throw new CliUsageError(`${argument} requires one bounded saved-session ID.`);
|
|
1464
|
+
if (argument === '--left-session')
|
|
1465
|
+
leftSessionId = value;
|
|
1466
|
+
else
|
|
1467
|
+
rightSessionId = value;
|
|
1468
|
+
}
|
|
1337
1469
|
else if (argument === '--quiet')
|
|
1338
1470
|
quiet = true;
|
|
1339
1471
|
else if (argument === '--no-color')
|
|
@@ -1345,15 +1477,15 @@ function parseDiffArguments(argv) {
|
|
|
1345
1477
|
else if (rightSource === null)
|
|
1346
1478
|
rightSource = argument;
|
|
1347
1479
|
else
|
|
1348
|
-
throw new CliUsageError('diff accepts exactly two
|
|
1480
|
+
throw new CliUsageError('diff accepts exactly two compatible retained JSON files.');
|
|
1349
1481
|
}
|
|
1350
1482
|
if (!leftSource || !rightSource)
|
|
1351
|
-
throw new CliUsageError('diff requires exactly two
|
|
1483
|
+
throw new CliUsageError('diff requires exactly two compatible retained JSON files.');
|
|
1352
1484
|
if (leftSource === rightSource)
|
|
1353
1485
|
throw new CliUsageError('diff requires two different input files.');
|
|
1354
1486
|
if (quiet && output !== 'terminal')
|
|
1355
1487
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1356
|
-
return { action: 'diff', leftSource, rightSource, output, quiet, color };
|
|
1488
|
+
return { action: 'diff', leftSource, rightSource, leftSessionId, rightSessionId, output, quiet, color };
|
|
1357
1489
|
}
|
|
1358
1490
|
function parseReconcileArguments(argv) {
|
|
1359
1491
|
const sources = [];
|
|
@@ -1451,8 +1583,30 @@ function parseRegistryScaffoldArguments(argv) {
|
|
|
1451
1583
|
return { action: 'registry-scaffold', profile, suffix, scenario: scenario };
|
|
1452
1584
|
}
|
|
1453
1585
|
function parseRiskCalibrateArguments(argv) {
|
|
1454
|
-
|
|
1455
|
-
|
|
1586
|
+
let source = null;
|
|
1587
|
+
let output = 'terminal';
|
|
1588
|
+
let quiet = false;
|
|
1589
|
+
let color = true;
|
|
1590
|
+
for (const argument of argv) {
|
|
1591
|
+
if (argument === '--json' || argument === '--summary-json') {
|
|
1592
|
+
if (output !== 'terminal')
|
|
1593
|
+
throw new CliUsageError('--json and --summary-json are mutually exclusive and may be supplied only once.');
|
|
1594
|
+
output = argument === '--json' ? 'json' : 'summary_json';
|
|
1595
|
+
}
|
|
1596
|
+
else if (argument === '--quiet')
|
|
1597
|
+
quiet = true;
|
|
1598
|
+
else if (argument === '--no-color')
|
|
1599
|
+
color = false;
|
|
1600
|
+
else if (argument.startsWith('-'))
|
|
1601
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1602
|
+
else if (source === null)
|
|
1603
|
+
source = argument;
|
|
1604
|
+
else
|
|
1605
|
+
throw new CliUsageError('risk-calibrate accepts one optional dataset file. Otherwise pipe one dataset on stdin.');
|
|
1606
|
+
}
|
|
1607
|
+
if (quiet && output !== 'terminal')
|
|
1608
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1609
|
+
return { action: 'risk-calibrate', source, output, quiet, color };
|
|
1456
1610
|
}
|
|
1457
1611
|
function parseLookalikeCalibrateArguments(argv) {
|
|
1458
1612
|
const parsed = parseSingleJsonInput(argv, 'lookalike-calibrate accepts one optional reviewed dataset JSON file.');
|
|
@@ -1461,7 +1615,10 @@ function parseLookalikeCalibrateArguments(argv) {
|
|
|
1461
1615
|
function parseVerifyArtifactArguments(argv) {
|
|
1462
1616
|
let source = null;
|
|
1463
1617
|
let passphraseSource = null;
|
|
1618
|
+
let manifestSource = null;
|
|
1619
|
+
let manifestEntryId = null;
|
|
1464
1620
|
let output = 'terminal';
|
|
1621
|
+
let strictExit = false;
|
|
1465
1622
|
let quiet = false;
|
|
1466
1623
|
let color = true;
|
|
1467
1624
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -1482,6 +1639,28 @@ function parseVerifyArtifactArguments(argv) {
|
|
|
1482
1639
|
}
|
|
1483
1640
|
passphraseSource = value;
|
|
1484
1641
|
}
|
|
1642
|
+
else if (argument === '--manifest') {
|
|
1643
|
+
if (manifestSource !== null)
|
|
1644
|
+
throw new CliUsageError('--manifest may be supplied only once.');
|
|
1645
|
+
const value = argv[++index];
|
|
1646
|
+
if (!value || value.startsWith('-'))
|
|
1647
|
+
throw new CliUsageError('--manifest requires one bounded investigation manifest file.');
|
|
1648
|
+
manifestSource = value;
|
|
1649
|
+
}
|
|
1650
|
+
else if (argument === '--manifest-entry') {
|
|
1651
|
+
if (manifestEntryId !== null)
|
|
1652
|
+
throw new CliUsageError('--manifest-entry may be supplied only once.');
|
|
1653
|
+
const value = argv[++index];
|
|
1654
|
+
if (!value || !/^artifact-(?:[1-9]|1[0-6])$/u.test(value)) {
|
|
1655
|
+
throw new CliUsageError('--manifest-entry requires an ID from artifact-1 through artifact-16.');
|
|
1656
|
+
}
|
|
1657
|
+
manifestEntryId = value;
|
|
1658
|
+
}
|
|
1659
|
+
else if (argument === '--strict-exit') {
|
|
1660
|
+
if (strictExit)
|
|
1661
|
+
throw new CliUsageError('--strict-exit may be supplied only once.');
|
|
1662
|
+
strictExit = true;
|
|
1663
|
+
}
|
|
1485
1664
|
else if (argument === '--quiet')
|
|
1486
1665
|
quiet = true;
|
|
1487
1666
|
else if (argument === '--no-color')
|
|
@@ -1493,9 +1672,50 @@ function parseVerifyArtifactArguments(argv) {
|
|
|
1493
1672
|
else
|
|
1494
1673
|
throw new CliUsageError('verify-artifact accepts one optional JSON file. Otherwise pipe one artefact on stdin.');
|
|
1495
1674
|
}
|
|
1675
|
+
if ((manifestSource === null) !== (manifestEntryId === null)) {
|
|
1676
|
+
throw new CliUsageError('--manifest and --manifest-entry must be supplied together.');
|
|
1677
|
+
}
|
|
1496
1678
|
if (quiet && output !== 'terminal')
|
|
1497
1679
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1498
|
-
return { action: 'verify-artifact', source, passphraseSource, output, quiet, color };
|
|
1680
|
+
return { action: 'verify-artifact', source, passphraseSource, manifestSource, manifestEntryId, output, strictExit, quiet, color };
|
|
1681
|
+
}
|
|
1682
|
+
function parseInterchangeReportArguments(argv) {
|
|
1683
|
+
let source = null;
|
|
1684
|
+
let passphraseSource = null;
|
|
1685
|
+
let output = 'terminal';
|
|
1686
|
+
let quiet = false;
|
|
1687
|
+
let color = true;
|
|
1688
|
+
for (let index = 0; index < argv.length; index++) {
|
|
1689
|
+
const argument = argv[index];
|
|
1690
|
+
if (argument === undefined)
|
|
1691
|
+
break;
|
|
1692
|
+
if (argument === '--json') {
|
|
1693
|
+
if (output !== 'terminal')
|
|
1694
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1695
|
+
output = 'json';
|
|
1696
|
+
}
|
|
1697
|
+
else if (argument === '--passphrase-file') {
|
|
1698
|
+
if (passphraseSource !== null)
|
|
1699
|
+
throw new CliUsageError('--passphrase-file may be supplied only once.');
|
|
1700
|
+
const value = argv[++index];
|
|
1701
|
+
if (!value || value.startsWith('-'))
|
|
1702
|
+
throw new CliUsageError('--passphrase-file requires one bounded UTF-8 file.');
|
|
1703
|
+
passphraseSource = value;
|
|
1704
|
+
}
|
|
1705
|
+
else if (argument === '--quiet')
|
|
1706
|
+
quiet = true;
|
|
1707
|
+
else if (argument === '--no-color')
|
|
1708
|
+
color = false;
|
|
1709
|
+
else if (argument.startsWith('-'))
|
|
1710
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1711
|
+
else if (source === null)
|
|
1712
|
+
source = argument;
|
|
1713
|
+
else
|
|
1714
|
+
throw new CliUsageError('interchange-report accepts one optional JSON file. Otherwise pipe one artefact on stdin.');
|
|
1715
|
+
}
|
|
1716
|
+
if (quiet && output !== 'terminal')
|
|
1717
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1718
|
+
return { action: 'interchange-report', source, passphraseSource, output, quiet, color };
|
|
1499
1719
|
}
|
|
1500
1720
|
function parseSourceReportArguments(argv) {
|
|
1501
1721
|
const parsed = parseSingleJsonInput(argv, 'source-report accepts one optional JSON file. Otherwise pipe lookup documents on stdin.');
|
|
@@ -1504,6 +1724,7 @@ function parseSourceReportArguments(argv) {
|
|
|
1504
1724
|
function parseExportArguments(argv) {
|
|
1505
1725
|
let source = null;
|
|
1506
1726
|
let compact = false;
|
|
1727
|
+
let includeAttribution = true;
|
|
1507
1728
|
let format = 'json';
|
|
1508
1729
|
for (const argument of argv) {
|
|
1509
1730
|
if (argument === '--compact') {
|
|
@@ -1511,6 +1732,11 @@ function parseExportArguments(argv) {
|
|
|
1511
1732
|
throw new CliUsageError('--compact may be supplied only once.');
|
|
1512
1733
|
compact = true;
|
|
1513
1734
|
}
|
|
1735
|
+
else if (argument === '--no-attribution') {
|
|
1736
|
+
if (!includeAttribution)
|
|
1737
|
+
throw new CliUsageError('--no-attribution may be supplied only once.');
|
|
1738
|
+
includeAttribution = false;
|
|
1739
|
+
}
|
|
1514
1740
|
else if (argument === '--markdown' || argument === '--html') {
|
|
1515
1741
|
if (format !== 'json')
|
|
1516
1742
|
throw new CliUsageError('Choose only one evidence export format.');
|
|
@@ -1525,6 +1751,57 @@ function parseExportArguments(argv) {
|
|
|
1525
1751
|
}
|
|
1526
1752
|
if (compact && format !== 'json')
|
|
1527
1753
|
throw new CliUsageError('--compact applies to JSON export and cannot be combined with --markdown or --html.');
|
|
1528
|
-
|
|
1754
|
+
if (!includeAttribution && format === 'json')
|
|
1755
|
+
throw new CliUsageError('--no-attribution applies only to Markdown or HTML reports.');
|
|
1756
|
+
return { action: 'export', source, format, compact, includeAttribution };
|
|
1529
1757
|
}
|
|
1530
|
-
|
|
1758
|
+
const CLI_PARSERS = Object.freeze({
|
|
1759
|
+
completion: parseCompletionArguments,
|
|
1760
|
+
doctor: parseDoctorArguments,
|
|
1761
|
+
commands: parseCommandsArguments,
|
|
1762
|
+
manual: parseManualArguments,
|
|
1763
|
+
manifest: parseManifestArguments,
|
|
1764
|
+
'map-observations': (argv) => parseOfflineImportArguments('map-observations', argv),
|
|
1765
|
+
'oam-export': (argv) => parseOfflineImportArguments('oam-export', argv),
|
|
1766
|
+
lookup: parseLookupArguments,
|
|
1767
|
+
bulk: parseBulkArguments,
|
|
1768
|
+
'ct-search': parseCtSearchArguments,
|
|
1769
|
+
'ct-intake': parseCtIntakeArguments,
|
|
1770
|
+
discover: parseDiscoverArguments,
|
|
1771
|
+
'discover-scan': parseDiscoverScanArguments,
|
|
1772
|
+
posture: parsePostureArguments,
|
|
1773
|
+
http: parseHttpArguments,
|
|
1774
|
+
tls: parseTlsArguments,
|
|
1775
|
+
'dnssec-validate': parseDnssecValidateArguments,
|
|
1776
|
+
'mail-transport': parseMailTransportArguments,
|
|
1777
|
+
'registry-support': parseRegistrySupportArguments,
|
|
1778
|
+
'registry-doctor': parseRegistryDoctorArguments,
|
|
1779
|
+
'registry-cohort': parseRegistryCohortArguments,
|
|
1780
|
+
'registry-scaffold': parseRegistryScaffoldArguments,
|
|
1781
|
+
'risk-calibrate': parseRiskCalibrateArguments,
|
|
1782
|
+
'lookalike-calibrate': parseLookalikeCalibrateArguments,
|
|
1783
|
+
'verify-artifact': parseVerifyArtifactArguments,
|
|
1784
|
+
'interchange-report': parseInterchangeReportArguments,
|
|
1785
|
+
'inspect-archive': parseInspectArchiveArguments,
|
|
1786
|
+
'sign-artifact': parseSignArtifactArguments,
|
|
1787
|
+
'verify-signature': parseVerifySignatureArguments,
|
|
1788
|
+
'source-report': parseSourceReportArguments,
|
|
1789
|
+
compare: parseCompareArguments,
|
|
1790
|
+
'page-compare': parsePageCompareArguments,
|
|
1791
|
+
'mail-review': parseMailReviewArguments,
|
|
1792
|
+
'review-evidence': parseReviewEvidenceArguments,
|
|
1793
|
+
brief: parseBriefArguments,
|
|
1794
|
+
'case-pack': parseCasePackArguments,
|
|
1795
|
+
'domain-control': parseDomainControlArguments,
|
|
1796
|
+
'monitor-once': parseMonitorOnceArguments,
|
|
1797
|
+
assurance: parseAssuranceArguments,
|
|
1798
|
+
'change-packet': parseChangePacketArguments,
|
|
1799
|
+
'sharing-review': parseSharingReviewArguments,
|
|
1800
|
+
'workflow-plan': parseWorkflowPlanArguments,
|
|
1801
|
+
'workflow-run': parseWorkflowRunArguments,
|
|
1802
|
+
diff: parseDiffArguments,
|
|
1803
|
+
reconcile: parseReconcileArguments,
|
|
1804
|
+
timeline: parseTimelineArguments,
|
|
1805
|
+
export: parseExportArguments,
|
|
1806
|
+
});
|
|
1807
|
+
export { CLI_COMMANDS, CLI_PARSERS, CliUsageError, MAX_CLI_ARGUMENTS, MAX_CLI_ARGUMENT_LENGTH, parseCliArguments };
|