@slicedearth/whoisleuth-cli 1.39.1 → 1.45.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/README.md +23 -4
- package/bin/whoisleuth.mjs +31 -9
- package/cli/archive-inspect.mjs +37 -2
- package/cli/arguments.mjs +361 -256
- package/cli/artifact-verify.mjs +35 -1
- package/cli/bulk-checkpoint.mjs +72 -39
- package/cli/bulk-command-runner.mjs +34 -4
- package/cli/bulk.mjs +18 -7
- package/cli/case-pack.mjs +136 -0
- package/cli/ci-report.mjs +73 -0
- package/cli/collection-preflight.mjs +59 -0
- package/cli/command-reference.mjs +395 -0
- package/cli/completion.mjs +67 -13
- package/cli/config-profile.mjs +183 -0
- package/cli/ct-event-intake.mjs +159 -0
- package/cli/discovery-observation-snapshot.mjs +8 -14
- package/cli/discovery-scan-command-runner.mjs +29 -2
- package/cli/discovery-snapshot.mjs +8 -15
- package/cli/domain-control-monitor.mjs +104 -0
- package/cli/domain-control-observations.mjs +235 -0
- package/cli/errors.mjs +7 -1
- package/cli/evidence-command-arguments.mjs +11 -0
- package/cli/evidence-command-runner.mjs +1 -0
- package/cli/external-observation-mapping.mjs +162 -0
- package/cli/fail-policy.mjs +64 -0
- package/cli/formatters/html.mjs +1 -1
- package/cli/formatters/json.mjs +12 -4
- package/cli/formatters/terminal.mjs +1 -1
- package/cli/http.mjs +2 -1
- package/cli/investigation-manifest.mjs +113 -0
- package/cli/investigation-run.mjs +112 -0
- package/cli/lookup-brief.mjs +116 -0
- package/cli/lookup-command-runner.mjs +8 -1
- package/cli/lookup-diff.mjs +17 -19
- package/cli/lookup-reconcile.mjs +1 -1
- package/cli/mail-review.mjs +5 -5
- package/cli/manual.mjs +1 -1
- package/cli/network-command-runner.mjs +4 -1
- package/cli/offline-evidence-review.mjs +105 -8
- package/cli/open-asset-model-bridge.mjs +169 -0
- package/cli/registry-cohort.mjs +121 -0
- package/cli/registry-doctor.mjs +106 -11
- package/cli/registry-fixture-scaffold.mjs +74 -0
- package/cli/runner.mjs +388 -315
- package/docs/cli-reference.md +266 -21
- package/docs/cli.md +43 -7
- package/frontend/src/lib/analysis/acquisition-due-diligence.js +2 -2
- package/frontend/src/lib/analysis/brand-profile-model.js +50 -6
- package/frontend/src/lib/analysis/case-evidence-model.js +1 -1
- package/frontend/src/lib/analysis/case-record-core.js +1 -1
- package/frontend/src/lib/analysis/case-record-operations.js +1 -1
- package/frontend/src/lib/analysis/case-report.js +573 -0
- package/frontend/src/lib/analysis/case-response-model.js +30 -1
- package/frontend/src/lib/analysis/detection-rule-model.js +36 -0
- package/frontend/src/lib/analysis/external-findings-import.js +320 -0
- package/frontend/src/lib/analysis/lookup-asset-graph.js +8 -8
- package/frontend/src/lib/analysis/lookup-decision-support.js +9 -2
- package/frontend/src/lib/analysis/lookup-presentation.js +14 -30
- package/frontend/src/lib/analysis/lookup-source-refresh.js +36 -63
- package/frontend/src/lib/analysis/scoring.js +1 -1
- package/frontend/src/lib/analysis/utils.js +1 -1
- package/frontend/src/lib/analysis/visualization-models.js +2 -3
- package/frontend/src/lib/analysis/workspace-archive-crypto.js +1 -4
- package/frontend/src/lib/analysis/workspace-archive.js +5 -7
- package/lib/abusech-auth.mjs +1 -1
- package/lib/availability.mjs +1 -1
- package/lib/bounded-contract-normalizers.mjs +33 -0
- package/lib/bounded-file.mjs +69 -0
- package/lib/bounded-json-response.mjs +157 -0
- package/lib/browser-library-profile.mjs +167 -38
- package/lib/dns-convergence-review.mjs +183 -0
- package/lib/dns-delegation-health.mjs +196 -3
- package/lib/domain-change-packet.mjs +133 -0
- package/lib/domain-change-review.mjs +307 -0
- package/lib/domain-control-flight-recorder.mjs +268 -0
- package/lib/domain-portfolio-review.mjs +151 -0
- package/lib/evidence-export.mjs +1 -1
- package/lib/html-signals.mjs +5 -11
- package/lib/lookup-cache.mjs +5 -1
- package/lib/nameserver-preflight-review.mjs +159 -0
- package/lib/page-language-signals.mjs +124 -0
- package/lib/rdap-transport.mjs +6 -6
- package/lib/risk-scoring.mjs +42 -1
- package/lib/safe-fetch.mjs +37 -32
- package/lib/static-html-analysis.mjs +29 -1
- package/lib/threat-intelligence-runtime.mjs +1 -2
- package/lib/trust-store-comparison.mjs +146 -0
- package/lib/website-technology.mjs +22 -12
- package/lib/zone-intent-review.mjs +534 -0
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { normalizeDomain } from "./case-model.js";
|
|
|
5
5
|
import { normalizePageBaseline } from "./page-baseline.js";
|
|
6
6
|
import { isInformativeFaviconHash } from "./utils.js";
|
|
7
7
|
export const BRAND_PROFILE_SCHEMA = 'whoisleuth.brand-profiles';
|
|
8
|
-
export const BRAND_PROFILE_SCHEMA_VERSION =
|
|
8
|
+
export const BRAND_PROFILE_SCHEMA_VERSION = 6;
|
|
9
9
|
export const MAX_PROFILES = 100;
|
|
10
10
|
export const MAX_PROFILE_VALUES = 200;
|
|
11
11
|
export const MAX_PROFILE_VALUE_INPUTS = MAX_PROFILE_VALUES * 4;
|
|
@@ -22,6 +22,8 @@ export const MAX_PROTECTION_ATTESTATIONS = 6;
|
|
|
22
22
|
export const MAX_DESIRED_POSTURE_BASELINES = 20;
|
|
23
23
|
export const MAX_DESIRED_POSTURE_RECORDS = 32;
|
|
24
24
|
export const MAX_DESIRED_POSTURE_SUPPRESSIONS = 12;
|
|
25
|
+
export const MAX_DESIRED_POSTURE_OBSERVATIONS = 12;
|
|
26
|
+
export const MAX_DESIRED_POSTURE_CHANGE_WINDOWS = 8;
|
|
25
27
|
const SAFE_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
26
28
|
const SHA256_RE = /^[a-f0-9]{64}$/i;
|
|
27
29
|
const CONTROL_RE = /[\x00-\x1f\x7f]/;
|
|
@@ -65,7 +67,7 @@ function boundedText(value, maximum = MAX_PROFILE_TEXT_LENGTH) {
|
|
|
65
67
|
return '';
|
|
66
68
|
return value.slice(0, maximum * 4).replace(/\s+/g, ' ').trim().slice(0, maximum).trim();
|
|
67
69
|
}
|
|
68
|
-
function
|
|
70
|
+
export function normalizeBrandProfileId(value) {
|
|
69
71
|
return typeof value === 'string' && SAFE_ID_RE.test(value) ? value : null;
|
|
70
72
|
}
|
|
71
73
|
function timestamp(value, fallback) {
|
|
@@ -203,6 +205,35 @@ function normalizeDesiredPostureObservation(value) {
|
|
|
203
205
|
}
|
|
204
206
|
return checks.length ? { observedAt, checks } : null;
|
|
205
207
|
}
|
|
208
|
+
function normalizeDesiredPostureObservationHistory(value, previous) {
|
|
209
|
+
const candidates = Array.isArray(value) ? value : previous ? [previous] : [];
|
|
210
|
+
const byTime = new Map();
|
|
211
|
+
for (const item of candidates.slice(0, MAX_DESIRED_POSTURE_OBSERVATIONS * 4)) {
|
|
212
|
+
const normalized = normalizeDesiredPostureObservation(item);
|
|
213
|
+
if (normalized)
|
|
214
|
+
byTime.set(normalized.observedAt, normalized);
|
|
215
|
+
}
|
|
216
|
+
return [...byTime.values()]
|
|
217
|
+
.sort((left, right) => Date.parse(left.observedAt) - Date.parse(right.observedAt))
|
|
218
|
+
.slice(-MAX_DESIRED_POSTURE_OBSERVATIONS);
|
|
219
|
+
}
|
|
220
|
+
function normalizeDesiredPostureChangeWindows(value) {
|
|
221
|
+
if (!Array.isArray(value))
|
|
222
|
+
return [];
|
|
223
|
+
const output = [];
|
|
224
|
+
for (const item of value.slice(0, MAX_DESIRED_POSTURE_CHANGE_WINDOWS * 4)) {
|
|
225
|
+
const candidate = record(item);
|
|
226
|
+
const startsAt = timestamp(candidate.startsAt, null);
|
|
227
|
+
const endsAt = timestamp(candidate.endsAt, null);
|
|
228
|
+
const summary = boundedText(candidate.summary, 300);
|
|
229
|
+
if (!startsAt || !endsAt || Date.parse(endsAt) <= Date.parse(startsAt) || !summary)
|
|
230
|
+
continue;
|
|
231
|
+
output.push({ startsAt, endsAt, summary });
|
|
232
|
+
if (output.length >= MAX_DESIRED_POSTURE_CHANGE_WINDOWS)
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
return output.sort((left, right) => Date.parse(left.startsAt) - Date.parse(right.startsAt));
|
|
236
|
+
}
|
|
206
237
|
function normalizeTlsSanPatterns(value) {
|
|
207
238
|
if (!Array.isArray(value))
|
|
208
239
|
return [];
|
|
@@ -250,6 +281,8 @@ export function normalizeDesiredPostureBaselines(value, officialDomains, fallbac
|
|
|
250
281
|
break;
|
|
251
282
|
}
|
|
252
283
|
seen.add(domain);
|
|
284
|
+
const previousObservation = normalizeDesiredPostureObservation(candidate.previousObservation);
|
|
285
|
+
const observationHistory = normalizeDesiredPostureObservationHistory(candidate.observationHistory, previousObservation);
|
|
253
286
|
output.push({
|
|
254
287
|
version: 1,
|
|
255
288
|
domain,
|
|
@@ -266,9 +299,18 @@ export function normalizeDesiredPostureBaselines(value, officialDomains, fallbac
|
|
|
266
299
|
? candidate.registrarLock
|
|
267
300
|
: 'unconfigured',
|
|
268
301
|
renewalReviewAt: timestamp(candidate.renewalReviewAt, null),
|
|
302
|
+
zoneIntent: ['active_service', 'defensive_registration', 'no_service', 'parked', 'redirect_only'].includes(String(candidate.zoneIntent))
|
|
303
|
+
? candidate.zoneIntent
|
|
304
|
+
: 'unconfigured',
|
|
305
|
+
lifecycle: ['active', 'change_planned', 'retiring', 'retired'].includes(String(candidate.lifecycle))
|
|
306
|
+
? candidate.lifecycle
|
|
307
|
+
: 'active',
|
|
308
|
+
recoveryDependency: boundedText(candidate.recoveryDependency, 200),
|
|
309
|
+
approvedChangeWindows: normalizeDesiredPostureChangeWindows(candidate.approvedChangeWindows),
|
|
269
310
|
suppressions,
|
|
270
311
|
note: boundedText(candidate.note, MAX_PROFILE_TEXT_LENGTH),
|
|
271
|
-
previousObservation:
|
|
312
|
+
previousObservation: observationHistory.at(-1) ?? previousObservation,
|
|
313
|
+
observationHistory,
|
|
272
314
|
updatedAt: timestamp(candidate.updatedAt, fallback),
|
|
273
315
|
});
|
|
274
316
|
if (output.length >= MAX_DESIRED_POSTURE_BASELINES)
|
|
@@ -295,7 +337,9 @@ export function normalizeBrandProfile(raw, options = {}) {
|
|
|
295
337
|
const pageBaseline = candidateBaseline && officialDomains.includes(candidateBaseline.domain)
|
|
296
338
|
? candidateBaseline
|
|
297
339
|
: null;
|
|
298
|
-
const profileId =
|
|
340
|
+
const profileId = normalizeBrandProfileId(existing?.id)
|
|
341
|
+
|| normalizeBrandProfileId(value.id)
|
|
342
|
+
|| (typeof options.makeId === 'function' ? normalizeBrandProfileId(options.makeId()) : null);
|
|
299
343
|
const name = boundedText(value.name, MAX_PROFILE_NAME_LENGTH);
|
|
300
344
|
if (!profileId || !name)
|
|
301
345
|
return null;
|
|
@@ -376,8 +420,8 @@ export function mergeBrandProfiles(localRaw, importedRaw, options = {}) {
|
|
|
376
420
|
if (importedVersion !== null && importedVersion > BRAND_PROFILE_SCHEMA_VERSION) {
|
|
377
421
|
throw new Error(`This Brand Profile file uses newer schema ${importedVersion}. Update the app before importing it.`);
|
|
378
422
|
}
|
|
379
|
-
if (![2, 3, 4, BRAND_PROFILE_SCHEMA_VERSION].includes(importedVersion ?? 0)) {
|
|
380
|
-
throw new Error(`Expected a WHOISleuth Brand Profile export using schema 2, 3, 4, or ${BRAND_PROFILE_SCHEMA_VERSION}.`);
|
|
423
|
+
if (![2, 3, 4, 5, BRAND_PROFILE_SCHEMA_VERSION].includes(importedVersion ?? 0)) {
|
|
424
|
+
throw new Error(`Expected a WHOISleuth Brand Profile export using schema 2, 3, 4, 5, or ${BRAND_PROFILE_SCHEMA_VERSION}.`);
|
|
381
425
|
}
|
|
382
426
|
const local = normalizeBrandProfileStore(localRaw).profiles;
|
|
383
427
|
const byName = new Map(local.map((profile) => [profile.name.toLowerCase(), profile]));
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// record normalization, and analyst updates.
|
|
3
3
|
import { normalizeHttpSummary } from "./http-summary.js";
|
|
4
4
|
import { normalizeOpportunityModelVersion, normalizeRiskModelVersion } from "./scoring.js";
|
|
5
|
-
import {
|
|
5
|
+
import { MAX_EVIDENCE_CHANGES, MAX_EVIDENCE_DETAIL_LENGTH, MAX_EVIDENCE_FACTORS, MAX_EVIDENCE_MUTATIONS, MAX_EVIDENCE_NAMESERVERS, MAX_EVIDENCE_SNAPSHOTS_PER_CASE, MAX_EVIDENCE_STRING_LENGTH, MAX_EVIDENCE_TITLE_LENGTH, } from "./case-record-contracts.js";
|
|
6
6
|
import { CONCLUSIVE_AVAILABILITY, DEFAULT_EVIDENCE_SOURCE, EVIDENCE_SOURCE_RANK, EVIDENCE_SOURCE_SET, REGISTERED_LIKE, hashString, isoOrNull, objectRecord, safeId, } from "./case-record-core.js";
|
|
7
7
|
function evidenceString(value, max = MAX_EVIDENCE_STRING_LENGTH) {
|
|
8
8
|
if (value == null)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Pure, framework-neutral analyst-case records, evidence histories, bounded
|
|
2
2
|
// record normalization, and analyst updates.
|
|
3
|
-
import { CASE_DISPOSITIONS, CASE_REVIEW_REASONS,
|
|
3
|
+
import { CASE_DISPOSITIONS, CASE_REVIEW_REASONS, CASE_SOURCES, CASE_STATUSES, DEFAULT_DISPOSITION, DEFAULT_SOURCE, DEFAULT_STATUS, EVIDENCE_SOURCES, MAX_DOMAIN_LENGTH, MAX_NOTES_PER_CASE, MAX_NOTE_LENGTH, MAX_TAGS_PER_CASE, MAX_TAG_LENGTH, } from "./case-record-contracts.js";
|
|
4
4
|
// Forward-version policy (two distinct guarantees):
|
|
5
5
|
// - A locally-stored envelope that declares a version greater than this is
|
|
6
6
|
// still read best-effort on load (known fields kept, unknown dropped), but
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Pure, framework-neutral analyst-case records, evidence histories, bounded
|
|
2
2
|
// record normalization, and analyst updates.
|
|
3
3
|
import { appendCaseAction, appendCaseAssertion, appendCaseDecision, appendCaseEvidencePin, appendCaseEvidencePins, appendCaseManualTrailEvent, appendCaseSighting, normalizeCaseActions, normalizeCaseAssertions, normalizeCaseDecisions, normalizeCaseEvidencePins, normalizeCaseManualTrail, normalizeCaseSightings, updateCaseAction, updateCaseAssertion, } from "./case-response-model.js";
|
|
4
|
-
import {
|
|
4
|
+
import { MAX_CASES, MAX_NOTES_PER_CASE, } from "./case-record-contracts.js";
|
|
5
5
|
import { DEFAULT_EVIDENCE_SOURCE, EVIDENCE_SOURCE_SET, deterministicId, isoOrNow, makeId, normalizeDisposition, normalizeDomain, normalizeNoteBody, normalizeReviewReasonCode, normalizeNotes, normalizeSource, normalizeStatus, normalizeTags, objectRecord, safeId, } from "./case-record-core.js";
|
|
6
6
|
import { normalizeEvidenceHistory, } from "./case-evidence-model.js";
|
|
7
7
|
// ---------------------------------------------------------------------------
|