@slicedearth/whoisleuth-cli 1.45.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DISCLOSURE +7 -3
- package/NOTICE +5 -4
- package/README.md +74 -124
- package/SECURITY.md +6 -3
- package/bin/whoisleuth.mjs +7 -4
- package/cli/archive-inspect.mjs +28 -10
- package/cli/arguments.mjs +450 -173
- package/cli/artifact-structure.mjs +2507 -0
- package/cli/artifact-verify.mjs +286 -64
- package/cli/bounded-json.mjs +1 -0
- package/cli/bulk-checkpoint.mjs +35 -23
- package/cli/bulk-command-runner.mjs +2 -5
- package/cli/bulk.mjs +9 -2
- package/cli/case-pack.mjs +435 -46
- package/cli/ci-report.mjs +6 -1
- package/cli/collection-preflight.mjs +2 -2
- package/cli/command-catalogue.mjs +12 -3
- package/cli/command-reference.mjs +948 -106
- package/cli/completion.mjs +504 -218
- package/cli/config-profile.mjs +13 -3
- package/cli/ct-event-intake.mjs +32 -2
- package/cli/discover.mjs +7 -1
- package/cli/discovery-observation-snapshot.mjs +24 -28
- package/cli/discovery-scan-command-runner.mjs +13 -14
- package/cli/discovery-scan.mjs +17 -3
- package/cli/discovery-snapshot.mjs +15 -7
- package/cli/discovery-workflow.mjs +2 -3
- package/cli/doctor.mjs +2 -2
- package/cli/domain-control-monitor.mjs +215 -36
- package/cli/domain-control-observations.mjs +34 -17
- package/cli/errors.mjs +8 -3
- package/cli/evidence-command-runner.mjs +17 -11
- package/cli/evidence-signing.mjs +68 -50
- package/cli/export-evidence.mjs +11 -4
- package/cli/external-observation-mapping.mjs +3 -3
- package/cli/formatters/html.mjs +3 -3
- package/cli/formatters/json.mjs +20 -32
- package/cli/formatters/terminal.mjs +586 -22
- package/cli/input.mjs +62 -0
- package/cli/interactive-launcher.mjs +226 -0
- package/cli/interchange-report.mjs +226 -0
- package/cli/investigation-manifest.mjs +16 -17
- package/cli/investigation-plan.mjs +167 -16
- package/cli/investigation-run.mjs +80 -24
- package/cli/local-mmdb-review.mjs +45 -14
- package/cli/lookalike-calibration.mjs +3 -1
- package/cli/lookup-brief.mjs +13 -8
- package/cli/lookup-browser.mjs +844 -0
- package/cli/lookup-command-runner.mjs +96 -33
- package/cli/lookup-diff.mjs +11 -2
- package/cli/lookup-plan.mjs +1 -0
- package/cli/mail-review.mjs +206 -94
- package/cli/manual.mjs +5 -5
- package/cli/network-command-runner.mjs +78 -0
- package/cli/offline-evidence-review.mjs +45 -14
- package/cli/open-asset-model-bridge.mjs +1 -1
- package/cli/output-file.mjs +70 -14
- package/cli/page-compare.mjs +18 -10
- package/cli/progress-events.mjs +2 -1
- package/cli/progress.mjs +3 -3
- package/cli/registry-cohort.mjs +343 -61
- package/cli/registry-doctor.mjs +67 -28
- package/cli/registry-support.mjs +2 -1
- package/cli/retained-artifact-diff.mjs +375 -0
- package/cli/risk-calibration.mjs +89 -40
- package/cli/runner.mjs +322 -246
- package/cli/saved-lookup.mjs +439 -49
- package/cli/sharing-review.mjs +18 -7
- package/cli/source-reliability.mjs +75 -36
- package/cli/terminal-presentation.mjs +43 -18
- package/docs/cli-reference.md +125 -1334
- package/docs/cli.md +87 -236
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -0
- package/frontend/src/lib/analysis/domain-control-records.js +3 -0
- package/lib/application-version.mjs +3 -0
- package/lib/availability.mjs +72 -52
- package/lib/bounded-contract-normalizers.mjs +2 -4
- package/lib/bounded-file.mjs +91 -4
- package/lib/bounded-json.mjs +287 -0
- package/lib/browser-library-profile.mjs +1 -1
- package/lib/certificate-extension-profile.mjs +2 -1
- package/lib/classify.mjs +56 -5
- package/lib/client-behavior-profile.mjs +1 -1
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/cryptographic-assurance.mjs +173 -0
- package/lib/ct-query.mjs +3 -3
- package/lib/ct-search.mjs +120 -64
- package/lib/distributed-operation-budget.mjs +4 -2
- package/lib/dns-delegation-health.mjs +9 -6
- package/lib/dns-intelligence.mjs +4 -3
- package/lib/dns-resolver-selection.mjs +6 -4
- package/lib/dnssec-chain-validation.mjs +1052 -0
- package/lib/dnssec-evidence-validation.mjs +23 -10
- package/lib/domain-assurance.mjs +12 -12
- package/lib/domain-change-packet.mjs +7 -5
- package/lib/domain-control-flight-recorder.mjs +442 -78
- package/lib/domain-control-manifest.mjs +314 -171
- package/lib/domain-posture-analysis.mjs +5 -2
- package/lib/domain-posture.mjs +7 -4
- package/lib/encrypted-dns-contract.mjs +2 -3
- package/lib/evidence-export.mjs +1243 -88
- package/lib/evidence-report-markdown.mjs +3 -0
- package/lib/evidence-report.mjs +5 -0
- package/lib/external-intelligence-risk.mjs +26 -10
- package/lib/favicon.mjs +8 -5
- package/lib/feature-policy.mjs +1 -1
- package/lib/homepage-metadata-contract.mjs +290 -0
- package/lib/hostname.mjs +1 -1
- package/lib/html-signals.mjs +89 -5
- package/lib/http-evidence-bounds.mjs +3 -0
- package/lib/http-intelligence.mjs +332 -6
- package/lib/interchange-fidelity-registry.mjs +124 -0
- package/lib/local-geoip-evidence.mjs +2 -1
- package/lib/lookup-cache.mjs +6 -4
- package/lib/lookup-network-evidence-bounds.mjs +9 -0
- package/lib/lookup.mjs +27 -4
- package/lib/observed-network-context.mjs +1 -1
- package/lib/outbound-identity.mjs +2 -3
- package/lib/page-fingerprints.mjs +2 -2
- package/lib/page-role-profile.mjs +1 -1
- package/lib/perceptual-hash.mjs +12 -4
- package/lib/portable-generator.mjs +29 -0
- package/lib/provider-policy-admission.mjs +11 -4
- package/lib/rdap-bootstrap.mjs +76 -16
- package/lib/rdap-client.mjs +20 -12
- package/lib/rdap-normalization.mjs +324 -80
- package/lib/rdap-registrar.mjs +5 -1
- package/lib/rdap-transport.mjs +2 -2
- package/lib/rdap-validation.mjs +65 -14
- package/lib/rdap.mjs +2 -2
- package/lib/registrable-domain.mjs +36 -0
- package/lib/registry-capabilities.mjs +3 -0
- package/lib/registry-capability-catalogue.mjs +12 -2
- package/lib/registry-comparison.mjs +28 -11
- package/lib/registry-insights.mjs +7 -0
- package/lib/response-policy.mjs +27 -4
- package/lib/risk-calibration-summary.mjs +340 -0
- package/lib/risk-scoring.mjs +4 -3
- package/lib/rpki-evidence.mjs +9 -8
- package/lib/safe-fetch.mjs +121 -50
- package/lib/security-txt.mjs +27 -4
- package/lib/service-binding-dns.mjs +6 -1
- package/lib/smtp-transport-review.mjs +787 -0
- package/lib/sslbl-certificates.generated.mjs +136 -134
- package/lib/sslbl-intelligence.mjs +6 -8
- package/lib/static-html-analysis.mjs +301 -4
- package/lib/structured-data-identity.mjs +51 -19
- package/lib/threat-intelligence-fixtures.mjs +2 -4
- package/lib/threat-intelligence-manifests.mjs +2 -7
- package/lib/threat-intelligence-runtime.mjs +5 -15
- package/lib/threat-intelligence-types.mjs +21 -1
- package/lib/threatfox-intelligence.mjs +7 -3
- package/lib/tls-intelligence.mjs +15 -9
- package/lib/tlsa-evidence.mjs +37 -7
- package/lib/urlhaus-intelligence.mjs +4 -2
- package/lib/urlscan-intelligence.mjs +6 -5
- package/lib/website-security-posture.mjs +1 -1
- package/lib/website-technology.mjs +1 -1
- package/lib/whois-authority.mjs +43 -8
- package/lib/whois-chain.mjs +25 -3
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-transport.mjs +12 -3
- package/lib/zone-intent-review.mjs +1 -1
- package/package.json +12 -4
- package/packages/cases/case-brand-profile-references.mjs +85 -0
- package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +43 -14
- package/packages/cases/case-investigation-branch-model.mjs +132 -0
- package/packages/cases/case-migration-model.mjs +476 -0
- package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
- package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -19
- package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
- package/packages/cases/case-record-model.mjs +9 -0
- package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +106 -33
- package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +143 -16
- package/packages/cases/case-response-model.mjs +1604 -0
- package/packages/cases/case-response-packet.mjs +1312 -0
- package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
- package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
- package/packages/cases/opaque-reference-id.mjs +9 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +366 -0
- package/packages/comparison/comparison-ledger-contract.mjs +241 -0
- package/packages/comparison/comparison-ledger-json.mjs +80 -0
- package/packages/comparison/comparison-ledger-serialization.mjs +52 -0
- package/packages/comparison/favicon-similarity.mjs +78 -0
- package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
- package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
- package/packages/contracts/analyst-review-state-contract.mjs +61 -0
- package/packages/contracts/analyst-review-state.mjs +50 -0
- package/packages/contracts/case-portability.mjs +1310 -0
- package/packages/contracts/cli-command-catalogue.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +394 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
- package/packages/contracts/domain-control-manifest.mjs +1027 -0
- package/packages/contracts/domain-control-monitor.mjs +310 -0
- package/packages/contracts/domain-control-review.mjs +828 -0
- package/packages/contracts/domain-name.mjs +4 -0
- package/packages/contracts/external-observation-interchange.mjs +224 -0
- package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
- package/packages/contracts/investigation-portability.mjs +331 -0
- package/packages/contracts/offline-comparison.mjs +160 -0
- package/packages/contracts/risk-calibration.mjs +1288 -0
- package/packages/contracts/schema-compatibility.mjs +71 -0
- package/packages/contracts/schema-lifecycle.mjs +1702 -0
- package/packages/contracts/sslbl-snapshot.mjs +17 -0
- package/packages/contracts/workspace-portability.mjs +1347 -0
- package/packages/evidence/artifact-integrity.mjs +65 -0
- package/packages/evidence/decision-fact.mjs +735 -0
- package/packages/evidence/domain-control-runtime.mjs +547 -0
- package/packages/evidence/domain-name.mjs +41 -0
- package/packages/evidence/observation.mjs +384 -0
- package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +86 -20
- package/packages/interchange/retained-text.mjs +8 -0
- package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +11 -8
- package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +28 -9
- package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +67 -18
- package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +10 -9
- package/packages/investigation/bulk-review-policy.mjs +1 -0
- package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +50 -18
- package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
- package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
- package/packages/investigation/lookup-claim-passport.mjs +232 -0
- package/packages/investigation/lookup-claim-readiness.mjs +287 -0
- package/packages/investigation/lookup-investigation-brief.mjs +172 -0
- package/packages/monitoring/analyst-review-state.mjs +680 -0
- package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +97 -50
- package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +15 -15
- package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +179 -39
- package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +12 -5
- package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
- package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
- package/packages/workspace/hostile-input.mjs +172 -0
- package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +51 -46
- package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +15 -15
- package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
- package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
- package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
- package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
- package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
- package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +18 -12
- package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
- package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +195 -84
- package/third-party-notices.txt +112 -5834
- package/frontend/src/lib/analysis/artifact-integrity.js +0 -21
- package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
- package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
- package/frontend/src/lib/analysis/case-migration-model.js +0 -253
- package/frontend/src/lib/analysis/case-record-model.js +0 -7
- package/frontend/src/lib/analysis/case-response-model.js +0 -669
- package/frontend/src/lib/analysis/case-response-packet.js +0 -573
- package/frontend/src/lib/analysis/lookup-decision-support.js +0 -487
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
- package/frontend/src/lib/analysis/lookup-presentation.js +0 -58
- package/frontend/src/lib/analysis/lookup-response.js +0 -2
- package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
- package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
- package/frontend/src/lib/analysis/scoring.js +0 -55
- package/frontend/src/lib/analysis/utils.js +0 -232
- package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
- package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
- package/frontend/src/lib/analysis/visualization-matrix.js +0 -88
- package/frontend/src/lib/analysis/visualization-models.js +0 -369
- package/frontend/src/lib/analysis/visualization-time-series.js +0 -144
- package/lib/bounded-json-response.mjs +0 -157
- package/lib/lookup-response-contract.mjs +0 -346
- package/lib/observation.mjs +0 -114
- /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
- /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
- /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
package/cli/bulk-checkpoint.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { lstat } from 'node:fs/promises';
|
|
3
3
|
import { recordOrNull } from "../lib/bounded-contract-normalizers.mjs";
|
|
4
4
|
import { readBoundedRegularTextFile } from "../lib/bounded-file.mjs";
|
|
5
|
+
import { scanBoundedJson } from "../lib/bounded-json.mjs";
|
|
6
|
+
import { normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
|
|
5
7
|
import { boundedCliInputError, CliUsageError } from "./errors.mjs";
|
|
6
8
|
import { writePrivateFile } from "./output-file.mjs";
|
|
7
9
|
export const CLI_BULK_CHECKPOINT_SCHEMA = 'whoisleuth.cli.bulk-checkpoint';
|
|
8
10
|
export const CLI_BULK_CHECKPOINT_VERSION = 2;
|
|
9
|
-
const SUPPORTED_BULK_CHECKPOINT_VERSIONS = new Set([1, CLI_BULK_CHECKPOINT_VERSION]);
|
|
10
11
|
export const MAX_BULK_CHECKPOINT_BYTES = 16 * 1024 * 1024;
|
|
11
12
|
const MAX_CHECKPOINT_JSON_DEPTH = 12;
|
|
12
13
|
const MAX_CHECKPOINT_OBJECT_KEYS = 256;
|
|
@@ -19,8 +20,8 @@ function checkpointDigest(queries, deep) {
|
|
|
19
20
|
.update(queries.join('\n'))
|
|
20
21
|
.digest('hex');
|
|
21
22
|
}
|
|
22
|
-
function
|
|
23
|
-
return
|
|
23
|
+
function normalizedTimestamp(value) {
|
|
24
|
+
return normalizeExplicitIsoTimestamp(value);
|
|
24
25
|
}
|
|
25
26
|
function isBoundedCheckpointJson(value, depth = 0) {
|
|
26
27
|
if (depth > MAX_CHECKPOINT_JSON_DEPTH)
|
|
@@ -53,7 +54,7 @@ function normalizeCompactResult(value) {
|
|
|
53
54
|
return null;
|
|
54
55
|
return result;
|
|
55
56
|
}
|
|
56
|
-
function normalizeCheckpointResult(value, queries, classifyQuery
|
|
57
|
+
function normalizeCheckpointResult(value, queries, classifyQuery) {
|
|
57
58
|
const item = recordOrNull(value);
|
|
58
59
|
const index = item?.index;
|
|
59
60
|
if (!Number.isSafeInteger(index) || Number(index) < 0 || Number(index) >= queries.length)
|
|
@@ -61,11 +62,9 @@ function normalizeCheckpointResult(value, queries, classifyQuery, version = CLI_
|
|
|
61
62
|
const query = queries[Number(index)];
|
|
62
63
|
if (typeof item?.query !== 'string' || item.query !== query)
|
|
63
64
|
return null;
|
|
64
|
-
const observedAt =
|
|
65
|
-
?
|
|
66
|
-
|
|
67
|
-
: validTimestamp(item.observedAt) ? new Date(item.observedAt).toISOString() : undefined
|
|
68
|
-
: null;
|
|
65
|
+
const observedAt = item.observedAt === null
|
|
66
|
+
? null
|
|
67
|
+
: normalizedTimestamp(item.observedAt) ?? undefined;
|
|
69
68
|
if (observedAt === undefined)
|
|
70
69
|
return null;
|
|
71
70
|
if (item.ok === false) {
|
|
@@ -86,33 +85,37 @@ function parseBulkCheckpoint(text, options) {
|
|
|
86
85
|
if (typeof text !== 'string' || Buffer.byteLength(text, 'utf8') > MAX_BULK_CHECKPOINT_BYTES) {
|
|
87
86
|
throw new CliUsageError(`Bulk checkpoint input is limited to ${MAX_BULK_CHECKPOINT_BYTES} bytes.`);
|
|
88
87
|
}
|
|
88
|
+
const normalized = text.replace(/^\uFEFF/u, '');
|
|
89
89
|
let parsed;
|
|
90
90
|
try {
|
|
91
|
-
|
|
91
|
+
scanBoundedJson(normalized);
|
|
92
|
+
parsed = JSON.parse(normalized);
|
|
92
93
|
}
|
|
93
94
|
catch {
|
|
94
|
-
throw new CliUsageError('Bulk checkpoint must be valid JSON.');
|
|
95
|
+
throw new CliUsageError('Bulk checkpoint must be valid bounded JSON without duplicate keys.');
|
|
95
96
|
}
|
|
96
97
|
const document = recordOrNull(parsed);
|
|
97
98
|
const expectedDigest = checkpointDigest(options.queries, options.deep);
|
|
98
99
|
const version = typeof document?.version === 'number' && Number.isSafeInteger(document.version)
|
|
99
100
|
? document.version
|
|
100
101
|
: 0;
|
|
101
|
-
if (document?.schema !== CLI_BULK_CHECKPOINT_SCHEMA ||
|
|
102
|
-
throw new CliUsageError(`Bulk checkpoint must use ${CLI_BULK_CHECKPOINT_SCHEMA} version
|
|
102
|
+
if (document?.schema !== CLI_BULK_CHECKPOINT_SCHEMA || version !== CLI_BULK_CHECKPOINT_VERSION) {
|
|
103
|
+
throw new CliUsageError(`Bulk checkpoint must use ${CLI_BULK_CHECKPOINT_SCHEMA} version ${CLI_BULK_CHECKPOINT_VERSION}.`);
|
|
103
104
|
}
|
|
104
105
|
if (document.mode !== (options.deep ? 'deep' : 'fast')
|
|
105
106
|
|| document.inputDigestSha256 !== expectedDigest
|
|
106
107
|
|| document.queryCount !== options.queries.length) {
|
|
107
108
|
throw new CliUsageError('Bulk checkpoint does not match the current input or scan mode.');
|
|
108
109
|
}
|
|
109
|
-
|
|
110
|
+
const startedAt = normalizedTimestamp(document.startedAt);
|
|
111
|
+
const updatedAt = normalizedTimestamp(document.updatedAt);
|
|
112
|
+
if (!startedAt || !updatedAt || !Array.isArray(document.results)) {
|
|
110
113
|
throw new CliUsageError('Bulk checkpoint metadata is invalid.');
|
|
111
114
|
}
|
|
112
115
|
const results = [];
|
|
113
116
|
const seen = new Set();
|
|
114
117
|
for (const candidate of document.results.slice(0, options.queries.length + 1)) {
|
|
115
|
-
const result = normalizeCheckpointResult(candidate, options.queries, options.classifyQuery
|
|
118
|
+
const result = normalizeCheckpointResult(candidate, options.queries, options.classifyQuery);
|
|
116
119
|
if (!result || seen.has(result.index))
|
|
117
120
|
throw new CliUsageError('Bulk checkpoint contains an invalid or duplicate result.');
|
|
118
121
|
seen.add(result.index);
|
|
@@ -124,8 +127,8 @@ function parseBulkCheckpoint(text, options) {
|
|
|
124
127
|
mode: options.deep ? 'deep' : 'fast',
|
|
125
128
|
inputDigestSha256: expectedDigest,
|
|
126
129
|
queryCount: options.queries.length,
|
|
127
|
-
startedAt
|
|
128
|
-
updatedAt
|
|
130
|
+
startedAt,
|
|
131
|
+
updatedAt,
|
|
129
132
|
results: results.sort((left, right) => left.index - right.index),
|
|
130
133
|
};
|
|
131
134
|
}
|
|
@@ -143,8 +146,7 @@ async function readCheckpointFile(path) {
|
|
|
143
146
|
}
|
|
144
147
|
async function checkpointFileExists(path) {
|
|
145
148
|
try {
|
|
146
|
-
|
|
147
|
-
await handle.close();
|
|
149
|
+
await lstat(path);
|
|
148
150
|
return true;
|
|
149
151
|
}
|
|
150
152
|
catch (error) {
|
|
@@ -165,14 +167,20 @@ async function createBulkCheckpointWriter(options) {
|
|
|
165
167
|
const resumed = options.resume
|
|
166
168
|
? parseBulkCheckpoint(await readCheckpointFile(options.path), options)
|
|
167
169
|
: null;
|
|
168
|
-
const
|
|
170
|
+
const currentTimestamp = () => {
|
|
171
|
+
const timestamp = normalizedTimestamp(now());
|
|
172
|
+
if (!timestamp)
|
|
173
|
+
throw new CliUsageError('Bulk checkpoint collection time is invalid.');
|
|
174
|
+
return timestamp;
|
|
175
|
+
};
|
|
176
|
+
const startedAt = resumed?.startedAt || currentTimestamp();
|
|
169
177
|
const results = new Map((resumed?.results || []).map((item) => [item.index, item]));
|
|
170
178
|
let activeWrite = null;
|
|
171
179
|
let dirty = false;
|
|
172
180
|
let writeFailure = null;
|
|
173
181
|
let created = resumed !== null;
|
|
174
182
|
function document() {
|
|
175
|
-
const updatedAt =
|
|
183
|
+
const updatedAt = currentTimestamp();
|
|
176
184
|
return {
|
|
177
185
|
schema: CLI_BULK_CHECKPOINT_SCHEMA,
|
|
178
186
|
version: CLI_BULK_CHECKPOINT_VERSION,
|
|
@@ -227,7 +235,11 @@ async function createBulkCheckpointWriter(options) {
|
|
|
227
235
|
return Object.freeze({
|
|
228
236
|
initialResults: Object.freeze([...results.values()].sort((left, right) => left.index - right.index)),
|
|
229
237
|
record(result) {
|
|
230
|
-
const observedAt =
|
|
238
|
+
const observedAt = normalizedTimestamp(result.observedAt) ?? normalizedTimestamp(now());
|
|
239
|
+
if (!observedAt) {
|
|
240
|
+
writeFailure ||= new CliUsageError('Bulk checkpoint collection time is invalid.');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
231
243
|
const normalized = normalizeCheckpointResult({ ...result, observedAt }, options.queries, options.classifyQuery);
|
|
232
244
|
if (!normalized) {
|
|
233
245
|
writeFailure ||= new CliUsageError('Bulk checkpoint result is not a bounded compact lookup result.');
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { createReadStream } from 'node:fs';
|
|
2
1
|
import { classifyQuery } from "../lib/classify.mjs";
|
|
3
2
|
import { runUnifiedLookup } from "../lib/lookup.mjs";
|
|
4
3
|
import { createBulkCheckpointWriter } from "./bulk-checkpoint.mjs";
|
|
5
|
-
import { MAX_BULK_INPUT_BYTES, parseBulkQueries,
|
|
4
|
+
import { MAX_BULK_INPUT_BYTES, parseBulkQueries, runBulkLookups, } from "./bulk.mjs";
|
|
6
5
|
import { formatBulkCsv, formatBulkDomainList, formatBulkQueryList, selectBulkItems } from "./bulk-output.mjs";
|
|
7
6
|
import { boundedCliErrorMessage, CliUsageError } from "./errors.mjs";
|
|
8
7
|
import EXIT_CODES from "./exit-codes.mjs";
|
|
@@ -24,9 +23,7 @@ async function runBulkCommand(args, dependencies, context) {
|
|
|
24
23
|
try {
|
|
25
24
|
input = dependencies.readBulkInput
|
|
26
25
|
? await dependencies.readBulkInput(args.source)
|
|
27
|
-
: await
|
|
28
|
-
? createReadStream(args.source, { highWaterMark: 64 * 1024 })
|
|
29
|
-
: dependencies.stdin || process.stdin, MAX_BULK_INPUT_BYTES, dependencies.signal);
|
|
26
|
+
: await context.readInput(args.source, MAX_BULK_INPUT_BYTES, 'Bulk input');
|
|
30
27
|
}
|
|
31
28
|
catch (error) {
|
|
32
29
|
if (error instanceof CliUsageError)
|
package/cli/bulk.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { abortable } from "../lib/abort.mjs";
|
|
3
|
+
import { decodeBoundedUtf8 } from "../lib/bounded-file.mjs";
|
|
3
4
|
import { CliUsageError } from "./arguments.mjs";
|
|
5
|
+
import { hasUnsafeCliText } from "./errors.mjs";
|
|
4
6
|
import { MAX_DEEP_BULK_QUERIES, MAX_FAST_BULK_QUERIES, } from "../lib/bulk-limits.mjs";
|
|
5
7
|
const MAX_BULK_INPUT_BYTES = 1024 * 1024;
|
|
6
8
|
async function readTextStreamBounded(stream, limit = MAX_BULK_INPUT_BYTES, signal) {
|
|
@@ -26,7 +28,12 @@ async function readTextStreamBounded(stream, limit = MAX_BULK_INPUT_BYTES, signa
|
|
|
26
28
|
finally {
|
|
27
29
|
signal?.removeEventListener('abort', abort);
|
|
28
30
|
}
|
|
29
|
-
|
|
31
|
+
try {
|
|
32
|
+
return decodeBoundedUtf8(Buffer.concat(chunks), 'Bulk input');
|
|
33
|
+
}
|
|
34
|
+
catch (cause) {
|
|
35
|
+
throw new CliUsageError(cause instanceof Error ? cause.message : 'Bulk input must contain valid UTF-8 text.');
|
|
36
|
+
}
|
|
30
37
|
}
|
|
31
38
|
function parseBulkQueries(text, { deep = false } = {}) {
|
|
32
39
|
if (typeof text !== 'string')
|
|
@@ -43,7 +50,7 @@ function parseBulkQueries(text, { deep = false } = {}) {
|
|
|
43
50
|
const query = line.trim();
|
|
44
51
|
if (!query)
|
|
45
52
|
continue;
|
|
46
|
-
if (query.length > 1024 ||
|
|
53
|
+
if (query.length > 1024 || hasUnsafeCliText(query)) {
|
|
47
54
|
throw new CliUsageError('Bulk input contains an overlong query or unsupported control character.');
|
|
48
55
|
}
|
|
49
56
|
const key = query.toLowerCase();
|