@slicedearth/whoisleuth-cli 1.47.4 → 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/README.md +74 -177
- package/SECURITY.md +6 -3
- package/bin/whoisleuth.mjs +2 -1
- package/cli/archive-inspect.mjs +28 -10
- package/cli/arguments.mjs +204 -174
- package/cli/artifact-structure.mjs +837 -163
- package/cli/artifact-verify.mjs +90 -61
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +33 -20
- package/cli/bulk.mjs +9 -2
- package/cli/case-pack.mjs +180 -576
- 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 +910 -92
- package/cli/completion.mjs +504 -226
- package/cli/config-profile.mjs +13 -3
- package/cli/discover.mjs +7 -1
- package/cli/discovery-observation-snapshot.mjs +24 -28
- package/cli/discovery-scan-command-runner.mjs +9 -9
- package/cli/discovery-scan.mjs +17 -3
- package/cli/discovery-snapshot.mjs +15 -7
- package/cli/domain-control-monitor.mjs +203 -30
- package/cli/domain-control-observations.mjs +15 -21
- package/cli/errors.mjs +8 -3
- package/cli/evidence-signing.mjs +10 -20
- package/cli/external-observation-mapping.mjs +3 -3
- package/cli/formatters/json.mjs +20 -32
- package/cli/formatters/terminal.mjs +583 -22
- package/cli/input.mjs +7 -2
- package/cli/interactive-launcher.mjs +226 -0
- package/cli/interchange-report.mjs +19 -19
- package/cli/investigation-manifest.mjs +1 -1
- package/cli/investigation-plan.mjs +167 -16
- package/cli/investigation-run.mjs +9 -1
- 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 +94 -31
- package/cli/lookup-diff.mjs +2 -2
- package/cli/lookup-plan.mjs +1 -0
- package/cli/mail-review.mjs +206 -94
- package/cli/manual.mjs +5 -5
- package/cli/offline-evidence-review.mjs +18 -7
- package/cli/open-asset-model-bridge.mjs +1 -1
- package/cli/output-file.mjs +70 -14
- package/cli/page-compare.mjs +11 -6
- package/cli/progress-events.mjs +2 -1
- package/cli/progress.mjs +3 -3
- package/cli/registry-cohort.mjs +9 -51
- package/cli/registry-doctor.mjs +67 -28
- package/cli/retained-artifact-diff.mjs +7 -8
- package/cli/risk-calibration.mjs +85 -38
- package/cli/runner.mjs +189 -75
- package/cli/saved-lookup.mjs +424 -57
- package/cli/sharing-review.mjs +6 -1
- package/cli/source-reliability.mjs +75 -36
- package/cli/terminal-presentation.mjs +43 -18
- package/docs/cli-reference.md +125 -1708
- package/docs/cli.md +86 -254
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -145
- package/frontend/src/lib/analysis/domain-control-records.js +3 -88
- package/lib/availability.mjs +58 -40
- package/lib/bounded-contract-normalizers.mjs +2 -4
- package/lib/bounded-file.mjs +78 -4
- package/lib/bounded-json.mjs +51 -13
- package/lib/browser-library-profile.mjs +1 -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 +35 -17
- package/lib/ct-query.mjs +3 -3
- package/lib/ct-search.mjs +37 -21
- package/lib/distributed-operation-budget.mjs +4 -2
- package/lib/dns-delegation-health.mjs +9 -6
- package/lib/dns-intelligence.mjs +1 -1
- package/lib/dnssec-chain-validation.mjs +2 -3
- package/lib/dnssec-evidence-validation.mjs +13 -7
- package/lib/domain-assurance.mjs +12 -12
- package/lib/domain-change-packet.mjs +1 -1
- package/lib/domain-control-flight-recorder.mjs +442 -78
- package/lib/domain-control-manifest.mjs +314 -59
- package/lib/domain-posture-analysis.mjs +5 -2
- package/lib/encrypted-dns-contract.mjs +2 -3
- package/lib/evidence-export.mjs +518 -36
- package/lib/external-intelligence-risk.mjs +26 -10
- 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-intelligence.mjs +331 -2
- package/lib/interchange-fidelity-registry.mjs +28 -26
- package/lib/lookup-cache.mjs +6 -4
- package/lib/lookup.mjs +25 -2
- package/lib/observed-network-context.mjs +1 -1
- package/lib/page-fingerprints.mjs +2 -2
- package/lib/page-role-profile.mjs +1 -1
- package/lib/portable-generator.mjs +8 -0
- package/lib/provider-policy-admission.mjs +3 -1
- package/lib/rdap-bootstrap.mjs +76 -16
- package/lib/rdap-client.mjs +20 -12
- package/lib/rdap-normalization.mjs +306 -75
- 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-insights.mjs +7 -0
- package/lib/response-policy.mjs +27 -4
- package/lib/risk-calibration-summary.mjs +35 -33
- package/lib/risk-scoring.mjs +4 -3
- package/lib/safe-fetch.mjs +62 -40
- package/lib/security-txt.mjs +1 -1
- package/lib/smtp-transport-review.mjs +8 -5
- 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 +3 -5
- package/lib/threat-intelligence-types.mjs +3 -0
- package/lib/threatfox-intelligence.mjs +7 -3
- package/lib/tls-intelligence.mjs +4 -4
- 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/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
- package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
- package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
- 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 -21
- 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} +72 -29
- package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
- 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/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
- package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
- package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
- package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -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/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
- 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} +24 -22
- package/packages/interchange/retained-text.mjs +8 -0
- package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
- package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
- package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
- package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
- package/packages/investigation/bulk-review-policy.mjs +1 -0
- package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
- 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/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
- package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
- 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} +70 -47
- package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
- package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
- package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -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} +8 -45
- package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
- 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} +13 -13
- 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} +110 -89
- package/third-party-notices.txt +14 -14
- 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 -305
- package/frontend/src/lib/analysis/case-record-model.js +0 -9
- package/frontend/src/lib/analysis/case-response-model.js +0 -728
- package/frontend/src/lib/analysis/case-response-packet.js +0 -583
- package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
- package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
- 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 -255
- 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 -131
- package/frontend/src/lib/analysis/visualization-models.js +0 -338
- package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
- package/lib/bounded-json-response.mjs +0 -170
- package/lib/lookup-response-contract.mjs +0 -1034
- package/lib/observation.mjs +0 -115
- /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
- /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,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { canonicalArtifactJsonV2, sha256ArtifactDigestV2, SORTED_JSON_V2, } from "../evidence/artifact-integrity.mjs";
|
|
2
|
+
import { LOOKUP_ASSET_GRAPH_SCHEMA, } from "./lookup-asset-graph.mjs";
|
|
3
|
+
import { LOOKUP_INVESTIGATION_BRIEF_SCHEMA, LOOKUP_INVESTIGATION_BRIEF_VERSION, } from "./lookup-investigation-brief.mjs";
|
|
4
|
+
import { INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA, INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION, INVESTIGATION_CAPSULE_SCHEMA, INVESTIGATION_CAPSULE_VERSION, PUBLIC_INVESTIGATION_CAPSULE_VERSION, SUPPORTED_INVESTIGATION_CAPSULE_VERSIONS, } from "../contracts/investigation-portability.mjs";
|
|
5
|
+
export { INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA, INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION, INVESTIGATION_CAPSULE_SCHEMA, INVESTIGATION_CAPSULE_VERSION, PUBLIC_INVESTIGATION_CAPSULE_VERSION, SUPPORTED_INVESTIGATION_CAPSULE_VERSIONS, };
|
|
5
6
|
function safeVersion(value) {
|
|
6
7
|
return typeof value === 'string' && /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u.test(value)
|
|
7
8
|
? value
|
|
@@ -62,10 +63,16 @@ export async function buildInvestigationCapsule(input) {
|
|
|
62
63
|
target: { value: input.brief.target, type: input.brief.targetType },
|
|
63
64
|
sourceContracts: [
|
|
64
65
|
{ id: 'lookup-evidence', schema: evidenceSchema, version: evidenceVersion, digest: evidenceDigest, embedded: false },
|
|
65
|
-
{ id: 'investigation-brief', schema:
|
|
66
|
-
{ id: 'asset-graph', schema:
|
|
66
|
+
{ id: 'investigation-brief', schema: LOOKUP_INVESTIGATION_BRIEF_SCHEMA, version: LOOKUP_INVESTIGATION_BRIEF_VERSION, digest: briefDigest, embedded: true },
|
|
67
|
+
{ id: 'asset-graph', schema: LOOKUP_ASSET_GRAPH_SCHEMA, version: input.graph.version, digest: graphDigest, embedded: true },
|
|
67
68
|
...(analystRecords && analystRecordsDigest
|
|
68
|
-
? [{
|
|
69
|
+
? [{
|
|
70
|
+
id: 'analyst-records',
|
|
71
|
+
schema: INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA,
|
|
72
|
+
version: INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION,
|
|
73
|
+
digest: analystRecordsDigest,
|
|
74
|
+
embedded: true,
|
|
75
|
+
}]
|
|
69
76
|
: []),
|
|
70
77
|
],
|
|
71
78
|
investigationBrief: input.brief,
|
|
@@ -74,7 +81,7 @@ export async function buildInvestigationCapsule(input) {
|
|
|
74
81
|
limitations: [
|
|
75
82
|
'The Lookup evidence file is linked by digest but is not embedded; keep or share that exact file separately when verification is required.',
|
|
76
83
|
'Digest verification can detect changed content but does not establish who created the capsule. No digital signature is applied.',
|
|
77
|
-
'The graph and brief are bounded projections of collected and derived evidence, not attribution, ownership, safety, availability, or maliciousness conclusions.',
|
|
84
|
+
'The graph and canonical Decision Fact brief are bounded projections of collected and derived evidence, not attribution, ownership, safety, availability, or maliciousness conclusions.',
|
|
78
85
|
analystRecords
|
|
79
86
|
? 'Analyst decisions and assertions were deliberately included; review them for sensitive or personal information before sharing.'
|
|
80
87
|
: 'Analyst decisions, assertions, notes, contacts, actions, and raw source payloads are excluded.',
|
|
@@ -95,21 +102,17 @@ export async function buildInvestigationCapsule(input) {
|
|
|
95
102
|
}
|
|
96
103
|
export async function verifyInvestigationCapsule(capsule) {
|
|
97
104
|
const current = capsule.schemaVersion === INVESTIGATION_CAPSULE_VERSION;
|
|
98
|
-
const
|
|
105
|
+
const publicVersion = capsule.schemaVersion === PUBLIC_INVESTIGATION_CAPSULE_VERSION;
|
|
99
106
|
const integrityRecord = capsule.integrity;
|
|
100
|
-
if ((!current && !
|
|
101
|
-
||
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|| Object.hasOwn(integrityRecord, 'scope')
|
|
105
|
-
|| Object.hasOwn(integrityRecord, 'digestSha256')))) {
|
|
106
|
-
return { valid: false, brief: false, graph: false, analystRecords: null, whole: current ? false : null };
|
|
107
|
+
if ((!current && !publicVersion)
|
|
108
|
+
|| integrityRecord.canonicalization !== SORTED_JSON_V2
|
|
109
|
+
|| integrityRecord.scope !== 'capsule excluding integrity') {
|
|
110
|
+
return { valid: false, brief: false, graph: false, analystRecords: null, whole: false };
|
|
107
111
|
}
|
|
108
|
-
const projectionDigest = current ? sha256ArtifactDigestV2 : sha256ArtifactDigest;
|
|
109
112
|
const [briefDigest, graphDigest, analystRecordsDigest] = await Promise.all([
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
capsule.analystRecords ?
|
|
113
|
+
sha256ArtifactDigestV2(capsule.investigationBrief),
|
|
114
|
+
sha256ArtifactDigestV2(capsule.graphSnapshot),
|
|
115
|
+
capsule.analystRecords ? sha256ArtifactDigestV2(capsule.analystRecords) : Promise.resolve(null),
|
|
113
116
|
]);
|
|
114
117
|
const brief = briefDigest === capsule.integrity.briefDigest;
|
|
115
118
|
const graph = graphDigest === capsule.integrity.graphDigest;
|
|
@@ -118,22 +121,17 @@ export async function verifyInvestigationCapsule(capsule) {
|
|
|
118
121
|
: capsule.integrity.analystRecordsDigest === null
|
|
119
122
|
? null
|
|
120
123
|
: false;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
&& integrity.digestSha256 === await sha256ArtifactDigestV2(unsigned);
|
|
128
|
-
}
|
|
129
|
-
return { valid: brief && graph && analystRecords !== false && whole !== false, brief, graph, analystRecords, whole };
|
|
124
|
+
const { integrity: _integrity, ...unsigned } = capsule;
|
|
125
|
+
const whole = integrityRecord.algorithm === 'SHA-256'
|
|
126
|
+
&& integrityRecord.canonicalization === SORTED_JSON_V2
|
|
127
|
+
&& integrityRecord.scope === 'capsule excluding integrity'
|
|
128
|
+
&& integrityRecord.digestSha256 === await sha256ArtifactDigestV2(unsigned);
|
|
129
|
+
return { valid: brief && graph && analystRecords !== false && whole, brief, graph, analystRecords, whole };
|
|
130
130
|
}
|
|
131
131
|
export function investigationCapsuleFilename(capsule) {
|
|
132
132
|
const target = capsule.target.value.toLowerCase().replace(/[^a-z0-9.-]+/gu, '-').replace(/^-|-$/gu, '').slice(0, 80) || 'lookup';
|
|
133
133
|
return `whoisleuth-investigation-capsule-${target}-${capsule.generatedAt.slice(0, 10)}.json`;
|
|
134
134
|
}
|
|
135
135
|
export function serializeInvestigationCapsule(capsule) {
|
|
136
|
-
return `${capsule
|
|
137
|
-
? canonicalArtifactJsonV2(capsule)
|
|
138
|
-
: canonicalArtifactJson(capsule)}\n`;
|
|
136
|
+
return `${canonicalArtifactJsonV2(capsule)}\n`;
|
|
139
137
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LOOKUP_ASSET_GRAPH_SCHEMA, LOOKUP_ASSET_GRAPH_VERSION, } from "../contracts/investigation-portability.mjs";
|
|
2
|
+
export { LOOKUP_ASSET_GRAPH_SCHEMA, LOOKUP_ASSET_GRAPH_VERSION };
|
|
1
3
|
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/gu;
|
|
2
4
|
const MAX_NODES = 72;
|
|
3
5
|
const MAX_EDGES = 120;
|
|
@@ -158,7 +160,7 @@ export function buildLookupAssetGraph(input) {
|
|
|
158
160
|
const target = hostname(input.target);
|
|
159
161
|
if (!target) {
|
|
160
162
|
return {
|
|
161
|
-
version:
|
|
163
|
+
version: LOOKUP_ASSET_GRAPH_VERSION,
|
|
162
164
|
targetId: '',
|
|
163
165
|
nodes: [],
|
|
164
166
|
edges: [],
|
|
@@ -705,7 +707,7 @@ export function buildLookupAssetGraph(input) {
|
|
|
705
707
|
}
|
|
706
708
|
}
|
|
707
709
|
return {
|
|
708
|
-
version:
|
|
710
|
+
version: LOOKUP_ASSET_GRAPH_VERSION,
|
|
709
711
|
targetId,
|
|
710
712
|
nodes: [...nodes.values()],
|
|
711
713
|
edges: [...edges.values()],
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import { LOOKUP_CLAIM_READINESS_VERSION, LOOKUP_CLAIM_IDS, LOOKUP_CLAIM_REQUIREMENT_IDS, } from "./lookup-claim-readiness.
|
|
2
|
-
import { SORTED_JSON_V2, sha256ArtifactDigestV2, } from "
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
export const MAX_LOOKUP_CLAIM_PASSPORT_BYTES = 64 * 1024;
|
|
6
|
-
export const MAX_LOOKUP_CLAIM_PASSPORT_REQUIREMENTS = 16;
|
|
7
|
-
export const MAX_LOOKUP_CLAIM_PASSPORT_LIMITATIONS = 16;
|
|
8
|
-
export const LOOKUP_CLAIM_PASSPORT_TARGET_TYPES = ['domain', 'ipv4', 'ipv6', 'asn'];
|
|
1
|
+
import { LOOKUP_CLAIM_READINESS_VERSION, LOOKUP_CLAIM_IDS, LOOKUP_CLAIM_REQUIREMENT_IDS, } from "./lookup-claim-readiness.mjs";
|
|
2
|
+
import { SORTED_JSON_V2, sha256ArtifactDigestV2, } from "../evidence/artifact-integrity.mjs";
|
|
3
|
+
import { LOOKUP_CLAIM_PASSPORT_SCHEMA, LOOKUP_CLAIM_PASSPORT_TARGET_TYPES, LOOKUP_CLAIM_PASSPORT_VERSION, MAX_LOOKUP_CLAIM_PASSPORT_BYTES, MAX_LOOKUP_CLAIM_PASSPORT_LIMITATIONS, MAX_LOOKUP_CLAIM_PASSPORT_REQUIREMENTS, } from "../contracts/investigation-portability.mjs";
|
|
4
|
+
export { LOOKUP_CLAIM_PASSPORT_SCHEMA, LOOKUP_CLAIM_PASSPORT_TARGET_TYPES, LOOKUP_CLAIM_PASSPORT_VERSION, MAX_LOOKUP_CLAIM_PASSPORT_BYTES, MAX_LOOKUP_CLAIM_PASSPORT_LIMITATIONS, MAX_LOOKUP_CLAIM_PASSPORT_REQUIREMENTS, };
|
|
9
5
|
const SOURCE_STATES = new Set([
|
|
10
6
|
'complete',
|
|
11
7
|
'not_found',
|
|
@@ -118,10 +114,10 @@ function requirementId(value) {
|
|
|
118
114
|
}
|
|
119
115
|
export async function buildLookupClaimPassport(input) {
|
|
120
116
|
if (input.readiness.version !== LOOKUP_CLAIM_READINESS_VERSION) {
|
|
121
|
-
throw new TypeError('The
|
|
117
|
+
throw new TypeError('The Evidence Readiness contract is not supported for passport export.');
|
|
122
118
|
}
|
|
123
119
|
if (input.readiness.entries.length > LOOKUP_CLAIM_IDS.length) {
|
|
124
|
-
throw new TypeError('The
|
|
120
|
+
throw new TypeError('The Evidence Readiness contract contains too many entries for passport export.');
|
|
125
121
|
}
|
|
126
122
|
const rawClaimId = exactText(input.claimId, 80);
|
|
127
123
|
if (!CLAIM_IDS.has(rawClaimId))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { LOOKUP_CLAIM_READINESS_VERSION } from "../contracts/investigation-portability.mjs";
|
|
2
|
+
export { LOOKUP_CLAIM_READINESS_VERSION };
|
|
2
3
|
export const LOOKUP_CLAIM_IDS = [
|
|
3
4
|
'registration-state',
|
|
4
5
|
'current-web-observation',
|
|
@@ -89,6 +90,7 @@ function requirement(id, coverage, override) {
|
|
|
89
90
|
const definition = LOOKUP_CLAIM_REQUIREMENTS[id];
|
|
90
91
|
const evidence = definition.evidenceId ? coverage.get(definition.evidenceId) : undefined;
|
|
91
92
|
const state = override?.state ?? evidence?.state ?? 'unknown';
|
|
93
|
+
const coverageState = definition.evidenceId ? evidence?.state ?? 'unknown' : null;
|
|
92
94
|
const limitations = override?.limitation
|
|
93
95
|
? [override.limitation]
|
|
94
96
|
: evidence?.limitations.length
|
|
@@ -96,7 +98,12 @@ function requirement(id, coverage, override) {
|
|
|
96
98
|
: state === 'complete'
|
|
97
99
|
? []
|
|
98
100
|
: [evidence?.statusLabel || 'Not supplied'];
|
|
99
|
-
return Object.freeze({
|
|
101
|
+
return Object.freeze({
|
|
102
|
+
...definition,
|
|
103
|
+
coverageState,
|
|
104
|
+
state,
|
|
105
|
+
limitations: Object.freeze(limitations),
|
|
106
|
+
});
|
|
100
107
|
}
|
|
101
108
|
function readinessEntry(input) {
|
|
102
109
|
const evaluated = evaluateRequirements(input.requirements);
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { DECISION_FACT_PRESENTATION_LABELS, projectDecisionFacts, } from "../evidence/decision-fact.mjs";
|
|
2
|
+
import { PUBLIC_LOOKUP_INVESTIGATION_BRIEF_VERSION, LOOKUP_INVESTIGATION_BRIEF_SCHEMA, LOOKUP_INVESTIGATION_BRIEF_VERSION, MAX_LOOKUP_INVESTIGATION_BRIEF_BYTES, SUPPORTED_LOOKUP_INVESTIGATION_BRIEF_VERSIONS, } from "../contracts/investigation-portability.mjs";
|
|
3
|
+
export { PUBLIC_LOOKUP_INVESTIGATION_BRIEF_VERSION, LOOKUP_INVESTIGATION_BRIEF_SCHEMA, LOOKUP_INVESTIGATION_BRIEF_VERSION, MAX_LOOKUP_INVESTIGATION_BRIEF_BYTES, SUPPORTED_LOOKUP_INVESTIGATION_BRIEF_VERSIONS, };
|
|
4
|
+
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/gu;
|
|
5
|
+
const MAX_LIMITATIONS = 20;
|
|
6
|
+
function text(value, maximum = 320) {
|
|
7
|
+
return String(value ?? '')
|
|
8
|
+
.replace(CONTROL_CHARACTERS, ' ')
|
|
9
|
+
.replace(/\s+/gu, ' ')
|
|
10
|
+
.trim()
|
|
11
|
+
.slice(0, maximum);
|
|
12
|
+
}
|
|
13
|
+
function timestamp(value) {
|
|
14
|
+
if (typeof value !== 'string')
|
|
15
|
+
return null;
|
|
16
|
+
const parsed = Date.parse(value);
|
|
17
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
|
|
18
|
+
}
|
|
19
|
+
function uniqueText(values, maximum = MAX_LIMITATIONS) {
|
|
20
|
+
const output = [];
|
|
21
|
+
const seen = new Set();
|
|
22
|
+
for (const value of values) {
|
|
23
|
+
const normalized = text(value);
|
|
24
|
+
if (!normalized || seen.has(normalized))
|
|
25
|
+
continue;
|
|
26
|
+
output.push(normalized);
|
|
27
|
+
seen.add(normalized);
|
|
28
|
+
if (output.length >= maximum)
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
return output;
|
|
32
|
+
}
|
|
33
|
+
function taskQuestion(support) {
|
|
34
|
+
return support.guidance.questions[0]
|
|
35
|
+
?? 'What does the collected evidence establish, and what remains uncertain?';
|
|
36
|
+
}
|
|
37
|
+
export function buildLookupInvestigationBrief(input) {
|
|
38
|
+
const generatedAt = timestamp(input.generatedAt) ?? new Date().toISOString();
|
|
39
|
+
const decisionFacts = projectDecisionFacts(input.decisionFacts);
|
|
40
|
+
const limitedSources = input.quality.entries
|
|
41
|
+
.filter((entry) => entry.state !== 'complete')
|
|
42
|
+
.map((entry) => `${entry.label}: ${entry.statusLabel}`);
|
|
43
|
+
const graphKinds = uniqueText(input.graph.edges.map((edge) => edge.label), 12);
|
|
44
|
+
const brief = Object.freeze({
|
|
45
|
+
schema: LOOKUP_INVESTIGATION_BRIEF_SCHEMA,
|
|
46
|
+
schemaVersion: LOOKUP_INVESTIGATION_BRIEF_VERSION,
|
|
47
|
+
generatedAt,
|
|
48
|
+
target: text(input.target, 253) || 'Unknown target',
|
|
49
|
+
targetType: text(input.targetType, 40) || 'unknown',
|
|
50
|
+
task: input.task,
|
|
51
|
+
taskLabel: input.decisionSupport.guidance.label,
|
|
52
|
+
question: taskQuestion(input.decisionSupport),
|
|
53
|
+
summary: input.decisionSupport.guidance.summary,
|
|
54
|
+
observation: Object.freeze({
|
|
55
|
+
observedAt: input.quality.observedAt,
|
|
56
|
+
evidenceAgeDays: input.quality.ageDays,
|
|
57
|
+
completeSources: input.quality.completeCount,
|
|
58
|
+
limitedSources: input.quality.limitedCount,
|
|
59
|
+
freshnessPolicy: input.quality.freshnessPolicy,
|
|
60
|
+
}),
|
|
61
|
+
decisionFacts,
|
|
62
|
+
relationships: Object.freeze({
|
|
63
|
+
nodes: input.graph.nodes.length,
|
|
64
|
+
edges: input.graph.edges.length,
|
|
65
|
+
truncated: input.graph.truncated,
|
|
66
|
+
kinds: Object.freeze(graphKinds),
|
|
67
|
+
}),
|
|
68
|
+
limitations: Object.freeze(uniqueText([
|
|
69
|
+
...limitedSources,
|
|
70
|
+
...input.graph.limitations,
|
|
71
|
+
'This brief is a deterministic organisation of collected and derived evidence, not an attribution, ownership, safety, availability, or maliciousness conclusion.',
|
|
72
|
+
'Analyst assertions, hypotheses, and decisions must remain separate from observed evidence.',
|
|
73
|
+
])),
|
|
74
|
+
});
|
|
75
|
+
if (new TextEncoder().encode(JSON.stringify(brief)).byteLength > MAX_LOOKUP_INVESTIGATION_BRIEF_BYTES) {
|
|
76
|
+
throw new RangeError('Lookup investigation brief exceeded its byte limit.');
|
|
77
|
+
}
|
|
78
|
+
return brief;
|
|
79
|
+
}
|
|
80
|
+
function markdown(value) {
|
|
81
|
+
return text(value, 2_000)
|
|
82
|
+
.replace(/&/gu, '&')
|
|
83
|
+
.replace(/</gu, '<')
|
|
84
|
+
.replace(/>/gu, '>')
|
|
85
|
+
.replace(/([\\`*_{}[\]()#+\-.!|=~])/gu, '\\$1');
|
|
86
|
+
}
|
|
87
|
+
function countedValues(collection) {
|
|
88
|
+
const values = collection.items.length ? collection.items.map(markdown).join(', ') : 'none';
|
|
89
|
+
return `${collection.displayed} of ${collection.total} displayed; ${collection.omitted} omitted. ${values}`;
|
|
90
|
+
}
|
|
91
|
+
function decisionFactLines(fact) {
|
|
92
|
+
const lines = [
|
|
93
|
+
`### ${markdown(fact.question)}`,
|
|
94
|
+
'',
|
|
95
|
+
`- **Fact ID:** ${markdown(fact.id)}`,
|
|
96
|
+
`- **Conclusion:** ${markdown(fact.conclusion)}`,
|
|
97
|
+
`- **Importance:** ${markdown(DECISION_FACT_PRESENTATION_LABELS.importance[fact.importance])}`,
|
|
98
|
+
`- **Evidence state:** ${markdown(DECISION_FACT_PRESENTATION_LABELS.evidenceState[fact.evidenceState])} (${markdown(fact.evidenceState)})`,
|
|
99
|
+
`- **Completeness:** ${markdown(DECISION_FACT_PRESENTATION_LABELS.completeness[fact.completeness])} (${markdown(fact.completeness)})`,
|
|
100
|
+
`- **Freshness:** ${markdown(DECISION_FACT_PRESENTATION_LABELS.freshness[fact.freshness])} (${markdown(fact.freshness)})`,
|
|
101
|
+
`- **Consistency:** ${markdown(DECISION_FACT_PRESENTATION_LABELS.consistency[fact.consistency])} (${markdown(fact.consistency)})`,
|
|
102
|
+
`- **Dependencies:** ${countedValues(fact.dependencies)}`,
|
|
103
|
+
`- **Source references:** ${countedValues(fact.sourceReferences)}`,
|
|
104
|
+
`- **Attributed sources:** ${fact.sources.displayed} of ${fact.sources.total} displayed; ${fact.sources.omitted} omitted.`,
|
|
105
|
+
];
|
|
106
|
+
if (fact.sources.items.length) {
|
|
107
|
+
for (const source of fact.sources.items) {
|
|
108
|
+
lines.push(` - **${markdown(source.label)}** (${markdown(source.id)}): ${markdown(DECISION_FACT_PRESENTATION_LABELS.provenance[source.provenance])}; ${markdown(DECISION_FACT_PRESENTATION_LABELS.evidenceState[source.evidenceState])}; observed ${markdown(source.observedAt ?? 'not reported')}.`, ` - References: ${countedValues(source.references)}`, ` - Limitations: ${countedValues(source.limitations)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
lines.push(' - No attributed source was retained for this fact.');
|
|
113
|
+
}
|
|
114
|
+
lines.push(`- **Contradictions:** ${countedValues(fact.contradictions)}`, `- **Limitations:** ${countedValues(fact.limitations)}`, `- **Safe next actions:** ${fact.safeNextActions.displayed} of ${fact.safeNextActions.total} displayed; ${fact.safeNextActions.omitted} omitted.`);
|
|
115
|
+
if (fact.safeNextActions.items.length) {
|
|
116
|
+
for (const action of fact.safeNextActions.items) {
|
|
117
|
+
lines.push(` - **${markdown(action.label)}** (${markdown(action.id)}; ${markdown(action.importance)}): ${markdown(action.reason)} Expected outcome: ${markdown(action.expectedOutcome)} Review destination: ${markdown(action.href)}.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
lines.push(' - No fact-specific action was retained. Review the attributed evidence and limitations.');
|
|
122
|
+
}
|
|
123
|
+
lines.push('');
|
|
124
|
+
return lines;
|
|
125
|
+
}
|
|
126
|
+
export function formatLookupInvestigationBriefMarkdown(brief) {
|
|
127
|
+
const lines = [
|
|
128
|
+
`# Investigation brief: ${markdown(brief.target)}`,
|
|
129
|
+
'',
|
|
130
|
+
`Generated ${markdown(brief.generatedAt)} from a ${markdown(brief.targetType)} Lookup.`,
|
|
131
|
+
'',
|
|
132
|
+
'## Decision context',
|
|
133
|
+
'',
|
|
134
|
+
`- **Task:** ${markdown(brief.taskLabel)}`,
|
|
135
|
+
`- **Question:** ${markdown(brief.question)}`,
|
|
136
|
+
`- **Summary:** ${markdown(brief.summary)}`,
|
|
137
|
+
`- **Evidence observed:** ${markdown(brief.observation.observedAt ?? 'not reported')}`,
|
|
138
|
+
`- **Source quality:** ${brief.observation.completeSources} complete; ${brief.observation.limitedSources} limited`,
|
|
139
|
+
`- **Freshness policy:** v${brief.observation.freshnessPolicy.version} ${markdown(brief.observation.freshnessPolicy.id)} for ${markdown(brief.observation.freshnessPolicy.task)}; registration ${brief.observation.freshnessPolicy.thresholdsDays.registration}d, network ${brief.observation.freshnessPolicy.thresholdsDays.network}d, web ${brief.observation.freshnessPolicy.thresholdsDays.web}d`,
|
|
140
|
+
'',
|
|
141
|
+
'## Canonical Decision Facts',
|
|
142
|
+
'',
|
|
143
|
+
`Displaying ${brief.decisionFacts.displayed} of ${brief.decisionFacts.total} canonical Decision Facts; ${brief.decisionFacts.omitted} omitted by the bounded projection. ${brief.decisionFacts.contradictory} contradictory and ${brief.decisionFacts.unresolved} unresolved across the complete canonical set.`,
|
|
144
|
+
'',
|
|
145
|
+
...(brief.decisionFacts.facts.length
|
|
146
|
+
? brief.decisionFacts.facts.flatMap(decisionFactLines)
|
|
147
|
+
: ['No canonical Decision Fact was available. Review source quality before drawing a conclusion.', '']),
|
|
148
|
+
'',
|
|
149
|
+
'## Observed relationship map',
|
|
150
|
+
'',
|
|
151
|
+
`- ${brief.relationships.nodes} nodes and ${brief.relationships.edges} edges${brief.relationships.truncated ? '; bounded output was truncated' : ''}.`,
|
|
152
|
+
`- Relationship types: ${brief.relationships.kinds.map(markdown).join(', ') || 'none observed'}.`,
|
|
153
|
+
'',
|
|
154
|
+
'## Limitations',
|
|
155
|
+
'',
|
|
156
|
+
...brief.limitations.map((limitation) => `- ${markdown(limitation)}`),
|
|
157
|
+
'',
|
|
158
|
+
];
|
|
159
|
+
const output = lines.join('\n');
|
|
160
|
+
if (new TextEncoder().encode(output).byteLength > MAX_LOOKUP_INVESTIGATION_BRIEF_BYTES) {
|
|
161
|
+
throw new RangeError('Lookup investigation brief Markdown exceeded its byte limit.');
|
|
162
|
+
}
|
|
163
|
+
return output;
|
|
164
|
+
}
|
|
165
|
+
export function lookupInvestigationBriefFilename(brief) {
|
|
166
|
+
const target = brief.target
|
|
167
|
+
.toLowerCase()
|
|
168
|
+
.replace(/[^a-z0-9.-]+/gu, '-')
|
|
169
|
+
.replace(/^-+|-+$/gu, '')
|
|
170
|
+
.slice(0, 80) || 'lookup';
|
|
171
|
+
return `whoisleuth-investigation-brief-${target}-${brief.generatedAt.slice(0, 10)}.md`;
|
|
172
|
+
}
|