@slicedearth/whoisleuth-cli 1.32.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/LICENSE +661 -0
- package/LICENSES/Retire.js-Apache-2.0.txt +199 -0
- package/NOTICE +76 -0
- package/README.md +84 -0
- package/TRADEMARKS.md +41 -0
- package/bin/whoisleuth.mjs +35 -0
- package/cli/archive-inspect.mjs +198 -0
- package/cli/arguments.mjs +1022 -0
- package/cli/artifact-verify.mjs +210 -0
- package/cli/bulk-checkpoint.mjs +218 -0
- package/cli/bulk-command-runner.mjs +109 -0
- package/cli/bulk-output.mjs +112 -0
- package/cli/bulk.mjs +133 -0
- package/cli/compare.mjs +238 -0
- package/cli/completion.mjs +175 -0
- package/cli/csv.mjs +1 -0
- package/cli/discover.mjs +50 -0
- package/cli/discovery-command-runner.mjs +36 -0
- package/cli/discovery-observation-snapshot.mjs +315 -0
- package/cli/discovery-scan-command-runner.mjs +141 -0
- package/cli/discovery-scan.mjs +325 -0
- package/cli/discovery-snapshot.mjs +133 -0
- package/cli/discovery-workflow.mjs +73 -0
- package/cli/doctor.mjs +137 -0
- package/cli/errors.mjs +21 -0
- package/cli/evidence-command-arguments.mjs +145 -0
- package/cli/evidence-command-runner.mjs +106 -0
- package/cli/evidence-signing.mjs +215 -0
- package/cli/exit-codes.mjs +9 -0
- package/cli/export-evidence.mjs +28 -0
- package/cli/formatters/evidence-report.mjs +3 -0
- package/cli/formatters/html.mjs +69 -0
- package/cli/formatters/json.mjs +135 -0
- package/cli/formatters/markdown.mjs +3 -0
- package/cli/formatters/terminal.mjs +658 -0
- package/cli/http.mjs +27 -0
- package/cli/lookup-command-runner.mjs +87 -0
- package/cli/lookup-diff.mjs +171 -0
- package/cli/mail-review.mjs +204 -0
- package/cli/manual.mjs +34 -0
- package/cli/network-command-runner.mjs +88 -0
- package/cli/output-file.mjs +97 -0
- package/cli/page-compare.mjs +123 -0
- package/cli/posture.mjs +20 -0
- package/cli/progress-events.mjs +53 -0
- package/cli/progress.mjs +56 -0
- package/cli/registry-access.mjs +3 -0
- package/cli/registry-support.mjs +120 -0
- package/cli/risk-calibration.mjs +294 -0
- package/cli/runner-types.mjs +1 -0
- package/cli/runner.mjs +726 -0
- package/cli/saved-lookup.mjs +87 -0
- package/cli/source-reliability.mjs +727 -0
- package/cli/strict-exit.mjs +55 -0
- package/cli/terminal-presentation.mjs +147 -0
- package/docs/cli.md +827 -0
- package/frontend/src/lib/analysis/acquisition-decision-packet.js +93 -0
- package/frontend/src/lib/analysis/acquisition-due-diligence.js +391 -0
- package/frontend/src/lib/analysis/artifact-integrity.js +21 -0
- package/frontend/src/lib/analysis/brand-profile-model.js +421 -0
- package/frontend/src/lib/analysis/bulk-domain-comparison.js +246 -0
- package/frontend/src/lib/analysis/bulk-mail-exposure.js +192 -0
- package/frontend/src/lib/analysis/bulk-retry-plan.js +86 -0
- package/frontend/src/lib/analysis/bulk-review-export.js +62 -0
- package/frontend/src/lib/analysis/bulk-review-model.js +266 -0
- package/frontend/src/lib/analysis/bulk-session-model.js +515 -0
- package/frontend/src/lib/analysis/bulk-sort.js +49 -0
- package/frontend/src/lib/analysis/campaign-model.js +265 -0
- package/frontend/src/lib/analysis/case-evidence-model.js +642 -0
- package/frontend/src/lib/analysis/case-migration-model.js +248 -0
- package/frontend/src/lib/analysis/case-model.js +5 -0
- package/frontend/src/lib/analysis/case-record-contracts.js +44 -0
- package/frontend/src/lib/analysis/case-record-core.js +232 -0
- package/frontend/src/lib/analysis/case-record-model.js +7 -0
- package/frontend/src/lib/analysis/case-record-operations.js +224 -0
- package/frontend/src/lib/analysis/case-response-model.js +640 -0
- package/frontend/src/lib/analysis/case-response-packet.js +573 -0
- package/frontend/src/lib/analysis/case-storage-model.js +93 -0
- package/frontend/src/lib/analysis/detection-rule-model.js +306 -0
- package/frontend/src/lib/analysis/dns-record-normalization.js +11 -0
- package/frontend/src/lib/analysis/http-summary.js +165 -0
- package/frontend/src/lib/analysis/investigation-guide.js +500 -0
- package/frontend/src/lib/analysis/investigation-template-model.js +144 -0
- package/frontend/src/lib/analysis/page-baseline.js +272 -0
- package/frontend/src/lib/analysis/page-similarity.js +179 -0
- package/frontend/src/lib/analysis/relationship-evidence.js +265 -0
- package/frontend/src/lib/analysis/relationship-observation-model.js +423 -0
- package/frontend/src/lib/analysis/scoring.js +135 -0
- package/frontend/src/lib/analysis/shortlist-model.js +209 -0
- package/frontend/src/lib/analysis/utils.js +265 -0
- package/frontend/src/lib/analysis/watchlist-history.js +643 -0
- package/frontend/src/lib/analysis/watchlist-store.js +127 -0
- package/frontend/src/lib/analysis/website-snapshot-model.js +302 -0
- package/frontend/src/lib/analysis/workspace-archive-crypto.js +282 -0
- package/frontend/src/lib/analysis/workspace-archive.js +472 -0
- package/lib/abort.mjs +26 -0
- package/lib/abusech-auth.mjs +19 -0
- package/lib/availability.mjs +716 -0
- package/lib/bounded-contract-normalizers.mjs +61 -0
- package/lib/browser-library-profile.mjs +310 -0
- package/lib/bulk-comparison-evidence.mjs +83 -0
- package/lib/classify.mjs +93 -0
- package/lib/client-behavior-profile.mjs +83 -0
- package/lib/credential-surface-profile.mjs +66 -0
- package/lib/csv.mjs +23 -0
- package/lib/ct-query.mjs +27 -0
- package/lib/ct-search.mjs +393 -0
- package/lib/distributed-operation-budget.mjs +461 -0
- package/lib/dns-delegation-health.mjs +344 -0
- package/lib/dns-intelligence.mjs +739 -0
- package/lib/dns-mx.mjs +24 -0
- package/lib/dns-resolver-selection.mjs +33 -0
- package/lib/domain-posture-analysis.mjs +254 -0
- package/lib/domain-posture-parsers.mjs +358 -0
- package/lib/domain-posture.mjs +745 -0
- package/lib/error-detail.mjs +10 -0
- package/lib/evidence-export.mjs +431 -0
- package/lib/evidence-report-markdown.mjs +71 -0
- package/lib/evidence-report.mjs +391 -0
- package/lib/external-intelligence-risk.mjs +108 -0
- package/lib/favicon.mjs +146 -0
- package/lib/feature-policy.mjs +70 -0
- package/lib/generated/retire-browser-catalog.mjs +8724 -0
- package/lib/generated/unicode-confusables-17.mjs +93 -0
- package/lib/hostname.mjs +38 -0
- package/lib/html-signals.mjs +680 -0
- package/lib/http-intelligence.mjs +232 -0
- package/lib/idn-confusable-policy.mjs +86 -0
- package/lib/idn-confusables.mjs +465 -0
- package/lib/lookup-cache.mjs +130 -0
- package/lib/lookup-progress.mjs +291 -0
- package/lib/lookup-source-progress.mjs +109 -0
- package/lib/lookup-threat-provider-inventory.mjs +13 -0
- package/lib/lookup.mjs +592 -0
- package/lib/observation.mjs +114 -0
- package/lib/observed-network-context.mjs +355 -0
- package/lib/operation-budget.mjs +489 -0
- package/lib/page-fingerprints.mjs +481 -0
- package/lib/page-role-profile.mjs +117 -0
- package/lib/perceptual-hash.mjs +451 -0
- package/lib/rdap-attempts.mjs +34 -0
- package/lib/rdap-bootstrap.mjs +173 -0
- package/lib/rdap-capabilities.mjs +292 -0
- package/lib/rdap-client.mjs +125 -0
- package/lib/rdap-normalization.mjs +691 -0
- package/lib/rdap-registrar.mjs +196 -0
- package/lib/rdap-transport.mjs +39 -0
- package/lib/rdap-types.mjs +1 -0
- package/lib/rdap-validation.mjs +94 -0
- package/lib/rdap.mjs +16 -0
- package/lib/registry-access.mjs +32 -0
- package/lib/registry-capabilities.mjs +117 -0
- package/lib/registry-capability-catalogue.mjs +1206 -0
- package/lib/registry-comparison.mjs +318 -0
- package/lib/registry-dates.mjs +190 -0
- package/lib/registry-insights.mjs +325 -0
- package/lib/response-policy.mjs +256 -0
- package/lib/risk-scoring.mjs +164 -0
- package/lib/safe-fetch.mjs +446 -0
- package/lib/security-txt.mjs +359 -0
- package/lib/service-binding-dns.mjs +608 -0
- package/lib/sslbl-certificates.generated.mjs +143 -0
- package/lib/sslbl-intelligence.mjs +241 -0
- package/lib/static-html-analysis.mjs +340 -0
- package/lib/structured-data-identity.mjs +323 -0
- package/lib/threat-intelligence-contract.mjs +5 -0
- package/lib/threat-intelligence-definition-registry.mjs +34 -0
- package/lib/threat-intelligence-fixtures.mjs +82 -0
- package/lib/threat-intelligence-manifests.mjs +512 -0
- package/lib/threat-intelligence-runtime.mjs +662 -0
- package/lib/threat-intelligence-targets.mjs +168 -0
- package/lib/threat-intelligence-types.mjs +3 -0
- package/lib/threatfox-intelligence.mjs +307 -0
- package/lib/tls-intelligence.mjs +814 -0
- package/lib/typosquat-generator.mjs +884 -0
- package/lib/urlhaus-intelligence.mjs +361 -0
- package/lib/urlscan-intelligence.mjs +349 -0
- package/lib/website-security-posture.mjs +325 -0
- package/lib/website-technology.mjs +437 -0
- package/lib/whois-authority.mjs +98 -0
- package/lib/whois-chain.mjs +261 -0
- package/lib/whois-contacts.mjs +163 -0
- package/lib/whois-contracts.mjs +3 -0
- package/lib/whois-normalization.mjs +27 -0
- package/lib/whois-transport.mjs +100 -0
- package/lib/whois-values.mjs +92 -0
- package/lib/whois.mjs +1187 -0
- package/package.json +55 -0
- package/third-party-notices.txt +5571 -0
|
@@ -0,0 +1,1022 @@
|
|
|
1
|
+
import { parseInspectArchiveArguments, parseSignArtifactArguments, parseVerifySignatureArguments, } from "./evidence-command-arguments.mjs";
|
|
2
|
+
import { CliUsageError } from "./errors.mjs";
|
|
3
|
+
const MAX_CLI_ARGUMENTS = 32;
|
|
4
|
+
const MAX_CLI_ARGUMENT_LENGTH = 1024;
|
|
5
|
+
const CLI_COMMANDS = [
|
|
6
|
+
'completion',
|
|
7
|
+
'doctor',
|
|
8
|
+
'manual',
|
|
9
|
+
'lookup',
|
|
10
|
+
'bulk',
|
|
11
|
+
'ct-search',
|
|
12
|
+
'discover',
|
|
13
|
+
'discover-scan',
|
|
14
|
+
'posture',
|
|
15
|
+
'http',
|
|
16
|
+
'tls',
|
|
17
|
+
'registry-support',
|
|
18
|
+
'risk-calibrate',
|
|
19
|
+
'verify-artifact',
|
|
20
|
+
'inspect-archive',
|
|
21
|
+
'sign-artifact',
|
|
22
|
+
'verify-signature',
|
|
23
|
+
'source-report',
|
|
24
|
+
'compare',
|
|
25
|
+
'page-compare',
|
|
26
|
+
'mail-review',
|
|
27
|
+
'diff',
|
|
28
|
+
'export',
|
|
29
|
+
];
|
|
30
|
+
function boundedArgument(value) {
|
|
31
|
+
if (typeof value !== 'string' || value.length > MAX_CLI_ARGUMENT_LENGTH || /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(value)) {
|
|
32
|
+
throw new CliUsageError('Arguments must be bounded text without control characters.');
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function isCliCommand(value) {
|
|
37
|
+
return CLI_COMMANDS.includes(value);
|
|
38
|
+
}
|
|
39
|
+
function extractFileOutputArguments(argv) {
|
|
40
|
+
const retained = [];
|
|
41
|
+
let destination = null;
|
|
42
|
+
let force = false;
|
|
43
|
+
for (let index = 0; index < argv.length; index++) {
|
|
44
|
+
const argument = argv[index];
|
|
45
|
+
if (argument === undefined)
|
|
46
|
+
break;
|
|
47
|
+
if (index > 0 && argument === '--output') {
|
|
48
|
+
if (destination !== null)
|
|
49
|
+
throw new CliUsageError('--output may be supplied only once.');
|
|
50
|
+
const value = argv[++index];
|
|
51
|
+
if (!value || value.startsWith('-'))
|
|
52
|
+
throw new CliUsageError('--output requires one bounded file path.');
|
|
53
|
+
destination = value;
|
|
54
|
+
}
|
|
55
|
+
else if (index > 0 && argument === '--force') {
|
|
56
|
+
if (force)
|
|
57
|
+
throw new CliUsageError('--force may be supplied only once.');
|
|
58
|
+
force = true;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
retained.push(argument);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (force && destination === null)
|
|
65
|
+
throw new CliUsageError('--force requires --output.');
|
|
66
|
+
return { argv: retained, destination, force };
|
|
67
|
+
}
|
|
68
|
+
function parseCliArguments(rawArgv) {
|
|
69
|
+
if (!Array.isArray(rawArgv) || rawArgv.length > MAX_CLI_ARGUMENTS) {
|
|
70
|
+
throw new CliUsageError(`At most ${MAX_CLI_ARGUMENTS} command arguments are supported.`);
|
|
71
|
+
}
|
|
72
|
+
const extracted = extractFileOutputArguments(rawArgv.map(boundedArgument));
|
|
73
|
+
const parsed = parseCliArgumentsCore(extracted.argv);
|
|
74
|
+
if ('quiet' in parsed && parsed.quiet && extracted.destination !== null) {
|
|
75
|
+
throw new CliUsageError('--quiet cannot be combined with --output.');
|
|
76
|
+
}
|
|
77
|
+
if ('events' in parsed && parsed.events && extracted.destination !== null) {
|
|
78
|
+
throw new CliUsageError('--events cannot be combined with --output because completion must describe the final output delivery.');
|
|
79
|
+
}
|
|
80
|
+
return extracted.destination === null
|
|
81
|
+
? parsed
|
|
82
|
+
: { ...parsed, destination: extracted.destination, ...(extracted.force ? { force: true } : {}) };
|
|
83
|
+
}
|
|
84
|
+
function parseCliArgumentsCore(argv) {
|
|
85
|
+
if (!argv.length)
|
|
86
|
+
return { action: 'help' };
|
|
87
|
+
const firstArgument = argv[0] ?? '';
|
|
88
|
+
const helpRequested = argv.includes('--help') || argv.includes('-h');
|
|
89
|
+
if (helpRequested) {
|
|
90
|
+
if (argv.length === 1)
|
|
91
|
+
return { action: 'help' };
|
|
92
|
+
if (argv.length === 2 && isCliCommand(firstArgument))
|
|
93
|
+
return { action: 'help', command: firstArgument };
|
|
94
|
+
throw new CliUsageError('Help accepts only an optional command name.');
|
|
95
|
+
}
|
|
96
|
+
if (firstArgument === '--version' || firstArgument === '-V') {
|
|
97
|
+
if (argv.length !== 1)
|
|
98
|
+
throw new CliUsageError('--version does not accept other arguments.');
|
|
99
|
+
return { action: 'version' };
|
|
100
|
+
}
|
|
101
|
+
const command = firstArgument;
|
|
102
|
+
if (!isCliCommand(command)) {
|
|
103
|
+
throw new CliUsageError(`Unknown command "${command}". This release supports: ${CLI_COMMANDS.join(', ')}.`);
|
|
104
|
+
}
|
|
105
|
+
if (command === 'bulk')
|
|
106
|
+
return parseBulkArguments(argv.slice(1));
|
|
107
|
+
if (command === 'completion')
|
|
108
|
+
return parseCompletionArguments(argv.slice(1));
|
|
109
|
+
if (command === 'doctor')
|
|
110
|
+
return parseDoctorArguments(argv.slice(1));
|
|
111
|
+
if (command === 'manual')
|
|
112
|
+
return parseManualArguments(argv.slice(1));
|
|
113
|
+
if (command === 'ct-search')
|
|
114
|
+
return parseCtSearchArguments(argv.slice(1));
|
|
115
|
+
if (command === 'discover')
|
|
116
|
+
return parseDiscoverArguments(argv.slice(1));
|
|
117
|
+
if (command === 'discover-scan')
|
|
118
|
+
return parseDiscoverScanArguments(argv.slice(1));
|
|
119
|
+
if (command === 'posture')
|
|
120
|
+
return parsePostureArguments(argv.slice(1));
|
|
121
|
+
if (command === 'http')
|
|
122
|
+
return parseHttpArguments(argv.slice(1));
|
|
123
|
+
if (command === 'tls')
|
|
124
|
+
return parseTlsArguments(argv.slice(1));
|
|
125
|
+
if (command === 'registry-support')
|
|
126
|
+
return parseRegistrySupportArguments(argv.slice(1));
|
|
127
|
+
if (command === 'risk-calibrate')
|
|
128
|
+
return parseRiskCalibrateArguments(argv.slice(1));
|
|
129
|
+
if (command === 'verify-artifact')
|
|
130
|
+
return parseVerifyArtifactArguments(argv.slice(1));
|
|
131
|
+
if (command === 'inspect-archive')
|
|
132
|
+
return parseInspectArchiveArguments(argv.slice(1));
|
|
133
|
+
if (command === 'sign-artifact')
|
|
134
|
+
return parseSignArtifactArguments(argv.slice(1));
|
|
135
|
+
if (command === 'verify-signature')
|
|
136
|
+
return parseVerifySignatureArguments(argv.slice(1));
|
|
137
|
+
if (command === 'source-report')
|
|
138
|
+
return parseSourceReportArguments(argv.slice(1));
|
|
139
|
+
if (command === 'compare')
|
|
140
|
+
return parseCompareArguments(argv.slice(1));
|
|
141
|
+
if (command === 'page-compare')
|
|
142
|
+
return parsePageCompareArguments(argv.slice(1));
|
|
143
|
+
if (command === 'mail-review')
|
|
144
|
+
return parseMailReviewArguments(argv.slice(1));
|
|
145
|
+
if (command === 'diff')
|
|
146
|
+
return parseDiffArguments(argv.slice(1));
|
|
147
|
+
if (command === 'export')
|
|
148
|
+
return parseExportArguments(argv.slice(1));
|
|
149
|
+
let query = null;
|
|
150
|
+
let output = 'terminal';
|
|
151
|
+
let deep = false;
|
|
152
|
+
let scanMode = null;
|
|
153
|
+
let quiet = false;
|
|
154
|
+
let color = true;
|
|
155
|
+
let detail = 'standard';
|
|
156
|
+
let detailSet = false;
|
|
157
|
+
let strictExit = false;
|
|
158
|
+
let events = false;
|
|
159
|
+
for (const argument of argv.slice(1)) {
|
|
160
|
+
if (argument === '--json') {
|
|
161
|
+
if (output !== 'terminal')
|
|
162
|
+
throw new CliUsageError('Choose only one output format.');
|
|
163
|
+
output = 'json';
|
|
164
|
+
}
|
|
165
|
+
else if (argument === '--markdown' || argument === '--html') {
|
|
166
|
+
if (output !== 'terminal')
|
|
167
|
+
throw new CliUsageError('Choose only one output format.');
|
|
168
|
+
output = argument === '--markdown' ? 'markdown' : 'html';
|
|
169
|
+
}
|
|
170
|
+
else if (argument === '--deep') {
|
|
171
|
+
if (scanMode)
|
|
172
|
+
throw new CliUsageError('--fast and --deep are mutually exclusive and may be supplied only once.');
|
|
173
|
+
deep = true;
|
|
174
|
+
scanMode = 'deep';
|
|
175
|
+
}
|
|
176
|
+
else if (argument === '--fast') {
|
|
177
|
+
if (scanMode)
|
|
178
|
+
throw new CliUsageError('--fast and --deep are mutually exclusive and may be supplied only once.');
|
|
179
|
+
deep = false;
|
|
180
|
+
scanMode = 'fast';
|
|
181
|
+
}
|
|
182
|
+
else if (argument === '--summary' || argument === '--verbose') {
|
|
183
|
+
if (detailSet)
|
|
184
|
+
throw new CliUsageError('--summary and --verbose are mutually exclusive and may be supplied only once.');
|
|
185
|
+
detail = argument === '--summary' ? 'summary' : 'verbose';
|
|
186
|
+
detailSet = true;
|
|
187
|
+
}
|
|
188
|
+
else if (argument === '--strict-exit') {
|
|
189
|
+
if (strictExit)
|
|
190
|
+
throw new CliUsageError('--strict-exit may be supplied only once.');
|
|
191
|
+
strictExit = true;
|
|
192
|
+
}
|
|
193
|
+
else if (argument === '--events') {
|
|
194
|
+
if (events)
|
|
195
|
+
throw new CliUsageError('--events may be supplied only once.');
|
|
196
|
+
events = true;
|
|
197
|
+
}
|
|
198
|
+
else if (argument === '--quiet')
|
|
199
|
+
quiet = true;
|
|
200
|
+
else if (argument === '--no-color')
|
|
201
|
+
color = false;
|
|
202
|
+
else if (argument.startsWith('-'))
|
|
203
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
204
|
+
else if (query === null)
|
|
205
|
+
query = argument;
|
|
206
|
+
else
|
|
207
|
+
throw new CliUsageError('lookup accepts one query. Use the bulk command for multiple inputs.');
|
|
208
|
+
}
|
|
209
|
+
if (quiet && output !== 'terminal')
|
|
210
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
211
|
+
if (detailSet && output !== 'terminal')
|
|
212
|
+
throw new CliUsageError('--summary and --verbose apply only to terminal output.');
|
|
213
|
+
return { action: 'lookup', query, output, deep, detail, strictExit, events, quiet, color };
|
|
214
|
+
}
|
|
215
|
+
function parseCompletionArguments(argv) {
|
|
216
|
+
if (argv.length !== 1 || !['bash', 'zsh', 'fish'].includes(argv[0] || '')) {
|
|
217
|
+
throw new CliUsageError('completion requires exactly one shell: bash, zsh, or fish.');
|
|
218
|
+
}
|
|
219
|
+
return { action: 'completion', shell: argv[0] };
|
|
220
|
+
}
|
|
221
|
+
function parseManualArguments(argv) {
|
|
222
|
+
if (argv.length !== 0)
|
|
223
|
+
throw new CliUsageError('manual does not accept command arguments.');
|
|
224
|
+
return { action: 'manual' };
|
|
225
|
+
}
|
|
226
|
+
function parseDoctorArguments(argv) {
|
|
227
|
+
let network = false;
|
|
228
|
+
let output = 'terminal';
|
|
229
|
+
let quiet = false;
|
|
230
|
+
let color = true;
|
|
231
|
+
for (const argument of argv) {
|
|
232
|
+
if (argument === '--network') {
|
|
233
|
+
if (network)
|
|
234
|
+
throw new CliUsageError('--network may be supplied only once.');
|
|
235
|
+
network = true;
|
|
236
|
+
}
|
|
237
|
+
else if (argument === '--json') {
|
|
238
|
+
if (output !== 'terminal')
|
|
239
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
240
|
+
output = 'json';
|
|
241
|
+
}
|
|
242
|
+
else if (argument === '--quiet')
|
|
243
|
+
quiet = true;
|
|
244
|
+
else if (argument === '--no-color')
|
|
245
|
+
color = false;
|
|
246
|
+
else
|
|
247
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
248
|
+
}
|
|
249
|
+
if (quiet && output !== 'terminal')
|
|
250
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
251
|
+
return { action: 'doctor', network, output, quiet, color };
|
|
252
|
+
}
|
|
253
|
+
function parseBulkArguments(argv) {
|
|
254
|
+
let source = null;
|
|
255
|
+
let output = 'terminal';
|
|
256
|
+
let deep = false;
|
|
257
|
+
let scanMode = null;
|
|
258
|
+
let quiet = false;
|
|
259
|
+
let color = true;
|
|
260
|
+
let concurrency = null;
|
|
261
|
+
let checkpoint = null;
|
|
262
|
+
let resume = false;
|
|
263
|
+
let events = false;
|
|
264
|
+
let filter = 'all';
|
|
265
|
+
for (let index = 0; index < argv.length; index++) {
|
|
266
|
+
const argument = argv[index];
|
|
267
|
+
if (argument === undefined)
|
|
268
|
+
break;
|
|
269
|
+
if (argument === '--json' || argument === '--jsonl' || argument === '--csv' || argument === '--domains') {
|
|
270
|
+
if (output !== 'terminal')
|
|
271
|
+
throw new CliUsageError('Choose only one output format.');
|
|
272
|
+
output = argument === '--json' ? 'json' : argument === '--jsonl' ? 'jsonl' : argument === '--csv' ? 'csv' : 'domains';
|
|
273
|
+
}
|
|
274
|
+
else if (argument === '--deep' || argument === '--fast') {
|
|
275
|
+
if (scanMode)
|
|
276
|
+
throw new CliUsageError('--fast and --deep are mutually exclusive and may be supplied only once.');
|
|
277
|
+
scanMode = argument === '--deep' ? 'deep' : 'fast';
|
|
278
|
+
deep = scanMode === 'deep';
|
|
279
|
+
}
|
|
280
|
+
else if (argument === '--concurrency') {
|
|
281
|
+
if (concurrency !== null)
|
|
282
|
+
throw new CliUsageError('--concurrency may be supplied only once.');
|
|
283
|
+
const raw = argv[++index];
|
|
284
|
+
if (!raw || !/^\d+$/.test(raw))
|
|
285
|
+
throw new CliUsageError('--concurrency requires an integer from 1 to 8.');
|
|
286
|
+
concurrency = Number(raw);
|
|
287
|
+
if (concurrency < 1 || concurrency > 8)
|
|
288
|
+
throw new CliUsageError('--concurrency must be from 1 to 8.');
|
|
289
|
+
}
|
|
290
|
+
else if (argument === '--checkpoint') {
|
|
291
|
+
if (checkpoint !== null)
|
|
292
|
+
throw new CliUsageError('--checkpoint may be supplied only once.');
|
|
293
|
+
const value = argv[++index];
|
|
294
|
+
if (!value || value.startsWith('-'))
|
|
295
|
+
throw new CliUsageError('--checkpoint requires one bounded file path.');
|
|
296
|
+
checkpoint = value;
|
|
297
|
+
}
|
|
298
|
+
else if (argument === '--resume') {
|
|
299
|
+
if (resume)
|
|
300
|
+
throw new CliUsageError('--resume may be supplied only once.');
|
|
301
|
+
resume = true;
|
|
302
|
+
}
|
|
303
|
+
else if (argument === '--events') {
|
|
304
|
+
if (events)
|
|
305
|
+
throw new CliUsageError('--events may be supplied only once.');
|
|
306
|
+
events = true;
|
|
307
|
+
}
|
|
308
|
+
else if (argument === '--registered-only' || argument === '--inconclusive-only') {
|
|
309
|
+
if (filter !== 'all')
|
|
310
|
+
throw new CliUsageError('--registered-only and --inconclusive-only are mutually exclusive and may be supplied only once.');
|
|
311
|
+
filter = argument === '--registered-only' ? 'registered' : 'inconclusive';
|
|
312
|
+
}
|
|
313
|
+
else if (argument === '--quiet')
|
|
314
|
+
quiet = true;
|
|
315
|
+
else if (argument === '--no-color')
|
|
316
|
+
color = false;
|
|
317
|
+
else if (argument.startsWith('-'))
|
|
318
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
319
|
+
else if (source === null)
|
|
320
|
+
source = argument;
|
|
321
|
+
else
|
|
322
|
+
throw new CliUsageError('bulk accepts one optional input file. Otherwise pipe newline-delimited queries on stdin.');
|
|
323
|
+
}
|
|
324
|
+
if (quiet && output !== 'terminal')
|
|
325
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
326
|
+
if (resume && checkpoint === null)
|
|
327
|
+
throw new CliUsageError('--resume requires --checkpoint.');
|
|
328
|
+
const maximum = deep ? 3 : 8;
|
|
329
|
+
if (concurrency !== null && concurrency > maximum) {
|
|
330
|
+
throw new CliUsageError(`--concurrency is capped at ${maximum} in ${deep ? 'deep' : 'fast'} bulk mode.`);
|
|
331
|
+
}
|
|
332
|
+
return { action: 'bulk', source, output, deep, quiet, color, concurrency: concurrency ?? (deep ? 2 : 4), checkpoint, resume, events, filter };
|
|
333
|
+
}
|
|
334
|
+
function parseCtSearchArguments(argv) {
|
|
335
|
+
let keyword = null;
|
|
336
|
+
let output = 'terminal';
|
|
337
|
+
let quiet = false;
|
|
338
|
+
let color = true;
|
|
339
|
+
for (const argument of argv) {
|
|
340
|
+
if (argument === '--json') {
|
|
341
|
+
if (output !== 'terminal')
|
|
342
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
343
|
+
output = 'json';
|
|
344
|
+
}
|
|
345
|
+
else if (argument === '--quiet')
|
|
346
|
+
quiet = true;
|
|
347
|
+
else if (argument === '--no-color')
|
|
348
|
+
color = false;
|
|
349
|
+
else if (argument.startsWith('-'))
|
|
350
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
351
|
+
else if (keyword === null)
|
|
352
|
+
keyword = argument;
|
|
353
|
+
else
|
|
354
|
+
throw new CliUsageError('ct-search accepts one keyword. Quote multi-word keywords as one argument.');
|
|
355
|
+
}
|
|
356
|
+
if (quiet && output !== 'terminal')
|
|
357
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
358
|
+
return { action: 'ct-search', keyword, output, quiet, color };
|
|
359
|
+
}
|
|
360
|
+
function parseDiscoverArguments(argv) {
|
|
361
|
+
let seed = null;
|
|
362
|
+
let output = 'terminal';
|
|
363
|
+
let quiet = false;
|
|
364
|
+
let color = true;
|
|
365
|
+
let preset = 'all';
|
|
366
|
+
let keyboardLayout = 'qwerty';
|
|
367
|
+
let tldText = null;
|
|
368
|
+
let dictionarySource = null;
|
|
369
|
+
let familyText = null;
|
|
370
|
+
let snapshotSource = null;
|
|
371
|
+
let presetSet = false;
|
|
372
|
+
let keyboardSet = false;
|
|
373
|
+
for (let index = 0; index < argv.length; index++) {
|
|
374
|
+
const argument = argv[index];
|
|
375
|
+
if (argument === undefined)
|
|
376
|
+
break;
|
|
377
|
+
if (argument === '--json' || argument === '--jsonl' || argument === '--domains') {
|
|
378
|
+
if (output !== 'terminal')
|
|
379
|
+
throw new CliUsageError('Choose only one output format.');
|
|
380
|
+
output = argument === '--json' ? 'json' : argument === '--jsonl' ? 'jsonl' : 'domains';
|
|
381
|
+
}
|
|
382
|
+
else if (argument === '--preset') {
|
|
383
|
+
if (presetSet)
|
|
384
|
+
throw new CliUsageError('--preset may be supplied only once.');
|
|
385
|
+
if (familyText !== null)
|
|
386
|
+
throw new CliUsageError('--preset cannot be combined with --families.');
|
|
387
|
+
const value = argv[++index];
|
|
388
|
+
if (value !== 'common' && value !== 'impersonation' && value !== 'all') {
|
|
389
|
+
throw new CliUsageError('--preset requires common, impersonation, or all.');
|
|
390
|
+
}
|
|
391
|
+
preset = value;
|
|
392
|
+
presetSet = true;
|
|
393
|
+
}
|
|
394
|
+
else if (argument === '--keyboard') {
|
|
395
|
+
if (keyboardSet)
|
|
396
|
+
throw new CliUsageError('--keyboard may be supplied only once.');
|
|
397
|
+
const value = argv[++index];
|
|
398
|
+
if (value !== 'qwerty' && value !== 'azerty' && value !== 'qwertz' && value !== 'all') {
|
|
399
|
+
throw new CliUsageError('--keyboard requires qwerty, azerty, qwertz, or all.');
|
|
400
|
+
}
|
|
401
|
+
keyboardLayout = value;
|
|
402
|
+
keyboardSet = true;
|
|
403
|
+
}
|
|
404
|
+
else if (argument === '--tlds') {
|
|
405
|
+
if (tldText !== null)
|
|
406
|
+
throw new CliUsageError('--tlds may be supplied only once.');
|
|
407
|
+
const value = argv[++index];
|
|
408
|
+
if (!value)
|
|
409
|
+
throw new CliUsageError('--tlds requires a comma-separated list.');
|
|
410
|
+
tldText = value;
|
|
411
|
+
}
|
|
412
|
+
else if (argument === '--dictionary') {
|
|
413
|
+
if (dictionarySource !== null)
|
|
414
|
+
throw new CliUsageError('--dictionary may be supplied only once.');
|
|
415
|
+
const value = argv[++index];
|
|
416
|
+
if (!value || value.startsWith('-'))
|
|
417
|
+
throw new CliUsageError('--dictionary requires one UTF-8 text file.');
|
|
418
|
+
dictionarySource = value;
|
|
419
|
+
}
|
|
420
|
+
else if (argument === '--families') {
|
|
421
|
+
if (familyText !== null)
|
|
422
|
+
throw new CliUsageError('--families may be supplied only once.');
|
|
423
|
+
if (presetSet)
|
|
424
|
+
throw new CliUsageError('--families cannot be combined with --preset.');
|
|
425
|
+
const value = argv[++index];
|
|
426
|
+
if (!value || value.startsWith('-'))
|
|
427
|
+
throw new CliUsageError('--families requires a comma-separated list of mutation family IDs.');
|
|
428
|
+
familyText = value;
|
|
429
|
+
preset = 'custom';
|
|
430
|
+
}
|
|
431
|
+
else if (argument === '--snapshot') {
|
|
432
|
+
if (snapshotSource !== null)
|
|
433
|
+
throw new CliUsageError('--snapshot may be supplied only once.');
|
|
434
|
+
const value = argv[++index];
|
|
435
|
+
if (!value || value.startsWith('-'))
|
|
436
|
+
throw new CliUsageError('--snapshot requires one bounded local state file path.');
|
|
437
|
+
snapshotSource = value;
|
|
438
|
+
}
|
|
439
|
+
else if (argument === '--quiet')
|
|
440
|
+
quiet = true;
|
|
441
|
+
else if (argument === '--no-color')
|
|
442
|
+
color = false;
|
|
443
|
+
else if (argument.startsWith('-'))
|
|
444
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
445
|
+
else if (seed === null)
|
|
446
|
+
seed = argument;
|
|
447
|
+
else
|
|
448
|
+
throw new CliUsageError('discover accepts one brand label or domain. Quote multi-word labels as one argument.');
|
|
449
|
+
}
|
|
450
|
+
if (quiet && output !== 'terminal')
|
|
451
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
452
|
+
if (dictionarySource && preset === 'common') {
|
|
453
|
+
throw new CliUsageError('--dictionary requires the impersonation or all preset.');
|
|
454
|
+
}
|
|
455
|
+
return { action: 'discover', seed, output, quiet, color, preset, keyboardLayout, tldText, dictionarySource, familyText, snapshotSource };
|
|
456
|
+
}
|
|
457
|
+
function parseDiscoverScanArguments(argv) {
|
|
458
|
+
let seed = null;
|
|
459
|
+
let output = 'terminal';
|
|
460
|
+
let quiet = false;
|
|
461
|
+
let color = true;
|
|
462
|
+
let preset = 'all';
|
|
463
|
+
let keyboardLayout = 'qwerty';
|
|
464
|
+
let tldText = null;
|
|
465
|
+
let dictionarySource = null;
|
|
466
|
+
let familyText = null;
|
|
467
|
+
let presetSet = false;
|
|
468
|
+
let keyboardSet = false;
|
|
469
|
+
let deep = false;
|
|
470
|
+
let scanModeSet = false;
|
|
471
|
+
let scanLimit = null;
|
|
472
|
+
let chunkSize = null;
|
|
473
|
+
let concurrency = null;
|
|
474
|
+
let checkpoint = null;
|
|
475
|
+
let resume = false;
|
|
476
|
+
let resolverText = null;
|
|
477
|
+
let observationSnapshot = null;
|
|
478
|
+
let allowlistSource = null;
|
|
479
|
+
let filter = 'all';
|
|
480
|
+
let filterSet = false;
|
|
481
|
+
let events = false;
|
|
482
|
+
for (let index = 0; index < argv.length; index++) {
|
|
483
|
+
const argument = argv[index];
|
|
484
|
+
if (argument === undefined)
|
|
485
|
+
break;
|
|
486
|
+
if (argument === '--json' || argument === '--jsonl' || argument === '--csv' || argument === '--domains') {
|
|
487
|
+
if (output !== 'terminal')
|
|
488
|
+
throw new CliUsageError('Choose only one output format.');
|
|
489
|
+
output = argument === '--json' ? 'json' : argument === '--jsonl' ? 'jsonl' : argument === '--csv' ? 'csv' : 'domains';
|
|
490
|
+
}
|
|
491
|
+
else if (argument === '--preset') {
|
|
492
|
+
if (presetSet)
|
|
493
|
+
throw new CliUsageError('--preset may be supplied only once.');
|
|
494
|
+
if (familyText !== null)
|
|
495
|
+
throw new CliUsageError('--preset cannot be combined with --families.');
|
|
496
|
+
const value = argv[++index];
|
|
497
|
+
if (value !== 'common' && value !== 'impersonation' && value !== 'all') {
|
|
498
|
+
throw new CliUsageError('--preset requires common, impersonation, or all.');
|
|
499
|
+
}
|
|
500
|
+
preset = value;
|
|
501
|
+
presetSet = true;
|
|
502
|
+
}
|
|
503
|
+
else if (argument === '--keyboard') {
|
|
504
|
+
if (keyboardSet)
|
|
505
|
+
throw new CliUsageError('--keyboard may be supplied only once.');
|
|
506
|
+
const value = argv[++index];
|
|
507
|
+
if (value !== 'qwerty' && value !== 'azerty' && value !== 'qwertz' && value !== 'all') {
|
|
508
|
+
throw new CliUsageError('--keyboard requires qwerty, azerty, qwertz, or all.');
|
|
509
|
+
}
|
|
510
|
+
keyboardLayout = value;
|
|
511
|
+
keyboardSet = true;
|
|
512
|
+
}
|
|
513
|
+
else if (argument === '--tlds') {
|
|
514
|
+
if (tldText !== null)
|
|
515
|
+
throw new CliUsageError('--tlds may be supplied only once.');
|
|
516
|
+
const value = argv[++index];
|
|
517
|
+
if (!value)
|
|
518
|
+
throw new CliUsageError('--tlds requires a comma-separated list.');
|
|
519
|
+
tldText = value;
|
|
520
|
+
}
|
|
521
|
+
else if (argument === '--dictionary') {
|
|
522
|
+
if (dictionarySource !== null)
|
|
523
|
+
throw new CliUsageError('--dictionary may be supplied only once.');
|
|
524
|
+
const value = argv[++index];
|
|
525
|
+
if (!value || value.startsWith('-'))
|
|
526
|
+
throw new CliUsageError('--dictionary requires one UTF-8 text file.');
|
|
527
|
+
dictionarySource = value;
|
|
528
|
+
}
|
|
529
|
+
else if (argument === '--families') {
|
|
530
|
+
if (familyText !== null)
|
|
531
|
+
throw new CliUsageError('--families may be supplied only once.');
|
|
532
|
+
if (presetSet)
|
|
533
|
+
throw new CliUsageError('--families cannot be combined with --preset.');
|
|
534
|
+
const value = argv[++index];
|
|
535
|
+
if (!value || value.startsWith('-'))
|
|
536
|
+
throw new CliUsageError('--families requires a comma-separated list of mutation family IDs.');
|
|
537
|
+
familyText = value;
|
|
538
|
+
preset = 'custom';
|
|
539
|
+
}
|
|
540
|
+
else if (argument === '--deep' || argument === '--fast') {
|
|
541
|
+
if (scanModeSet)
|
|
542
|
+
throw new CliUsageError('--fast and --deep are mutually exclusive and may be supplied only once.');
|
|
543
|
+
deep = argument === '--deep';
|
|
544
|
+
scanModeSet = true;
|
|
545
|
+
}
|
|
546
|
+
else if (argument === '--scan-limit') {
|
|
547
|
+
if (scanLimit !== null)
|
|
548
|
+
throw new CliUsageError('--scan-limit may be supplied only once.');
|
|
549
|
+
scanLimit = positiveIntegerOption(argv[++index], '--scan-limit', 500);
|
|
550
|
+
}
|
|
551
|
+
else if (argument === '--chunk-size') {
|
|
552
|
+
if (chunkSize !== null)
|
|
553
|
+
throw new CliUsageError('--chunk-size may be supplied only once.');
|
|
554
|
+
chunkSize = positiveIntegerOption(argv[++index], '--chunk-size', 100);
|
|
555
|
+
}
|
|
556
|
+
else if (argument === '--concurrency') {
|
|
557
|
+
if (concurrency !== null)
|
|
558
|
+
throw new CliUsageError('--concurrency may be supplied only once.');
|
|
559
|
+
concurrency = positiveIntegerOption(argv[++index], '--concurrency', 8);
|
|
560
|
+
}
|
|
561
|
+
else if (argument === '--checkpoint') {
|
|
562
|
+
if (checkpoint !== null)
|
|
563
|
+
throw new CliUsageError('--checkpoint may be supplied only once.');
|
|
564
|
+
const value = argv[++index];
|
|
565
|
+
if (!value || value.startsWith('-'))
|
|
566
|
+
throw new CliUsageError('--checkpoint requires one bounded file path.');
|
|
567
|
+
checkpoint = value;
|
|
568
|
+
}
|
|
569
|
+
else if (argument === '--resume') {
|
|
570
|
+
if (resume)
|
|
571
|
+
throw new CliUsageError('--resume may be supplied only once.');
|
|
572
|
+
resume = true;
|
|
573
|
+
}
|
|
574
|
+
else if (argument === '--resolver') {
|
|
575
|
+
if (resolverText !== null)
|
|
576
|
+
throw new CliUsageError('--resolver may be supplied only once.');
|
|
577
|
+
const value = argv[++index];
|
|
578
|
+
if (!value || value.startsWith('-'))
|
|
579
|
+
throw new CliUsageError('--resolver requires a comma-separated list of IP addresses.');
|
|
580
|
+
resolverText = value;
|
|
581
|
+
}
|
|
582
|
+
else if (argument === '--observation-snapshot') {
|
|
583
|
+
if (observationSnapshot !== null)
|
|
584
|
+
throw new CliUsageError('--observation-snapshot may be supplied only once.');
|
|
585
|
+
const value = argv[++index];
|
|
586
|
+
if (!value || value.startsWith('-'))
|
|
587
|
+
throw new CliUsageError('--observation-snapshot requires one bounded local state file path.');
|
|
588
|
+
observationSnapshot = value;
|
|
589
|
+
}
|
|
590
|
+
else if (argument === '--allowlist') {
|
|
591
|
+
if (allowlistSource !== null)
|
|
592
|
+
throw new CliUsageError('--allowlist may be supplied only once.');
|
|
593
|
+
const value = argv[++index];
|
|
594
|
+
if (!value || value.startsWith('-'))
|
|
595
|
+
throw new CliUsageError('--allowlist requires one newline-delimited local file.');
|
|
596
|
+
allowlistSource = value;
|
|
597
|
+
}
|
|
598
|
+
else if (argument === '--registered-only' || argument === '--inconclusive-only'
|
|
599
|
+
|| argument === '--acquisition-only' || argument === '--suppressed-only') {
|
|
600
|
+
if (filterSet)
|
|
601
|
+
throw new CliUsageError('Discovery scan filters are mutually exclusive and may be supplied only once.');
|
|
602
|
+
filter = argument === '--registered-only' ? 'registered'
|
|
603
|
+
: argument === '--inconclusive-only' ? 'inconclusive'
|
|
604
|
+
: argument === '--acquisition-only' ? 'acquisition' : 'suppressed';
|
|
605
|
+
filterSet = true;
|
|
606
|
+
}
|
|
607
|
+
else if (argument === '--events') {
|
|
608
|
+
if (events)
|
|
609
|
+
throw new CliUsageError('--events may be supplied only once.');
|
|
610
|
+
events = true;
|
|
611
|
+
}
|
|
612
|
+
else if (argument === '--quiet')
|
|
613
|
+
quiet = true;
|
|
614
|
+
else if (argument === '--no-color')
|
|
615
|
+
color = false;
|
|
616
|
+
else if (argument.startsWith('-'))
|
|
617
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
618
|
+
else if (seed === null)
|
|
619
|
+
seed = argument;
|
|
620
|
+
else
|
|
621
|
+
throw new CliUsageError('discover-scan accepts one brand label or domain. Quote multi-word labels as one argument.');
|
|
622
|
+
}
|
|
623
|
+
const maximum = deep ? 50 : 500;
|
|
624
|
+
if (scanLimit !== null && scanLimit > maximum)
|
|
625
|
+
throw new CliUsageError(`--scan-limit is capped at ${maximum} in ${deep ? 'deep' : 'fast'} mode.`);
|
|
626
|
+
const maximumConcurrency = deep ? 3 : 8;
|
|
627
|
+
if ((concurrency ?? (deep ? 2 : 4)) > maximumConcurrency) {
|
|
628
|
+
throw new CliUsageError(`--concurrency is capped at ${maximumConcurrency} in ${deep ? 'deep' : 'fast'} mode.`);
|
|
629
|
+
}
|
|
630
|
+
if (resume && checkpoint === null)
|
|
631
|
+
throw new CliUsageError('--resume requires --checkpoint.');
|
|
632
|
+
if (quiet && output !== 'terminal')
|
|
633
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
634
|
+
if (dictionarySource && preset === 'common')
|
|
635
|
+
throw new CliUsageError('--dictionary requires the impersonation or all preset.');
|
|
636
|
+
return {
|
|
637
|
+
action: 'discover-scan', seed, output, quiet, color, preset, keyboardLayout, tldText,
|
|
638
|
+
dictionarySource, familyText, deep, scanLimit: scanLimit ?? Math.min(100, maximum),
|
|
639
|
+
chunkSize: chunkSize ?? 25, concurrency: concurrency ?? (deep ? 2 : 4), checkpoint,
|
|
640
|
+
resume, resolverText, observationSnapshot, allowlistSource, filter, events,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function positiveIntegerOption(value, option, maximum) {
|
|
644
|
+
if (!value || !/^\d+$/u.test(value))
|
|
645
|
+
throw new CliUsageError(`${option} requires an integer from 1 to ${maximum}.`);
|
|
646
|
+
const parsed = Number(value);
|
|
647
|
+
if (parsed < 1 || parsed > maximum)
|
|
648
|
+
throw new CliUsageError(`${option} must be from 1 to ${maximum}.`);
|
|
649
|
+
return parsed;
|
|
650
|
+
}
|
|
651
|
+
function parsePostureArguments(argv) {
|
|
652
|
+
let domain = null;
|
|
653
|
+
let output = 'terminal';
|
|
654
|
+
let quiet = false;
|
|
655
|
+
let color = true;
|
|
656
|
+
let selectorText = null;
|
|
657
|
+
let retiredSelectorText = null;
|
|
658
|
+
let mailProfile = 'standard';
|
|
659
|
+
let mailProfileSeen = false;
|
|
660
|
+
for (let index = 0; index < argv.length; index++) {
|
|
661
|
+
const argument = argv[index];
|
|
662
|
+
if (argument === undefined)
|
|
663
|
+
break;
|
|
664
|
+
if (argument === '--json') {
|
|
665
|
+
if (output !== 'terminal')
|
|
666
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
667
|
+
output = 'json';
|
|
668
|
+
}
|
|
669
|
+
else if (argument === '--selectors') {
|
|
670
|
+
if (selectorText !== null)
|
|
671
|
+
throw new CliUsageError('--selectors may be supplied only once.');
|
|
672
|
+
const value = argv[++index];
|
|
673
|
+
if (!value)
|
|
674
|
+
throw new CliUsageError('--selectors requires a comma-separated list.');
|
|
675
|
+
selectorText = value;
|
|
676
|
+
}
|
|
677
|
+
else if (argument === '--retired-selectors') {
|
|
678
|
+
if (retiredSelectorText !== null)
|
|
679
|
+
throw new CliUsageError('--retired-selectors may be supplied only once.');
|
|
680
|
+
const value = argv[++index];
|
|
681
|
+
if (!value)
|
|
682
|
+
throw new CliUsageError('--retired-selectors requires a comma-separated list.');
|
|
683
|
+
retiredSelectorText = value;
|
|
684
|
+
}
|
|
685
|
+
else if (argument === '--mail-profile') {
|
|
686
|
+
if (mailProfileSeen)
|
|
687
|
+
throw new CliUsageError('--mail-profile may be supplied only once.');
|
|
688
|
+
const value = argv[++index];
|
|
689
|
+
if (!value || !['standard', 'defensive-no-mail', 'parked'].includes(value)) {
|
|
690
|
+
throw new CliUsageError('--mail-profile must be standard, defensive-no-mail, or parked.');
|
|
691
|
+
}
|
|
692
|
+
mailProfile = value === 'defensive-no-mail' ? 'defensive_no_mail' : value;
|
|
693
|
+
mailProfileSeen = true;
|
|
694
|
+
}
|
|
695
|
+
else if (argument === '--quiet')
|
|
696
|
+
quiet = true;
|
|
697
|
+
else if (argument === '--no-color')
|
|
698
|
+
color = false;
|
|
699
|
+
else if (argument.startsWith('-'))
|
|
700
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
701
|
+
else if (domain === null)
|
|
702
|
+
domain = argument;
|
|
703
|
+
else
|
|
704
|
+
throw new CliUsageError('posture accepts one domain.');
|
|
705
|
+
}
|
|
706
|
+
if (quiet && output !== 'terminal')
|
|
707
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
708
|
+
return { action: 'posture', domain, output, quiet, color, selectorText, retiredSelectorText, mailProfile };
|
|
709
|
+
}
|
|
710
|
+
function parseHttpArguments(argv) {
|
|
711
|
+
let domain = null;
|
|
712
|
+
let output = 'terminal';
|
|
713
|
+
let quiet = false;
|
|
714
|
+
let color = true;
|
|
715
|
+
for (const argument of argv) {
|
|
716
|
+
if (argument === '--json') {
|
|
717
|
+
if (output !== 'terminal')
|
|
718
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
719
|
+
output = 'json';
|
|
720
|
+
}
|
|
721
|
+
else if (argument === '--quiet')
|
|
722
|
+
quiet = true;
|
|
723
|
+
else if (argument === '--no-color')
|
|
724
|
+
color = false;
|
|
725
|
+
else if (argument.startsWith('-'))
|
|
726
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
727
|
+
else if (domain === null)
|
|
728
|
+
domain = argument;
|
|
729
|
+
else
|
|
730
|
+
throw new CliUsageError('http accepts one domain.');
|
|
731
|
+
}
|
|
732
|
+
if (quiet && output !== 'terminal')
|
|
733
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
734
|
+
return { action: 'http', domain, output, quiet, color };
|
|
735
|
+
}
|
|
736
|
+
function parseTlsArguments(argv) {
|
|
737
|
+
let hostname = null;
|
|
738
|
+
let output = 'terminal';
|
|
739
|
+
let quiet = false;
|
|
740
|
+
let color = true;
|
|
741
|
+
for (const argument of argv) {
|
|
742
|
+
if (argument === '--json') {
|
|
743
|
+
if (output !== 'terminal')
|
|
744
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
745
|
+
output = 'json';
|
|
746
|
+
}
|
|
747
|
+
else if (argument === '--quiet')
|
|
748
|
+
quiet = true;
|
|
749
|
+
else if (argument === '--no-color')
|
|
750
|
+
color = false;
|
|
751
|
+
else if (argument.startsWith('-'))
|
|
752
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
753
|
+
else if (hostname === null)
|
|
754
|
+
hostname = argument;
|
|
755
|
+
else
|
|
756
|
+
throw new CliUsageError('tls accepts one hostname.');
|
|
757
|
+
}
|
|
758
|
+
if (quiet && output !== 'terminal')
|
|
759
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
760
|
+
return { action: 'tls', hostname, output, quiet, color };
|
|
761
|
+
}
|
|
762
|
+
function parseCompareArguments(argv) {
|
|
763
|
+
let source = null;
|
|
764
|
+
let output = 'terminal';
|
|
765
|
+
let quiet = false;
|
|
766
|
+
let color = true;
|
|
767
|
+
for (const argument of argv) {
|
|
768
|
+
if (argument === '--json') {
|
|
769
|
+
if (output !== 'terminal')
|
|
770
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
771
|
+
output = 'json';
|
|
772
|
+
}
|
|
773
|
+
else if (argument === '--quiet')
|
|
774
|
+
quiet = true;
|
|
775
|
+
else if (argument === '--no-color')
|
|
776
|
+
color = false;
|
|
777
|
+
else if (argument.startsWith('-'))
|
|
778
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
779
|
+
else if (source === null)
|
|
780
|
+
source = argument;
|
|
781
|
+
else
|
|
782
|
+
throw new CliUsageError('compare accepts one optional lookup JSON file. Otherwise pipe one lookup document on stdin.');
|
|
783
|
+
}
|
|
784
|
+
if (quiet && output !== 'terminal')
|
|
785
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
786
|
+
return { action: 'compare', source, output, quiet, color };
|
|
787
|
+
}
|
|
788
|
+
function parsePageCompareArguments(argv) {
|
|
789
|
+
const parsed = parseTwoFileComparisonArguments(argv, 'page-compare');
|
|
790
|
+
return { action: 'page-compare', ...parsed };
|
|
791
|
+
}
|
|
792
|
+
function parseTwoFileComparisonArguments(argv, command) {
|
|
793
|
+
const sources = [];
|
|
794
|
+
let output = 'terminal';
|
|
795
|
+
let quiet = false;
|
|
796
|
+
let color = true;
|
|
797
|
+
for (const argument of argv) {
|
|
798
|
+
if (argument === '--json') {
|
|
799
|
+
if (output !== 'terminal')
|
|
800
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
801
|
+
output = 'json';
|
|
802
|
+
}
|
|
803
|
+
else if (argument === '--quiet')
|
|
804
|
+
quiet = true;
|
|
805
|
+
else if (argument === '--no-color')
|
|
806
|
+
color = false;
|
|
807
|
+
else if (argument.startsWith('-'))
|
|
808
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
809
|
+
else
|
|
810
|
+
sources.push(argument);
|
|
811
|
+
}
|
|
812
|
+
if (sources.length !== 2 || !sources[0] || !sources[1])
|
|
813
|
+
throw new CliUsageError(`${command} requires two input JSON files.`);
|
|
814
|
+
if (sources[0] === sources[1])
|
|
815
|
+
throw new CliUsageError(`${command} requires two different input files.`);
|
|
816
|
+
if (quiet && output !== 'terminal')
|
|
817
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
818
|
+
return { leftSource: sources[0], rightSource: sources[1], output, quiet, color };
|
|
819
|
+
}
|
|
820
|
+
function parseMailReviewArguments(argv) {
|
|
821
|
+
let source = null;
|
|
822
|
+
let output = 'terminal';
|
|
823
|
+
let quiet = false;
|
|
824
|
+
let color = true;
|
|
825
|
+
for (const argument of argv) {
|
|
826
|
+
if (argument === '--json') {
|
|
827
|
+
if (output !== 'terminal')
|
|
828
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
829
|
+
output = 'json';
|
|
830
|
+
}
|
|
831
|
+
else if (argument === '--quiet')
|
|
832
|
+
quiet = true;
|
|
833
|
+
else if (argument === '--no-color')
|
|
834
|
+
color = false;
|
|
835
|
+
else if (argument.startsWith('-'))
|
|
836
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
837
|
+
else if (source === null)
|
|
838
|
+
source = argument;
|
|
839
|
+
else
|
|
840
|
+
throw new CliUsageError('mail-review accepts one optional Bulk JSON or JSONL input file.');
|
|
841
|
+
}
|
|
842
|
+
if (quiet && output !== 'terminal')
|
|
843
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
844
|
+
return { action: 'mail-review', source, output, quiet, color };
|
|
845
|
+
}
|
|
846
|
+
function parseDiffArguments(argv) {
|
|
847
|
+
let leftSource = null;
|
|
848
|
+
let rightSource = null;
|
|
849
|
+
let output = 'terminal';
|
|
850
|
+
let quiet = false;
|
|
851
|
+
let color = true;
|
|
852
|
+
for (const argument of argv) {
|
|
853
|
+
if (argument === '--json') {
|
|
854
|
+
if (output !== 'terminal')
|
|
855
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
856
|
+
output = 'json';
|
|
857
|
+
}
|
|
858
|
+
else if (argument === '--quiet')
|
|
859
|
+
quiet = true;
|
|
860
|
+
else if (argument === '--no-color')
|
|
861
|
+
color = false;
|
|
862
|
+
else if (argument.startsWith('-'))
|
|
863
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
864
|
+
else if (leftSource === null)
|
|
865
|
+
leftSource = argument;
|
|
866
|
+
else if (rightSource === null)
|
|
867
|
+
rightSource = argument;
|
|
868
|
+
else
|
|
869
|
+
throw new CliUsageError('diff accepts exactly two saved lookup JSON files.');
|
|
870
|
+
}
|
|
871
|
+
if (!leftSource || !rightSource)
|
|
872
|
+
throw new CliUsageError('diff requires exactly two saved lookup JSON files.');
|
|
873
|
+
if (leftSource === rightSource)
|
|
874
|
+
throw new CliUsageError('diff requires two different input files.');
|
|
875
|
+
if (quiet && output !== 'terminal')
|
|
876
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
877
|
+
return { action: 'diff', leftSource, rightSource, output, quiet, color };
|
|
878
|
+
}
|
|
879
|
+
function parseRegistrySupportArguments(argv) {
|
|
880
|
+
let target = null;
|
|
881
|
+
let output = 'terminal';
|
|
882
|
+
let quiet = false;
|
|
883
|
+
let color = true;
|
|
884
|
+
for (const argument of argv) {
|
|
885
|
+
if (argument === '--json') {
|
|
886
|
+
if (output !== 'terminal')
|
|
887
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
888
|
+
output = 'json';
|
|
889
|
+
}
|
|
890
|
+
else if (argument === '--quiet')
|
|
891
|
+
quiet = true;
|
|
892
|
+
else if (argument === '--no-color')
|
|
893
|
+
color = false;
|
|
894
|
+
else if (argument.startsWith('-'))
|
|
895
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
896
|
+
else if (target === null)
|
|
897
|
+
target = argument;
|
|
898
|
+
else
|
|
899
|
+
throw new CliUsageError('registry-support accepts one domain or suffix.');
|
|
900
|
+
}
|
|
901
|
+
if (quiet && output !== 'terminal')
|
|
902
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
903
|
+
return { action: 'registry-support', target, output, quiet, color };
|
|
904
|
+
}
|
|
905
|
+
function parseRiskCalibrateArguments(argv) {
|
|
906
|
+
let source = null;
|
|
907
|
+
let output = 'terminal';
|
|
908
|
+
let quiet = false;
|
|
909
|
+
let color = true;
|
|
910
|
+
for (const argument of argv) {
|
|
911
|
+
if (argument === '--json') {
|
|
912
|
+
if (output !== 'terminal')
|
|
913
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
914
|
+
output = 'json';
|
|
915
|
+
}
|
|
916
|
+
else if (argument === '--quiet')
|
|
917
|
+
quiet = true;
|
|
918
|
+
else if (argument === '--no-color')
|
|
919
|
+
color = false;
|
|
920
|
+
else if (argument.startsWith('-'))
|
|
921
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
922
|
+
else if (source === null)
|
|
923
|
+
source = argument;
|
|
924
|
+
else
|
|
925
|
+
throw new CliUsageError('risk-calibrate accepts one optional dataset file. Otherwise pipe one dataset on stdin.');
|
|
926
|
+
}
|
|
927
|
+
if (quiet && output !== 'terminal')
|
|
928
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
929
|
+
return { action: 'risk-calibrate', source, output, quiet, color };
|
|
930
|
+
}
|
|
931
|
+
function parseVerifyArtifactArguments(argv) {
|
|
932
|
+
let source = null;
|
|
933
|
+
let passphraseSource = null;
|
|
934
|
+
let output = 'terminal';
|
|
935
|
+
let quiet = false;
|
|
936
|
+
let color = true;
|
|
937
|
+
for (let index = 0; index < argv.length; index++) {
|
|
938
|
+
const argument = argv[index];
|
|
939
|
+
if (argument === undefined)
|
|
940
|
+
break;
|
|
941
|
+
if (argument === '--json') {
|
|
942
|
+
if (output !== 'terminal')
|
|
943
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
944
|
+
output = 'json';
|
|
945
|
+
}
|
|
946
|
+
else if (argument === '--passphrase-file') {
|
|
947
|
+
if (passphraseSource !== null)
|
|
948
|
+
throw new CliUsageError('--passphrase-file may be supplied only once.');
|
|
949
|
+
const value = argv[++index];
|
|
950
|
+
if (!value || value.startsWith('-')) {
|
|
951
|
+
throw new CliUsageError('--passphrase-file requires one bounded UTF-8 file.');
|
|
952
|
+
}
|
|
953
|
+
passphraseSource = value;
|
|
954
|
+
}
|
|
955
|
+
else if (argument === '--quiet')
|
|
956
|
+
quiet = true;
|
|
957
|
+
else if (argument === '--no-color')
|
|
958
|
+
color = false;
|
|
959
|
+
else if (argument.startsWith('-'))
|
|
960
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
961
|
+
else if (source === null)
|
|
962
|
+
source = argument;
|
|
963
|
+
else
|
|
964
|
+
throw new CliUsageError('verify-artifact accepts one optional JSON file. Otherwise pipe one artifact on stdin.');
|
|
965
|
+
}
|
|
966
|
+
if (quiet && output !== 'terminal')
|
|
967
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
968
|
+
return { action: 'verify-artifact', source, passphraseSource, output, quiet, color };
|
|
969
|
+
}
|
|
970
|
+
function parseSourceReportArguments(argv) {
|
|
971
|
+
let source = null;
|
|
972
|
+
let output = 'terminal';
|
|
973
|
+
let quiet = false;
|
|
974
|
+
let color = true;
|
|
975
|
+
for (const argument of argv) {
|
|
976
|
+
if (argument === '--json') {
|
|
977
|
+
if (output !== 'terminal')
|
|
978
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
979
|
+
output = 'json';
|
|
980
|
+
}
|
|
981
|
+
else if (argument === '--quiet')
|
|
982
|
+
quiet = true;
|
|
983
|
+
else if (argument === '--no-color')
|
|
984
|
+
color = false;
|
|
985
|
+
else if (argument.startsWith('-'))
|
|
986
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
987
|
+
else if (source === null)
|
|
988
|
+
source = argument;
|
|
989
|
+
else
|
|
990
|
+
throw new CliUsageError('source-report accepts one optional JSON file. Otherwise pipe lookup documents on stdin.');
|
|
991
|
+
}
|
|
992
|
+
if (quiet && output !== 'terminal')
|
|
993
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
994
|
+
return { action: 'source-report', source, output, quiet, color };
|
|
995
|
+
}
|
|
996
|
+
function parseExportArguments(argv) {
|
|
997
|
+
let source = null;
|
|
998
|
+
let compact = false;
|
|
999
|
+
let format = 'json';
|
|
1000
|
+
for (const argument of argv) {
|
|
1001
|
+
if (argument === '--compact') {
|
|
1002
|
+
if (compact)
|
|
1003
|
+
throw new CliUsageError('--compact may be supplied only once.');
|
|
1004
|
+
compact = true;
|
|
1005
|
+
}
|
|
1006
|
+
else if (argument === '--markdown' || argument === '--html') {
|
|
1007
|
+
if (format !== 'json')
|
|
1008
|
+
throw new CliUsageError('Choose only one evidence export format.');
|
|
1009
|
+
format = argument === '--markdown' ? 'markdown' : 'html';
|
|
1010
|
+
}
|
|
1011
|
+
else if (argument.startsWith('-'))
|
|
1012
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1013
|
+
else if (source === null)
|
|
1014
|
+
source = argument;
|
|
1015
|
+
else
|
|
1016
|
+
throw new CliUsageError('export accepts one optional lookup JSON file. Otherwise pipe one lookup document on stdin.');
|
|
1017
|
+
}
|
|
1018
|
+
if (compact && format !== 'json')
|
|
1019
|
+
throw new CliUsageError('--compact applies to JSON export and cannot be combined with --markdown or --html.');
|
|
1020
|
+
return { action: 'export', source, format, compact };
|
|
1021
|
+
}
|
|
1022
|
+
export { CLI_COMMANDS, CliUsageError, MAX_CLI_ARGUMENTS, MAX_CLI_ARGUMENT_LENGTH, parseCliArguments };
|