@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.
Files changed (235) hide show
  1. package/README.md +74 -177
  2. package/SECURITY.md +6 -3
  3. package/bin/whoisleuth.mjs +2 -1
  4. package/cli/archive-inspect.mjs +28 -10
  5. package/cli/arguments.mjs +204 -174
  6. package/cli/artifact-structure.mjs +837 -163
  7. package/cli/artifact-verify.mjs +90 -61
  8. package/cli/bounded-json.mjs +1 -1
  9. package/cli/bulk-checkpoint.mjs +33 -20
  10. package/cli/bulk.mjs +9 -2
  11. package/cli/case-pack.mjs +180 -576
  12. package/cli/ci-report.mjs +6 -1
  13. package/cli/collection-preflight.mjs +2 -2
  14. package/cli/command-catalogue.mjs +12 -3
  15. package/cli/command-reference.mjs +910 -92
  16. package/cli/completion.mjs +504 -226
  17. package/cli/config-profile.mjs +13 -3
  18. package/cli/discover.mjs +7 -1
  19. package/cli/discovery-observation-snapshot.mjs +24 -28
  20. package/cli/discovery-scan-command-runner.mjs +9 -9
  21. package/cli/discovery-scan.mjs +17 -3
  22. package/cli/discovery-snapshot.mjs +15 -7
  23. package/cli/domain-control-monitor.mjs +203 -30
  24. package/cli/domain-control-observations.mjs +15 -21
  25. package/cli/errors.mjs +8 -3
  26. package/cli/evidence-signing.mjs +10 -20
  27. package/cli/external-observation-mapping.mjs +3 -3
  28. package/cli/formatters/json.mjs +20 -32
  29. package/cli/formatters/terminal.mjs +583 -22
  30. package/cli/input.mjs +7 -2
  31. package/cli/interactive-launcher.mjs +226 -0
  32. package/cli/interchange-report.mjs +19 -19
  33. package/cli/investigation-manifest.mjs +1 -1
  34. package/cli/investigation-plan.mjs +167 -16
  35. package/cli/investigation-run.mjs +9 -1
  36. package/cli/lookalike-calibration.mjs +3 -1
  37. package/cli/lookup-brief.mjs +13 -8
  38. package/cli/lookup-browser.mjs +844 -0
  39. package/cli/lookup-command-runner.mjs +94 -31
  40. package/cli/lookup-diff.mjs +2 -2
  41. package/cli/lookup-plan.mjs +1 -0
  42. package/cli/mail-review.mjs +206 -94
  43. package/cli/manual.mjs +5 -5
  44. package/cli/offline-evidence-review.mjs +18 -7
  45. package/cli/open-asset-model-bridge.mjs +1 -1
  46. package/cli/output-file.mjs +70 -14
  47. package/cli/page-compare.mjs +11 -6
  48. package/cli/progress-events.mjs +2 -1
  49. package/cli/progress.mjs +3 -3
  50. package/cli/registry-cohort.mjs +9 -51
  51. package/cli/registry-doctor.mjs +67 -28
  52. package/cli/retained-artifact-diff.mjs +7 -8
  53. package/cli/risk-calibration.mjs +85 -38
  54. package/cli/runner.mjs +189 -75
  55. package/cli/saved-lookup.mjs +424 -57
  56. package/cli/sharing-review.mjs +6 -1
  57. package/cli/source-reliability.mjs +75 -36
  58. package/cli/terminal-presentation.mjs +43 -18
  59. package/docs/cli-reference.md +125 -1708
  60. package/docs/cli.md +86 -254
  61. package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -145
  62. package/frontend/src/lib/analysis/domain-control-records.js +3 -88
  63. package/lib/availability.mjs +58 -40
  64. package/lib/bounded-contract-normalizers.mjs +2 -4
  65. package/lib/bounded-file.mjs +78 -4
  66. package/lib/bounded-json.mjs +51 -13
  67. package/lib/browser-library-profile.mjs +1 -1
  68. package/lib/classify.mjs +56 -5
  69. package/lib/client-behavior-profile.mjs +1 -1
  70. package/lib/credential-surface-profile.mjs +1 -1
  71. package/lib/cryptographic-assurance.mjs +35 -17
  72. package/lib/ct-query.mjs +3 -3
  73. package/lib/ct-search.mjs +37 -21
  74. package/lib/distributed-operation-budget.mjs +4 -2
  75. package/lib/dns-delegation-health.mjs +9 -6
  76. package/lib/dns-intelligence.mjs +1 -1
  77. package/lib/dnssec-chain-validation.mjs +2 -3
  78. package/lib/dnssec-evidence-validation.mjs +13 -7
  79. package/lib/domain-assurance.mjs +12 -12
  80. package/lib/domain-change-packet.mjs +1 -1
  81. package/lib/domain-control-flight-recorder.mjs +442 -78
  82. package/lib/domain-control-manifest.mjs +314 -59
  83. package/lib/domain-posture-analysis.mjs +5 -2
  84. package/lib/encrypted-dns-contract.mjs +2 -3
  85. package/lib/evidence-export.mjs +518 -36
  86. package/lib/external-intelligence-risk.mjs +26 -10
  87. package/lib/feature-policy.mjs +1 -1
  88. package/lib/homepage-metadata-contract.mjs +290 -0
  89. package/lib/hostname.mjs +1 -1
  90. package/lib/html-signals.mjs +89 -5
  91. package/lib/http-intelligence.mjs +331 -2
  92. package/lib/interchange-fidelity-registry.mjs +28 -26
  93. package/lib/lookup-cache.mjs +6 -4
  94. package/lib/lookup.mjs +25 -2
  95. package/lib/observed-network-context.mjs +1 -1
  96. package/lib/page-fingerprints.mjs +2 -2
  97. package/lib/page-role-profile.mjs +1 -1
  98. package/lib/portable-generator.mjs +8 -0
  99. package/lib/provider-policy-admission.mjs +3 -1
  100. package/lib/rdap-bootstrap.mjs +76 -16
  101. package/lib/rdap-client.mjs +20 -12
  102. package/lib/rdap-normalization.mjs +306 -75
  103. package/lib/rdap-transport.mjs +2 -2
  104. package/lib/rdap-validation.mjs +65 -14
  105. package/lib/rdap.mjs +2 -2
  106. package/lib/registrable-domain.mjs +36 -0
  107. package/lib/registry-insights.mjs +7 -0
  108. package/lib/response-policy.mjs +27 -4
  109. package/lib/risk-calibration-summary.mjs +35 -33
  110. package/lib/risk-scoring.mjs +4 -3
  111. package/lib/safe-fetch.mjs +62 -40
  112. package/lib/security-txt.mjs +1 -1
  113. package/lib/smtp-transport-review.mjs +8 -5
  114. package/lib/sslbl-intelligence.mjs +6 -8
  115. package/lib/static-html-analysis.mjs +301 -4
  116. package/lib/structured-data-identity.mjs +51 -19
  117. package/lib/threat-intelligence-fixtures.mjs +2 -4
  118. package/lib/threat-intelligence-manifests.mjs +2 -7
  119. package/lib/threat-intelligence-runtime.mjs +3 -5
  120. package/lib/threat-intelligence-types.mjs +3 -0
  121. package/lib/threatfox-intelligence.mjs +7 -3
  122. package/lib/tls-intelligence.mjs +4 -4
  123. package/lib/urlhaus-intelligence.mjs +4 -2
  124. package/lib/urlscan-intelligence.mjs +6 -5
  125. package/lib/website-security-posture.mjs +1 -1
  126. package/lib/website-technology.mjs +1 -1
  127. package/lib/whois-authority.mjs +43 -8
  128. package/lib/whois-chain.mjs +25 -3
  129. package/lib/whois-normalization.mjs +3 -1
  130. package/lib/whois-transport.mjs +12 -3
  131. package/lib/zone-intent-review.mjs +1 -1
  132. package/package.json +12 -4
  133. package/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
  134. package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
  135. package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
  136. package/packages/cases/case-migration-model.mjs +476 -0
  137. package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
  138. package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -21
  139. package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
  140. package/packages/cases/case-record-model.mjs +9 -0
  141. package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +72 -29
  142. package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
  143. package/packages/cases/case-response-model.mjs +1604 -0
  144. package/packages/cases/case-response-packet.mjs +1312 -0
  145. package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
  146. package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
  147. package/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
  148. package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
  149. package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
  150. package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -0
  151. package/packages/comparison/favicon-similarity.mjs +78 -0
  152. package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
  153. package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
  154. package/packages/contracts/analyst-review-state-contract.mjs +61 -0
  155. package/packages/contracts/analyst-review-state.mjs +50 -0
  156. package/packages/contracts/case-portability.mjs +1310 -0
  157. package/packages/contracts/cli-command-catalogue.mjs +2 -0
  158. package/packages/contracts/cli-lookup.mjs +394 -0
  159. package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
  160. package/packages/contracts/domain-control-manifest.mjs +1027 -0
  161. package/packages/contracts/domain-control-monitor.mjs +310 -0
  162. package/packages/contracts/domain-control-review.mjs +828 -0
  163. package/packages/contracts/domain-name.mjs +4 -0
  164. package/packages/contracts/external-observation-interchange.mjs +224 -0
  165. package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
  166. package/packages/contracts/investigation-portability.mjs +331 -0
  167. package/packages/contracts/offline-comparison.mjs +160 -0
  168. package/packages/contracts/risk-calibration.mjs +1288 -0
  169. package/packages/contracts/schema-compatibility.mjs +71 -0
  170. package/packages/contracts/schema-lifecycle.mjs +1702 -0
  171. package/packages/contracts/sslbl-snapshot.mjs +17 -0
  172. package/packages/contracts/workspace-portability.mjs +1347 -0
  173. package/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
  174. package/packages/evidence/decision-fact.mjs +735 -0
  175. package/packages/evidence/domain-control-runtime.mjs +547 -0
  176. package/packages/evidence/domain-name.mjs +41 -0
  177. package/packages/evidence/observation.mjs +384 -0
  178. package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +24 -22
  179. package/packages/interchange/retained-text.mjs +8 -0
  180. package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
  181. package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
  182. package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
  183. package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
  184. package/packages/investigation/bulk-review-policy.mjs +1 -0
  185. package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
  186. package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
  187. package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
  188. package/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
  189. package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
  190. package/packages/investigation/lookup-investigation-brief.mjs +172 -0
  191. package/packages/monitoring/analyst-review-state.mjs +680 -0
  192. package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +70 -47
  193. package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
  194. package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
  195. package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -5
  196. package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
  197. package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
  198. package/packages/workspace/hostile-input.mjs +172 -0
  199. package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +8 -45
  200. package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
  201. package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
  202. package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
  203. package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
  204. package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
  205. package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
  206. package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +13 -13
  207. package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
  208. package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +110 -89
  209. package/third-party-notices.txt +14 -14
  210. package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
  211. package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
  212. package/frontend/src/lib/analysis/case-migration-model.js +0 -305
  213. package/frontend/src/lib/analysis/case-record-model.js +0 -9
  214. package/frontend/src/lib/analysis/case-response-model.js +0 -728
  215. package/frontend/src/lib/analysis/case-response-packet.js +0 -583
  216. package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
  217. package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
  218. package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
  219. package/frontend/src/lib/analysis/lookup-response.js +0 -2
  220. package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
  221. package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
  222. package/frontend/src/lib/analysis/scoring.js +0 -55
  223. package/frontend/src/lib/analysis/utils.js +0 -255
  224. package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
  225. package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
  226. package/frontend/src/lib/analysis/visualization-matrix.js +0 -131
  227. package/frontend/src/lib/analysis/visualization-models.js +0 -338
  228. package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
  229. package/lib/bounded-json-response.mjs +0 -170
  230. package/lib/lookup-response-contract.mjs +0 -1034
  231. package/lib/observation.mjs +0 -115
  232. /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
  233. /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
  234. /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
  235. /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
