@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
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import { canonicalArtifactJson } from "../
|
|
2
|
+
import { canonicalArtifactJson } from "../packages/evidence/artifact-integrity.mjs";
|
|
3
|
+
import { scanBoundedJson } from "../lib/bounded-json.mjs";
|
|
4
|
+
import { normalizeExplicitIsoTimestamp, normalizeLegacyIsoTimestamp, } from "../packages/evidence/observation.mjs";
|
|
5
|
+
import { CLI_LOOKUP_SCHEMA, MAX_CLI_LOOKUP_BYTES, } from "../packages/contracts/cli-lookup.mjs";
|
|
6
|
+
import { normalizeCliLookupDocument } from "./saved-lookup.mjs";
|
|
3
7
|
export const SOURCE_RELIABILITY_REPORT_SCHEMA = 'whoisleuth.source-reliability-report';
|
|
4
8
|
export const SOURCE_RELIABILITY_REPORT_VERSION = 1;
|
|
5
9
|
export const MAX_SOURCE_RELIABILITY_INPUT_BYTES = 12 * 1024 * 1024;
|
|
@@ -22,7 +26,7 @@ const STATES = new Set([
|
|
|
22
26
|
'stale',
|
|
23
27
|
]);
|
|
24
28
|
const LOOKUP_SCHEMAS = new Set([
|
|
25
|
-
|
|
29
|
+
CLI_LOOKUP_SCHEMA,
|
|
26
30
|
'whoisleuth.cli.bulk',
|
|
27
31
|
'whoisleuth.cli.bulk.item',
|
|
28
32
|
]);
|
|
@@ -134,10 +138,11 @@ function parseValues(raw) {
|
|
|
134
138
|
}
|
|
135
139
|
let parsed;
|
|
136
140
|
try {
|
|
141
|
+
scanBoundedJson(raw);
|
|
137
142
|
parsed = JSON.parse(raw);
|
|
138
143
|
}
|
|
139
144
|
catch {
|
|
140
|
-
throw new TypeError('Source reliability input must be valid JSON.');
|
|
145
|
+
throw new TypeError('Source reliability input must be valid bounded JSON without duplicate keys.');
|
|
141
146
|
}
|
|
142
147
|
const values = Array.isArray(parsed) ? parsed : [parsed];
|
|
143
148
|
if (values.length === 0 || values.length > MAX_SOURCE_RELIABILITY_DOCUMENTS) {
|
|
@@ -150,10 +155,29 @@ function parseValues(raw) {
|
|
|
150
155
|
return document;
|
|
151
156
|
});
|
|
152
157
|
}
|
|
158
|
+
function hasJsonArrayEnvelope(raw) {
|
|
159
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
160
|
+
const code = raw.charCodeAt(index);
|
|
161
|
+
if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d)
|
|
162
|
+
continue;
|
|
163
|
+
return code === 0x5b;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
153
167
|
function parseLookupDocuments(values) {
|
|
154
168
|
return values.map((document) => {
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
const schema = String(document.schema);
|
|
170
|
+
if (schema === CLI_LOOKUP_SCHEMA) {
|
|
171
|
+
try {
|
|
172
|
+
return normalizeCliLookupDocument(document, { label: 'Source reliability Lookup input' });
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
throw new TypeError('Source reliability input requires a supported bounded CLI Lookup document.');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const supported = document.version === 1;
|
|
179
|
+
if (!LOOKUP_SCHEMAS.has(schema) || !supported) {
|
|
180
|
+
throw new TypeError('Source reliability input requires CLI Lookup version 1 or 2, version 1 Bulk or Bulk-item documents, or only source reliability reports.');
|
|
157
181
|
}
|
|
158
182
|
return document;
|
|
159
183
|
});
|
|
@@ -162,8 +186,9 @@ function accumulator(store, source) {
|
|
|
162
186
|
const existing = store.get(source);
|
|
163
187
|
if (existing)
|
|
164
188
|
return existing;
|
|
165
|
-
if (store.size >= MAX_SOURCE_RELIABILITY_SOURCES)
|
|
166
|
-
|
|
189
|
+
if (store.size >= MAX_SOURCE_RELIABILITY_SOURCES) {
|
|
190
|
+
throw new TypeError(`Source reliability input exceeds the ${MAX_SOURCE_RELIABILITY_SOURCES}-source limit.`);
|
|
191
|
+
}
|
|
167
192
|
const created = {
|
|
168
193
|
states: new Map(),
|
|
169
194
|
observationDurations: [],
|
|
@@ -183,8 +208,6 @@ function accumulator(store, source) {
|
|
|
183
208
|
}
|
|
184
209
|
function addState(store, source, state) {
|
|
185
210
|
const item = accumulator(store, source);
|
|
186
|
-
if (!item)
|
|
187
|
-
return;
|
|
188
211
|
item.states.set(state, (item.states.get(state) ?? 0) + 1);
|
|
189
212
|
if (state === 'rate_limited')
|
|
190
213
|
item.rateLimited += 1;
|
|
@@ -194,15 +217,16 @@ function collectTiming(document, store) {
|
|
|
194
217
|
const timing = record(diagnostics?.timing);
|
|
195
218
|
if (timing?.version !== 1 || !Array.isArray(timing.sources))
|
|
196
219
|
return;
|
|
197
|
-
|
|
220
|
+
if (timing.sources.length > 16) {
|
|
221
|
+
throw new TypeError('Source reliability input exceeds the 16-source timing limit.');
|
|
222
|
+
}
|
|
223
|
+
for (const rawEntry of timing.sources) {
|
|
198
224
|
const entry = record(rawEntry);
|
|
199
225
|
const source = safeSource(entry?.source);
|
|
200
226
|
const duration = boundedDuration(entry?.durationMs);
|
|
201
227
|
if (!source || !TIMING_SOURCES.has(source) || duration === null)
|
|
202
228
|
continue;
|
|
203
229
|
const item = accumulator(store, source);
|
|
204
|
-
if (!item)
|
|
205
|
-
continue;
|
|
206
230
|
item.timingDurations.push(duration);
|
|
207
231
|
item.timingSamples += 1;
|
|
208
232
|
}
|
|
@@ -239,8 +263,6 @@ function collectObservation(value, store) {
|
|
|
239
263
|
if (!source || !state)
|
|
240
264
|
return;
|
|
241
265
|
const item = accumulator(store, source);
|
|
242
|
-
if (!item)
|
|
243
|
-
return;
|
|
244
266
|
item.observationSamples += 1;
|
|
245
267
|
addState(store, source, state);
|
|
246
268
|
const duration = boundedDuration(value.durationMs);
|
|
@@ -254,8 +276,14 @@ function traverse(document, store) {
|
|
|
254
276
|
const stack = [{ value: document, depth: 0 }];
|
|
255
277
|
while (stack.length) {
|
|
256
278
|
const current = stack.pop();
|
|
257
|
-
if (!current
|
|
279
|
+
if (!current)
|
|
258
280
|
continue;
|
|
281
|
+
if (current.depth > 12) {
|
|
282
|
+
throw new TypeError('Source reliability input exceeds the 12-level traversal limit.');
|
|
283
|
+
}
|
|
284
|
+
if (visited >= MAX_SOURCE_RELIABILITY_TRAVERSAL_NODES) {
|
|
285
|
+
throw new TypeError(`Source reliability input exceeds the ${MAX_SOURCE_RELIABILITY_TRAVERSAL_NODES}-node traversal limit.`);
|
|
286
|
+
}
|
|
259
287
|
visited += 1;
|
|
260
288
|
const object = record(current.value);
|
|
261
289
|
if (object) {
|
|
@@ -267,13 +295,20 @@ function traverse(document, store) {
|
|
|
267
295
|
if (source)
|
|
268
296
|
addState(store, source, 'rate_limited');
|
|
269
297
|
}
|
|
270
|
-
|
|
298
|
+
const children = Object.values(object);
|
|
299
|
+
if (children.length > 200) {
|
|
300
|
+
throw new TypeError('Source reliability input contains an object with more than 200 fields.');
|
|
301
|
+
}
|
|
302
|
+
for (const child of children) {
|
|
271
303
|
if (child && typeof child === 'object')
|
|
272
304
|
stack.push({ value: child, depth: current.depth + 1 });
|
|
273
305
|
}
|
|
274
306
|
}
|
|
275
307
|
else if (Array.isArray(current.value)) {
|
|
276
|
-
|
|
308
|
+
if (current.value.length > 1_000) {
|
|
309
|
+
throw new TypeError('Source reliability input contains an array with more than 1000 items.');
|
|
310
|
+
}
|
|
311
|
+
for (const child of current.value) {
|
|
277
312
|
if (child && typeof child === 'object')
|
|
278
313
|
stack.push({ value: child, depth: current.depth + 1 });
|
|
279
314
|
}
|
|
@@ -298,13 +333,19 @@ function ratio(numerator, denominator) {
|
|
|
298
333
|
return denominator > 0 ? Number((numerator / denominator).toFixed(4)) : null;
|
|
299
334
|
}
|
|
300
335
|
function reportTimestamp(value) {
|
|
301
|
-
|
|
336
|
+
const normalized = normalizeExplicitIsoTimestamp(value);
|
|
337
|
+
if (!normalized) {
|
|
302
338
|
throw new TypeError('Source reliability report has an invalid generation time.');
|
|
303
339
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
340
|
+
return normalized;
|
|
341
|
+
}
|
|
342
|
+
function lookupDocumentTimestamp(document) {
|
|
343
|
+
const explicit = normalizeExplicitIsoTimestamp(document.generatedAt);
|
|
344
|
+
if (explicit)
|
|
345
|
+
return explicit;
|
|
346
|
+
return document.version === 1
|
|
347
|
+
? normalizeLegacyIsoTimestamp(document.generatedAt)
|
|
348
|
+
: null;
|
|
308
349
|
}
|
|
309
350
|
function optionalReportTimestamp(value, label) {
|
|
310
351
|
if (value === null)
|
|
@@ -475,8 +516,6 @@ function collectReport(report, store) {
|
|
|
475
516
|
for (const value of Object.values(durationTrend))
|
|
476
517
|
reportedDurationPoint(value);
|
|
477
518
|
const item = accumulator(store, source);
|
|
478
|
-
if (!item)
|
|
479
|
-
throw new TypeError('Source reliability report exceeds the source limit.');
|
|
480
519
|
const observationSamples = safeCount(samples.observations, `${source} observation sample count`);
|
|
481
520
|
const timingSamples = safeCount(samples.timing, `${source} timing sample count`);
|
|
482
521
|
const truncationCount = safeCount(sourceRecord.truncationCount, `${source} truncation count`);
|
|
@@ -583,9 +622,14 @@ function collectReport(report, store) {
|
|
|
583
622
|
};
|
|
584
623
|
}
|
|
585
624
|
export function buildSourceReliabilityReport(raw, generatedAt = new Date().toISOString()) {
|
|
586
|
-
const
|
|
587
|
-
if (!
|
|
588
|
-
throw new TypeError('Source reliability report time must
|
|
625
|
+
const normalizedGeneratedAt = normalizeExplicitIsoTimestamp(generatedAt);
|
|
626
|
+
if (!normalizedGeneratedAt)
|
|
627
|
+
throw new TypeError('Source reliability report time must use an explicit timezone.');
|
|
628
|
+
if (typeof raw === 'string'
|
|
629
|
+
&& !hasJsonArrayEnvelope(raw)
|
|
630
|
+
&& Buffer.byteLength(raw, 'utf8') > MAX_CLI_LOOKUP_BYTES) {
|
|
631
|
+
throw new TypeError('Source reliability input requires a supported bounded CLI Lookup document.');
|
|
632
|
+
}
|
|
589
633
|
const values = parseValues(raw);
|
|
590
634
|
const store = new Map();
|
|
591
635
|
const reportInputs = values.every((value) => value.schema === SOURCE_RELIABILITY_REPORT_SCHEMA);
|
|
@@ -616,14 +660,9 @@ export function buildSourceReliabilityReport(raw, generatedAt = new Date().toISO
|
|
|
616
660
|
for (const document of documents) {
|
|
617
661
|
const mode = document.mode === 'fast' || document.mode === 'deep' ? document.mode : 'unknown';
|
|
618
662
|
lookupModes[mode] += 1;
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
catch {
|
|
624
|
-
// Invalid or missing document dates remain unmeasured.
|
|
625
|
-
}
|
|
626
|
-
}
|
|
663
|
+
const sampleDate = lookupDocumentTimestamp(document);
|
|
664
|
+
if (sampleDate)
|
|
665
|
+
sampleDates.push(sampleDate);
|
|
627
666
|
traverse(document, store);
|
|
628
667
|
}
|
|
629
668
|
}
|
|
@@ -669,7 +708,7 @@ export function buildSourceReliabilityReport(raw, generatedAt = new Date().toISO
|
|
|
669
708
|
return Object.freeze({
|
|
670
709
|
schema: SOURCE_RELIABILITY_REPORT_SCHEMA,
|
|
671
710
|
version: SOURCE_RELIABILITY_REPORT_VERSION,
|
|
672
|
-
generatedAt:
|
|
711
|
+
generatedAt: normalizedGeneratedAt,
|
|
673
712
|
mode: 'offline_local',
|
|
674
713
|
reportsMerged,
|
|
675
714
|
documentsReviewed,
|
|
@@ -2,20 +2,40 @@ const MIN_TERMINAL_WIDTH = 40;
|
|
|
2
2
|
const MAX_TERMINAL_WIDTH = 160;
|
|
3
3
|
const DEFAULT_TERMINAL_WIDTH = 100;
|
|
4
4
|
const ANSI_RESET = '\u001b[0m';
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
const ANSI_BY_PALETTE = Object.freeze({
|
|
6
|
+
auto: Object.freeze({
|
|
7
|
+
accent: '\u001b[1;36m',
|
|
8
|
+
danger: '\u001b[31m',
|
|
9
|
+
dim: '\u001b[2m',
|
|
10
|
+
info: '\u001b[36m',
|
|
11
|
+
success: '\u001b[32m',
|
|
12
|
+
warning: '\u001b[33m',
|
|
13
|
+
}),
|
|
14
|
+
light: Object.freeze({
|
|
15
|
+
accent: '\u001b[1;34m',
|
|
16
|
+
danger: '\u001b[31m',
|
|
17
|
+
dim: '\u001b[2;30m',
|
|
18
|
+
info: '\u001b[34m',
|
|
19
|
+
success: '\u001b[32m',
|
|
20
|
+
warning: '\u001b[33m',
|
|
21
|
+
}),
|
|
22
|
+
dark: Object.freeze({
|
|
23
|
+
accent: '\u001b[1;96m',
|
|
24
|
+
danger: '\u001b[91m',
|
|
25
|
+
dim: '\u001b[2m',
|
|
26
|
+
info: '\u001b[96m',
|
|
27
|
+
success: '\u001b[92m',
|
|
28
|
+
warning: '\u001b[93m',
|
|
29
|
+
}),
|
|
12
30
|
});
|
|
13
31
|
const SECTION_LABELS = new Set([
|
|
14
32
|
'Collection',
|
|
33
|
+
'DNS',
|
|
15
34
|
'Discover',
|
|
16
35
|
'Integrity and calibration',
|
|
17
36
|
'Investigate',
|
|
18
37
|
'Network',
|
|
38
|
+
'Mail',
|
|
19
39
|
'Quick start',
|
|
20
40
|
'Registration',
|
|
21
41
|
'Result',
|
|
@@ -24,6 +44,7 @@ const SECTION_LABELS = new Set([
|
|
|
24
44
|
'Source health',
|
|
25
45
|
'Target',
|
|
26
46
|
'Terminal',
|
|
47
|
+
'TLS and certificate',
|
|
27
48
|
'Website and security',
|
|
28
49
|
]);
|
|
29
50
|
function boundedTerminalWidth(value) {
|
|
@@ -32,7 +53,7 @@ function boundedTerminalWidth(value) {
|
|
|
32
53
|
return DEFAULT_TERMINAL_WIDTH;
|
|
33
54
|
return Math.max(MIN_TERMINAL_WIDTH, Math.min(MAX_TERMINAL_WIDTH, width));
|
|
34
55
|
}
|
|
35
|
-
function terminalPresentation(stream, requestedColor = true, environment = process.env) {
|
|
56
|
+
function terminalPresentation(stream, requestedColor = true, environment = process.env, palette = 'auto') {
|
|
36
57
|
const interactive = stream?.isTTY === true
|
|
37
58
|
&& environment.TERM !== 'dumb'
|
|
38
59
|
&& !environment.CI;
|
|
@@ -42,11 +63,15 @@ function terminalPresentation(stream, requestedColor = true, environment = proce
|
|
|
42
63
|
return Object.freeze({
|
|
43
64
|
color,
|
|
44
65
|
interactive,
|
|
66
|
+
palette,
|
|
45
67
|
width: interactive ? boundedTerminalWidth(stream?.columns) : null,
|
|
46
68
|
});
|
|
47
69
|
}
|
|
48
|
-
function tone(value, selectedTone,
|
|
49
|
-
|
|
70
|
+
function tone(value, selectedTone, presentation) {
|
|
71
|
+
if (presentation === false)
|
|
72
|
+
return value;
|
|
73
|
+
const palette = presentation === true ? 'auto' : presentation;
|
|
74
|
+
return `${ANSI_BY_PALETTE[palette][selectedTone]}${value}${ANSI_RESET}`;
|
|
50
75
|
}
|
|
51
76
|
function statusTone(value) {
|
|
52
77
|
const normalized = value.trim().toLowerCase();
|
|
@@ -107,32 +132,32 @@ function wrapTerminalOutput(value, width) {
|
|
|
107
132
|
const lines = value.replace(/\n$/u, '').split('\n').flatMap((line) => wrapLine(line, width));
|
|
108
133
|
return `${lines.join('\n')}${trailingNewline ? '\n' : ''}`;
|
|
109
134
|
}
|
|
110
|
-
function colorizeLine(line) {
|
|
135
|
+
function colorizeLine(line, palette) {
|
|
111
136
|
const trimmed = line.trim();
|
|
112
137
|
if (!trimmed)
|
|
113
138
|
return line;
|
|
114
139
|
const section = trimmed.endsWith(':') ? trimmed.slice(0, -1) : trimmed;
|
|
115
140
|
if (SECTION_LABELS.has(section)) {
|
|
116
141
|
const prefixLength = line.indexOf(trimmed);
|
|
117
|
-
return `${line.slice(0, prefixLength)}${tone(trimmed, 'accent',
|
|
142
|
+
return `${line.slice(0, prefixLength)}${tone(trimmed, 'accent', palette)}${line.slice(prefixLength + trimmed.length)}`;
|
|
118
143
|
}
|
|
119
144
|
if (trimmed.startsWith('✓'))
|
|
120
|
-
return line.replace('✓', tone('✓', 'success',
|
|
145
|
+
return line.replace('✓', tone('✓', 'success', palette));
|
|
121
146
|
if (trimmed.startsWith('!'))
|
|
122
|
-
return line.replace('!', tone('!', 'danger',
|
|
147
|
+
return line.replace('!', tone('!', 'danger', palette));
|
|
123
148
|
if (/^(?:Limitation|Access note|Boundary)\s{2,}/u.test(trimmed)) {
|
|
124
|
-
return tone(line, 'dim',
|
|
149
|
+
return tone(line, 'dim', palette);
|
|
125
150
|
}
|
|
126
151
|
const bracket = line.match(/^(\s*)\[([^\]]+)\](.*)$/u);
|
|
127
152
|
if (bracket) {
|
|
128
153
|
const selectedTone = statusTone(bracket[2] || '') || 'info';
|
|
129
|
-
return `${bracket[1]}${tone(`[${bracket[2]}]`, selectedTone,
|
|
154
|
+
return `${bracket[1]}${tone(`[${bracket[2]}]`, selectedTone, palette)}${bracket[3]}`;
|
|
130
155
|
}
|
|
131
156
|
const keyValue = line.match(/^(\s*)(\S(?:.*?\S)?)(\s{2,})(\S.*)$/u);
|
|
132
157
|
if (keyValue) {
|
|
133
158
|
const value = keyValue[4] || '';
|
|
134
159
|
const selectedTone = statusTone(value);
|
|
135
|
-
return `${keyValue[1]}${tone(keyValue[2] || '', 'info',
|
|
160
|
+
return `${keyValue[1]}${tone(keyValue[2] || '', 'info', palette)}${keyValue[3]}${selectedTone ? tone(value, selectedTone, palette) : value}`;
|
|
136
161
|
}
|
|
137
162
|
return line;
|
|
138
163
|
}
|
|
@@ -141,7 +166,7 @@ function presentTerminalOutput(value, presentation) {
|
|
|
141
166
|
if (!presentation.color)
|
|
142
167
|
return wrapped;
|
|
143
168
|
const trailingNewline = wrapped.endsWith('\n');
|
|
144
|
-
const lines = wrapped.replace(/\n$/u, '').split('\n').map(colorizeLine);
|
|
169
|
+
const lines = wrapped.replace(/\n$/u, '').split('\n').map((line) => colorizeLine(line, presentation.palette));
|
|
145
170
|
return `${lines.join('\n')}${trailingNewline ? '\n' : ''}`;
|
|
146
171
|
}
|
|
147
172
|
export { presentTerminalOutput, terminalPresentation, tone, wrapTerminalOutput, };
|