@slicedearth/whoisleuth-cli 1.34.0 → 1.39.1
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 +19 -2
- package/bin/whoisleuth.mjs +41 -24
- package/cli/arguments.mjs +326 -3
- package/cli/artifact-verify.mjs +60 -14
- package/cli/bulk.mjs +1 -2
- package/cli/compare.mjs +1 -1
- package/cli/completion.mjs +36 -2
- package/cli/doctor.mjs +11 -5
- package/cli/evidence-command-arguments.mjs +1 -1
- package/cli/evidence-command-runner.mjs +2 -2
- package/cli/evidence-signing.mjs +4 -4
- package/cli/export-evidence.mjs +9 -2
- package/cli/formatters/html.mjs +1 -1
- package/cli/formatters/json.mjs +13 -1
- package/cli/formatters/terminal.mjs +14 -4
- package/cli/investigation-plan.mjs +112 -0
- package/cli/local-mmdb-review.mjs +76 -0
- package/cli/lookalike-calibration.mjs +121 -0
- package/cli/lookup-command-runner.mjs +4 -1
- 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 +48 -3
- package/cli/offline-evidence-review.mjs +159 -0
- package/cli/registry-doctor.mjs +125 -0
- package/cli/registry-support.mjs +1 -4
- package/cli/risk-calibration.mjs +117 -9
- package/cli/runner.mjs +342 -15
- 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 +1110 -0
- package/docs/cli.md +224 -847
- package/frontend/src/lib/analysis/acquisition-decision-packet.js +1 -1
- package/frontend/src/lib/analysis/acquisition-due-diligence.js +6 -6
- 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-retry-plan.js +3 -10
- package/frontend/src/lib/analysis/bulk-session-model.js +7 -2
- package/frontend/src/lib/analysis/bulk-source-coverage.js +76 -0
- 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-response-packet.js +7 -7
- package/frontend/src/lib/analysis/evidence-coverage-ledger.js +328 -0
- package/frontend/src/lib/analysis/http-summary.js +2 -1
- 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 +480 -0
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +153 -0
- package/frontend/src/lib/analysis/lookup-presentation.js +74 -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 +8 -88
- package/frontend/src/lib/analysis/shortlist-model.js +8 -4
- package/frontend/src/lib/analysis/utils.js +7 -40
- 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 +370 -0
- package/frontend/src/lib/analysis/visualization-time-series.js +144 -0
- package/frontend/src/lib/analysis/website-snapshot-model.js +57 -3
- package/frontend/src/lib/analysis/workspace-archive-crypto.js +1 -1
- package/frontend/src/lib/analysis/workspace-archive.js +2 -2
- package/lib/analyst-taxonomy.mjs +40 -0
- package/lib/availability.mjs +30 -10
- package/lib/browser-library-profile.mjs +15 -2
- package/lib/bulk-limits.mjs +13 -0
- package/lib/certificate-extension-profile.mjs +71 -0
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/ct-response-bounds.mjs +9 -0
- package/lib/ct-search.mjs +66 -12
- package/lib/dns-delegation-health.mjs +56 -5
- package/lib/dnssec-evidence-validation.mjs +235 -0
- package/lib/domain-assurance.mjs +368 -0
- package/lib/domain-control-manifest.mjs +382 -0
- package/lib/domain-posture.mjs +11 -10
- package/lib/encrypted-dns-contract.mjs +81 -0
- package/lib/evidence-export.mjs +17 -3
- package/lib/evidence-report-markdown.mjs +1 -1
- package/lib/evidence-report.mjs +4 -1
- package/lib/favicon.mjs +3 -8
- package/lib/generated/cisa-kev-catalog.mjs +1668 -0
- package/lib/html-signals.mjs +4 -0
- package/lib/http-evidence-bounds.mjs +4 -0
- package/lib/http-intelligence.mjs +2 -1
- package/lib/ip-prefix.mjs +87 -0
- package/lib/local-geoip-evidence.mjs +89 -0
- package/lib/lookup-response-contract.mjs +346 -0
- package/lib/observed-network-context.mjs +1 -1
- package/lib/opportunity-scoring.mjs +107 -0
- package/lib/outbound-identity.mjs +9 -0
- package/lib/outbound-request-bounds.mjs +9 -0
- package/lib/page-fingerprints.mjs +1 -1
- package/lib/perceptual-hash-comparison.mjs +34 -0
- package/lib/perceptual-hash.mjs +3 -31
- package/lib/project-metadata.mjs +8 -0
- package/lib/provider-policy-admission.mjs +49 -0
- package/lib/rdap-capabilities.mjs +6 -6
- package/lib/rdap-search-workbench.mjs +247 -0
- package/lib/rdap.mjs +7 -2
- package/lib/registry-access.mjs +1 -1
- package/lib/registry-capabilities.mjs +31 -1
- package/lib/registry-capability-catalogue.mjs +15 -14
- package/lib/registry-insights.mjs +6 -6
- package/lib/response-policy.mjs +81 -6
- package/lib/risk-scoring.mjs +206 -83
- package/lib/rpki-evidence.mjs +105 -0
- package/lib/safe-fetch.mjs +2 -1
- package/lib/scoring-evidence-quality.mjs +107 -0
- package/lib/security-txt.mjs +3 -3
- package/lib/semantic-version.mjs +54 -0
- package/lib/static-html-analysis.mjs +27 -0
- package/lib/threat-intelligence-manifests.mjs +1 -1
- package/lib/threatfox-intelligence.mjs +11 -6
- package/lib/tls-intelligence.mjs +13 -3
- package/lib/tlsa-evidence.mjs +194 -0
- package/lib/urlhaus-intelligence.mjs +11 -6
- package/lib/urlscan-intelligence.mjs +10 -5
- package/lib/website-security-posture.mjs +16 -9
- package/lib/website-technology.mjs +1 -1
- package/lib/whois-chain.mjs +14 -152
- package/lib/whois.mjs +2 -2
- package/package.json +6 -2
- package/third-party-notices.txt +911 -19
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ node bin/whoisleuth.mts --help
|
|
|
50
50
|
node bin/whoisleuth.mts lookup example.test
|
|
51
51
|
node bin/whoisleuth.mts lookup example.test --deep --summary
|
|
52
52
|
node bin/whoisleuth.mts lookup example.test --deep --plan --json
|
|
53
|
+
node bin/whoisleuth.mts lookup example.test --deep --observer workstation-a --vantage office-egress --json
|
|
53
54
|
node bin/whoisleuth.mts lookup example.test --deep --markdown --output lookup.md
|
|
54
55
|
node bin/whoisleuth.mts bulk domains.txt --deep --checkpoint bulk-checkpoint.json
|
|
55
56
|
node bin/whoisleuth.mts bulk domains.txt --csv --registered-only
|
|
@@ -58,9 +59,16 @@ node bin/whoisleuth.mts discover example.test --dictionary private-terms.txt --s
|
|
|
58
59
|
node bin/whoisleuth.mts discover-scan example.test --scan-limit 50 --checkpoint candidate-scan.json --json
|
|
59
60
|
node bin/whoisleuth.mts page-compare official.json candidate.json --json
|
|
60
61
|
node bin/whoisleuth.mts mail-review bulk.json --json
|
|
62
|
+
node bin/whoisleuth.mts domain-control domain-control-input.json --json
|
|
63
|
+
node bin/whoisleuth.mts assurance domain-assurance-input.json --json
|
|
64
|
+
node bin/whoisleuth.mts sharing-review response-packet.json --marking amber --recipient-scope organization --purpose 'Reviewed handoff' --human-reviewed --personal-data-reviewed --redactions-confirmed --json
|
|
65
|
+
node bin/whoisleuth.mts workflow-plan domain-triage example.test --json
|
|
61
66
|
node bin/whoisleuth.mts diff first-lookup.json second-lookup.json --json
|
|
67
|
+
node bin/whoisleuth.mts reconcile office.json mobile.json external.json --json
|
|
62
68
|
node bin/whoisleuth.mts timeline first-observation.json second-observation.json latest-observation.json --json
|
|
63
69
|
node bin/whoisleuth.mts registry-support example.test --json
|
|
70
|
+
node bin/whoisleuth.mts registry-doctor lookup.json --json
|
|
71
|
+
node bin/whoisleuth.mts lookalike-calibrate reviewed-candidates.json --json
|
|
64
72
|
node bin/whoisleuth.mts doctor
|
|
65
73
|
node bin/whoisleuth.mts commands --json
|
|
66
74
|
node bin/whoisleuth.mts completion zsh
|
|
@@ -95,10 +103,18 @@ cleanup before exiting immediately.
|
|
|
95
103
|
|
|
96
104
|
The offline `timeline` command orders 2 to 20 saved Lookup documents for one
|
|
97
105
|
domain and compares each adjacent observation. It retains observation times and
|
|
98
|
-
|
|
106
|
+
normalised field states but no input filenames or raw registry payloads.
|
|
99
107
|
Observed differences remain separate from missing, unavailable, and changed
|
|
100
108
|
collection conditions.
|
|
101
109
|
|
|
110
|
+
Optional bounded observer and vantage labels let analysts distinguish saved
|
|
111
|
+
collection contexts without changing collection. `reconcile` compares 2 to 5
|
|
112
|
+
same-domain observations while keeping agreement, disagreement, and
|
|
113
|
+
non-comparable evidence separate; the labels are context, not proof of
|
|
114
|
+
independence. `registry-doctor`, `assurance`, `sharing-review`, and
|
|
115
|
+
`lookalike-calibrate` add offline compatibility, change/recovery, sharing, and
|
|
116
|
+
review-yield workflows without contacting the hosted application.
|
|
117
|
+
|
|
102
118
|
Bulk also supports fixed-column CSV, domain-only or exact-query output,
|
|
103
119
|
registered, inconclusive, and hard-failure output filters, and bounded A, AAAA,
|
|
104
120
|
NS, and MX summaries. Discover
|
|
@@ -113,7 +129,8 @@ lookups can be compared across static page, parse5-tokenized DOM structure,
|
|
|
113
129
|
favicon, technology, and TLS evidence, while saved Bulk output can be reviewed
|
|
114
130
|
for passive MX, null MX, SPF, DMARC, and shared-provider context.
|
|
115
131
|
|
|
116
|
-
See `docs/cli.md` in the package for
|
|
132
|
+
See `docs/cli.md` in the package for installation and first use, and
|
|
133
|
+
`docs/cli-reference.md` for the complete command, privacy, output, and
|
|
117
134
|
evidence-contract reference. Report suspected vulnerabilities through the
|
|
118
135
|
private channel described in `SECURITY.md`.
|
|
119
136
|
|
package/bin/whoisleuth.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { cleanupPendingOutputFilesSync } from "../cli/output-file.mjs";
|
|
4
|
-
import { runCli } from "../cli/runner.mjs";
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
5
3
|
for (const stream of [process.stdout, process.stderr]) {
|
|
6
4
|
stream.on('error', (error) => {
|
|
7
5
|
if (error.code === 'EPIPE')
|
|
@@ -9,27 +7,46 @@ for (const stream of [process.stdout, process.stderr]) {
|
|
|
9
7
|
throw error;
|
|
10
8
|
});
|
|
11
9
|
}
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
|
|
10
|
+
const argv = process.argv.slice(2);
|
|
11
|
+
if (argv.length === 1 && (argv[0] === '--version' || argv[0] === '-V')) {
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const metadata = require('../package.json');
|
|
14
|
+
if (typeof metadata.version !== 'string' || !metadata.version) {
|
|
15
|
+
process.stderr.write('Internal CLI error: Package version is unavailable.\n');
|
|
16
|
+
process.exitCode = 70;
|
|
17
|
+
}
|
|
18
18
|
else {
|
|
19
|
-
|
|
20
|
-
process.exit(130);
|
|
19
|
+
process.stdout.write(`${metadata.version}\n`);
|
|
21
20
|
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const [errorsModule, outputModule, runnerModule] = await Promise.all([
|
|
24
|
+
import("../cli/errors.mjs"),
|
|
25
|
+
import("../cli/output-file.mjs"),
|
|
26
|
+
import("../cli/runner.mjs"),
|
|
27
|
+
]);
|
|
28
|
+
const cancellation = new AbortController();
|
|
29
|
+
let interruptionCount = 0;
|
|
30
|
+
const interrupt = () => {
|
|
31
|
+
interruptionCount += 1;
|
|
32
|
+
if (interruptionCount === 1)
|
|
33
|
+
cancellation.abort(new DOMException('Aborted', 'AbortError'));
|
|
34
|
+
else {
|
|
35
|
+
outputModule.cleanupPendingOutputFilesSync();
|
|
36
|
+
process.exit(130);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
process.on('SIGINT', interrupt);
|
|
40
|
+
runnerModule.runCli(argv, { signal: cancellation.signal }).then((code) => {
|
|
41
|
+
if (code === 130) {
|
|
42
|
+
process.removeListener('SIGINT', interrupt);
|
|
43
|
+
process.exit(130);
|
|
44
|
+
}
|
|
45
|
+
process.exitCode = code;
|
|
46
|
+
}).catch((error) => {
|
|
47
|
+
process.stderr.write(`Internal CLI error: ${errorsModule.boundedCliErrorMessage(error)}\n`);
|
|
48
|
+
process.exitCode = 70;
|
|
49
|
+
}).finally(() => {
|
|
26
50
|
process.removeListener('SIGINT', interrupt);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
process.exitCode = code;
|
|
30
|
-
}).catch((error) => {
|
|
31
|
-
process.stderr.write(`Internal CLI error: ${boundedCliErrorMessage(error)}\n`);
|
|
32
|
-
process.exitCode = 70;
|
|
33
|
-
}).finally(() => {
|
|
34
|
-
process.removeListener('SIGINT', interrupt);
|
|
35
|
-
});
|
|
51
|
+
});
|
|
52
|
+
}
|
package/cli/arguments.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseInspectArchiveArguments, parseSignArtifactArguments, parseVerifySignatureArguments, } from "./evidence-command-arguments.mjs";
|
|
2
2
|
import { CliUsageError } from "./errors.mjs";
|
|
3
|
+
import { INVESTIGATION_PLAN_RECIPES, } from "./investigation-plan.mjs";
|
|
3
4
|
const MAX_CLI_ARGUMENTS = 32;
|
|
4
5
|
const MAX_CLI_ARGUMENT_LENGTH = 1024;
|
|
5
6
|
const CLI_COMMANDS = [
|
|
@@ -16,7 +17,9 @@ const CLI_COMMANDS = [
|
|
|
16
17
|
'http',
|
|
17
18
|
'tls',
|
|
18
19
|
'registry-support',
|
|
20
|
+
'registry-doctor',
|
|
19
21
|
'risk-calibrate',
|
|
22
|
+
'lookalike-calibrate',
|
|
20
23
|
'verify-artifact',
|
|
21
24
|
'inspect-archive',
|
|
22
25
|
'sign-artifact',
|
|
@@ -25,7 +28,13 @@ const CLI_COMMANDS = [
|
|
|
25
28
|
'compare',
|
|
26
29
|
'page-compare',
|
|
27
30
|
'mail-review',
|
|
31
|
+
'review-evidence',
|
|
32
|
+
'domain-control',
|
|
33
|
+
'assurance',
|
|
34
|
+
'sharing-review',
|
|
35
|
+
'workflow-plan',
|
|
28
36
|
'diff',
|
|
37
|
+
'reconcile',
|
|
29
38
|
'timeline',
|
|
30
39
|
'export',
|
|
31
40
|
];
|
|
@@ -128,8 +137,12 @@ function parseCliArgumentsCore(argv) {
|
|
|
128
137
|
return parseTlsArguments(argv.slice(1));
|
|
129
138
|
if (command === 'registry-support')
|
|
130
139
|
return parseRegistrySupportArguments(argv.slice(1));
|
|
140
|
+
if (command === 'registry-doctor')
|
|
141
|
+
return parseRegistryDoctorArguments(argv.slice(1));
|
|
131
142
|
if (command === 'risk-calibrate')
|
|
132
143
|
return parseRiskCalibrateArguments(argv.slice(1));
|
|
144
|
+
if (command === 'lookalike-calibrate')
|
|
145
|
+
return parseLookalikeCalibrateArguments(argv.slice(1));
|
|
133
146
|
if (command === 'verify-artifact')
|
|
134
147
|
return parseVerifyArtifactArguments(argv.slice(1));
|
|
135
148
|
if (command === 'inspect-archive')
|
|
@@ -146,8 +159,20 @@ function parseCliArgumentsCore(argv) {
|
|
|
146
159
|
return parsePageCompareArguments(argv.slice(1));
|
|
147
160
|
if (command === 'mail-review')
|
|
148
161
|
return parseMailReviewArguments(argv.slice(1));
|
|
162
|
+
if (command === 'review-evidence')
|
|
163
|
+
return parseReviewEvidenceArguments(argv.slice(1));
|
|
164
|
+
if (command === 'domain-control')
|
|
165
|
+
return parseDomainControlArguments(argv.slice(1));
|
|
166
|
+
if (command === 'assurance')
|
|
167
|
+
return parseAssuranceArguments(argv.slice(1));
|
|
168
|
+
if (command === 'sharing-review')
|
|
169
|
+
return parseSharingReviewArguments(argv.slice(1));
|
|
170
|
+
if (command === 'workflow-plan')
|
|
171
|
+
return parseWorkflowPlanArguments(argv.slice(1));
|
|
149
172
|
if (command === 'diff')
|
|
150
173
|
return parseDiffArguments(argv.slice(1));
|
|
174
|
+
if (command === 'reconcile')
|
|
175
|
+
return parseReconcileArguments(argv.slice(1));
|
|
151
176
|
if (command === 'timeline')
|
|
152
177
|
return parseTimelineArguments(argv.slice(1));
|
|
153
178
|
if (command === 'export')
|
|
@@ -163,7 +188,13 @@ function parseCliArgumentsCore(argv) {
|
|
|
163
188
|
let strictExit = false;
|
|
164
189
|
let events = false;
|
|
165
190
|
let plan = false;
|
|
166
|
-
|
|
191
|
+
let observerLabel = null;
|
|
192
|
+
let vantageLabel = null;
|
|
193
|
+
const lookupArguments = argv.slice(1);
|
|
194
|
+
for (let index = 0; index < lookupArguments.length; index++) {
|
|
195
|
+
const argument = lookupArguments[index];
|
|
196
|
+
if (argument === undefined)
|
|
197
|
+
break;
|
|
167
198
|
if (argument === '--json') {
|
|
168
199
|
if (output !== 'terminal')
|
|
169
200
|
throw new CliUsageError('Choose only one output format.');
|
|
@@ -207,6 +238,22 @@ function parseCliArgumentsCore(argv) {
|
|
|
207
238
|
throw new CliUsageError('--plan may be supplied only once.');
|
|
208
239
|
plan = true;
|
|
209
240
|
}
|
|
241
|
+
else if (argument === '--observer' || argument === '--vantage') {
|
|
242
|
+
const isObserver = argument === '--observer';
|
|
243
|
+
if (isObserver ? observerLabel !== null : vantageLabel !== null) {
|
|
244
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
245
|
+
}
|
|
246
|
+
const value = lookupArguments[++index];
|
|
247
|
+
if (!value || value.startsWith('-'))
|
|
248
|
+
throw new CliUsageError(`${argument} requires one bounded label.`);
|
|
249
|
+
const normalized = value.replace(/\s+/gu, ' ').trim();
|
|
250
|
+
if (!normalized || normalized.length > 80)
|
|
251
|
+
throw new CliUsageError(`${argument} requires a label of at most 80 characters.`);
|
|
252
|
+
if (isObserver)
|
|
253
|
+
observerLabel = normalized;
|
|
254
|
+
else
|
|
255
|
+
vantageLabel = normalized;
|
|
256
|
+
}
|
|
210
257
|
else if (argument === '--quiet')
|
|
211
258
|
quiet = true;
|
|
212
259
|
else if (argument === '--no-color')
|
|
@@ -227,7 +274,7 @@ function parseCliArgumentsCore(argv) {
|
|
|
227
274
|
if (plan && (detailSet || strictExit || events || quiet)) {
|
|
228
275
|
throw new CliUsageError('--plan cannot be combined with detail, strict-exit, event, or quiet options.');
|
|
229
276
|
}
|
|
230
|
-
return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, quiet, color };
|
|
277
|
+
return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, observerLabel, vantageLabel, quiet, color };
|
|
231
278
|
}
|
|
232
279
|
function parseCompletionArguments(argv) {
|
|
233
280
|
if (argv.length !== 1 || !['bash', 'zsh', 'fish', 'powershell'].includes(argv[0] || '')) {
|
|
@@ -893,6 +940,200 @@ function parseMailReviewArguments(argv) {
|
|
|
893
940
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
894
941
|
return { action: 'mail-review', source, output, quiet, color };
|
|
895
942
|
}
|
|
943
|
+
function parseReviewEvidenceArguments(argv) {
|
|
944
|
+
let source = null;
|
|
945
|
+
let mmdbSource = null;
|
|
946
|
+
let output = 'terminal';
|
|
947
|
+
let quiet = false;
|
|
948
|
+
let color = true;
|
|
949
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
950
|
+
const argument = argv[index];
|
|
951
|
+
if (argument === undefined)
|
|
952
|
+
break;
|
|
953
|
+
if (argument === '--json') {
|
|
954
|
+
if (output !== 'terminal')
|
|
955
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
956
|
+
output = 'json';
|
|
957
|
+
}
|
|
958
|
+
else if (argument === '--mmdb') {
|
|
959
|
+
const value = argv[++index];
|
|
960
|
+
if (!value || value.startsWith('-') || mmdbSource !== null)
|
|
961
|
+
throw new CliUsageError('--mmdb requires one database file and may be supplied only once.');
|
|
962
|
+
mmdbSource = value;
|
|
963
|
+
}
|
|
964
|
+
else if (argument === '--quiet')
|
|
965
|
+
quiet = true;
|
|
966
|
+
else if (argument === '--no-color')
|
|
967
|
+
color = false;
|
|
968
|
+
else if (argument.startsWith('-'))
|
|
969
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
970
|
+
else if (source === null)
|
|
971
|
+
source = argument;
|
|
972
|
+
else
|
|
973
|
+
throw new CliUsageError('review-evidence accepts one optional versioned JSON input file.');
|
|
974
|
+
}
|
|
975
|
+
if (quiet && output !== 'terminal')
|
|
976
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
977
|
+
return { action: 'review-evidence', source, mmdbSource, output, quiet, color };
|
|
978
|
+
}
|
|
979
|
+
function parseDomainControlArguments(argv) {
|
|
980
|
+
let source = null;
|
|
981
|
+
let output = 'terminal';
|
|
982
|
+
let quiet = false;
|
|
983
|
+
let color = true;
|
|
984
|
+
for (const argument of argv) {
|
|
985
|
+
if (argument === '--json') {
|
|
986
|
+
if (output !== 'terminal')
|
|
987
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
988
|
+
output = 'json';
|
|
989
|
+
}
|
|
990
|
+
else if (argument === '--quiet')
|
|
991
|
+
quiet = true;
|
|
992
|
+
else if (argument === '--no-color')
|
|
993
|
+
color = false;
|
|
994
|
+
else if (argument.startsWith('-'))
|
|
995
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
996
|
+
else if (source === null)
|
|
997
|
+
source = argument;
|
|
998
|
+
else
|
|
999
|
+
throw new CliUsageError('domain-control accepts one optional versioned JSON input file.');
|
|
1000
|
+
}
|
|
1001
|
+
if (quiet && output !== 'terminal')
|
|
1002
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1003
|
+
return { action: 'domain-control', source, output, quiet, color };
|
|
1004
|
+
}
|
|
1005
|
+
function parseAssuranceArguments(argv) {
|
|
1006
|
+
let source = null;
|
|
1007
|
+
let output = 'terminal';
|
|
1008
|
+
let quiet = false;
|
|
1009
|
+
let color = true;
|
|
1010
|
+
for (const argument of argv) {
|
|
1011
|
+
if (argument === '--json') {
|
|
1012
|
+
if (output !== 'terminal')
|
|
1013
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1014
|
+
output = 'json';
|
|
1015
|
+
}
|
|
1016
|
+
else if (argument === '--quiet')
|
|
1017
|
+
quiet = true;
|
|
1018
|
+
else if (argument === '--no-color')
|
|
1019
|
+
color = false;
|
|
1020
|
+
else if (argument.startsWith('-'))
|
|
1021
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1022
|
+
else if (source === null)
|
|
1023
|
+
source = argument;
|
|
1024
|
+
else
|
|
1025
|
+
throw new CliUsageError('assurance accepts one optional versioned JSON input file.');
|
|
1026
|
+
}
|
|
1027
|
+
if (quiet && output !== 'terminal')
|
|
1028
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1029
|
+
return { action: 'assurance', source, output, quiet, color };
|
|
1030
|
+
}
|
|
1031
|
+
function parseSharingReviewArguments(argv) {
|
|
1032
|
+
let source = null;
|
|
1033
|
+
let output = 'terminal';
|
|
1034
|
+
let marking = null;
|
|
1035
|
+
let recipientScope = null;
|
|
1036
|
+
let purpose = null;
|
|
1037
|
+
let humanReviewed = false;
|
|
1038
|
+
let personalDataReviewed = false;
|
|
1039
|
+
let redactionsConfirmed = false;
|
|
1040
|
+
let quiet = false;
|
|
1041
|
+
let color = true;
|
|
1042
|
+
for (let index = 0; index < argv.length; index++) {
|
|
1043
|
+
const argument = argv[index];
|
|
1044
|
+
if (argument === undefined)
|
|
1045
|
+
break;
|
|
1046
|
+
if (argument === '--json') {
|
|
1047
|
+
if (output !== 'terminal')
|
|
1048
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1049
|
+
output = 'json';
|
|
1050
|
+
}
|
|
1051
|
+
else if (argument === '--marking') {
|
|
1052
|
+
if (marking !== null)
|
|
1053
|
+
throw new CliUsageError('--marking may be supplied only once.');
|
|
1054
|
+
const value = argv[++index];
|
|
1055
|
+
if (!['clear', 'green', 'amber', 'amber-strict', 'red'].includes(value || '')) {
|
|
1056
|
+
throw new CliUsageError('--marking requires clear, green, amber, amber-strict, or red.');
|
|
1057
|
+
}
|
|
1058
|
+
marking = value;
|
|
1059
|
+
}
|
|
1060
|
+
else if (argument === '--recipient-scope') {
|
|
1061
|
+
if (recipientScope !== null)
|
|
1062
|
+
throw new CliUsageError('--recipient-scope may be supplied only once.');
|
|
1063
|
+
const value = argv[++index];
|
|
1064
|
+
if (!['public', 'community', 'organization', 'named-recipients'].includes(value || '')) {
|
|
1065
|
+
throw new CliUsageError('--recipient-scope requires public, community, organization, or named-recipients.');
|
|
1066
|
+
}
|
|
1067
|
+
recipientScope = value;
|
|
1068
|
+
}
|
|
1069
|
+
else if (argument === '--purpose') {
|
|
1070
|
+
if (purpose !== null)
|
|
1071
|
+
throw new CliUsageError('--purpose may be supplied only once.');
|
|
1072
|
+
const value = argv[++index];
|
|
1073
|
+
if (!value || value.startsWith('-'))
|
|
1074
|
+
throw new CliUsageError('--purpose requires one bounded description.');
|
|
1075
|
+
const normalized = value.replace(/\s+/gu, ' ').trim();
|
|
1076
|
+
if (!normalized || normalized.length > 200)
|
|
1077
|
+
throw new CliUsageError('--purpose is limited to 200 characters.');
|
|
1078
|
+
purpose = normalized;
|
|
1079
|
+
}
|
|
1080
|
+
else if (argument === '--human-reviewed')
|
|
1081
|
+
humanReviewed = true;
|
|
1082
|
+
else if (argument === '--personal-data-reviewed')
|
|
1083
|
+
personalDataReviewed = true;
|
|
1084
|
+
else if (argument === '--redactions-confirmed')
|
|
1085
|
+
redactionsConfirmed = true;
|
|
1086
|
+
else if (argument === '--quiet')
|
|
1087
|
+
quiet = true;
|
|
1088
|
+
else if (argument === '--no-color')
|
|
1089
|
+
color = false;
|
|
1090
|
+
else if (argument.startsWith('-'))
|
|
1091
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1092
|
+
else if (source === null)
|
|
1093
|
+
source = argument;
|
|
1094
|
+
else
|
|
1095
|
+
throw new CliUsageError('sharing-review accepts one optional artefact JSON file.');
|
|
1096
|
+
}
|
|
1097
|
+
if (!marking || !recipientScope || !purpose) {
|
|
1098
|
+
throw new CliUsageError('sharing-review requires --marking, --recipient-scope, and --purpose.');
|
|
1099
|
+
}
|
|
1100
|
+
if (quiet && output !== 'terminal')
|
|
1101
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1102
|
+
return {
|
|
1103
|
+
action: 'sharing-review', source, output, marking, recipientScope, purpose,
|
|
1104
|
+
humanReviewed, personalDataReviewed, redactionsConfirmed, quiet, color,
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
function parseWorkflowPlanArguments(argv) {
|
|
1108
|
+
const positional = [];
|
|
1109
|
+
let output = 'terminal';
|
|
1110
|
+
let quiet = false;
|
|
1111
|
+
let color = true;
|
|
1112
|
+
for (const argument of argv) {
|
|
1113
|
+
if (argument === '--json') {
|
|
1114
|
+
if (output !== 'terminal')
|
|
1115
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1116
|
+
output = 'json';
|
|
1117
|
+
}
|
|
1118
|
+
else if (argument === '--quiet')
|
|
1119
|
+
quiet = true;
|
|
1120
|
+
else if (argument === '--no-color')
|
|
1121
|
+
color = false;
|
|
1122
|
+
else if (argument.startsWith('-'))
|
|
1123
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1124
|
+
else
|
|
1125
|
+
positional.push(argument);
|
|
1126
|
+
}
|
|
1127
|
+
if (positional.length !== 2)
|
|
1128
|
+
throw new CliUsageError('workflow-plan requires one fixed recipe and one subject.');
|
|
1129
|
+
const recipe = positional[0];
|
|
1130
|
+
if (!INVESTIGATION_PLAN_RECIPES.includes(recipe)) {
|
|
1131
|
+
throw new CliUsageError(`workflow-plan recipe must be one of: ${INVESTIGATION_PLAN_RECIPES.join(', ')}.`);
|
|
1132
|
+
}
|
|
1133
|
+
if (quiet && output !== 'terminal')
|
|
1134
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1135
|
+
return { action: 'workflow-plan', recipe: recipe, subject: positional[1], output, quiet, color };
|
|
1136
|
+
}
|
|
896
1137
|
function parseDiffArguments(argv) {
|
|
897
1138
|
let leftSource = null;
|
|
898
1139
|
let rightSource = null;
|
|
@@ -926,6 +1167,36 @@ function parseDiffArguments(argv) {
|
|
|
926
1167
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
927
1168
|
return { action: 'diff', leftSource, rightSource, output, quiet, color };
|
|
928
1169
|
}
|
|
1170
|
+
function parseReconcileArguments(argv) {
|
|
1171
|
+
const sources = [];
|
|
1172
|
+
let output = 'terminal';
|
|
1173
|
+
let quiet = false;
|
|
1174
|
+
let color = true;
|
|
1175
|
+
for (const argument of argv) {
|
|
1176
|
+
if (argument === '--json') {
|
|
1177
|
+
if (output !== 'terminal')
|
|
1178
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1179
|
+
output = 'json';
|
|
1180
|
+
}
|
|
1181
|
+
else if (argument === '--quiet')
|
|
1182
|
+
quiet = true;
|
|
1183
|
+
else if (argument === '--no-color')
|
|
1184
|
+
color = false;
|
|
1185
|
+
else if (argument.startsWith('-'))
|
|
1186
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1187
|
+
else
|
|
1188
|
+
sources.push(argument);
|
|
1189
|
+
}
|
|
1190
|
+
if (sources.length < 2 || sources.length > 5) {
|
|
1191
|
+
throw new CliUsageError('reconcile requires from 2 to 5 saved lookup JSON files.');
|
|
1192
|
+
}
|
|
1193
|
+
if (new Set(sources).size !== sources.length) {
|
|
1194
|
+
throw new CliUsageError('reconcile input files must be different.');
|
|
1195
|
+
}
|
|
1196
|
+
if (quiet && output !== 'terminal')
|
|
1197
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1198
|
+
return { action: 'reconcile', sources, output, quiet, color };
|
|
1199
|
+
}
|
|
929
1200
|
function parseTimelineArguments(argv) {
|
|
930
1201
|
const sources = [];
|
|
931
1202
|
let output = 'terminal';
|
|
@@ -982,6 +1253,32 @@ function parseRegistrySupportArguments(argv) {
|
|
|
982
1253
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
983
1254
|
return { action: 'registry-support', target, output, quiet, color };
|
|
984
1255
|
}
|
|
1256
|
+
function parseRegistryDoctorArguments(argv) {
|
|
1257
|
+
let source = null;
|
|
1258
|
+
let output = 'terminal';
|
|
1259
|
+
let quiet = false;
|
|
1260
|
+
let color = true;
|
|
1261
|
+
for (const argument of argv) {
|
|
1262
|
+
if (argument === '--json') {
|
|
1263
|
+
if (output !== 'terminal')
|
|
1264
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1265
|
+
output = 'json';
|
|
1266
|
+
}
|
|
1267
|
+
else if (argument === '--quiet')
|
|
1268
|
+
quiet = true;
|
|
1269
|
+
else if (argument === '--no-color')
|
|
1270
|
+
color = false;
|
|
1271
|
+
else if (argument.startsWith('-'))
|
|
1272
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1273
|
+
else if (source === null)
|
|
1274
|
+
source = argument;
|
|
1275
|
+
else
|
|
1276
|
+
throw new CliUsageError('registry-doctor accepts one optional saved Lookup JSON file.');
|
|
1277
|
+
}
|
|
1278
|
+
if (quiet && output !== 'terminal')
|
|
1279
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1280
|
+
return { action: 'registry-doctor', source, output, quiet, color };
|
|
1281
|
+
}
|
|
985
1282
|
function parseRiskCalibrateArguments(argv) {
|
|
986
1283
|
let source = null;
|
|
987
1284
|
let output = 'terminal';
|
|
@@ -1008,6 +1305,32 @@ function parseRiskCalibrateArguments(argv) {
|
|
|
1008
1305
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1009
1306
|
return { action: 'risk-calibrate', source, output, quiet, color };
|
|
1010
1307
|
}
|
|
1308
|
+
function parseLookalikeCalibrateArguments(argv) {
|
|
1309
|
+
let source = null;
|
|
1310
|
+
let output = 'terminal';
|
|
1311
|
+
let quiet = false;
|
|
1312
|
+
let color = true;
|
|
1313
|
+
for (const argument of argv) {
|
|
1314
|
+
if (argument === '--json') {
|
|
1315
|
+
if (output !== 'terminal')
|
|
1316
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1317
|
+
output = 'json';
|
|
1318
|
+
}
|
|
1319
|
+
else if (argument === '--quiet')
|
|
1320
|
+
quiet = true;
|
|
1321
|
+
else if (argument === '--no-color')
|
|
1322
|
+
color = false;
|
|
1323
|
+
else if (argument.startsWith('-'))
|
|
1324
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1325
|
+
else if (source === null)
|
|
1326
|
+
source = argument;
|
|
1327
|
+
else
|
|
1328
|
+
throw new CliUsageError('lookalike-calibrate accepts one optional reviewed dataset JSON file.');
|
|
1329
|
+
}
|
|
1330
|
+
if (quiet && output !== 'terminal')
|
|
1331
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1332
|
+
return { action: 'lookalike-calibrate', source, output, quiet, color };
|
|
1333
|
+
}
|
|
1011
1334
|
function parseVerifyArtifactArguments(argv) {
|
|
1012
1335
|
let source = null;
|
|
1013
1336
|
let passphraseSource = null;
|
|
@@ -1041,7 +1364,7 @@ function parseVerifyArtifactArguments(argv) {
|
|
|
1041
1364
|
else if (source === null)
|
|
1042
1365
|
source = argument;
|
|
1043
1366
|
else
|
|
1044
|
-
throw new CliUsageError('verify-artifact accepts one optional JSON file. Otherwise pipe one
|
|
1367
|
+
throw new CliUsageError('verify-artifact accepts one optional JSON file. Otherwise pipe one artefact on stdin.');
|
|
1045
1368
|
}
|
|
1046
1369
|
if (quiet && output !== 'terminal')
|
|
1047
1370
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|