@@ -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 { RISK_CALIBRATION_SUMMARY_SCHEMA, RISK_CALIBRATION_SUMMARY_VERSION, RISK_CALIBRATION_SUMMARY_INTERPRETATION, RISK_CALIBRATION_SUMMARY_THRESHOLDS, } from "../lib/risk-calibration-summary.mjs";
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
- export const RISK_CALIBRATION_DATASET_SCHEMA = 'whoisleuth.risk-calibration-dataset';
8
- export const RISK_CALIBRATION_DATASET_VERSION = 2;
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, MAX_TIMESTAMP_LENGTH);
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 > MAX_PROVIDERS)
61
- throw new CliUsageError(`${field}.providers exceeds the ${MAX_PROVIDERS}-provider limit.`);
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 > MAX_FINDINGS_PER_PROVIDER) {
73
- throw new CliUsageError(`${prefix}.findings exceeds the ${MAX_FINDINGS_PER_PROVIDER}-finding limit.`);
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`, 64) },
80
- state: boundedString(provider.state, `${prefix}.state`, 32),
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`, 64),
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`, 32);
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 = { availability };
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`, 32);
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`, 16);
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 > 100_000) {
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 > MAX_MUTATIONS)
142
- throw new CliUsageError(`${field}.mutationTypes exceeds the ${MAX_MUTATIONS}-item limit.`);
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}]`, 64);
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
- return Buffer.concat(chunks).toString('utf8');
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
- parsed = JSON.parse(text.replace(/^\uFEFF/, ''));
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`, 128);
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`, 253).toLowerCase().replace(/\.$/, '');
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`, 32);
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`, 64);
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.evidence);
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.evidence);
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
+ }