@slicedearth/whoisleuth-cli 1.47.4 → 2.0.1
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/project-metadata.mjs +1 -1
- 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 +13 -5
- 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} +71 -48
- 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
package/cli/risk-calibration.mjs
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { decodeBoundedUtf8 } from "../lib/bounded-file.mjs";
|
|
2
3
|
import { isValidAsciiDomainName } from "../lib/hostname.mjs";
|
|
4
|
+
import { scanBoundedJson } from "../lib/bounded-json.mjs";
|
|
5
|
+
import { canonicalRegistrableDomain } from "../lib/registrable-domain.mjs";
|
|
3
6
|
import { ANALYST_REVIEW_REASON_VALUES, analystInteroperabilityTags } from "../lib/analyst-taxonomy.mjs";
|
|
4
|
-
import {
|
|
7
|
+
import { buildRiskCalibrationSummaryReport, parseRiskCalibrationSummaryReport, RISK_CALIBRATION_SUMMARY_INTERPRETATION, RISK_CALIBRATION_SUMMARY_THRESHOLDS, } from "../lib/risk-calibration-summary.mjs";
|
|
8
|
+
import { MAX_RISK_CALIBRATION_ACTIVITY_LENGTH, MAX_RISK_CALIBRATION_AVAILABILITY_LENGTH, MAX_RISK_CALIBRATION_DISPOSITION_LENGTH, MAX_RISK_CALIBRATION_DOMAIN_AGE_DAYS, MAX_RISK_CALIBRATION_DOMAIN_LENGTH, MAX_RISK_CALIBRATION_FINDING_CATEGORY_LENGTH, MAX_RISK_CALIBRATION_FINDINGS_PER_PROVIDER, MAX_RISK_CALIBRATION_INPUT_BYTES, MAX_RISK_CALIBRATION_JSON_CONTAINER_ITEMS, MAX_RISK_CALIBRATION_JSON_DEPTH, MAX_RISK_CALIBRATION_JSON_KEYS, MAX_RISK_CALIBRATION_JSON_VALUES, MAX_RISK_CALIBRATION_MUTATIONS, MAX_RISK_CALIBRATION_PROVIDER_ID_LENGTH, MAX_RISK_CALIBRATION_PROVIDER_STATE_LENGTH, MAX_RISK_CALIBRATION_PROVIDERS, MAX_RISK_CALIBRATION_RECORD_ID_LENGTH, MAX_RISK_CALIBRATION_RECORDS, MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH, MAX_RISK_CALIBRATION_SCAN_DEPTH_LENGTH, MAX_RISK_CALIBRATION_STRING_LENGTH, MAX_RISK_CALIBRATION_TIMESTAMP_LENGTH, RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_DATASET_VERSION, RISK_CALIBRATION_REPORT_SCHEMA, RISK_CALIBRATION_REPORT_VERSION, serializeRiskCalibrationSnapshot, snapshotRiskCalibrationReportForSerialization, SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS, } from "../packages/contracts/risk-calibration.mjs";
|
|
5
9
|
import { CliUsageError } from "./arguments.mjs";
|
|
6
10
|
import { RISK_MUTATION_TYPES } from "../lib/risk-scoring.mjs";
|
|
7
|
-
|
|
8
|
-
export
|
|
9
|
-
export const SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS = Object.freeze([1, RISK_CALIBRATION_DATASET_VERSION]);
|
|
10
|
-
export const RISK_CALIBRATION_REPORT_SCHEMA = RISK_CALIBRATION_SUMMARY_SCHEMA;
|
|
11
|
-
export const RISK_CALIBRATION_REPORT_VERSION = RISK_CALIBRATION_SUMMARY_VERSION;
|
|
12
|
-
export const MAX_RISK_CALIBRATION_INPUT_BYTES = 2 * 1024 * 1024;
|
|
13
|
-
export const MAX_RISK_CALIBRATION_RECORDS = 500;
|
|
14
|
-
export const MAX_RISK_CALIBRATION_STRING_LENGTH = 256;
|
|
11
|
+
import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_ENVELOPE_VERSION, THREAT_INTELLIGENCE_SCHEMA, } from "../lib/threat-intelligence-types.mjs";
|
|
12
|
+
export { MAX_RISK_CALIBRATION_INPUT_BYTES, MAX_RISK_CALIBRATION_RECORDS, MAX_RISK_CALIBRATION_STRING_LENGTH, RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_DATASET_VERSION, RISK_CALIBRATION_REPORT_SCHEMA, RISK_CALIBRATION_REPORT_VERSION, SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS, } from "../packages/contracts/risk-calibration.mjs";
|
|
15
13
|
export const RISK_CALIBRATION_THRESHOLDS = RISK_CALIBRATION_SUMMARY_THRESHOLDS;
|
|
16
|
-
const MAX_MUTATIONS = 30;
|
|
17
|
-
const MAX_PROVIDERS = 10;
|
|
18
|
-
const MAX_FINDINGS_PER_PROVIDER = 100;
|
|
19
|
-
const MAX_TIMESTAMP_LENGTH = 64;
|
|
20
14
|
const CONTROL_RE = /[\x00-\x1f\x7f]/;
|
|
21
15
|
const DISPOSITIONS = new Set([
|
|
22
16
|
'unreviewed', 'suspicious', 'confirmed_abuse', 'false_positive', 'expected', 'closed_no_action',
|
|
@@ -49,7 +43,7 @@ function boundedString(value, field, maximum = MAX_RISK_CALIBRATION_STRING_LENGT
|
|
|
49
43
|
function optionalTimestamp(value, field) {
|
|
50
44
|
if (value === null || value === undefined)
|
|
51
45
|
return undefined;
|
|
52
|
-
return boundedString(value, field,
|
|
46
|
+
return boundedString(value, field, MAX_RISK_CALIBRATION_TIMESTAMP_LENGTH);
|
|
53
47
|
}
|
|
54
48
|
function projectThreatIntelligence(value, field) {
|
|
55
49
|
if (value === null || value === undefined)
|
|
@@ -57,8 +51,8 @@ function projectThreatIntelligence(value, field) {
|
|
|
57
51
|
const envelope = object(value, field);
|
|
58
52
|
if (!Array.isArray(envelope.providers))
|
|
59
53
|
throw new CliUsageError(`${field}.providers must be an array.`);
|
|
60
|
-
if (envelope.providers.length >
|
|
61
|
-
throw new CliUsageError(`${field}.providers exceeds the ${
|
|
54
|
+
if (envelope.providers.length > MAX_RISK_CALIBRATION_PROVIDERS)
|
|
55
|
+
throw new CliUsageError(`${field}.providers exceeds the ${MAX_RISK_CALIBRATION_PROVIDERS}-provider limit.`);
|
|
62
56
|
return {
|
|
63
57
|
providers: envelope.providers.map((item, providerIndex) => {
|
|
64
58
|
const prefix = `${field}.providers[${providerIndex}]`;
|
|
@@ -69,15 +63,15 @@ function projectThreatIntelligence(value, field) {
|
|
|
69
63
|
: object(provider.observation, `${prefix}.observation`);
|
|
70
64
|
if (!Array.isArray(provider.findings))
|
|
71
65
|
throw new CliUsageError(`${prefix}.findings must be an array.`);
|
|
72
|
-
if (provider.findings.length >
|
|
73
|
-
throw new CliUsageError(`${prefix}.findings exceeds the ${
|
|
66
|
+
if (provider.findings.length > MAX_RISK_CALIBRATION_FINDINGS_PER_PROVIDER) {
|
|
67
|
+
throw new CliUsageError(`${prefix}.findings exceeds the ${MAX_RISK_CALIBRATION_FINDINGS_PER_PROVIDER}-finding limit.`);
|
|
74
68
|
}
|
|
75
69
|
const observedAt = observation
|
|
76
70
|
? optionalTimestamp(observation.observedAt, `${prefix}.observation.observedAt`)
|
|
77
71
|
: undefined;
|
|
78
72
|
return {
|
|
79
|
-
provider: { id: boundedString(identity.id, `${prefix}.provider.id`,
|
|
80
|
-
state: boundedString(provider.state, `${prefix}.state`,
|
|
73
|
+
provider: { id: boundedString(identity.id, `${prefix}.provider.id`, MAX_RISK_CALIBRATION_PROVIDER_ID_LENGTH) },
|
|
74
|
+
state: boundedString(provider.state, `${prefix}.state`, MAX_RISK_CALIBRATION_PROVIDER_STATE_LENGTH),
|
|
81
75
|
...(observation ? { observation: observedAt ? { observedAt } : {} } : {}),
|
|
82
76
|
findings: provider.findings.map((findingValue, findingIndex) => {
|
|
83
77
|
const findingPrefix = `${prefix}.findings[${findingIndex}]`;
|
|
@@ -85,7 +79,7 @@ function projectThreatIntelligence(value, field) {
|
|
|
85
79
|
const firstObservedAt = optionalTimestamp(finding.firstObservedAt, `${findingPrefix}.firstObservedAt`);
|
|
86
80
|
const lastObservedAt = optionalTimestamp(finding.lastObservedAt, `${findingPrefix}.lastObservedAt`);
|
|
87
81
|
return {
|
|
88
|
-
category: boundedString(finding.category, `${findingPrefix}.category`,
|
|
82
|
+
category: boundedString(finding.category, `${findingPrefix}.category`, MAX_RISK_CALIBRATION_FINDING_CATEGORY_LENGTH),
|
|
89
83
|
...(firstObservedAt ? { firstObservedAt } : {}),
|
|
90
84
|
...(lastObservedAt ? { lastObservedAt } : {}),
|
|
91
85
|
};
|
|
@@ -96,10 +90,12 @@ function projectThreatIntelligence(value, field) {
|
|
|
96
90
|
}
|
|
97
91
|
function projectEvidence(value, field) {
|
|
98
92
|
const source = object(value, field);
|
|
99
|
-
const availability = boundedString(source.availability ?? source.state, `${field}.availability`,
|
|
93
|
+
const availability = boundedString(source.availability ?? source.state, `${field}.availability`, MAX_RISK_CALIBRATION_AVAILABILITY_LENGTH);
|
|
100
94
|
if (!AVAILABILITY_STATES.has(availability))
|
|
101
95
|
throw new CliUsageError(`${field}.availability is unsupported.`);
|
|
102
|
-
const result = {
|
|
96
|
+
const result = {
|
|
97
|
+
availability: availability,
|
|
98
|
+
};
|
|
103
99
|
for (const name of BOOLEAN_FIELDS) {
|
|
104
100
|
const candidate = source[name];
|
|
105
101
|
if (candidate === null || candidate === undefined)
|
|
@@ -109,13 +105,13 @@ function projectEvidence(value, field) {
|
|
|
109
105
|
result[name] = candidate;
|
|
110
106
|
}
|
|
111
107
|
if (source.activityStatus !== null && source.activityStatus !== undefined) {
|
|
112
|
-
const activity = boundedString(source.activityStatus, `${field}.activityStatus`,
|
|
108
|
+
const activity = boundedString(source.activityStatus, `${field}.activityStatus`, MAX_RISK_CALIBRATION_ACTIVITY_LENGTH);
|
|
113
109
|
if (!ACTIVITY_STATES.has(activity))
|
|
114
110
|
throw new CliUsageError(`${field}.activityStatus is unsupported.`);
|
|
115
111
|
result.activityStatus = activity;
|
|
116
112
|
}
|
|
117
113
|
if (source.scanDepth !== null && source.scanDepth !== undefined) {
|
|
118
|
-
const depth = boundedString(source.scanDepth, `${field}.scanDepth`,
|
|
114
|
+
const depth = boundedString(source.scanDepth, `${field}.scanDepth`, MAX_RISK_CALIBRATION_SCAN_DEPTH_LENGTH);
|
|
119
115
|
if (depth !== 'fast' && depth !== 'deep')
|
|
120
116
|
throw new CliUsageError(`${field}.scanDepth must be fast or deep.`);
|
|
121
117
|
Object.assign(result, { scanDepth: depth });
|
|
@@ -130,7 +126,7 @@ function projectEvidence(value, field) {
|
|
|
130
126
|
}
|
|
131
127
|
if (source.domainAgeDays !== null && source.domainAgeDays !== undefined) {
|
|
132
128
|
if (typeof source.domainAgeDays !== 'number' || !Number.isFinite(source.domainAgeDays)
|
|
133
|
-
|| source.domainAgeDays < 0 || source.domainAgeDays >
|
|
129
|
+
|| source.domainAgeDays < 0 || source.domainAgeDays > MAX_RISK_CALIBRATION_DOMAIN_AGE_DAYS) {
|
|
134
130
|
throw new CliUsageError(`${field}.domainAgeDays must be a finite number from 0 to 100000.`);
|
|
135
131
|
}
|
|
136
132
|
result.domainAgeDays = source.domainAgeDays;
|
|
@@ -138,10 +134,10 @@ function projectEvidence(value, field) {
|
|
|
138
134
|
if (source.mutationTypes !== null && source.mutationTypes !== undefined) {
|
|
139
135
|
if (!Array.isArray(source.mutationTypes))
|
|
140
136
|
throw new CliUsageError(`${field}.mutationTypes must be an array.`);
|
|
141
|
-
if (source.mutationTypes.length >
|
|
142
|
-
throw new CliUsageError(`${field}.mutationTypes exceeds the ${
|
|
137
|
+
if (source.mutationTypes.length > MAX_RISK_CALIBRATION_MUTATIONS)
|
|
138
|
+
throw new CliUsageError(`${field}.mutationTypes exceeds the ${MAX_RISK_CALIBRATION_MUTATIONS}-item limit.`);
|
|
143
139
|
result.mutationTypes = [...new Set(source.mutationTypes.map((item, index) => {
|
|
144
|
-
const mutation = boundedString(item, `${field}.mutationTypes[${index}]`,
|
|
140
|
+
const mutation = boundedString(item, `${field}.mutationTypes[${index}]`, MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH);
|
|
145
141
|
if (!MUTATION_TYPES.has(mutation))
|
|
146
142
|
throw new CliUsageError(`${field}.mutationTypes[${index}] is unsupported.`);
|
|
147
143
|
return mutation;
|
|
@@ -164,7 +160,12 @@ export async function readRiskCalibrationInputBounded(stream, limit = MAX_RISK_C
|
|
|
164
160
|
throw new CliUsageError(`Risk calibration input is limited to ${limit} bytes.`);
|
|
165
161
|
chunks.push(buffer);
|
|
166
162
|
}
|
|
167
|
-
|
|
163
|
+
try {
|
|
164
|
+
return decodeBoundedUtf8(Buffer.concat(chunks), 'Risk calibration input');
|
|
165
|
+
}
|
|
166
|
+
catch (cause) {
|
|
167
|
+
throw new CliUsageError(cause instanceof Error ? cause.message : 'Risk calibration input must contain valid UTF-8 text.');
|
|
168
|
+
}
|
|
168
169
|
}
|
|
169
170
|
export function parseRiskCalibrationDataset(text) {
|
|
170
171
|
if (typeof text !== 'string')
|
|
@@ -172,12 +173,19 @@ export function parseRiskCalibrationDataset(text) {
|
|
|
172
173
|
if (Buffer.byteLength(text, 'utf8') > MAX_RISK_CALIBRATION_INPUT_BYTES) {
|
|
173
174
|
throw new CliUsageError(`Risk calibration input is limited to ${MAX_RISK_CALIBRATION_INPUT_BYTES} bytes.`);
|
|
174
175
|
}
|
|
176
|
+
const normalized = text.replace(/^\uFEFF/u, '');
|
|
175
177
|
let parsed;
|
|
176
178
|
try {
|
|
177
|
-
|
|
179
|
+
scanBoundedJson(normalized, {
|
|
180
|
+
maximumDepth: MAX_RISK_CALIBRATION_JSON_DEPTH,
|
|
181
|
+
maximumKeys: MAX_RISK_CALIBRATION_JSON_KEYS,
|
|
182
|
+
maximumValues: MAX_RISK_CALIBRATION_JSON_VALUES,
|
|
183
|
+
maximumContainerItems: MAX_RISK_CALIBRATION_JSON_CONTAINER_ITEMS,
|
|
184
|
+
});
|
|
185
|
+
parsed = JSON.parse(normalized);
|
|
178
186
|
}
|
|
179
187
|
catch {
|
|
180
|
-
throw new CliUsageError('Risk calibration input must be valid JSON.');
|
|
188
|
+
throw new CliUsageError('Risk calibration input must be valid bounded JSON without duplicate keys.');
|
|
181
189
|
}
|
|
182
190
|
const document = object(parsed, 'Risk calibration input');
|
|
183
191
|
const documentVersion = typeof document.version === 'number' && Number.isInteger(document.version)
|
|
@@ -198,19 +206,19 @@ export function parseRiskCalibrationDataset(text) {
|
|
|
198
206
|
const records = document.records.map((value, index) => {
|
|
199
207
|
const prefix = `records[${index}]`;
|
|
200
208
|
const record = object(value, prefix);
|
|
201
|
-
const id = boundedString(record.id, `${prefix}.id`,
|
|
209
|
+
const id = boundedString(record.id, `${prefix}.id`, MAX_RISK_CALIBRATION_RECORD_ID_LENGTH);
|
|
202
210
|
if (ids.has(id))
|
|
203
211
|
throw new CliUsageError(`${prefix}.id must be unique.`);
|
|
204
212
|
ids.add(id);
|
|
205
|
-
const domain = boundedString(record.domain, `${prefix}.domain`,
|
|
213
|
+
const domain = boundedString(record.domain, `${prefix}.domain`, MAX_RISK_CALIBRATION_DOMAIN_LENGTH).toLowerCase().replace(/\.$/, '');
|
|
206
214
|
if (!isValidAsciiDomainName(domain, { requireDot: true }))
|
|
207
215
|
throw new CliUsageError(`${prefix}.domain must be a valid ASCII DNS hostname, not an IP address.`);
|
|
208
|
-
const analystDisposition = boundedString(record.analystDisposition, `${prefix}.analystDisposition`,
|
|
216
|
+
const analystDisposition = boundedString(record.analystDisposition, `${prefix}.analystDisposition`, MAX_RISK_CALIBRATION_DISPOSITION_LENGTH);
|
|
209
217
|
if (!DISPOSITIONS.has(analystDisposition))
|
|
210
218
|
throw new CliUsageError(`${prefix}.analystDisposition is unsupported.`);
|
|
211
219
|
let reviewReasonCode;
|
|
212
220
|
if (record.reviewReasonCode !== null && record.reviewReasonCode !== undefined) {
|
|
213
|
-
reviewReasonCode = boundedString(record.reviewReasonCode, `${prefix}.reviewReasonCode`,
|
|
221
|
+
reviewReasonCode = boundedString(record.reviewReasonCode, `${prefix}.reviewReasonCode`, MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH);
|
|
214
222
|
if (!REVIEW_REASON_CODES.has(reviewReasonCode))
|
|
215
223
|
throw new CliUsageError(`${prefix}.reviewReasonCode is unsupported.`);
|
|
216
224
|
}
|
|
@@ -297,9 +305,32 @@ function scoreBand(score) {
|
|
|
297
305
|
return '40_69';
|
|
298
306
|
return '0_39';
|
|
299
307
|
}
|
|
308
|
+
function scoringEvidence(record) {
|
|
309
|
+
const threatIntelligence = record.evidence.threatIntelligence;
|
|
310
|
+
const scoringDomain = canonicalRegistrableDomain(record.domain);
|
|
311
|
+
return {
|
|
312
|
+
...record.evidence,
|
|
313
|
+
domain: scoringDomain ?? record.domain,
|
|
314
|
+
...(threatIntelligence && scoringDomain ? {
|
|
315
|
+
threatIntelligence: {
|
|
316
|
+
version: THREAT_INTELLIGENCE_ENVELOPE_VERSION,
|
|
317
|
+
providers: threatIntelligence.providers.map((provider) => ({
|
|
318
|
+
...provider,
|
|
319
|
+
schema: THREAT_INTELLIGENCE_SCHEMA,
|
|
320
|
+
version: THREAT_INTELLIGENCE_CONTRACT_VERSION,
|
|
321
|
+
target: {
|
|
322
|
+
type: 'domain',
|
|
323
|
+
value: scoringDomain,
|
|
324
|
+
exposure: 'registrable_domain',
|
|
325
|
+
},
|
|
326
|
+
})),
|
|
327
|
+
},
|
|
328
|
+
} : {}),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
300
331
|
export function buildRiskCalibrationReport(dataset, explainRiskScore, options) {
|
|
301
332
|
const records = dataset.records.map((record) => {
|
|
302
|
-
const explained = explainRiskScore(record
|
|
333
|
+
const explained = explainRiskScore(scoringEvidence(record));
|
|
303
334
|
const classification = metricClass(record.analystDisposition);
|
|
304
335
|
const includedInMetrics = classification !== 'excluded' && explained !== null;
|
|
305
336
|
return {
|
|
@@ -363,7 +394,7 @@ export function buildRiskCalibrationReport(dataset, explainRiskScore, options) {
|
|
|
363
394
|
const current = records[index];
|
|
364
395
|
if (!source || !current)
|
|
365
396
|
continue;
|
|
366
|
-
const previous = options.explainPreviousRiskScore(source
|
|
397
|
+
const previous = options.explainPreviousRiskScore(scoringEvidence(source));
|
|
367
398
|
if (previous?.score !== current.score)
|
|
368
399
|
scoresChanged += 1;
|
|
369
400
|
if (scoreBand(previous?.score ?? null) !== current.band)
|
|
@@ -402,3 +433,19 @@ export function buildRiskCalibrationReport(dataset, explainRiskScore, options) {
|
|
|
402
433
|
},
|
|
403
434
|
};
|
|
404
435
|
}
|
|
436
|
+
export function serializeRiskCalibrationReport(report) {
|
|
437
|
+
try {
|
|
438
|
+
const snapshot = snapshotRiskCalibrationReportForSerialization(report);
|
|
439
|
+
const serialised = serializeRiskCalibrationSnapshot(snapshot);
|
|
440
|
+
if (snapshot.mode === 'summary')
|
|
441
|
+
parseRiskCalibrationSummaryReport(serialised);
|
|
442
|
+
else
|
|
443
|
+
buildRiskCalibrationSummaryReport(snapshot.document);
|
|
444
|
+
return serialised;
|
|
445
|
+
}
|
|
446
|
+
catch (cause) {
|
|
447
|
+
throw new CliUsageError(cause instanceof Error
|
|
448
|
+
? cause.message
|
|
449
|
+
: 'Risk calibration output must use the current detailed or summary report contract.');
|
|
450
|
+
}
|
|
451
|
+
}
|