@slicedearth/whoisleuth-cli 2.3.0 → 2.4.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/NOTICE +6 -0
- package/README.md +19 -15
- package/bin/whoisleuth.mjs +8 -1
- package/cli/archive-content-digest.mjs +14 -0
- package/cli/archive-inspect.mjs +20 -53
- package/cli/arguments.mjs +68 -6
- package/cli/artifact-validation/case-response.mjs +47 -41
- package/cli/artifact-validation/investigation-capsule.mjs +52 -31
- package/cli/artifact-validation/lookup-evidence.mjs +12 -6
- package/cli/artifact-validation/signed-review.mjs +83 -61
- package/cli/artifact-validation/structure-primitives.mjs +1 -105
- package/cli/artifact-verify.mjs +90 -24
- package/cli/assurance-command-runner.mjs +39 -4
- package/cli/bagit-folder.mjs +57 -0
- package/cli/bagit-review.mjs +23 -0
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-command-runner.mjs +2 -0
- package/cli/bulk-output.mjs +29 -3
- package/cli/case-command.mjs +195 -0
- package/cli/case-pack.mjs +39 -31
- package/cli/command-argument-grammar.mjs +4 -0
- package/cli/command-reference.mjs +145 -75
- package/cli/completion.mjs +298 -79
- package/cli/config-profile.mjs +7 -5
- package/cli/ct-event-intake.mjs +12 -12
- package/cli/discover.mjs +4 -3
- package/cli/discovery-observation-snapshot.mjs +44 -16
- package/cli/discovery-scan-command-runner.mjs +2 -0
- package/cli/discovery-scan.mjs +7 -4
- package/cli/discovery-workflow.mjs +1 -1
- package/cli/domain-control-monitor.mjs +50 -20
- package/cli/domain-control-observations.mjs +149 -102
- package/cli/errors.mjs +33 -1
- package/cli/evidence-command-runner.mjs +16 -5
- package/cli/evidence-signing.mjs +2 -2
- package/cli/external-observation-mapping.mjs +7 -8
- package/cli/formatters/terminal-lookup.mjs +18 -46
- package/cli/formatters/terminal-shared.mjs +5 -3
- package/cli/formatters/terminal-technology.mjs +114 -0
- package/cli/inline-command-families.mjs +1 -0
- package/cli/interactive-launcher.mjs +2 -160
- package/cli/interchange-report.mjs +4 -8
- package/cli/investigation-artifacts.mjs +176 -0
- package/cli/investigation-checkpoint.mjs +10 -0
- package/cli/investigation-folder.mjs +114 -0
- package/cli/investigation-manifest.mjs +1 -112
- package/cli/investigation-package-review.mjs +103 -0
- package/cli/investigation-plan.mjs +32 -19
- package/cli/investigation-run.mjs +188 -33
- package/cli/local-document-checkpoint.mjs +170 -0
- package/cli/lookup-browser.mjs +5 -12
- package/cli/lookup-command-runner.mjs +17 -6
- package/cli/lookup-diff.mjs +37 -3
- package/cli/lookup-plan.mjs +11 -5
- package/cli/lookup-reconcile.mjs +10 -8
- package/cli/mail-review.mjs +66 -14
- package/cli/manual.mjs +1 -0
- package/cli/network-command-runner.mjs +2 -0
- package/cli/output-file.mjs +31 -5
- package/cli/registry-doctor.mjs +3 -1
- package/cli/registry-support.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +2 -2
- package/cli/review-command-runner.mjs +45 -0
- package/cli/runner.mjs +55 -6
- package/cli/sharing-review.mjs +21 -34
- package/cli/signer-trust.mjs +104 -0
- package/cli/source-reliability.mjs +74 -83
- package/cli/terminal-input.mjs +168 -0
- package/cli/workflow-command-runner.mjs +68 -13
- package/docs/cli-reference.md +76 -30
- package/docs/cli.md +289 -25
- package/lib/abort.mjs +4 -1
- package/lib/availability.mjs +161 -112
- package/lib/bounded-file.mjs +3 -1
- package/lib/bounded-html-document.mjs +125 -0
- package/lib/bounded-json.mjs +43 -3
- package/lib/browser-library-profile.mjs +42 -51
- package/lib/browser-library-worker.mjs +112 -0
- package/lib/client-behavior-profile.mjs +3 -3
- package/lib/ct-search.mjs +11 -4
- package/lib/dns-delegation-health.mjs +24 -14
- package/lib/dns-inheritance-review.mjs +217 -0
- package/lib/dns-intelligence.mjs +36 -23
- package/lib/dns-mx.mjs +1 -1
- package/lib/dnssec-evidence-validation.mjs +11 -3
- package/lib/domain-change-packet.mjs +7 -2
- package/lib/domain-change-review.mjs +14 -8
- package/lib/domain-control-flight-recorder.mjs +219 -118
- package/lib/domain-control-manifest.mjs +117 -57
- package/lib/domain-posture-analysis.mjs +20 -7
- package/lib/domain-posture-parsers.mjs +88 -6
- package/lib/domain-posture.mjs +113 -26
- package/lib/evidence-export-privacy.mjs +561 -0
- package/lib/evidence-export.mjs +11 -592
- package/lib/evidence-report.mjs +3 -1
- package/lib/favicon.mjs +117 -41
- package/lib/generated/cisa-kev-catalog.mjs +12 -4
- package/lib/generated/registrar-standing-catalogue.mjs +7 -7
- package/lib/generated/retire-browser-catalog.mjs +2335 -940
- package/lib/hostname.mjs +2 -38
- package/lib/html-signals.mjs +82 -85
- package/lib/http-evidence-bounds.mjs +2 -1
- package/lib/http-intelligence.mjs +39 -26
- package/lib/lookup-cache.mjs +34 -11
- package/lib/lookup-child-profile-contract.mjs +24 -15
- package/lib/lookup-diagnostics.mjs +24 -1
- package/lib/lookup-progress.mjs +30 -13
- package/lib/lookup-response-contract.mjs +1048 -0
- package/lib/lookup-response.mjs +7 -10
- package/lib/lookup-source-progress.mjs +17 -14
- package/lib/lookup.mjs +48 -27
- package/lib/observed-network-context.mjs +43 -27
- package/lib/outbound-request-bounds.mjs +4 -1
- package/lib/page-fingerprints.mjs +69 -133
- package/lib/page-role-profile.mjs +12 -8
- package/lib/perceptual-hash.mjs +43 -14
- package/lib/rdap-bootstrap.mjs +72 -22
- package/lib/rdap-client.mjs +15 -7
- package/lib/rdap-normalization.mjs +33 -18
- package/lib/rdap-search-workbench.mjs +1 -1
- package/lib/rdap-transport.mjs +47 -20
- package/lib/rdap-types.mjs +2 -1
- package/lib/rdap.mjs +18 -3
- package/lib/registrable-domain.mjs +21 -1
- package/lib/registrar-standing-contract.mjs +1 -0
- package/lib/registry-capabilities.mjs +5 -1
- package/lib/registry-capability-catalogue.mjs +2 -1
- package/lib/registry-dates.mjs +2 -190
- package/lib/registry-insights.mjs +56 -18
- package/lib/response-policy.mjs +86 -48
- package/lib/risk-calibration-summary.mjs +17 -3
- package/lib/rpki-evidence.mjs +8 -5
- package/lib/safe-fetch.mjs +54 -47
- package/lib/security-txt.mjs +10 -7
- package/lib/service-binding-dns.mjs +8 -0
- package/lib/smtp-transport-review.mjs +27 -15
- package/lib/sslbl-certificates.generated.mjs +136 -135
- package/lib/static-html-analysis.mjs +117 -146
- package/lib/structured-data-identity.mjs +1 -0
- package/lib/technology-markup.mjs +100 -0
- package/lib/threat-intelligence-manifests.mjs +11 -139
- package/lib/threat-intelligence-runtime.mjs +24 -52
- package/lib/threat-intelligence-targets.mjs +6 -34
- package/lib/threat-intelligence-types.mjs +39 -0
- package/lib/typosquat-generator.mjs +19 -15
- package/lib/urlhaus-intelligence.mjs +8 -3
- package/lib/urlscan-intelligence.mjs +1 -1
- package/lib/website-security-posture.mjs +30 -6
- package/lib/website-technology.mjs +111 -71
- package/lib/whois-authority.mjs +14 -2
- package/lib/whois-chain.mjs +9 -2
- package/lib/whois-common-formats.mjs +5 -5
- package/lib/whois-contracts.mjs +2 -1
- package/lib/whois-dialects.mjs +2 -2
- package/lib/whois-parser-support.mjs +8 -1
- package/lib/whois-transport.mjs +23 -7
- package/package.json +2 -1
- package/packages/cases/case-attachment-model.mjs +83 -0
- package/packages/cases/case-evidence-model.mjs +156 -62
- package/packages/cases/case-export-input.mjs +53 -0
- package/packages/cases/case-migration-model.mjs +59 -25
- package/packages/cases/case-model.mjs +1 -0
- package/packages/cases/case-recheck-model.mjs +91 -0
- package/packages/cases/case-record-core.mjs +3 -2
- package/packages/cases/case-record-operations.mjs +89 -27
- package/packages/cases/case-record-projection.mjs +23 -20
- package/packages/cases/case-report.mjs +63 -52
- package/packages/cases/case-response-actions.mjs +654 -0
- package/packages/cases/case-response-model.mjs +41 -1129
- package/packages/cases/case-response-outcomes.mjs +317 -0
- package/packages/cases/case-response-packet-render.mjs +153 -0
- package/packages/cases/case-response-packet-types.mjs +1 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
- package/packages/cases/case-response-packet.mjs +38 -587
- package/packages/cases/case-response-records.mjs +94 -0
- package/packages/cases/case-response-review-inputs.mjs +371 -0
- package/packages/cases/case-response-values.mjs +85 -0
- package/packages/cases/case-review-package.mjs +45 -0
- package/packages/cases/case-selection.mjs +46 -0
- package/packages/cases/case-storage-model.mjs +25 -0
- package/packages/cases/response-route-freshness.mjs +16 -0
- package/packages/comparison/capture-context.mjs +61 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
- package/packages/comparison/page-similarity.mjs +2 -1
- package/packages/comparison/relationship-evidence.mjs +85 -8
- package/packages/comparison/relationship-provenance.mjs +93 -0
- package/packages/contracts/analyst-interchange.mjs +216 -0
- package/packages/contracts/analyst-review-state-contract.mjs +11 -0
- package/packages/contracts/analyst-review-state.mjs +5 -4
- package/packages/contracts/case-portability.mjs +279 -116
- package/packages/contracts/case-views-contract.mjs +12 -0
- package/packages/contracts/case-views.mjs +26 -0
- package/packages/contracts/cli-command-semantics.mjs +1 -0
- package/packages/contracts/cli-lookup.mjs +0 -1
- package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
- package/packages/contracts/domain-control-manifest.mjs +127 -73
- package/packages/contracts/domain-control-monitor.mjs +48 -45
- package/packages/contracts/domain-control-review.mjs +179 -121
- package/packages/contracts/domain-name.mjs +21 -0
- package/packages/contracts/external-observation-interchange.mjs +2 -1
- package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
- package/packages/contracts/http-url.mjs +2 -0
- package/packages/contracts/investigation-package-limits.mjs +24 -0
- package/packages/contracts/investigation-portability.mjs +56 -27
- package/packages/contracts/investigation-run.mjs +16 -0
- package/packages/contracts/ip-address.mjs +34 -0
- package/packages/contracts/lookup-evidence.mjs +35 -0
- package/packages/contracts/offline-comparison.mjs +1 -2
- package/packages/contracts/page-fingerprints.mjs +15 -0
- package/packages/contracts/risk-calibration.mjs +0 -1
- package/packages/contracts/schema-compatibility.mjs +1 -1
- package/packages/contracts/schema-lifecycle.mjs +83 -153
- package/packages/contracts/selected-file-limits.mjs +6 -0
- package/packages/contracts/vulnerability-identifiers.mjs +5 -0
- package/packages/contracts/web-capture.mjs +10 -0
- package/packages/contracts/workspace-portability.mjs +160 -225
- package/packages/evidence/artifact-integrity.mjs +8 -2
- package/packages/evidence/artifact-structure.mjs +105 -0
- package/packages/evidence/decision-fact.mjs +4 -1
- package/packages/evidence/domain-control-runtime.mjs +155 -27
- package/packages/evidence/domain-posture-context.mjs +79 -0
- package/packages/evidence/image-regions.mjs +56 -0
- package/packages/evidence/latest-observations.mjs +28 -0
- package/packages/evidence/lookup-target.mjs +104 -0
- package/packages/evidence/observation.mjs +16 -1
- package/packages/evidence/passphrase-encryption.mjs +44 -0
- package/packages/evidence/record-identity.mjs +72 -0
- package/packages/evidence/registry-dates.mjs +190 -0
- package/packages/evidence/retained-file.mjs +69 -0
- package/packages/evidence/review-age.mjs +14 -0
- package/packages/interchange/bagit.mjs +342 -0
- package/packages/interchange/bounded-zip-extraction.mjs +278 -0
- package/packages/interchange/crc32.mjs +17 -0
- package/packages/interchange/external-findings-import.mjs +89 -49
- package/packages/interchange/web-capture-import.mjs +382 -0
- package/packages/investigation/bulk-mail-exposure.mjs +2 -1
- package/packages/investigation/investigation-bagit.mjs +14 -0
- package/packages/investigation/investigation-capsule.mjs +33 -5
- package/packages/investigation/investigation-manifest.mjs +200 -0
- package/packages/investigation/investigation-package-crypto.mjs +98 -0
- package/packages/investigation/investigation-package.mjs +200 -0
- package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
- package/packages/investigation/lookup-asset-graph.mjs +161 -141
- package/packages/investigation/lookup-claim-readiness.mjs +18 -0
- package/packages/investigation/lookup-investigation-brief.mjs +3 -3
- package/packages/investigation/lookup-task-guidance.mjs +58 -0
- package/packages/monitoring/analyst-review-state.mjs +38 -90
- package/packages/workspace/brand-profile-model.mjs +158 -20
- package/packages/workspace/bulk-columns.mjs +17 -0
- package/packages/workspace/bulk-review-model.mjs +11 -5
- package/packages/workspace/bulk-session-model.mjs +95 -20
- package/packages/workspace/bulk-sort.mjs +1 -0
- package/packages/workspace/case-views.mjs +102 -0
- package/packages/workspace/hostile-input.mjs +29 -8
- package/packages/workspace/investigation-template-model.mjs +19 -4
- package/packages/workspace/page-baseline.mjs +18 -15
- package/packages/workspace/relationship-observation-model.mjs +41 -26
- package/packages/workspace/watchlist-history.mjs +2 -2
- package/packages/workspace/watchlist-store.mjs +8 -2
- package/packages/workspace/website-snapshot-model.mjs +70 -30
- package/packages/workspace/workspace-archive-crypto.mjs +41 -61
- package/packages/workspace/workspace-archive.mjs +85 -21
- package/third-party-notices.txt +29 -2
- package/packages/investigation/bulk-review-export.mjs +0 -63
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
2
|
+
import { CliUsageError, hasUnsafeCliText } from "./errors.mjs";
|
|
3
|
+
export function canReadInteractiveLine(input, output, environment = process.env) {
|
|
4
|
+
return input?.isTTY === true && output?.isTTY === true
|
|
5
|
+
&& typeof input.setRawMode === 'function' && typeof input.resume === 'function'
|
|
6
|
+
&& typeof input.pause === 'function' && typeof input.on === 'function'
|
|
7
|
+
&& typeof input.off === 'function' && typeof output.write === 'function'
|
|
8
|
+
&& !environment.CI;
|
|
9
|
+
}
|
|
10
|
+
const MAX_INTERACTIVE_ANSWER_SCALARS = 1_024;
|
|
11
|
+
const MAX_INTERACTIVE_ANSWER_BYTES = 4_096;
|
|
12
|
+
function boundedInteractiveAnswer(value) {
|
|
13
|
+
const supplied = typeof value === 'string' ? value : '';
|
|
14
|
+
if (Buffer.byteLength(supplied, 'utf8') > MAX_INTERACTIVE_ANSWER_BYTES
|
|
15
|
+
|| Array.from(supplied).length > MAX_INTERACTIVE_ANSWER_SCALARS
|
|
16
|
+
|| hasUnsafeCliText(supplied)) {
|
|
17
|
+
throw new CliUsageError('Interactive input must be bounded text without control characters.');
|
|
18
|
+
}
|
|
19
|
+
return supplied.trim();
|
|
20
|
+
}
|
|
21
|
+
function abortError() {
|
|
22
|
+
return new DOMException('Aborted', 'AbortError');
|
|
23
|
+
}
|
|
24
|
+
function readBoundedInteractiveLine(prompt, options) {
|
|
25
|
+
const { input, output, signal } = options;
|
|
26
|
+
if (signal?.aborted)
|
|
27
|
+
return Promise.reject(signal.reason || abortError());
|
|
28
|
+
const previousRaw = input.isRaw === true;
|
|
29
|
+
const wasPaused = input.isPaused?.() === true;
|
|
30
|
+
let receivedBytes = 0;
|
|
31
|
+
let receivedScalars = 0;
|
|
32
|
+
let settled = false;
|
|
33
|
+
let characters = [];
|
|
34
|
+
const decoder = new StringDecoder('utf8');
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const cleanup = () => {
|
|
37
|
+
let failure = null;
|
|
38
|
+
const attempt = (operation) => {
|
|
39
|
+
try {
|
|
40
|
+
operation();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
failure ||= error instanceof Error ? error : new Error('Interactive terminal cleanup failed.');
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
attempt(() => input.off?.('data', onData));
|
|
47
|
+
attempt(() => input.off?.('end', onEnd));
|
|
48
|
+
attempt(() => signal?.removeEventListener('abort', onAbort));
|
|
49
|
+
attempt(() => input.setRawMode?.(previousRaw));
|
|
50
|
+
if (wasPaused)
|
|
51
|
+
attempt(() => input.pause?.());
|
|
52
|
+
return failure;
|
|
53
|
+
};
|
|
54
|
+
const finish = (value, error) => {
|
|
55
|
+
if (settled)
|
|
56
|
+
return;
|
|
57
|
+
settled = true;
|
|
58
|
+
const cleanupFailure = cleanup();
|
|
59
|
+
characters = [];
|
|
60
|
+
if (error && cleanupFailure) {
|
|
61
|
+
reject(new AggregateError([error, cleanupFailure], `${error.message}; terminal cleanup also failed: ${cleanupFailure.message}`));
|
|
62
|
+
}
|
|
63
|
+
else if (error || cleanupFailure)
|
|
64
|
+
reject(error || cleanupFailure);
|
|
65
|
+
else
|
|
66
|
+
resolve(value || '');
|
|
67
|
+
};
|
|
68
|
+
const rejectUsage = () => finish(undefined, new CliUsageError('Interactive input must be bounded text without control characters.'));
|
|
69
|
+
const onData = (chunk) => {
|
|
70
|
+
if (settled)
|
|
71
|
+
return;
|
|
72
|
+
let rawChunk;
|
|
73
|
+
if (Buffer.isBuffer(chunk)) {
|
|
74
|
+
if (chunk.length > MAX_INTERACTIVE_ANSWER_BYTES - receivedBytes) {
|
|
75
|
+
rejectUsage();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
rawChunk = chunk;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const supplied = String(chunk ?? '');
|
|
82
|
+
if (supplied.length > MAX_INTERACTIVE_ANSWER_BYTES - receivedBytes
|
|
83
|
+
|| Buffer.byteLength(supplied, 'utf8') > MAX_INTERACTIVE_ANSWER_BYTES - receivedBytes) {
|
|
84
|
+
rejectUsage();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
rawChunk = Buffer.from(supplied, 'utf8');
|
|
88
|
+
}
|
|
89
|
+
receivedBytes += rawChunk.length;
|
|
90
|
+
const value = decoder.write(rawChunk);
|
|
91
|
+
receivedScalars += Array.from(value).length;
|
|
92
|
+
if (receivedBytes > MAX_INTERACTIVE_ANSWER_BYTES
|
|
93
|
+
|| receivedScalars > MAX_INTERACTIVE_ANSWER_SCALARS
|
|
94
|
+
|| value.includes('\ufffd')) {
|
|
95
|
+
rejectUsage();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
for (const character of value) {
|
|
99
|
+
if (character === '\r' || character === '\n') {
|
|
100
|
+
try {
|
|
101
|
+
output.write('\n');
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
finish(undefined, error instanceof Error ? error : new Error('Interactive terminal output failed.'));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
finish(characters.join(''));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (character === '\u0003' || character === '\u0004' || character === '\u001b') {
|
|
111
|
+
finish(undefined, abortError());
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (character === '\u0008' || character === '\u007f') {
|
|
115
|
+
if (characters.length) {
|
|
116
|
+
characters.pop();
|
|
117
|
+
try {
|
|
118
|
+
output.write('\b \b');
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
finish(undefined, error instanceof Error ? error : new Error('Interactive terminal output failed.'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (hasUnsafeCliText(character)) {
|
|
128
|
+
rejectUsage();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
characters.push(character);
|
|
132
|
+
try {
|
|
133
|
+
output.write(character);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
finish(undefined, error instanceof Error ? error : new Error('Interactive terminal output failed.'));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const onEnd = () => {
|
|
142
|
+
const remainder = decoder.end();
|
|
143
|
+
if (remainder)
|
|
144
|
+
rejectUsage();
|
|
145
|
+
else
|
|
146
|
+
finish(undefined, abortError());
|
|
147
|
+
};
|
|
148
|
+
const onAbort = () => finish(undefined, signal?.reason instanceof Error ? signal.reason : abortError());
|
|
149
|
+
try {
|
|
150
|
+
output.write(prompt);
|
|
151
|
+
input.on?.('data', onData);
|
|
152
|
+
input.on?.('end', onEnd);
|
|
153
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
154
|
+
if (signal?.aborted) {
|
|
155
|
+
onAbort();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
input.setRawMode?.(true);
|
|
159
|
+
if (settled)
|
|
160
|
+
return;
|
|
161
|
+
input.resume?.();
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
finish(undefined, error instanceof Error ? error : new Error('Interactive terminal input failed.'));
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
export { MAX_INTERACTIVE_ANSWER_BYTES, MAX_INTERACTIVE_ANSWER_SCALARS, boundedInteractiveAnswer, readBoundedInteractiveLine };
|
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
import { formatCliJunit } from "./ci-report.mjs";
|
|
2
2
|
import { formatDomainControlMonitor, runDomainControlMonitor } from "./domain-control-monitor.mjs";
|
|
3
|
-
import { boundedCliErrorMessage, CliUsageError } from "./errors.mjs";
|
|
3
|
+
import { boundedCliErrorMessage, createCliDiagnosticOutput, CliUsageError } from "./errors.mjs";
|
|
4
4
|
import EXIT_CODES from "./exit-codes.mjs";
|
|
5
5
|
import { evaluateCliFailPolicies, formatFailPolicyNotice } from "./fail-policy.mjs";
|
|
6
6
|
import { formatJsonDocument } from "./formatters/json.mjs";
|
|
7
7
|
import { buildInvestigationPlan, buildWorkflowRecipeCatalogue, formatInvestigationPlan, formatWorkflowRecipeCatalogue, } from "./investigation-plan.mjs";
|
|
8
|
-
import { MAX_INVESTIGATION_RUN_BYTES, formatInvestigationRun, runInvestigationRecipe, } from "./investigation-run.mjs";
|
|
8
|
+
import { MAX_INVESTIGATION_RUN_BYTES, formatInvestigationRun, investigationRunExitCode, runInvestigationRecipe, } from "./investigation-run.mjs";
|
|
9
9
|
import { MAX_OFFLINE_EVIDENCE_INPUT_BYTES } from "./offline-evidence-review.mjs";
|
|
10
|
+
import { MAX_OFFLINE_ARTIFACT_BYTES } from "./artifact-verify.mjs";
|
|
11
|
+
import { MAX_RETAINED_ARTIFACT_DIFF_BYTES } from "./retained-artifact-diff.mjs";
|
|
12
|
+
import { MAX_SAVED_LOOKUP_INPUT_BYTES } from "./saved-lookup.mjs";
|
|
13
|
+
import { MAX_COMPARE_INPUT_BYTES } from "./compare.mjs";
|
|
14
|
+
import { MAX_SOURCE_RELIABILITY_INPUT_BYTES } from "./source-reliability.mjs";
|
|
15
|
+
import { MAX_SHARING_REVIEW_BYTES } from "./sharing-review.mjs";
|
|
10
16
|
import { createBufferedOutput } from "./output-file.mjs";
|
|
17
|
+
import { boundedInteractiveAnswer, canReadInteractiveLine, readBoundedInteractiveLine } from "./terminal-input.mjs";
|
|
11
18
|
import { WORKFLOW_INLINE_COMMANDS } from "./inline-command-families.mjs";
|
|
12
19
|
import { runDiscriminatedCommandHandler } from "./discriminated-command-handlers.mjs";
|
|
20
|
+
function boundWorkflowInputs(command, inputs, dependencies, context) {
|
|
21
|
+
if (!inputs.size)
|
|
22
|
+
return {};
|
|
23
|
+
const read = (source, fallback, maximumBytes, label) => typeof source === 'string' && inputs.has(source)
|
|
24
|
+
? inputs.get(source)
|
|
25
|
+
: fallback ? fallback(source) : context.readInput(source, maximumBytes, label);
|
|
26
|
+
switch (command) {
|
|
27
|
+
case 'export': return { readExportInput: (source) => read(source, dependencies.readExportInput, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Evidence export input') };
|
|
28
|
+
case 'verify-artifact': return { readArtifactInput: (source) => read(source, dependencies.readArtifactInput, MAX_OFFLINE_ARTIFACT_BYTES, 'Artefact input') };
|
|
29
|
+
case 'sharing-review': return { readArtifactInput: (source) => read(source, dependencies.readArtifactInput, MAX_SHARING_REVIEW_BYTES, 'Sharing review input') };
|
|
30
|
+
case 'compare': return { readCompareInput: (source) => read(source, dependencies.readCompareInput, MAX_COMPARE_INPUT_BYTES, 'Comparison input') };
|
|
31
|
+
case 'source-report': return { readSourceReliabilityInput: (source) => read(source, dependencies.readSourceReliabilityInput, MAX_SOURCE_RELIABILITY_INPUT_BYTES, 'Source reliability input') };
|
|
32
|
+
case 'diff':
|
|
33
|
+
case 'timeline': return { readDiffInput: (source) => read(source, dependencies.readDiffInput, command === 'diff' ? MAX_RETAINED_ARTIFACT_DIFF_BYTES : MAX_SAVED_LOOKUP_INPUT_BYTES, command === 'diff' ? 'Retained diff input' : 'Lookup timeline input') };
|
|
34
|
+
default: throw new CliUsageError('This fixed-workflow command does not accept retained artefacts.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
13
37
|
async function runMonitorOnceCommand(args, dependencies, context) {
|
|
14
38
|
context.setFailureLabel('One-shot domain control review');
|
|
15
39
|
let manifestInput;
|
|
@@ -82,12 +106,18 @@ async function runWorkflowPlanCommand(args, dependencies, context) {
|
|
|
82
106
|
}
|
|
83
107
|
async function runWorkflowRecipeCommand(args, dependencies, context) {
|
|
84
108
|
context.setFailureLabel('Investigation workflow');
|
|
109
|
+
const input = (dependencies.stdin ?? process.stdin);
|
|
110
|
+
if (args.interactive && !canReadInteractiveLine(input, context.stderr, dependencies.environment)) {
|
|
111
|
+
throw new CliUsageError('--interactive requires terminal input and terminal stderr. Use --select for unattended workflows.');
|
|
112
|
+
}
|
|
85
113
|
let resumeInput = null;
|
|
86
114
|
if (args.resumeSource) {
|
|
87
115
|
try {
|
|
88
|
-
resumeInput = dependencies.
|
|
89
|
-
?
|
|
90
|
-
:
|
|
116
|
+
resumeInput = dependencies.workflowResumeInput !== undefined
|
|
117
|
+
? dependencies.workflowResumeInput
|
|
118
|
+
: dependencies.readDiffInput
|
|
119
|
+
? await dependencies.readDiffInput(args.resumeSource)
|
|
120
|
+
: await context.readInput(args.resumeSource, MAX_INVESTIGATION_RUN_BYTES, 'Investigation resume state');
|
|
91
121
|
}
|
|
92
122
|
catch (error) {
|
|
93
123
|
if (error instanceof CliUsageError)
|
|
@@ -98,17 +128,42 @@ async function runWorkflowRecipeCommand(args, dependencies, context) {
|
|
|
98
128
|
const document = await runInvestigationRecipe(args.recipe, args.subject, {
|
|
99
129
|
approveNetwork: args.approveNetwork,
|
|
100
130
|
selections: args.selections,
|
|
131
|
+
artifactBindings: args.artifactBindings,
|
|
132
|
+
confirmedReviews: args.confirmedReviews,
|
|
133
|
+
...(args.interactive ? { selectInputs: async (request) => {
|
|
134
|
+
context.writeStderr(`${request.label}\nEnter literal input paths or values. Leave blank to pause; no approval is implied.\n`);
|
|
135
|
+
const answers = [];
|
|
136
|
+
for (const placeholder of request.inputs) {
|
|
137
|
+
const prompt = `${request.stepId} ${placeholder}: `;
|
|
138
|
+
const answer = boundedInteractiveAnswer(await (dependencies.workflowQuestion
|
|
139
|
+
? dependencies.workflowQuestion(prompt)
|
|
140
|
+
: readBoundedInteractiveLine(prompt, { input: input, output: context.stderr,
|
|
141
|
+
...(dependencies.signal ? { signal: dependencies.signal } : {}) })));
|
|
142
|
+
if (!answer)
|
|
143
|
+
break;
|
|
144
|
+
answers.push(answer);
|
|
145
|
+
}
|
|
146
|
+
return answers;
|
|
147
|
+
} } : {}),
|
|
101
148
|
resumeInput,
|
|
102
149
|
generatedAt: context.now(),
|
|
103
150
|
...(dependencies.signal ? { signal: dependencies.signal } : {}),
|
|
104
|
-
execute: async (command, stepArguments) => {
|
|
151
|
+
execute: async (command, stepArguments, inputs) => {
|
|
105
152
|
const stepStdout = createBufferedOutput();
|
|
106
|
-
const stepStderr =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
153
|
+
const stepStderr = createCliDiagnosticOutput();
|
|
154
|
+
try {
|
|
155
|
+
const exitCode = await context.executeCli([command, ...stepArguments], {
|
|
156
|
+
stdout: stepStdout.stream,
|
|
157
|
+
stderr: stepStderr.stream,
|
|
158
|
+
...boundWorkflowInputs(command, inputs, dependencies, context),
|
|
159
|
+
});
|
|
160
|
+
return { exitCode, stdout: stepStdout.value() };
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
const diagnostic = stepStderr.value();
|
|
164
|
+
if (diagnostic)
|
|
165
|
+
context.writeStderr(`${command}: ${diagnostic}\n`);
|
|
166
|
+
}
|
|
112
167
|
},
|
|
113
168
|
});
|
|
114
169
|
if (!args.quiet) {
|
|
@@ -116,7 +171,7 @@ async function runWorkflowRecipeCommand(args, dependencies, context) {
|
|
|
116
171
|
? formatJsonDocument(document)
|
|
117
172
|
: context.terminal(formatInvestigationRun(document), args.color));
|
|
118
173
|
}
|
|
119
|
-
return document
|
|
174
|
+
return investigationRunExitCode(document);
|
|
120
175
|
}
|
|
121
176
|
const WORKFLOW_COMMAND_HANDLERS = Object.freeze({
|
|
122
177
|
'monitor-once': runMonitorOnceCommand,
|
package/docs/cli-reference.md
CHANGED
|
@@ -12,20 +12,8 @@ contains the exhaustive recipient, retention and export metadata.
|
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
package on Node.js 26:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm exec --yes --ignore-scripts --package=@slicedearth/whoisleuth-cli -- whoisleuth --help
|
|
21
|
-
npm install --global --ignore-scripts @slicedearth/whoisleuth-cli
|
|
22
|
-
whoisleuth doctor
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
The scoped package and application share one semantic version. The package
|
|
26
|
-
requires no dependency lifecycle scripts and does not call the hosted
|
|
27
|
-
WHOISleuth deployment. From a repository checkout, replace `whoisleuth` with
|
|
28
|
-
`node bin/whoisleuth.mts`.
|
|
15
|
+
Use the [installation guide](cli.md#installation) for runtime requirements,
|
|
16
|
+
installation and updates. The CLI runs locally without a hosted account.
|
|
29
17
|
|
|
30
18
|
## Find and inspect commands
|
|
31
19
|
|
|
@@ -46,6 +34,13 @@ command position as Lookup shorthand. Use explicit `lookup` where input needs
|
|
|
46
34
|
supported URL-like normalisation. Credentials, paths, queries, fragments, ports
|
|
47
35
|
and unsupported special-use targets are rejected by the shorthand.
|
|
48
36
|
|
|
37
|
+
Explicit `lookup` normally uses only a pasted URL's hostname. Add
|
|
38
|
+
`--deep --exact-url` to collect that page's path and query instead of the
|
|
39
|
+
homepage; fragments are not sent. `--plan` discloses this scope without making
|
|
40
|
+
requests or copying the URL into the plan. Use stdin to keep a sensitive URL
|
|
41
|
+
out of shell history. Saved request provenance omits queries, but paths and
|
|
42
|
+
page-derived text still require review before sharing.
|
|
43
|
+
|
|
49
44
|
`registry-scaffold` has a separate fixture contract: its `--profile` selects one
|
|
50
45
|
fixed fixture profile and shared `--config` profiles are rejected. It produces
|
|
51
46
|
sanitised local fixture material and makes no registry request.
|
|
@@ -63,12 +58,8 @@ disclosures without collecting. Fast is the Lookup default. Deep adds the
|
|
|
63
58
|
applicable registration, DNS, HTTP, TLS, page, technology and network context.
|
|
64
59
|
Optional browser providers are not implicit CLI actions.
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
exact-domain alignment, and bounded `Received` routing. It retains no address
|
|
69
|
-
local parts, display names, subject, body, attachment, or raw header value in
|
|
70
|
-
its output. It does not independently validate SPF, DKIM, DMARC, or ARC and
|
|
71
|
-
does not treat divergence as proof of abuse.
|
|
61
|
+
The [message-header review](cli.md#message-header-review) is offline and reports
|
|
62
|
+
publisher claims, not independent DNS or cryptographic validation.
|
|
72
63
|
|
|
73
64
|
`dnssec-validate` and `mail-transport` are isolated authorised actions. Both
|
|
74
65
|
require a selected literal public resolver, a local trust-anchor document and
|
|
@@ -77,6 +68,8 @@ transport handles at most three selected MX hosts sequentially, sends `EHLO`
|
|
|
77
68
|
and uses `STARTTLS` only when advertised. It does not send mail, authenticate,
|
|
78
69
|
test relay, enumerate recipients or retry automatically. DNSSEC, TLSA/DANE,
|
|
79
70
|
PKIX, STARTTLS and SMTP transport remain separate evidence states.
|
|
71
|
+
STARTTLS detection uses the full bounded reply. If the retained capability
|
|
72
|
+
inventory is shortened, the report remains partial and names the omission.
|
|
80
73
|
|
|
81
74
|
The repository-only rendered-capture package is outside hosted and distributable
|
|
82
75
|
collection. It executes remote page JavaScript only for an explicitly
|
|
@@ -91,11 +84,20 @@ refused. Output goes to the terminal unless a command supports `--output`.
|
|
|
91
84
|
Private output is written atomically and an existing path is refused unless
|
|
92
85
|
replacement is explicit.
|
|
93
86
|
|
|
87
|
+
`--` ends option processing. All later arguments are literal positional inputs,
|
|
88
|
+
including names such as `--help`. Use `./-evidence.json` for a hyphen-prefixed
|
|
89
|
+
filename when completing paths across shells. Options must precede `--`.
|
|
90
|
+
|
|
94
91
|
Terminal text is the default. JSON, JSONL, CSV, Markdown, HTML and domain-only
|
|
95
92
|
formats are available only where the installed command declares them.
|
|
96
93
|
Redirected and machine output contains no ANSI or progress text. Diagnostics
|
|
97
94
|
and optional target-free `--events` output use standard error.
|
|
98
95
|
|
|
96
|
+
For `bulk` and `discover-scan`, `--csv-with-metadata` adds the source schema and
|
|
97
|
+
version, observation and report times, collection origin, scan mode, diagnostic
|
|
98
|
+
version and source-health states. Missing clocks remain `unknown`; a null
|
|
99
|
+
source state is unmeasured, not a negative result. `--csv` keeps the compact columns.
|
|
100
|
+
|
|
99
101
|
`--fail-on` and `--strict-exit` expose selected evidence states to automation
|
|
100
102
|
without changing the result document. Review focused help for the policies a
|
|
101
103
|
command supports.
|
|
@@ -116,16 +118,18 @@ Readers accept only their declared public and current versions. Unknown,
|
|
|
116
118
|
unreleased historical and future schemas fail before partial interpretation;
|
|
117
119
|
an invalid import is not treated as an empty document.
|
|
118
120
|
|
|
119
|
-
`inspect-archive` reads current workspace archive
|
|
121
|
+
`inspect-archive` reads current workspace archive v9 and exact versions 5–8. It
|
|
120
122
|
reports section metadata and digest-only search results unless `--reveal` is
|
|
121
123
|
explicitly selected. It never searches notes, contacts or arbitrary raw fields.
|
|
122
124
|
|
|
123
125
|
`export` reads supported saved Lookup v1 or v2 and writes current Lookup
|
|
124
|
-
evidence schema
|
|
125
|
-
readable. Versions 27
|
|
126
|
+
evidence schema 29. Published v2 schemas 27 and 28 and exact v1 schema 26 remain
|
|
127
|
+
readable. Versions 27–29 exclude raw registration payloads, expanded
|
|
126
128
|
contacts, credentials, complete query-bearing URLs and provider payloads;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
schemas 28 and 29 also retain the bounded registrar-standing projection.
|
|
130
|
+
Schema 29 identifies the hostname used for DNS, TLS and web observations,
|
|
131
|
+
separately from the registrable domain used by registration sources. Older
|
|
132
|
+
evidence retains its original collection scope.
|
|
129
133
|
|
|
130
134
|
`verify-artifact` checks a recognised structure and its applicable integrity
|
|
131
135
|
contract. `interchange-report` describes retained and omitted fields.
|
|
@@ -133,6 +137,45 @@ contract. `interchange-report` describes retained and omitted fields.
|
|
|
133
137
|
validity and signer trust separate. None of these checks establishes that
|
|
134
138
|
evidence is accurate, current, safe to share or attributable to a person.
|
|
135
139
|
|
|
140
|
+
### Signer trust
|
|
141
|
+
|
|
142
|
+
Use `verify-signature package.json --trust-store-file trust.json --json` to
|
|
143
|
+
check an explicitly selected fingerprint policy offline. The result contains
|
|
144
|
+
the ordinary signature verification and a separate current trust decision.
|
|
145
|
+
Only a matching `trusted` entry succeeds; unknown, `retired`, `revoked` or
|
|
146
|
+
future-reviewed entries return exit code 4, including with `--quiet`.
|
|
147
|
+
Malformed files return 2. Without this option, the existing verification
|
|
148
|
+
output is unchanged. A supplied `--public-key-file` must also match.
|
|
149
|
+
|
|
150
|
+
The trust file contains no keys. For example:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"schema": "whoisleuth.evidence-signer-trust-store",
|
|
155
|
+
"version": 1,
|
|
156
|
+
"entries": [{
|
|
157
|
+
"keyIdSha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
158
|
+
"label": "Evidence reviewer",
|
|
159
|
+
"status": "trusted",
|
|
160
|
+
"updatedAt": "2026-09-01T00:00:00.000Z",
|
|
161
|
+
"note": "Fingerprint confirmed through the established contact channel."
|
|
162
|
+
}]
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Replace the example fingerprint with the SHA-256 fingerprint of the signer's
|
|
167
|
+
SPKI DER public-key bytes, confirmed through an authenticated channel—not
|
|
168
|
+
merely copied from the package being checked. Review rotation and revocation
|
|
169
|
+
updates through that channel. On rotation, mark the old entry `retired` and
|
|
170
|
+
optionally record `successorKeyIdSha256`; independently confirm and add the
|
|
171
|
+
replacement entry. Use `revoked` for a withdrawn trust decision. Neither a
|
|
172
|
+
successor link nor a claimed signing date overrides current status.
|
|
173
|
+
|
|
174
|
+
Files support 1,024 distinct entries within 4 MiB. Labels and single-line notes
|
|
175
|
+
allow 160 and 2,048 characters respectively. Reports include only the matching
|
|
176
|
+
entry and the exact file digest. Keep the file and any saved reports under your
|
|
177
|
+
own retention policy. No automatic key discovery, key storage or trust refresh occurs.
|
|
178
|
+
|
|
136
179
|
## Command-family boundaries
|
|
137
180
|
|
|
138
181
|
- Evidence collection commands retain separate source states. A failed or
|
|
@@ -144,17 +187,20 @@ evidence is accurate, current, safe to share or attributable to a person.
|
|
|
144
187
|
- Bulk applies one declared collection contract per target. Fast accepts up to
|
|
145
188
|
500 targets and Deep up to 50; each target remains a separate request.
|
|
146
189
|
- Respond commands package browser-created Cases and prepare local packets,
|
|
147
|
-
reports and sharing reviews.
|
|
148
|
-
|
|
190
|
+
reports and sharing reviews. The offline [`case` workflow](cli.md#local-case-files)
|
|
191
|
+
also creates and updates ordinary local Case files through explicit, conflict-checked
|
|
192
|
+
output. Neither workflow submits, publishes, notifies or grants recipient authorisation.
|
|
149
193
|
- Assurance, comparison and calibration commands describe supplied or retained
|
|
150
194
|
evidence. They do not tune the running model, change infrastructure or turn an
|
|
151
195
|
analyst label into observed truth.
|
|
152
196
|
- `workflow-plan` lists fixed installed recipes without executing them.
|
|
153
197
|
`workflow-run` executes only installed steps, requires approval for network
|
|
154
198
|
work and pauses at unresolved analyst selections. Repeat
|
|
155
|
-
`--select <step-id>=<path-or-value>`
|
|
156
|
-
|
|
157
|
-
selections
|
|
199
|
+
`--select <step-id>=<path-or-value>` for remaining placeholders in order, or bind
|
|
200
|
+
a compatible earlier output with `--use-artifact <step-id>:<input-number>=<earlier-step-id>`.
|
|
201
|
+
Checkpoints retain exact selections and bindings and distinguish incomplete
|
|
202
|
+
collection from retryable failures; see [resuming a fixed workflow](cli.md#resuming-a-fixed-workflow)
|
|
203
|
+
for the supported versions and resume behaviour.
|
|
158
204
|
|
|
159
205
|
Use the installed focused help for positional inputs, exact ceilings, options,
|
|
160
206
|
network effects, outputs and command-specific exit behaviour.
|