@slicedearth/whoisleuth-cli 1.36.1 → 1.43.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/README.md +32 -2
- package/bin/whoisleuth.mjs +52 -24
- package/cli/archive-inspect.mjs +37 -2
- package/cli/arguments.mjs +493 -157
- package/cli/artifact-verify.mjs +91 -15
- package/cli/bulk-checkpoint.mjs +7 -11
- package/cli/bulk-command-runner.mjs +19 -2
- package/cli/case-pack.mjs +136 -0
- package/cli/ci-report.mjs +73 -0
- package/cli/collection-preflight.mjs +59 -0
- package/cli/command-reference.mjs +395 -0
- package/cli/compare.mjs +1 -1
- package/cli/completion.mjs +49 -11
- package/cli/config-profile.mjs +180 -0
- package/cli/ct-event-intake.mjs +159 -0
- package/cli/discovery-scan-command-runner.mjs +15 -1
- package/cli/doctor.mjs +11 -5
- package/cli/domain-control-monitor.mjs +104 -0
- package/cli/domain-control-observations.mjs +235 -0
- package/cli/evidence-command-arguments.mjs +12 -1
- package/cli/evidence-command-runner.mjs +3 -2
- package/cli/evidence-signing.mjs +4 -4
- package/cli/external-observation-mapping.mjs +162 -0
- package/cli/fail-policy.mjs +64 -0
- package/cli/formatters/html.mjs +2 -2
- package/cli/formatters/json.mjs +13 -1
- package/cli/formatters/terminal.mjs +7 -3
- package/cli/investigation-manifest.mjs +113 -0
- package/cli/investigation-plan.mjs +2 -2
- package/cli/investigation-run.mjs +112 -0
- package/cli/local-mmdb-review.mjs +76 -0
- package/cli/lookalike-calibration.mjs +121 -0
- package/cli/lookup-brief.mjs +116 -0
- package/cli/lookup-command-runner.mjs +12 -2
- package/cli/lookup-diff.mjs +17 -19
- package/cli/lookup-plan.mjs +3 -3
- package/cli/lookup-reconcile.mjs +141 -0
- package/cli/lookup-timeline.mjs +1 -1
- package/cli/mail-review.mjs +1 -1
- package/cli/network-command-runner.mjs +4 -1
- package/cli/offline-evidence-review.mjs +125 -10
- package/cli/open-asset-model-bridge.mjs +169 -0
- package/cli/registry-cohort.mjs +121 -0
- package/cli/registry-doctor.mjs +220 -0
- package/cli/registry-fixture-scaffold.mjs +73 -0
- package/cli/risk-calibration.mjs +117 -9
- package/cli/runner.mjs +561 -280
- package/cli/saved-lookup.mjs +2 -2
- package/cli/sharing-review.mjs +202 -0
- package/cli/source-reliability.mjs +1 -1
- package/docs/cli-reference.md +1336 -0
- package/docs/cli.md +259 -940
- package/frontend/src/lib/analysis/acquisition-decision-packet.js +1 -1
- package/frontend/src/lib/analysis/acquisition-due-diligence.js +7 -7
- package/frontend/src/lib/analysis/brand-profile-model.js +46 -4
- package/frontend/src/lib/analysis/bulk-domain-comparison.js +12 -12
- package/frontend/src/lib/analysis/bulk-mail-exposure.js +1 -1
- package/frontend/src/lib/analysis/bulk-session-model.js +7 -2
- package/frontend/src/lib/analysis/case-evidence-model.js +26 -4
- package/frontend/src/lib/analysis/case-migration-model.js +6 -1
- package/frontend/src/lib/analysis/case-record-contracts.js +4 -2
- package/frontend/src/lib/analysis/case-record-core.js +5 -1
- package/frontend/src/lib/analysis/case-record-operations.js +6 -1
- package/frontend/src/lib/analysis/case-report.js +573 -0
- package/frontend/src/lib/analysis/case-response-model.js +30 -1
- package/frontend/src/lib/analysis/case-response-packet.js +7 -7
- package/frontend/src/lib/analysis/detection-rule-model.js +36 -0
- package/frontend/src/lib/analysis/evidence-coverage-ledger.js +328 -0
- package/frontend/src/lib/analysis/external-findings-import.js +320 -0
- package/frontend/src/lib/analysis/investigation-capsule.js +105 -0
- package/frontend/src/lib/analysis/lookup-asset-graph.js +806 -0
- package/frontend/src/lib/analysis/lookup-decision-support.js +487 -0
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +153 -0
- package/frontend/src/lib/analysis/lookup-presentation.js +64 -0
- package/frontend/src/lib/analysis/lookup-response.js +2 -0
- package/frontend/src/lib/analysis/lookup-source-refresh.js +304 -0
- package/frontend/src/lib/analysis/lookup-summary-model.js +398 -0
- package/frontend/src/lib/analysis/page-similarity.js +1 -1
- package/frontend/src/lib/analysis/relationship-evidence.js +3 -3
- package/frontend/src/lib/analysis/relationship-observation-model.js +4 -4
- package/frontend/src/lib/analysis/scoring.js +9 -89
- package/frontend/src/lib/analysis/shortlist-model.js +8 -4
- package/frontend/src/lib/analysis/visualization-bounds.js +26 -0
- package/frontend/src/lib/analysis/visualization-force-graph.js +352 -0
- package/frontend/src/lib/analysis/visualization-matrix.js +88 -0
- package/frontend/src/lib/analysis/visualization-models.js +369 -0
- package/frontend/src/lib/analysis/visualization-time-series.js +144 -0
- package/frontend/src/lib/analysis/workspace-archive-crypto.js +1 -1
- package/frontend/src/lib/analysis/workspace-archive.js +4 -2
- package/lib/abusech-auth.mjs +1 -1
- package/lib/analyst-taxonomy.mjs +40 -0
- package/lib/availability.mjs +2 -2
- package/lib/bounded-contract-normalizers.mjs +33 -0
- package/lib/browser-library-profile.mjs +15 -2
- package/lib/certificate-extension-profile.mjs +71 -0
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/dns-convergence-review.mjs +183 -0
- package/lib/dns-delegation-health.mjs +196 -3
- package/lib/domain-assurance.mjs +368 -0
- package/lib/domain-change-packet.mjs +133 -0
- package/lib/domain-change-review.mjs +307 -0
- package/lib/domain-control-flight-recorder.mjs +268 -0
- package/lib/domain-control-manifest.mjs +50 -7
- package/lib/domain-portfolio-review.mjs +151 -0
- package/lib/domain-posture.mjs +9 -9
- package/lib/encrypted-dns-contract.mjs +1 -1
- package/lib/evidence-export.mjs +1 -1
- package/lib/evidence-report-markdown.mjs +1 -1
- package/lib/evidence-report.mjs +1 -1
- package/lib/generated/cisa-kev-catalog.mjs +1668 -0
- package/lib/html-signals.mjs +5 -11
- package/lib/ip-prefix.mjs +34 -1
- package/lib/local-geoip-evidence.mjs +2 -2
- package/lib/lookup-response-contract.mjs +346 -0
- package/lib/nameserver-preflight-review.mjs +159 -0
- package/lib/observed-network-context.mjs +1 -1
- package/lib/opportunity-scoring.mjs +107 -0
- package/lib/page-fingerprints.mjs +1 -1
- package/lib/page-language-signals.mjs +124 -0
- package/lib/rdap-capabilities.mjs +6 -6
- package/lib/rdap-search-workbench.mjs +96 -6
- package/lib/registry-access.mjs +1 -1
- package/lib/registry-capability-catalogue.mjs +4 -4
- package/lib/registry-insights.mjs +6 -6
- package/lib/risk-scoring.mjs +247 -83
- package/lib/scoring-evidence-quality.mjs +107 -0
- package/lib/security-txt.mjs +3 -3
- package/lib/static-html-analysis.mjs +29 -1
- package/lib/threat-intelligence-manifests.mjs +1 -1
- package/lib/threatfox-intelligence.mjs +1 -1
- package/lib/tls-intelligence.mjs +13 -3
- package/lib/trust-store-comparison.mjs +146 -0
- package/lib/urlhaus-intelligence.mjs +1 -1
- package/lib/website-security-posture.mjs +5 -5
- package/lib/website-technology.mjs +25 -11
- package/lib/whois-chain.mjs +1 -1
- package/lib/zone-intent-review.mjs +534 -0
- package/package.json +5 -1
- package/third-party-notices.txt +911 -19
package/cli/arguments.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseInspectArchiveArguments, parseSignArtifactArguments, parseVerifySignatureArguments, } from "./evidence-command-arguments.mjs";
|
|
2
2
|
import { CliUsageError } from "./errors.mjs";
|
|
3
3
|
import { INVESTIGATION_PLAN_RECIPES, } from "./investigation-plan.mjs";
|
|
4
|
+
import { parseCliFailPolicies } from "./fail-policy.mjs";
|
|
4
5
|
const MAX_CLI_ARGUMENTS = 32;
|
|
5
6
|
const MAX_CLI_ARGUMENT_LENGTH = 1024;
|
|
6
7
|
const CLI_COMMANDS = [
|
|
@@ -8,16 +9,24 @@ const CLI_COMMANDS = [
|
|
|
8
9
|
'doctor',
|
|
9
10
|
'commands',
|
|
10
11
|
'manual',
|
|
12
|
+
'manifest',
|
|
13
|
+
'map-observations',
|
|
14
|
+
'oam-export',
|
|
11
15
|
'lookup',
|
|
12
16
|
'bulk',
|
|
13
17
|
'ct-search',
|
|
18
|
+
'ct-intake',
|
|
14
19
|
'discover',
|
|
15
20
|
'discover-scan',
|
|
16
21
|
'posture',
|
|
17
22
|
'http',
|
|
18
23
|
'tls',
|
|
19
24
|
'registry-support',
|
|
25
|
+
'registry-doctor',
|
|
26
|
+
'registry-cohort',
|
|
27
|
+
'registry-scaffold',
|
|
20
28
|
'risk-calibrate',
|
|
29
|
+
'lookalike-calibrate',
|
|
21
30
|
'verify-artifact',
|
|
22
31
|
'inspect-archive',
|
|
23
32
|
'sign-artifact',
|
|
@@ -27,9 +36,17 @@ const CLI_COMMANDS = [
|
|
|
27
36
|
'page-compare',
|
|
28
37
|
'mail-review',
|
|
29
38
|
'review-evidence',
|
|
39
|
+
'brief',
|
|
40
|
+
'case-pack',
|
|
30
41
|
'domain-control',
|
|
42
|
+
'monitor-once',
|
|
43
|
+
'assurance',
|
|
44
|
+
'change-packet',
|
|
45
|
+
'sharing-review',
|
|
31
46
|
'workflow-plan',
|
|
47
|
+
'workflow-run',
|
|
32
48
|
'diff',
|
|
49
|
+
'reconcile',
|
|
33
50
|
'timeline',
|
|
34
51
|
'export',
|
|
35
52
|
];
|
|
@@ -106,7 +123,8 @@ function parseCliArgumentsCore(argv) {
|
|
|
106
123
|
}
|
|
107
124
|
const command = firstArgument;
|
|
108
125
|
if (!isCliCommand(command)) {
|
|
109
|
-
|
|
126
|
+
const displayedCommand = command.length > 80 ? `${command.slice(0, 79)}…` : command;
|
|
127
|
+
throw new CliUsageError(`Unknown command "${displayedCommand}". Run "whoisleuth commands" to list supported commands.`);
|
|
110
128
|
}
|
|
111
129
|
if (command === 'bulk')
|
|
112
130
|
return parseBulkArguments(argv.slice(1));
|
|
@@ -118,8 +136,16 @@ function parseCliArgumentsCore(argv) {
|
|
|
118
136
|
return parseCommandsArguments(argv.slice(1));
|
|
119
137
|
if (command === 'manual')
|
|
120
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));
|
|
121
145
|
if (command === 'ct-search')
|
|
122
146
|
return parseCtSearchArguments(argv.slice(1));
|
|
147
|
+
if (command === 'ct-intake')
|
|
148
|
+
return parseCtIntakeArguments(argv.slice(1));
|
|
123
149
|
if (command === 'discover')
|
|
124
150
|
return parseDiscoverArguments(argv.slice(1));
|
|
125
151
|
if (command === 'discover-scan')
|
|
@@ -132,8 +158,16 @@ function parseCliArgumentsCore(argv) {
|
|
|
132
158
|
return parseTlsArguments(argv.slice(1));
|
|
133
159
|
if (command === 'registry-support')
|
|
134
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));
|
|
135
167
|
if (command === 'risk-calibrate')
|
|
136
168
|
return parseRiskCalibrateArguments(argv.slice(1));
|
|
169
|
+
if (command === 'lookalike-calibrate')
|
|
170
|
+
return parseLookalikeCalibrateArguments(argv.slice(1));
|
|
137
171
|
if (command === 'verify-artifact')
|
|
138
172
|
return parseVerifyArtifactArguments(argv.slice(1));
|
|
139
173
|
if (command === 'inspect-archive')
|
|
@@ -152,12 +186,28 @@ function parseCliArgumentsCore(argv) {
|
|
|
152
186
|
return parseMailReviewArguments(argv.slice(1));
|
|
153
187
|
if (command === 'review-evidence')
|
|
154
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));
|
|
155
193
|
if (command === 'domain-control')
|
|
156
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));
|
|
157
203
|
if (command === 'workflow-plan')
|
|
158
204
|
return parseWorkflowPlanArguments(argv.slice(1));
|
|
205
|
+
if (command === 'workflow-run')
|
|
206
|
+
return parseWorkflowRunArguments(argv.slice(1));
|
|
159
207
|
if (command === 'diff')
|
|
160
208
|
return parseDiffArguments(argv.slice(1));
|
|
209
|
+
if (command === 'reconcile')
|
|
210
|
+
return parseReconcileArguments(argv.slice(1));
|
|
161
211
|
if (command === 'timeline')
|
|
162
212
|
return parseTimelineArguments(argv.slice(1));
|
|
163
213
|
if (command === 'export')
|
|
@@ -173,12 +223,24 @@ function parseCliArgumentsCore(argv) {
|
|
|
173
223
|
let strictExit = false;
|
|
174
224
|
let events = false;
|
|
175
225
|
let plan = false;
|
|
176
|
-
|
|
226
|
+
let failOn = null;
|
|
227
|
+
let observerLabel = null;
|
|
228
|
+
let vantageLabel = null;
|
|
229
|
+
const lookupArguments = argv.slice(1);
|
|
230
|
+
for (let index = 0; index < lookupArguments.length; index++) {
|
|
231
|
+
const argument = lookupArguments[index];
|
|
232
|
+
if (argument === undefined)
|
|
233
|
+
break;
|
|
177
234
|
if (argument === '--json') {
|
|
178
235
|
if (output !== 'terminal')
|
|
179
236
|
throw new CliUsageError('Choose only one output format.');
|
|
180
237
|
output = 'json';
|
|
181
238
|
}
|
|
239
|
+
else if (argument === '--junit') {
|
|
240
|
+
if (output !== 'terminal')
|
|
241
|
+
throw new CliUsageError('Choose only one output format.');
|
|
242
|
+
output = 'junit';
|
|
243
|
+
}
|
|
182
244
|
else if (argument === '--markdown' || argument === '--html') {
|
|
183
245
|
if (output !== 'terminal')
|
|
184
246
|
throw new CliUsageError('Choose only one output format.');
|
|
@@ -217,6 +279,27 @@ function parseCliArgumentsCore(argv) {
|
|
|
217
279
|
throw new CliUsageError('--plan may be supplied only once.');
|
|
218
280
|
plan = true;
|
|
219
281
|
}
|
|
282
|
+
else if (argument === '--fail-on') {
|
|
283
|
+
if (failOn !== null)
|
|
284
|
+
throw new CliUsageError('--fail-on may be supplied only once.');
|
|
285
|
+
failOn = parseCliFailPolicies(lookupArguments[++index]);
|
|
286
|
+
}
|
|
287
|
+
else if (argument === '--observer' || argument === '--vantage') {
|
|
288
|
+
const isObserver = argument === '--observer';
|
|
289
|
+
if (isObserver ? observerLabel !== null : vantageLabel !== null) {
|
|
290
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
291
|
+
}
|
|
292
|
+
const value = lookupArguments[++index];
|
|
293
|
+
if (!value || value.startsWith('-'))
|
|
294
|
+
throw new CliUsageError(`${argument} requires one bounded label.`);
|
|
295
|
+
const normalized = value.replace(/\s+/gu, ' ').trim();
|
|
296
|
+
if (!normalized || normalized.length > 80)
|
|
297
|
+
throw new CliUsageError(`${argument} requires a label of at most 80 characters.`);
|
|
298
|
+
if (isObserver)
|
|
299
|
+
observerLabel = normalized;
|
|
300
|
+
else
|
|
301
|
+
vantageLabel = normalized;
|
|
302
|
+
}
|
|
220
303
|
else if (argument === '--quiet')
|
|
221
304
|
quiet = true;
|
|
222
305
|
else if (argument === '--no-color')
|
|
@@ -232,12 +315,12 @@ function parseCliArgumentsCore(argv) {
|
|
|
232
315
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
233
316
|
if (detailSet && output !== 'terminal')
|
|
234
317
|
throw new CliUsageError('--summary and --verbose apply only to terminal output.');
|
|
235
|
-
if (plan &&
|
|
318
|
+
if (plan && !['terminal', 'json'].includes(output))
|
|
236
319
|
throw new CliUsageError('--plan supports terminal or JSON output only.');
|
|
237
|
-
if (plan && (detailSet || strictExit || events || quiet)) {
|
|
320
|
+
if (plan && (detailSet || strictExit || events || quiet || failOn !== null)) {
|
|
238
321
|
throw new CliUsageError('--plan cannot be combined with detail, strict-exit, event, or quiet options.');
|
|
239
322
|
}
|
|
240
|
-
return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, quiet, color };
|
|
323
|
+
return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, observerLabel, vantageLabel, quiet, color, ...(failOn ? { failOn } : {}) };
|
|
241
324
|
}
|
|
242
325
|
function parseCompletionArguments(argv) {
|
|
243
326
|
if (argv.length !== 1 || !['bash', 'zsh', 'fish', 'powershell'].includes(argv[0] || '')) {
|
|
@@ -250,6 +333,84 @@ function parseManualArguments(argv) {
|
|
|
250
333
|
throw new CliUsageError('manual does not accept command arguments.');
|
|
251
334
|
return { action: 'manual' };
|
|
252
335
|
}
|
|
336
|
+
function parseManifestArguments(argv) {
|
|
337
|
+
const sources = [];
|
|
338
|
+
let workflow = null;
|
|
339
|
+
let configurationDigestSha256 = null;
|
|
340
|
+
let output = 'terminal';
|
|
341
|
+
let quiet = false;
|
|
342
|
+
let color = true;
|
|
343
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
344
|
+
const argument = argv[index];
|
|
345
|
+
if (argument === '--json') {
|
|
346
|
+
if (output !== 'terminal')
|
|
347
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
348
|
+
output = 'json';
|
|
349
|
+
}
|
|
350
|
+
else if (argument === '--workflow') {
|
|
351
|
+
if (workflow !== null)
|
|
352
|
+
throw new CliUsageError('--workflow may be supplied only once.');
|
|
353
|
+
workflow = argv[++index] ?? null;
|
|
354
|
+
if (!workflow)
|
|
355
|
+
throw new CliUsageError('--workflow requires a bounded label.');
|
|
356
|
+
}
|
|
357
|
+
else if (argument === '--configuration-digest') {
|
|
358
|
+
if (configurationDigestSha256 !== null)
|
|
359
|
+
throw new CliUsageError('--configuration-digest may be supplied only once.');
|
|
360
|
+
configurationDigestSha256 = argv[++index] ?? null;
|
|
361
|
+
if (!configurationDigestSha256 || !/^sha256:[a-f0-9]{64}$/u.test(configurationDigestSha256)) {
|
|
362
|
+
throw new CliUsageError('--configuration-digest requires a sha256: hexadecimal digest.');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
else if (argument === '--quiet')
|
|
366
|
+
quiet = true;
|
|
367
|
+
else if (argument === '--no-color')
|
|
368
|
+
color = false;
|
|
369
|
+
else if (argument?.startsWith('-'))
|
|
370
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
371
|
+
else if (argument)
|
|
372
|
+
sources.push(argument);
|
|
373
|
+
}
|
|
374
|
+
if (!workflow)
|
|
375
|
+
throw new CliUsageError('manifest requires --workflow <label>.');
|
|
376
|
+
if (sources.length < 1 || sources.length > 16)
|
|
377
|
+
throw new CliUsageError('manifest requires from 1 to 16 JSON artefact files.');
|
|
378
|
+
if (new Set(sources).size !== sources.length)
|
|
379
|
+
throw new CliUsageError('manifest artefact files must be different.');
|
|
380
|
+
if (quiet && output !== 'terminal')
|
|
381
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
382
|
+
return { action: 'manifest', sources, workflow, configurationDigestSha256, output, quiet, color };
|
|
383
|
+
}
|
|
384
|
+
function parseSingleJsonInput(argv, overflowMessage) {
|
|
385
|
+
let source = null;
|
|
386
|
+
let output = 'terminal';
|
|
387
|
+
let quiet = false;
|
|
388
|
+
let color = true;
|
|
389
|
+
for (const argument of argv) {
|
|
390
|
+
if (argument === '--json') {
|
|
391
|
+
if (output !== 'terminal')
|
|
392
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
393
|
+
output = 'json';
|
|
394
|
+
}
|
|
395
|
+
else if (argument === '--quiet')
|
|
396
|
+
quiet = true;
|
|
397
|
+
else if (argument === '--no-color')
|
|
398
|
+
color = false;
|
|
399
|
+
else if (argument.startsWith('-'))
|
|
400
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
401
|
+
else if (source === null)
|
|
402
|
+
source = argument;
|
|
403
|
+
else
|
|
404
|
+
throw new CliUsageError(overflowMessage);
|
|
405
|
+
}
|
|
406
|
+
if (quiet && output !== 'terminal')
|
|
407
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
408
|
+
return { source, output, quiet, color };
|
|
409
|
+
}
|
|
410
|
+
function parseOfflineImportArguments(action, argv) {
|
|
411
|
+
const parsed = parseSingleJsonInput(argv, `${action} accepts one optional versioned JSON input file.`);
|
|
412
|
+
return { action, ...parsed };
|
|
413
|
+
}
|
|
253
414
|
function parseCommandsArguments(argv) {
|
|
254
415
|
let output = 'terminal';
|
|
255
416
|
let quiet = false;
|
|
@@ -309,12 +470,14 @@ function parseBulkArguments(argv) {
|
|
|
309
470
|
let checkpoint = null;
|
|
310
471
|
let resume = false;
|
|
311
472
|
let events = false;
|
|
473
|
+
let plan = false;
|
|
474
|
+
let failOn = null;
|
|
312
475
|
let filter = 'all';
|
|
313
476
|
for (let index = 0; index < argv.length; index++) {
|
|
314
477
|
const argument = argv[index];
|
|
315
478
|
if (argument === undefined)
|
|
316
479
|
break;
|
|
317
|
-
if (argument === '--json' || argument === '--jsonl' || argument === '--csv' || argument === '--domains' || argument === '--queries') {
|
|
480
|
+
if (argument === '--json' || argument === '--jsonl' || argument === '--csv' || argument === '--domains' || argument === '--queries' || argument === '--junit') {
|
|
318
481
|
if (output !== 'terminal')
|
|
319
482
|
throw new CliUsageError('Choose only one output format.');
|
|
320
483
|
output = argument === '--json'
|
|
@@ -325,7 +488,9 @@ function parseBulkArguments(argv) {
|
|
|
325
488
|
? 'csv'
|
|
326
489
|
: argument === '--domains'
|
|
327
490
|
? 'domains'
|
|
328
|
-
: 'queries'
|
|
491
|
+
: argument === '--queries'
|
|
492
|
+
? 'queries'
|
|
493
|
+
: 'junit';
|
|
329
494
|
}
|
|
330
495
|
else if (argument === '--deep' || argument === '--fast') {
|
|
331
496
|
if (scanMode)
|
|
@@ -361,6 +526,16 @@ function parseBulkArguments(argv) {
|
|
|
361
526
|
throw new CliUsageError('--events may be supplied only once.');
|
|
362
527
|
events = true;
|
|
363
528
|
}
|
|
529
|
+
else if (argument === '--plan') {
|
|
530
|
+
if (plan)
|
|
531
|
+
throw new CliUsageError('--plan may be supplied only once.');
|
|
532
|
+
plan = true;
|
|
533
|
+
}
|
|
534
|
+
else if (argument === '--fail-on') {
|
|
535
|
+
if (failOn !== null)
|
|
536
|
+
throw new CliUsageError('--fail-on may be supplied only once.');
|
|
537
|
+
failOn = parseCliFailPolicies(argv[++index]);
|
|
538
|
+
}
|
|
364
539
|
else if (argument === '--registered-only' || argument === '--inconclusive-only' || argument === '--errors-only') {
|
|
365
540
|
if (filter !== 'all')
|
|
366
541
|
throw new CliUsageError('Bulk output filters are mutually exclusive and may be supplied only once.');
|
|
@@ -385,37 +560,22 @@ function parseBulkArguments(argv) {
|
|
|
385
560
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
386
561
|
if (resume && checkpoint === null)
|
|
387
562
|
throw new CliUsageError('--resume requires --checkpoint.');
|
|
563
|
+
if (plan && (events || resume || checkpoint !== null || quiet || failOn !== null || !['terminal', 'json'].includes(output))) {
|
|
564
|
+
throw new CliUsageError('--plan supports terminal or JSON output and cannot be combined with events, checkpoint, resume, or quiet options.');
|
|
565
|
+
}
|
|
388
566
|
const maximum = deep ? 3 : 8;
|
|
389
567
|
if (concurrency !== null && concurrency > maximum) {
|
|
390
568
|
throw new CliUsageError(`--concurrency is capped at ${maximum} in ${deep ? 'deep' : 'fast'} bulk mode.`);
|
|
391
569
|
}
|
|
392
|
-
return { action: 'bulk', source, output, deep, quiet, color, concurrency: concurrency ?? (deep ? 2 : 4), checkpoint, resume, events, filter };
|
|
570
|
+
return { action: 'bulk', source, output, deep, quiet, color, concurrency: concurrency ?? (deep ? 2 : 4), checkpoint, resume, events, plan, filter, ...(failOn ? { failOn } : {}) };
|
|
393
571
|
}
|
|
394
572
|
function parseCtSearchArguments(argv) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (output !== 'terminal')
|
|
402
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
403
|
-
output = 'json';
|
|
404
|
-
}
|
|
405
|
-
else if (argument === '--quiet')
|
|
406
|
-
quiet = true;
|
|
407
|
-
else if (argument === '--no-color')
|
|
408
|
-
color = false;
|
|
409
|
-
else if (argument.startsWith('-'))
|
|
410
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
411
|
-
else if (keyword === null)
|
|
412
|
-
keyword = argument;
|
|
413
|
-
else
|
|
414
|
-
throw new CliUsageError('ct-search accepts one keyword. Quote multi-word keywords as one argument.');
|
|
415
|
-
}
|
|
416
|
-
if (quiet && output !== 'terminal')
|
|
417
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
418
|
-
return { action: 'ct-search', keyword, output, quiet, color };
|
|
573
|
+
const { source: keyword, ...options } = parseSingleJsonInput(argv, 'ct-search accepts one keyword. Quote multi-word keywords as one argument.');
|
|
574
|
+
return { action: 'ct-search', keyword, ...options };
|
|
575
|
+
}
|
|
576
|
+
function parseCtIntakeArguments(argv) {
|
|
577
|
+
const parsed = parseSingleJsonInput(argv, 'ct-intake accepts one optional versioned JSON event file.');
|
|
578
|
+
return { action: 'ct-intake', ...parsed };
|
|
419
579
|
}
|
|
420
580
|
function parseDiscoverArguments(argv) {
|
|
421
581
|
let seed = null;
|
|
@@ -539,6 +699,8 @@ function parseDiscoverScanArguments(argv) {
|
|
|
539
699
|
let filter = 'all';
|
|
540
700
|
let filterSet = false;
|
|
541
701
|
let events = false;
|
|
702
|
+
let plan = false;
|
|
703
|
+
let failOn = null;
|
|
542
704
|
for (let index = 0; index < argv.length; index++) {
|
|
543
705
|
const argument = argv[index];
|
|
544
706
|
if (argument === undefined)
|
|
@@ -669,6 +831,16 @@ function parseDiscoverScanArguments(argv) {
|
|
|
669
831
|
throw new CliUsageError('--events may be supplied only once.');
|
|
670
832
|
events = true;
|
|
671
833
|
}
|
|
834
|
+
else if (argument === '--plan') {
|
|
835
|
+
if (plan)
|
|
836
|
+
throw new CliUsageError('--plan may be supplied only once.');
|
|
837
|
+
plan = true;
|
|
838
|
+
}
|
|
839
|
+
else if (argument === '--fail-on') {
|
|
840
|
+
if (failOn !== null)
|
|
841
|
+
throw new CliUsageError('--fail-on may be supplied only once.');
|
|
842
|
+
failOn = parseCliFailPolicies(argv[++index]);
|
|
843
|
+
}
|
|
672
844
|
else if (argument === '--quiet')
|
|
673
845
|
quiet = true;
|
|
674
846
|
else if (argument === '--no-color')
|
|
@@ -689,6 +861,9 @@ function parseDiscoverScanArguments(argv) {
|
|
|
689
861
|
}
|
|
690
862
|
if (resume && checkpoint === null)
|
|
691
863
|
throw new CliUsageError('--resume requires --checkpoint.');
|
|
864
|
+
if (plan && (events || resume || checkpoint !== null || observationSnapshot !== null || quiet || failOn !== null || !['terminal', 'json'].includes(output))) {
|
|
865
|
+
throw new CliUsageError('--plan supports terminal or JSON output and cannot be combined with events, checkpoint, resume, observation snapshots, or quiet options.');
|
|
866
|
+
}
|
|
692
867
|
if (quiet && output !== 'terminal')
|
|
693
868
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
694
869
|
if (dictionarySource && preset === 'common')
|
|
@@ -697,7 +872,7 @@ function parseDiscoverScanArguments(argv) {
|
|
|
697
872
|
action: 'discover-scan', seed, output, quiet, color, preset, keyboardLayout, tldText,
|
|
698
873
|
dictionarySource, familyText, deep, scanLimit: scanLimit ?? Math.min(100, maximum),
|
|
699
874
|
chunkSize: chunkSize ?? 25, concurrency: concurrency ?? (deep ? 2 : 4), checkpoint,
|
|
700
|
-
resume, resolverText, observationSnapshot, allowlistSource, filter, events,
|
|
875
|
+
resume, resolverText, observationSnapshot, allowlistSource, filter, events, plan, ...(failOn ? { failOn } : {}),
|
|
701
876
|
};
|
|
702
877
|
}
|
|
703
878
|
function positiveIntegerOption(value, option, maximum) {
|
|
@@ -717,6 +892,7 @@ function parsePostureArguments(argv) {
|
|
|
717
892
|
let retiredSelectorText = null;
|
|
718
893
|
let mailProfile = 'standard';
|
|
719
894
|
let mailProfileSeen = false;
|
|
895
|
+
let ownedDomain = false;
|
|
720
896
|
for (let index = 0; index < argv.length; index++) {
|
|
721
897
|
const argument = argv[index];
|
|
722
898
|
if (argument === undefined)
|
|
@@ -726,6 +902,16 @@ function parsePostureArguments(argv) {
|
|
|
726
902
|
throw new CliUsageError('--json may be supplied only once.');
|
|
727
903
|
output = 'json';
|
|
728
904
|
}
|
|
905
|
+
else if (argument === '--sarif') {
|
|
906
|
+
if (output !== 'terminal')
|
|
907
|
+
throw new CliUsageError('Choose only one output format.');
|
|
908
|
+
output = 'sarif';
|
|
909
|
+
}
|
|
910
|
+
else if (argument === '--owned-domain') {
|
|
911
|
+
if (ownedDomain)
|
|
912
|
+
throw new CliUsageError('--owned-domain may be supplied only once.');
|
|
913
|
+
ownedDomain = true;
|
|
914
|
+
}
|
|
729
915
|
else if (argument === '--selectors') {
|
|
730
916
|
if (selectorText !== null)
|
|
731
917
|
throw new CliUsageError('--selectors may be supplied only once.');
|
|
@@ -765,36 +951,28 @@ function parsePostureArguments(argv) {
|
|
|
765
951
|
}
|
|
766
952
|
if (quiet && output !== 'terminal')
|
|
767
953
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
768
|
-
|
|
954
|
+
if (output === 'sarif' && !ownedDomain)
|
|
955
|
+
throw new CliUsageError('--sarif requires --owned-domain to confirm the passive posture target is controlled by the operator.');
|
|
956
|
+
return { action: 'posture', domain, output, quiet, color, selectorText, retiredSelectorText, mailProfile, ownedDomain };
|
|
769
957
|
}
|
|
770
958
|
function parseHttpArguments(argv) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
let quiet = false;
|
|
774
|
-
let color = true;
|
|
775
|
-
for (const argument of argv) {
|
|
776
|
-
if (argument === '--json') {
|
|
777
|
-
if (output !== 'terminal')
|
|
778
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
779
|
-
output = 'json';
|
|
780
|
-
}
|
|
781
|
-
else if (argument === '--quiet')
|
|
782
|
-
quiet = true;
|
|
783
|
-
else if (argument === '--no-color')
|
|
784
|
-
color = false;
|
|
785
|
-
else if (argument.startsWith('-'))
|
|
786
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
787
|
-
else if (domain === null)
|
|
788
|
-
domain = argument;
|
|
789
|
-
else
|
|
790
|
-
throw new CliUsageError('http accepts one domain.');
|
|
791
|
-
}
|
|
792
|
-
if (quiet && output !== 'terminal')
|
|
793
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
794
|
-
return { action: 'http', domain, output, quiet, color };
|
|
959
|
+
const { source: domain, ...options } = parseSingleJsonInput(argv, 'http accepts one domain.');
|
|
960
|
+
return { action: 'http', domain, ...options };
|
|
795
961
|
}
|
|
796
962
|
function parseTlsArguments(argv) {
|
|
797
|
-
|
|
963
|
+
const { source: hostname, ...options } = parseSingleJsonInput(argv, 'tls accepts one hostname.');
|
|
964
|
+
return { action: 'tls', hostname, ...options };
|
|
965
|
+
}
|
|
966
|
+
function parseCompareArguments(argv) {
|
|
967
|
+
const parsed = parseSingleJsonInput(argv, 'compare accepts one optional lookup JSON file. Otherwise pipe one lookup document on stdin.');
|
|
968
|
+
return { action: 'compare', ...parsed };
|
|
969
|
+
}
|
|
970
|
+
function parsePageCompareArguments(argv) {
|
|
971
|
+
const parsed = parseTwoFileComparisonArguments(argv, 'page-compare');
|
|
972
|
+
return { action: 'page-compare', ...parsed };
|
|
973
|
+
}
|
|
974
|
+
function parseTwoFileComparisonArguments(argv, command) {
|
|
975
|
+
const sources = [];
|
|
798
976
|
let output = 'terminal';
|
|
799
977
|
let quiet = false;
|
|
800
978
|
let color = true;
|
|
@@ -810,26 +988,48 @@ function parseTlsArguments(argv) {
|
|
|
810
988
|
color = false;
|
|
811
989
|
else if (argument.startsWith('-'))
|
|
812
990
|
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
813
|
-
else if (hostname === null)
|
|
814
|
-
hostname = argument;
|
|
815
991
|
else
|
|
816
|
-
|
|
992
|
+
sources.push(argument);
|
|
817
993
|
}
|
|
994
|
+
if (sources.length !== 2 || !sources[0] || !sources[1])
|
|
995
|
+
throw new CliUsageError(`${command} requires two input JSON files.`);
|
|
996
|
+
if (sources[0] === sources[1])
|
|
997
|
+
throw new CliUsageError(`${command} requires two different input files.`);
|
|
818
998
|
if (quiet && output !== 'terminal')
|
|
819
999
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
820
|
-
return {
|
|
1000
|
+
return { leftSource: sources[0], rightSource: sources[1], output, quiet, color };
|
|
821
1001
|
}
|
|
822
|
-
function
|
|
1002
|
+
function parseMailReviewArguments(argv) {
|
|
1003
|
+
const parsed = parseSingleJsonInput(argv, 'mail-review accepts one optional Bulk JSON or JSONL input file.');
|
|
1004
|
+
return { action: 'mail-review', ...parsed };
|
|
1005
|
+
}
|
|
1006
|
+
function parseReviewEvidenceArguments(argv) {
|
|
823
1007
|
let source = null;
|
|
1008
|
+
let mmdbSource = null;
|
|
824
1009
|
let output = 'terminal';
|
|
1010
|
+
let strictExit = false;
|
|
825
1011
|
let quiet = false;
|
|
826
1012
|
let color = true;
|
|
827
|
-
for (
|
|
1013
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1014
|
+
const argument = argv[index];
|
|
1015
|
+
if (argument === undefined)
|
|
1016
|
+
break;
|
|
828
1017
|
if (argument === '--json') {
|
|
829
1018
|
if (output !== 'terminal')
|
|
830
1019
|
throw new CliUsageError('--json may be supplied only once.');
|
|
831
1020
|
output = 'json';
|
|
832
1021
|
}
|
|
1022
|
+
else if (argument === '--mmdb') {
|
|
1023
|
+
const value = argv[++index];
|
|
1024
|
+
if (!value || value.startsWith('-') || mmdbSource !== null)
|
|
1025
|
+
throw new CliUsageError('--mmdb requires one database file and may be supplied only once.');
|
|
1026
|
+
mmdbSource = value;
|
|
1027
|
+
}
|
|
1028
|
+
else if (argument === '--strict-exit') {
|
|
1029
|
+
if (strictExit)
|
|
1030
|
+
throw new CliUsageError('--strict-exit may be supplied only once.');
|
|
1031
|
+
strictExit = true;
|
|
1032
|
+
}
|
|
833
1033
|
else if (argument === '--quiet')
|
|
834
1034
|
quiet = true;
|
|
835
1035
|
else if (argument === '--no-color')
|
|
@@ -839,55 +1039,108 @@ function parseCompareArguments(argv) {
|
|
|
839
1039
|
else if (source === null)
|
|
840
1040
|
source = argument;
|
|
841
1041
|
else
|
|
842
|
-
throw new CliUsageError('
|
|
1042
|
+
throw new CliUsageError('review-evidence accepts one optional versioned JSON input file.');
|
|
843
1043
|
}
|
|
844
1044
|
if (quiet && output !== 'terminal')
|
|
845
1045
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
846
|
-
return { action: '
|
|
1046
|
+
return { action: 'review-evidence', source, mmdbSource, output, strictExit, quiet, color };
|
|
847
1047
|
}
|
|
848
|
-
function
|
|
849
|
-
const parsed =
|
|
850
|
-
return { action: '
|
|
1048
|
+
function parseBriefArguments(argv) {
|
|
1049
|
+
const parsed = parseSingleJsonInput(argv, 'brief accepts one optional saved Lookup file.');
|
|
1050
|
+
return { action: 'brief', ...parsed };
|
|
851
1051
|
}
|
|
852
|
-
function
|
|
853
|
-
|
|
1052
|
+
function parseCasePackArguments(argv) {
|
|
1053
|
+
let source = null;
|
|
854
1054
|
let output = 'terminal';
|
|
1055
|
+
let audience = null;
|
|
1056
|
+
let reviewed = false;
|
|
855
1057
|
let quiet = false;
|
|
856
1058
|
let color = true;
|
|
857
|
-
for (
|
|
1059
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1060
|
+
const argument = argv[index];
|
|
1061
|
+
if (argument === undefined)
|
|
1062
|
+
break;
|
|
858
1063
|
if (argument === '--json') {
|
|
859
1064
|
if (output !== 'terminal')
|
|
860
1065
|
throw new CliUsageError('--json may be supplied only once.');
|
|
861
1066
|
output = 'json';
|
|
862
1067
|
}
|
|
1068
|
+
else if (argument === '--audience') {
|
|
1069
|
+
if (audience !== null)
|
|
1070
|
+
throw new CliUsageError('--audience may be supplied only once.');
|
|
1071
|
+
const value = argv[++index];
|
|
1072
|
+
if (!['internal', 'trusted', 'public'].includes(value || ''))
|
|
1073
|
+
throw new CliUsageError('--audience requires internal, trusted, or public.');
|
|
1074
|
+
audience = value;
|
|
1075
|
+
}
|
|
1076
|
+
else if (argument === '--reviewed') {
|
|
1077
|
+
if (reviewed)
|
|
1078
|
+
throw new CliUsageError('--reviewed may be supplied only once.');
|
|
1079
|
+
reviewed = true;
|
|
1080
|
+
}
|
|
863
1081
|
else if (argument === '--quiet')
|
|
864
1082
|
quiet = true;
|
|
865
1083
|
else if (argument === '--no-color')
|
|
866
1084
|
color = false;
|
|
867
1085
|
else if (argument.startsWith('-'))
|
|
868
1086
|
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1087
|
+
else if (source === null)
|
|
1088
|
+
source = argument;
|
|
869
1089
|
else
|
|
870
|
-
|
|
1090
|
+
throw new CliUsageError('case-pack accepts one optional case-export file.');
|
|
871
1091
|
}
|
|
872
|
-
if (
|
|
873
|
-
throw new CliUsageError(
|
|
874
|
-
if (sources[0] === sources[1])
|
|
875
|
-
throw new CliUsageError(`${command} requires two different input files.`);
|
|
1092
|
+
if (audience === null)
|
|
1093
|
+
throw new CliUsageError('case-pack requires --audience internal, trusted, or public.');
|
|
876
1094
|
if (quiet && output !== 'terminal')
|
|
877
1095
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
878
|
-
return {
|
|
1096
|
+
return { action: 'case-pack', source, output, audience, reviewed, quiet, color };
|
|
879
1097
|
}
|
|
880
|
-
function
|
|
1098
|
+
function parseMonitorOnceArguments(argv) {
|
|
881
1099
|
let source = null;
|
|
1100
|
+
let previousSource = null;
|
|
882
1101
|
let output = 'terminal';
|
|
1102
|
+
let limit = 20;
|
|
1103
|
+
let concurrency = 2;
|
|
1104
|
+
let failOn = null;
|
|
883
1105
|
let quiet = false;
|
|
884
1106
|
let color = true;
|
|
885
|
-
for (
|
|
1107
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1108
|
+
const argument = argv[index];
|
|
1109
|
+
if (argument === undefined)
|
|
1110
|
+
break;
|
|
886
1111
|
if (argument === '--json') {
|
|
887
1112
|
if (output !== 'terminal')
|
|
888
1113
|
throw new CliUsageError('--json may be supplied only once.');
|
|
889
1114
|
output = 'json';
|
|
890
1115
|
}
|
|
1116
|
+
else if (argument === '--junit') {
|
|
1117
|
+
if (output !== 'terminal')
|
|
1118
|
+
throw new CliUsageError('Choose only one output format.');
|
|
1119
|
+
output = 'junit';
|
|
1120
|
+
}
|
|
1121
|
+
else if (argument === '--previous') {
|
|
1122
|
+
if (previousSource !== null)
|
|
1123
|
+
throw new CliUsageError('--previous may be supplied only once.');
|
|
1124
|
+
const value = argv[++index];
|
|
1125
|
+
if (!value || value.startsWith('-'))
|
|
1126
|
+
throw new CliUsageError('--previous requires one prior monitor snapshot file.');
|
|
1127
|
+
previousSource = value;
|
|
1128
|
+
}
|
|
1129
|
+
else if (argument === '--limit' || argument === '--concurrency') {
|
|
1130
|
+
const value = Number(argv[++index]);
|
|
1131
|
+
const maximum = argument === '--limit' ? 20 : 3;
|
|
1132
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
|
|
1133
|
+
throw new CliUsageError(`${argument} requires an integer from 1 to ${maximum}.`);
|
|
1134
|
+
if (argument === '--limit')
|
|
1135
|
+
limit = value;
|
|
1136
|
+
else
|
|
1137
|
+
concurrency = value;
|
|
1138
|
+
}
|
|
1139
|
+
else if (argument === '--fail-on') {
|
|
1140
|
+
if (failOn !== null)
|
|
1141
|
+
throw new CliUsageError('--fail-on may be supplied only once.');
|
|
1142
|
+
failOn = parseCliFailPolicies(argv[++index]);
|
|
1143
|
+
}
|
|
891
1144
|
else if (argument === '--quiet')
|
|
892
1145
|
quiet = true;
|
|
893
1146
|
else if (argument === '--no-color')
|
|
@@ -897,23 +1150,79 @@ function parseMailReviewArguments(argv) {
|
|
|
897
1150
|
else if (source === null)
|
|
898
1151
|
source = argument;
|
|
899
1152
|
else
|
|
900
|
-
throw new CliUsageError('
|
|
1153
|
+
throw new CliUsageError('monitor-once accepts one optional domain-control manifest file.');
|
|
901
1154
|
}
|
|
902
1155
|
if (quiet && output !== 'terminal')
|
|
903
1156
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
904
|
-
return { action: '
|
|
1157
|
+
return { action: 'monitor-once', source, previousSource, output, limit, concurrency, quiet, color, ...(failOn ? { failOn } : {}) };
|
|
905
1158
|
}
|
|
906
|
-
function
|
|
1159
|
+
function parseDomainControlArguments(argv) {
|
|
1160
|
+
const parsed = parseSingleJsonInput(argv, 'domain-control accepts one optional versioned JSON input file.');
|
|
1161
|
+
return { action: 'domain-control', ...parsed };
|
|
1162
|
+
}
|
|
1163
|
+
function parseAssuranceArguments(argv) {
|
|
1164
|
+
const parsed = parseSingleJsonInput(argv, 'assurance accepts one optional versioned JSON input file.');
|
|
1165
|
+
return { action: 'assurance', ...parsed };
|
|
1166
|
+
}
|
|
1167
|
+
function parseChangePacketArguments(argv) {
|
|
1168
|
+
const parsed = parseSingleJsonInput(argv, 'change-packet accepts one optional versioned JSON input file.');
|
|
1169
|
+
return { action: 'change-packet', ...parsed };
|
|
1170
|
+
}
|
|
1171
|
+
function parseSharingReviewArguments(argv) {
|
|
907
1172
|
let source = null;
|
|
908
1173
|
let output = 'terminal';
|
|
1174
|
+
let marking = null;
|
|
1175
|
+
let recipientScope = null;
|
|
1176
|
+
let purpose = null;
|
|
1177
|
+
let humanReviewed = false;
|
|
1178
|
+
let personalDataReviewed = false;
|
|
1179
|
+
let redactionsConfirmed = false;
|
|
909
1180
|
let quiet = false;
|
|
910
1181
|
let color = true;
|
|
911
|
-
for (
|
|
1182
|
+
for (let index = 0; index < argv.length; index++) {
|
|
1183
|
+
const argument = argv[index];
|
|
1184
|
+
if (argument === undefined)
|
|
1185
|
+
break;
|
|
912
1186
|
if (argument === '--json') {
|
|
913
1187
|
if (output !== 'terminal')
|
|
914
1188
|
throw new CliUsageError('--json may be supplied only once.');
|
|
915
1189
|
output = 'json';
|
|
916
1190
|
}
|
|
1191
|
+
else if (argument === '--marking') {
|
|
1192
|
+
if (marking !== null)
|
|
1193
|
+
throw new CliUsageError('--marking may be supplied only once.');
|
|
1194
|
+
const value = argv[++index];
|
|
1195
|
+
if (!['clear', 'green', 'amber', 'amber-strict', 'red'].includes(value || '')) {
|
|
1196
|
+
throw new CliUsageError('--marking requires clear, green, amber, amber-strict, or red.');
|
|
1197
|
+
}
|
|
1198
|
+
marking = value;
|
|
1199
|
+
}
|
|
1200
|
+
else if (argument === '--recipient-scope') {
|
|
1201
|
+
if (recipientScope !== null)
|
|
1202
|
+
throw new CliUsageError('--recipient-scope may be supplied only once.');
|
|
1203
|
+
const value = argv[++index];
|
|
1204
|
+
if (!['public', 'community', 'organization', 'named-recipients'].includes(value || '')) {
|
|
1205
|
+
throw new CliUsageError('--recipient-scope requires public, community, organization, or named-recipients.');
|
|
1206
|
+
}
|
|
1207
|
+
recipientScope = value;
|
|
1208
|
+
}
|
|
1209
|
+
else if (argument === '--purpose') {
|
|
1210
|
+
if (purpose !== null)
|
|
1211
|
+
throw new CliUsageError('--purpose may be supplied only once.');
|
|
1212
|
+
const value = argv[++index];
|
|
1213
|
+
if (!value || value.startsWith('-'))
|
|
1214
|
+
throw new CliUsageError('--purpose requires one bounded description.');
|
|
1215
|
+
const normalized = value.replace(/\s+/gu, ' ').trim();
|
|
1216
|
+
if (!normalized || normalized.length > 200)
|
|
1217
|
+
throw new CliUsageError('--purpose is limited to 200 characters.');
|
|
1218
|
+
purpose = normalized;
|
|
1219
|
+
}
|
|
1220
|
+
else if (argument === '--human-reviewed')
|
|
1221
|
+
humanReviewed = true;
|
|
1222
|
+
else if (argument === '--personal-data-reviewed')
|
|
1223
|
+
personalDataReviewed = true;
|
|
1224
|
+
else if (argument === '--redactions-confirmed')
|
|
1225
|
+
redactionsConfirmed = true;
|
|
917
1226
|
else if (argument === '--quiet')
|
|
918
1227
|
quiet = true;
|
|
919
1228
|
else if (argument === '--no-color')
|
|
@@ -923,14 +1232,20 @@ function parseReviewEvidenceArguments(argv) {
|
|
|
923
1232
|
else if (source === null)
|
|
924
1233
|
source = argument;
|
|
925
1234
|
else
|
|
926
|
-
throw new CliUsageError('review
|
|
1235
|
+
throw new CliUsageError('sharing-review accepts one optional artefact JSON file.');
|
|
1236
|
+
}
|
|
1237
|
+
if (!marking || !recipientScope || !purpose) {
|
|
1238
|
+
throw new CliUsageError('sharing-review requires --marking, --recipient-scope, and --purpose.');
|
|
927
1239
|
}
|
|
928
1240
|
if (quiet && output !== 'terminal')
|
|
929
1241
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
930
|
-
return {
|
|
1242
|
+
return {
|
|
1243
|
+
action: 'sharing-review', source, output, marking, recipientScope, purpose,
|
|
1244
|
+
humanReviewed, personalDataReviewed, redactionsConfirmed, quiet, color,
|
|
1245
|
+
};
|
|
931
1246
|
}
|
|
932
|
-
function
|
|
933
|
-
|
|
1247
|
+
function parseWorkflowPlanArguments(argv) {
|
|
1248
|
+
const positional = [];
|
|
934
1249
|
let output = 'terminal';
|
|
935
1250
|
let quiet = false;
|
|
936
1251
|
let color = true;
|
|
@@ -946,26 +1261,48 @@ function parseDomainControlArguments(argv) {
|
|
|
946
1261
|
color = false;
|
|
947
1262
|
else if (argument.startsWith('-'))
|
|
948
1263
|
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
949
|
-
else if (source === null)
|
|
950
|
-
source = argument;
|
|
951
1264
|
else
|
|
952
|
-
|
|
1265
|
+
positional.push(argument);
|
|
1266
|
+
}
|
|
1267
|
+
if (positional.length !== 2)
|
|
1268
|
+
throw new CliUsageError('workflow-plan requires one fixed recipe and one subject.');
|
|
1269
|
+
const recipe = positional[0];
|
|
1270
|
+
if (!INVESTIGATION_PLAN_RECIPES.includes(recipe)) {
|
|
1271
|
+
throw new CliUsageError(`workflow-plan recipe must be one of: ${INVESTIGATION_PLAN_RECIPES.join(', ')}.`);
|
|
953
1272
|
}
|
|
954
1273
|
if (quiet && output !== 'terminal')
|
|
955
1274
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
956
|
-
return { action: '
|
|
1275
|
+
return { action: 'workflow-plan', recipe: recipe, subject: positional[1], output, quiet, color };
|
|
957
1276
|
}
|
|
958
|
-
function
|
|
1277
|
+
function parseWorkflowRunArguments(argv) {
|
|
959
1278
|
const positional = [];
|
|
1279
|
+
let resumeSource = null;
|
|
1280
|
+
let approveNetwork = false;
|
|
960
1281
|
let output = 'terminal';
|
|
961
1282
|
let quiet = false;
|
|
962
1283
|
let color = true;
|
|
963
|
-
for (
|
|
1284
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1285
|
+
const argument = argv[index];
|
|
1286
|
+
if (argument === undefined)
|
|
1287
|
+
break;
|
|
964
1288
|
if (argument === '--json') {
|
|
965
1289
|
if (output !== 'terminal')
|
|
966
1290
|
throw new CliUsageError('--json may be supplied only once.');
|
|
967
1291
|
output = 'json';
|
|
968
1292
|
}
|
|
1293
|
+
else if (argument === '--resume') {
|
|
1294
|
+
if (resumeSource !== null)
|
|
1295
|
+
throw new CliUsageError('--resume may be supplied only once.');
|
|
1296
|
+
const value = argv[++index];
|
|
1297
|
+
if (!value || value.startsWith('-'))
|
|
1298
|
+
throw new CliUsageError('--resume requires one prior workflow-run state file.');
|
|
1299
|
+
resumeSource = value;
|
|
1300
|
+
}
|
|
1301
|
+
else if (argument === '--approve-network') {
|
|
1302
|
+
if (approveNetwork)
|
|
1303
|
+
throw new CliUsageError('--approve-network may be supplied only once.');
|
|
1304
|
+
approveNetwork = true;
|
|
1305
|
+
}
|
|
969
1306
|
else if (argument === '--quiet')
|
|
970
1307
|
quiet = true;
|
|
971
1308
|
else if (argument === '--no-color')
|
|
@@ -976,14 +1313,14 @@ function parseWorkflowPlanArguments(argv) {
|
|
|
976
1313
|
positional.push(argument);
|
|
977
1314
|
}
|
|
978
1315
|
if (positional.length !== 2)
|
|
979
|
-
throw new CliUsageError('workflow-
|
|
1316
|
+
throw new CliUsageError('workflow-run requires one fixed recipe and one subject.');
|
|
980
1317
|
const recipe = positional[0];
|
|
981
1318
|
if (!INVESTIGATION_PLAN_RECIPES.includes(recipe)) {
|
|
982
|
-
throw new CliUsageError(`workflow-
|
|
1319
|
+
throw new CliUsageError(`workflow-run recipe must be one of: ${INVESTIGATION_PLAN_RECIPES.join(', ')}.`);
|
|
983
1320
|
}
|
|
984
1321
|
if (quiet && output !== 'terminal')
|
|
985
1322
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
986
|
-
return { action: 'workflow-
|
|
1323
|
+
return { action: 'workflow-run', recipe: recipe, subject: positional[1], resumeSource, approveNetwork, output, quiet, color };
|
|
987
1324
|
}
|
|
988
1325
|
function parseDiffArguments(argv) {
|
|
989
1326
|
let leftSource = null;
|
|
@@ -1018,7 +1355,7 @@ function parseDiffArguments(argv) {
|
|
|
1018
1355
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1019
1356
|
return { action: 'diff', leftSource, rightSource, output, quiet, color };
|
|
1020
1357
|
}
|
|
1021
|
-
function
|
|
1358
|
+
function parseReconcileArguments(argv) {
|
|
1022
1359
|
const sources = [];
|
|
1023
1360
|
let output = 'terminal';
|
|
1024
1361
|
let quiet = false;
|
|
@@ -1038,18 +1375,18 @@ function parseTimelineArguments(argv) {
|
|
|
1038
1375
|
else
|
|
1039
1376
|
sources.push(argument);
|
|
1040
1377
|
}
|
|
1041
|
-
if (sources.length < 2 || sources.length >
|
|
1042
|
-
throw new CliUsageError('
|
|
1378
|
+
if (sources.length < 2 || sources.length > 5) {
|
|
1379
|
+
throw new CliUsageError('reconcile requires from 2 to 5 saved lookup JSON files.');
|
|
1043
1380
|
}
|
|
1044
1381
|
if (new Set(sources).size !== sources.length) {
|
|
1045
|
-
throw new CliUsageError('
|
|
1382
|
+
throw new CliUsageError('reconcile input files must be different.');
|
|
1046
1383
|
}
|
|
1047
1384
|
if (quiet && output !== 'terminal')
|
|
1048
1385
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1049
|
-
return { action: '
|
|
1386
|
+
return { action: 'reconcile', sources, output, quiet, color };
|
|
1050
1387
|
}
|
|
1051
|
-
function
|
|
1052
|
-
|
|
1388
|
+
function parseTimelineArguments(argv) {
|
|
1389
|
+
const sources = [];
|
|
1053
1390
|
let output = 'terminal';
|
|
1054
1391
|
let quiet = false;
|
|
1055
1392
|
let color = true;
|
|
@@ -1065,40 +1402,61 @@ function parseRegistrySupportArguments(argv) {
|
|
|
1065
1402
|
color = false;
|
|
1066
1403
|
else if (argument.startsWith('-'))
|
|
1067
1404
|
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1068
|
-
else if (target === null)
|
|
1069
|
-
target = argument;
|
|
1070
1405
|
else
|
|
1071
|
-
|
|
1406
|
+
sources.push(argument);
|
|
1407
|
+
}
|
|
1408
|
+
if (sources.length < 2 || sources.length > 20) {
|
|
1409
|
+
throw new CliUsageError('timeline requires from 2 to 20 saved lookup JSON files.');
|
|
1410
|
+
}
|
|
1411
|
+
if (new Set(sources).size !== sources.length) {
|
|
1412
|
+
throw new CliUsageError('timeline input files must be different.');
|
|
1072
1413
|
}
|
|
1073
1414
|
if (quiet && output !== 'terminal')
|
|
1074
1415
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1075
|
-
return { action: '
|
|
1416
|
+
return { action: 'timeline', sources, output, quiet, color };
|
|
1076
1417
|
}
|
|
1077
|
-
function
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1418
|
+
function parseRegistrySupportArguments(argv) {
|
|
1419
|
+
const { source: target, ...options } = parseSingleJsonInput(argv, 'registry-support accepts one domain or suffix.');
|
|
1420
|
+
return { action: 'registry-support', target, ...options };
|
|
1421
|
+
}
|
|
1422
|
+
function parseRegistryDoctorArguments(argv) {
|
|
1423
|
+
const parsed = parseSingleJsonInput(argv, 'registry-doctor accepts one optional saved Lookup JSON file.');
|
|
1424
|
+
return { action: 'registry-doctor', ...parsed };
|
|
1425
|
+
}
|
|
1426
|
+
function parseRegistryCohortArguments(argv) {
|
|
1427
|
+
const parsed = parseSingleJsonInput(argv, 'registry-cohort accepts one optional JSON or JSONL input file.');
|
|
1428
|
+
return { action: 'registry-cohort', ...parsed };
|
|
1429
|
+
}
|
|
1430
|
+
function parseRegistryScaffoldArguments(argv) {
|
|
1431
|
+
let profile = '';
|
|
1432
|
+
let suffix = '';
|
|
1433
|
+
let scenario = '';
|
|
1434
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
1435
|
+
const option = argv[index];
|
|
1436
|
+
const value = argv[index + 1];
|
|
1437
|
+
if (!value)
|
|
1438
|
+
throw new CliUsageError(`${option || 'Option'} requires a value.`);
|
|
1439
|
+
if (option === '--profile' && !profile)
|
|
1440
|
+
profile = value;
|
|
1441
|
+
else if (option === '--suffix' && !suffix)
|
|
1442
|
+
suffix = value;
|
|
1443
|
+
else if (option === '--scenario' && !scenario)
|
|
1444
|
+
scenario = value;
|
|
1096
1445
|
else
|
|
1097
|
-
throw new CliUsageError(
|
|
1446
|
+
throw new CliUsageError(`Unknown or repeated option "${option || ''}".`);
|
|
1098
1447
|
}
|
|
1099
|
-
if (
|
|
1100
|
-
throw new CliUsageError('
|
|
1101
|
-
|
|
1448
|
+
if (!profile || !suffix || !['registered', 'not_found', 'inconclusive'].includes(scenario)) {
|
|
1449
|
+
throw new CliUsageError('registry-scaffold requires --profile, --suffix, and --scenario <registered|not_found|inconclusive>.');
|
|
1450
|
+
}
|
|
1451
|
+
return { action: 'registry-scaffold', profile, suffix, scenario: scenario };
|
|
1452
|
+
}
|
|
1453
|
+
function parseRiskCalibrateArguments(argv) {
|
|
1454
|
+
const parsed = parseSingleJsonInput(argv, 'risk-calibrate accepts one optional dataset file. Otherwise pipe one dataset on stdin.');
|
|
1455
|
+
return { action: 'risk-calibrate', ...parsed };
|
|
1456
|
+
}
|
|
1457
|
+
function parseLookalikeCalibrateArguments(argv) {
|
|
1458
|
+
const parsed = parseSingleJsonInput(argv, 'lookalike-calibrate accepts one optional reviewed dataset JSON file.');
|
|
1459
|
+
return { action: 'lookalike-calibrate', ...parsed };
|
|
1102
1460
|
}
|
|
1103
1461
|
function parseVerifyArtifactArguments(argv) {
|
|
1104
1462
|
let source = null;
|
|
@@ -1133,37 +1491,15 @@ function parseVerifyArtifactArguments(argv) {
|
|
|
1133
1491
|
else if (source === null)
|
|
1134
1492
|
source = argument;
|
|
1135
1493
|
else
|
|
1136
|
-
throw new CliUsageError('verify-artifact accepts one optional JSON file. Otherwise pipe one
|
|
1494
|
+
throw new CliUsageError('verify-artifact accepts one optional JSON file. Otherwise pipe one artefact on stdin.');
|
|
1137
1495
|
}
|
|
1138
1496
|
if (quiet && output !== 'terminal')
|
|
1139
1497
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1140
1498
|
return { action: 'verify-artifact', source, passphraseSource, output, quiet, color };
|
|
1141
1499
|
}
|
|
1142
1500
|
function parseSourceReportArguments(argv) {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
let quiet = false;
|
|
1146
|
-
let color = true;
|
|
1147
|
-
for (const argument of argv) {
|
|
1148
|
-
if (argument === '--json') {
|
|
1149
|
-
if (output !== 'terminal')
|
|
1150
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1151
|
-
output = 'json';
|
|
1152
|
-
}
|
|
1153
|
-
else if (argument === '--quiet')
|
|
1154
|
-
quiet = true;
|
|
1155
|
-
else if (argument === '--no-color')
|
|
1156
|
-
color = false;
|
|
1157
|
-
else if (argument.startsWith('-'))
|
|
1158
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1159
|
-
else if (source === null)
|
|
1160
|
-
source = argument;
|
|
1161
|
-
else
|
|
1162
|
-
throw new CliUsageError('source-report accepts one optional JSON file. Otherwise pipe lookup documents on stdin.');
|
|
1163
|
-
}
|
|
1164
|
-
if (quiet && output !== 'terminal')
|
|
1165
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1166
|
-
return { action: 'source-report', source, output, quiet, color };
|
|
1501
|
+
const parsed = parseSingleJsonInput(argv, 'source-report accepts one optional JSON file. Otherwise pipe lookup documents on stdin.');
|
|
1502
|
+
return { action: 'source-report', ...parsed };
|
|
1167
1503
|
}
|
|
1168
1504
|
function parseExportArguments(argv) {
|
|
1169
1505
|
let source = null;
|