@slicedearth/whoisleuth-cli 1.45.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/DISCLOSURE +7 -3
  2. package/NOTICE +5 -4
  3. package/README.md +74 -124
  4. package/SECURITY.md +6 -3
  5. package/bin/whoisleuth.mjs +7 -4
  6. package/cli/archive-inspect.mjs +28 -10
  7. package/cli/arguments.mjs +450 -173
  8. package/cli/artifact-structure.mjs +2507 -0
  9. package/cli/artifact-verify.mjs +286 -64
  10. package/cli/bounded-json.mjs +1 -0
  11. package/cli/bulk-checkpoint.mjs +35 -23
  12. package/cli/bulk-command-runner.mjs +2 -5
  13. package/cli/bulk.mjs +9 -2
  14. package/cli/case-pack.mjs +435 -46
  15. package/cli/ci-report.mjs +6 -1
  16. package/cli/collection-preflight.mjs +2 -2
  17. package/cli/command-catalogue.mjs +12 -3
  18. package/cli/command-reference.mjs +948 -106
  19. package/cli/completion.mjs +504 -218
  20. package/cli/config-profile.mjs +13 -3
  21. package/cli/ct-event-intake.mjs +32 -2
  22. package/cli/discover.mjs +7 -1
  23. package/cli/discovery-observation-snapshot.mjs +24 -28
  24. package/cli/discovery-scan-command-runner.mjs +13 -14
  25. package/cli/discovery-scan.mjs +17 -3
  26. package/cli/discovery-snapshot.mjs +15 -7
  27. package/cli/discovery-workflow.mjs +2 -3
  28. package/cli/doctor.mjs +2 -2
  29. package/cli/domain-control-monitor.mjs +215 -36
  30. package/cli/domain-control-observations.mjs +34 -17
  31. package/cli/errors.mjs +8 -3
  32. package/cli/evidence-command-runner.mjs +17 -11
  33. package/cli/evidence-signing.mjs +68 -50
  34. package/cli/export-evidence.mjs +11 -4
  35. package/cli/external-observation-mapping.mjs +3 -3
  36. package/cli/formatters/html.mjs +3 -3
  37. package/cli/formatters/json.mjs +20 -32
  38. package/cli/formatters/terminal.mjs +586 -22
  39. package/cli/input.mjs +62 -0
  40. package/cli/interactive-launcher.mjs +226 -0
  41. package/cli/interchange-report.mjs +226 -0
  42. package/cli/investigation-manifest.mjs +16 -17
  43. package/cli/investigation-plan.mjs +167 -16
  44. package/cli/investigation-run.mjs +80 -24
  45. package/cli/local-mmdb-review.mjs +45 -14
  46. package/cli/lookalike-calibration.mjs +3 -1
  47. package/cli/lookup-brief.mjs +13 -8
  48. package/cli/lookup-browser.mjs +844 -0
  49. package/cli/lookup-command-runner.mjs +96 -33
  50. package/cli/lookup-diff.mjs +11 -2
  51. package/cli/lookup-plan.mjs +1 -0
  52. package/cli/mail-review.mjs +206 -94
  53. package/cli/manual.mjs +5 -5
  54. package/cli/network-command-runner.mjs +78 -0
  55. package/cli/offline-evidence-review.mjs +45 -14
  56. package/cli/open-asset-model-bridge.mjs +1 -1
  57. package/cli/output-file.mjs +70 -14
  58. package/cli/page-compare.mjs +18 -10
  59. package/cli/progress-events.mjs +2 -1
  60. package/cli/progress.mjs +3 -3
  61. package/cli/registry-cohort.mjs +343 -61
  62. package/cli/registry-doctor.mjs +67 -28
  63. package/cli/registry-support.mjs +2 -1
  64. package/cli/retained-artifact-diff.mjs +375 -0
  65. package/cli/risk-calibration.mjs +89 -40
  66. package/cli/runner.mjs +322 -246
  67. package/cli/saved-lookup.mjs +439 -49
  68. package/cli/sharing-review.mjs +18 -7
  69. package/cli/source-reliability.mjs +75 -36
  70. package/cli/terminal-presentation.mjs +43 -18
  71. package/docs/cli-reference.md +125 -1334
  72. package/docs/cli.md +87 -236
  73. package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -0
  74. package/frontend/src/lib/analysis/domain-control-records.js +3 -0
  75. package/lib/application-version.mjs +3 -0
  76. package/lib/availability.mjs +72 -52
  77. package/lib/bounded-contract-normalizers.mjs +2 -4
  78. package/lib/bounded-file.mjs +91 -4
  79. package/lib/bounded-json.mjs +287 -0
  80. package/lib/browser-library-profile.mjs +1 -1
  81. package/lib/certificate-extension-profile.mjs +2 -1
  82. package/lib/classify.mjs +56 -5
  83. package/lib/client-behavior-profile.mjs +1 -1
  84. package/lib/credential-surface-profile.mjs +1 -1
  85. package/lib/cryptographic-assurance.mjs +173 -0
  86. package/lib/ct-query.mjs +3 -3
  87. package/lib/ct-search.mjs +120 -64
  88. package/lib/distributed-operation-budget.mjs +4 -2
  89. package/lib/dns-delegation-health.mjs +9 -6
  90. package/lib/dns-intelligence.mjs +4 -3
  91. package/lib/dns-resolver-selection.mjs +6 -4
  92. package/lib/dnssec-chain-validation.mjs +1052 -0
  93. package/lib/dnssec-evidence-validation.mjs +23 -10
  94. package/lib/domain-assurance.mjs +12 -12
  95. package/lib/domain-change-packet.mjs +7 -5
  96. package/lib/domain-control-flight-recorder.mjs +442 -78
  97. package/lib/domain-control-manifest.mjs +314 -171
  98. package/lib/domain-posture-analysis.mjs +5 -2
  99. package/lib/domain-posture.mjs +7 -4
  100. package/lib/encrypted-dns-contract.mjs +2 -3
  101. package/lib/evidence-export.mjs +1243 -88
  102. package/lib/evidence-report-markdown.mjs +3 -0
  103. package/lib/evidence-report.mjs +5 -0
  104. package/lib/external-intelligence-risk.mjs +26 -10
  105. package/lib/favicon.mjs +8 -5
  106. package/lib/feature-policy.mjs +1 -1
  107. package/lib/homepage-metadata-contract.mjs +290 -0
  108. package/lib/hostname.mjs +1 -1
  109. package/lib/html-signals.mjs +89 -5
  110. package/lib/http-evidence-bounds.mjs +3 -0
  111. package/lib/http-intelligence.mjs +332 -6
  112. package/lib/interchange-fidelity-registry.mjs +124 -0
  113. package/lib/local-geoip-evidence.mjs +2 -1
  114. package/lib/lookup-cache.mjs +6 -4
  115. package/lib/lookup-network-evidence-bounds.mjs +9 -0
  116. package/lib/lookup.mjs +27 -4
  117. package/lib/observed-network-context.mjs +1 -1
  118. package/lib/outbound-identity.mjs +2 -3
  119. package/lib/page-fingerprints.mjs +2 -2
  120. package/lib/page-role-profile.mjs +1 -1
  121. package/lib/perceptual-hash.mjs +12 -4
  122. package/lib/portable-generator.mjs +29 -0
  123. package/lib/provider-policy-admission.mjs +11 -4
  124. package/lib/rdap-bootstrap.mjs +76 -16
  125. package/lib/rdap-client.mjs +20 -12
  126. package/lib/rdap-normalization.mjs +324 -80
  127. package/lib/rdap-registrar.mjs +5 -1
  128. package/lib/rdap-transport.mjs +2 -2
  129. package/lib/rdap-validation.mjs +65 -14
  130. package/lib/rdap.mjs +2 -2
  131. package/lib/registrable-domain.mjs +36 -0
  132. package/lib/registry-capabilities.mjs +3 -0
  133. package/lib/registry-capability-catalogue.mjs +12 -2
  134. package/lib/registry-comparison.mjs +28 -11
  135. package/lib/registry-insights.mjs +7 -0
  136. package/lib/response-policy.mjs +27 -4
  137. package/lib/risk-calibration-summary.mjs +340 -0
  138. package/lib/risk-scoring.mjs +4 -3
  139. package/lib/rpki-evidence.mjs +9 -8
  140. package/lib/safe-fetch.mjs +121 -50
  141. package/lib/security-txt.mjs +27 -4
  142. package/lib/service-binding-dns.mjs +6 -1
  143. package/lib/smtp-transport-review.mjs +787 -0
  144. package/lib/sslbl-certificates.generated.mjs +136 -134
  145. package/lib/sslbl-intelligence.mjs +6 -8
  146. package/lib/static-html-analysis.mjs +301 -4
  147. package/lib/structured-data-identity.mjs +51 -19
  148. package/lib/threat-intelligence-fixtures.mjs +2 -4
  149. package/lib/threat-intelligence-manifests.mjs +2 -7
  150. package/lib/threat-intelligence-runtime.mjs +5 -15
  151. package/lib/threat-intelligence-types.mjs +21 -1
  152. package/lib/threatfox-intelligence.mjs +7 -3
  153. package/lib/tls-intelligence.mjs +15 -9
  154. package/lib/tlsa-evidence.mjs +37 -7
  155. package/lib/urlhaus-intelligence.mjs +4 -2
  156. package/lib/urlscan-intelligence.mjs +6 -5
  157. package/lib/website-security-posture.mjs +1 -1
  158. package/lib/website-technology.mjs +1 -1
  159. package/lib/whois-authority.mjs +43 -8
  160. package/lib/whois-chain.mjs +25 -3
  161. package/lib/whois-normalization.mjs +3 -1
  162. package/lib/whois-transport.mjs +12 -3
  163. package/lib/zone-intent-review.mjs +1 -1
  164. package/package.json +12 -4
  165. package/packages/cases/case-brand-profile-references.mjs +85 -0
  166. package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +43 -14
  167. package/packages/cases/case-investigation-branch-model.mjs +132 -0
  168. package/packages/cases/case-migration-model.mjs +476 -0
  169. package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
  170. package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -19
  171. package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
  172. package/packages/cases/case-record-model.mjs +9 -0
  173. package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +106 -33
  174. package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +143 -16
  175. package/packages/cases/case-response-model.mjs +1604 -0
  176. package/packages/cases/case-response-packet.mjs +1312 -0
  177. package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
  178. package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
  179. package/packages/cases/opaque-reference-id.mjs +9 -0
  180. package/packages/comparison/comparison-ledger-bulk.mjs +366 -0
  181. package/packages/comparison/comparison-ledger-contract.mjs +241 -0
  182. package/packages/comparison/comparison-ledger-json.mjs +80 -0
  183. package/packages/comparison/comparison-ledger-serialization.mjs +52 -0
  184. package/packages/comparison/favicon-similarity.mjs +78 -0
  185. package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
  186. package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
  187. package/packages/contracts/analyst-review-state-contract.mjs +61 -0
  188. package/packages/contracts/analyst-review-state.mjs +50 -0
  189. package/packages/contracts/case-portability.mjs +1310 -0
  190. package/packages/contracts/cli-command-catalogue.mjs +2 -0
  191. package/packages/contracts/cli-lookup.mjs +394 -0
  192. package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
  193. package/packages/contracts/domain-control-manifest.mjs +1027 -0
  194. package/packages/contracts/domain-control-monitor.mjs +310 -0
  195. package/packages/contracts/domain-control-review.mjs +828 -0
  196. package/packages/contracts/domain-name.mjs +4 -0
  197. package/packages/contracts/external-observation-interchange.mjs +224 -0
  198. package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
  199. package/packages/contracts/investigation-portability.mjs +331 -0
  200. package/packages/contracts/offline-comparison.mjs +160 -0
  201. package/packages/contracts/risk-calibration.mjs +1288 -0
  202. package/packages/contracts/schema-compatibility.mjs +71 -0
  203. package/packages/contracts/schema-lifecycle.mjs +1702 -0
  204. package/packages/contracts/sslbl-snapshot.mjs +17 -0
  205. package/packages/contracts/workspace-portability.mjs +1347 -0
  206. package/packages/evidence/artifact-integrity.mjs +65 -0
  207. package/packages/evidence/decision-fact.mjs +735 -0
  208. package/packages/evidence/domain-control-runtime.mjs +547 -0
  209. package/packages/evidence/domain-name.mjs +41 -0
  210. package/packages/evidence/observation.mjs +384 -0
  211. package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +86 -20
  212. package/packages/interchange/retained-text.mjs +8 -0
  213. package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +11 -8
  214. package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +28 -9
  215. package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +67 -18
  216. package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +10 -9
  217. package/packages/investigation/bulk-review-policy.mjs +1 -0
  218. package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +50 -18
  219. package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
  220. package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
  221. package/packages/investigation/lookup-claim-passport.mjs +232 -0
  222. package/packages/investigation/lookup-claim-readiness.mjs +287 -0
  223. package/packages/investigation/lookup-investigation-brief.mjs +172 -0
  224. package/packages/monitoring/analyst-review-state.mjs +680 -0
  225. package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +97 -50
  226. package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +15 -15
  227. package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +179 -39
  228. package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +12 -5
  229. package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
  230. package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
  231. package/packages/workspace/hostile-input.mjs +172 -0
  232. package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +51 -46
  233. package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +15 -15
  234. package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
  235. package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
  236. package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
  237. package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
  238. package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
  239. package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +18 -12
  240. package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
  241. package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +195 -84
  242. package/third-party-notices.txt +112 -5834
  243. package/frontend/src/lib/analysis/artifact-integrity.js +0 -21
  244. package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
  245. package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
  246. package/frontend/src/lib/analysis/case-migration-model.js +0 -253
  247. package/frontend/src/lib/analysis/case-record-model.js +0 -7
  248. package/frontend/src/lib/analysis/case-response-model.js +0 -669
  249. package/frontend/src/lib/analysis/case-response-packet.js +0 -573
  250. package/frontend/src/lib/analysis/lookup-decision-support.js +0 -487
  251. package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
  252. package/frontend/src/lib/analysis/lookup-presentation.js +0 -58
  253. package/frontend/src/lib/analysis/lookup-response.js +0 -2
  254. package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
  255. package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
  256. package/frontend/src/lib/analysis/scoring.js +0 -55
  257. package/frontend/src/lib/analysis/utils.js +0 -232
  258. package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
  259. package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
  260. package/frontend/src/lib/analysis/visualization-matrix.js +0 -88
  261. package/frontend/src/lib/analysis/visualization-models.js +0 -369
  262. package/frontend/src/lib/analysis/visualization-time-series.js +0 -144
  263. package/lib/bounded-json-response.mjs +0 -157
  264. package/lib/lookup-response-contract.mjs +0 -346
  265. package/lib/observation.mjs +0 -114
  266. /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
  267. /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
  268. /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
@@ -1,5 +1,9 @@
1
1
  import { Buffer } from 'node:buffer';
2
- import { canonicalArtifactJson } from "../frontend/src/lib/analysis/artifact-integrity.js";
2
+ import { canonicalArtifactJson } from "../packages/evidence/artifact-integrity.mjs";
3
+ import { scanBoundedJson } from "../lib/bounded-json.mjs";
4
+ import { normalizeExplicitIsoTimestamp, normalizeLegacyIsoTimestamp, } from "../packages/evidence/observation.mjs";
5
+ import { CLI_LOOKUP_SCHEMA, MAX_CLI_LOOKUP_BYTES, } from "../packages/contracts/cli-lookup.mjs";
6
+ import { normalizeCliLookupDocument } from "./saved-lookup.mjs";
3
7
  export const SOURCE_RELIABILITY_REPORT_SCHEMA = 'whoisleuth.source-reliability-report';
4
8
  export const SOURCE_RELIABILITY_REPORT_VERSION = 1;
5
9
  export const MAX_SOURCE_RELIABILITY_INPUT_BYTES = 12 * 1024 * 1024;
@@ -22,7 +26,7 @@ const STATES = new Set([
22
26
  'stale',
23
27
  ]);
24
28
  const LOOKUP_SCHEMAS = new Set([
25
- 'whoisleuth.cli.lookup',
29
+ CLI_LOOKUP_SCHEMA,
26
30
  'whoisleuth.cli.bulk',
27
31
  'whoisleuth.cli.bulk.item',
28
32
  ]);
@@ -134,10 +138,11 @@ function parseValues(raw) {
134
138
  }
135
139
  let parsed;
136
140
  try {
141
+ scanBoundedJson(raw);
137
142
  parsed = JSON.parse(raw);
138
143
  }
139
144
  catch {
140
- throw new TypeError('Source reliability input must be valid JSON.');
145
+ throw new TypeError('Source reliability input must be valid bounded JSON without duplicate keys.');
141
146
  }
142
147
  const values = Array.isArray(parsed) ? parsed : [parsed];
143
148
  if (values.length === 0 || values.length > MAX_SOURCE_RELIABILITY_DOCUMENTS) {
@@ -150,10 +155,29 @@ function parseValues(raw) {
150
155
  return document;
151
156
  });
152
157
  }
158
+ function hasJsonArrayEnvelope(raw) {
159
+ for (let index = 0; index < raw.length; index += 1) {
160
+ const code = raw.charCodeAt(index);
161
+ if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d)
162
+ continue;
163
+ return code === 0x5b;
164
+ }
165
+ return false;
166
+ }
153
167
  function parseLookupDocuments(values) {
154
168
  return values.map((document) => {
155
- if (!LOOKUP_SCHEMAS.has(String(document.schema)) || document.version !== 1) {
156
- throw new TypeError('Source reliability input requires only version 1 CLI lookup, Bulk, or Bulk-item documents, or only source reliability reports.');
169
+ const schema = String(document.schema);
170
+ if (schema === CLI_LOOKUP_SCHEMA) {
171
+ try {
172
+ return normalizeCliLookupDocument(document, { label: 'Source reliability Lookup input' });
173
+ }
174
+ catch {
175
+ throw new TypeError('Source reliability input requires a supported bounded CLI Lookup document.');
176
+ }
177
+ }
178
+ const supported = document.version === 1;
179
+ if (!LOOKUP_SCHEMAS.has(schema) || !supported) {
180
+ throw new TypeError('Source reliability input requires CLI Lookup version 1 or 2, version 1 Bulk or Bulk-item documents, or only source reliability reports.');
157
181
  }
158
182
  return document;
159
183
  });
@@ -162,8 +186,9 @@ function accumulator(store, source) {
162
186
  const existing = store.get(source);
163
187
  if (existing)
164
188
  return existing;
165
- if (store.size >= MAX_SOURCE_RELIABILITY_SOURCES)
166
- return null;
189
+ if (store.size >= MAX_SOURCE_RELIABILITY_SOURCES) {
190
+ throw new TypeError(`Source reliability input exceeds the ${MAX_SOURCE_RELIABILITY_SOURCES}-source limit.`);
191
+ }
167
192
  const created = {
168
193
  states: new Map(),
169
194
  observationDurations: [],
@@ -183,8 +208,6 @@ function accumulator(store, source) {
183
208
  }
184
209
  function addState(store, source, state) {
185
210
  const item = accumulator(store, source);
186
- if (!item)
187
- return;
188
211
  item.states.set(state, (item.states.get(state) ?? 0) + 1);
189
212
  if (state === 'rate_limited')
190
213
  item.rateLimited += 1;
@@ -194,15 +217,16 @@ function collectTiming(document, store) {
194
217
  const timing = record(diagnostics?.timing);
195
218
  if (timing?.version !== 1 || !Array.isArray(timing.sources))
196
219
  return;
197
- for (const rawEntry of timing.sources.slice(0, 16)) {
220
+ if (timing.sources.length > 16) {
221
+ throw new TypeError('Source reliability input exceeds the 16-source timing limit.');
222
+ }
223
+ for (const rawEntry of timing.sources) {
198
224
  const entry = record(rawEntry);
199
225
  const source = safeSource(entry?.source);
200
226
  const duration = boundedDuration(entry?.durationMs);
201
227
  if (!source || !TIMING_SOURCES.has(source) || duration === null)
202
228
  continue;
203
229
  const item = accumulator(store, source);
204
- if (!item)
205
- continue;
206
230
  item.timingDurations.push(duration);
207
231
  item.timingSamples += 1;
208
232
  }
@@ -239,8 +263,6 @@ function collectObservation(value, store) {
239
263
  if (!source || !state)
240
264
  return;
241
265
  const item = accumulator(store, source);
242
- if (!item)
243
- return;
244
266
  item.observationSamples += 1;
245
267
  addState(store, source, state);
246
268
  const duration = boundedDuration(value.durationMs);
@@ -254,8 +276,14 @@ function traverse(document, store) {
254
276
  const stack = [{ value: document, depth: 0 }];
255
277
  while (stack.length) {
256
278
  const current = stack.pop();
257
- if (!current || current.depth > 12 || visited >= MAX_SOURCE_RELIABILITY_TRAVERSAL_NODES)
279
+ if (!current)
258
280
  continue;
281
+ if (current.depth > 12) {
282
+ throw new TypeError('Source reliability input exceeds the 12-level traversal limit.');
283
+ }
284
+ if (visited >= MAX_SOURCE_RELIABILITY_TRAVERSAL_NODES) {
285
+ throw new TypeError(`Source reliability input exceeds the ${MAX_SOURCE_RELIABILITY_TRAVERSAL_NODES}-node traversal limit.`);
286
+ }
259
287
  visited += 1;
260
288
  const object = record(current.value);
261
289
  if (object) {
@@ -267,13 +295,20 @@ function traverse(document, store) {
267
295
  if (source)
268
296
  addState(store, source, 'rate_limited');
269
297
  }
270
- for (const child of Object.values(object).slice(0, 200)) {
298
+ const children = Object.values(object);
299
+ if (children.length > 200) {
300
+ throw new TypeError('Source reliability input contains an object with more than 200 fields.');
301
+ }
302
+ for (const child of children) {
271
303
  if (child && typeof child === 'object')
272
304
  stack.push({ value: child, depth: current.depth + 1 });
273
305
  }
274
306
  }
275
307
  else if (Array.isArray(current.value)) {
276
- for (const child of current.value.slice(0, 1_000)) {
308
+ if (current.value.length > 1_000) {
309
+ throw new TypeError('Source reliability input contains an array with more than 1000 items.');
310
+ }
311
+ for (const child of current.value) {
277
312
  if (child && typeof child === 'object')
278
313
  stack.push({ value: child, depth: current.depth + 1 });
279
314
  }
@@ -298,13 +333,19 @@ function ratio(numerator, denominator) {
298
333
  return denominator > 0 ? Number((numerator / denominator).toFixed(4)) : null;
299
334
  }
300
335
  function reportTimestamp(value) {
301
- if (typeof value !== 'string' || value.length > 64) {
336
+ const normalized = normalizeExplicitIsoTimestamp(value);
337
+ if (!normalized) {
302
338
  throw new TypeError('Source reliability report has an invalid generation time.');
303
339
  }
304
- const parsed = Date.parse(value);
305
- if (!Number.isFinite(parsed))
306
- throw new TypeError('Source reliability report has an invalid generation time.');
307
- return new Date(parsed).toISOString();
340
+ return normalized;
341
+ }
342
+ function lookupDocumentTimestamp(document) {
343
+ const explicit = normalizeExplicitIsoTimestamp(document.generatedAt);
344
+ if (explicit)
345
+ return explicit;
346
+ return document.version === 1
347
+ ? normalizeLegacyIsoTimestamp(document.generatedAt)
348
+ : null;
308
349
  }
309
350
  function optionalReportTimestamp(value, label) {
310
351
  if (value === null)
@@ -475,8 +516,6 @@ function collectReport(report, store) {
475
516
  for (const value of Object.values(durationTrend))
476
517
  reportedDurationPoint(value);
477
518
  const item = accumulator(store, source);
478
- if (!item)
479
- throw new TypeError('Source reliability report exceeds the source limit.');
480
519
  const observationSamples = safeCount(samples.observations, `${source} observation sample count`);
481
520
  const timingSamples = safeCount(samples.timing, `${source} timing sample count`);
482
521
  const truncationCount = safeCount(sourceRecord.truncationCount, `${source} truncation count`);
@@ -583,9 +622,14 @@ function collectReport(report, store) {
583
622
  };
584
623
  }
585
624
  export function buildSourceReliabilityReport(raw, generatedAt = new Date().toISOString()) {
586
- const timestamp = Date.parse(generatedAt);
587
- if (!Number.isFinite(timestamp))
588
- throw new TypeError('Source reliability report time must be valid.');
625
+ const normalizedGeneratedAt = normalizeExplicitIsoTimestamp(generatedAt);
626
+ if (!normalizedGeneratedAt)
627
+ throw new TypeError('Source reliability report time must use an explicit timezone.');
628
+ if (typeof raw === 'string'
629
+ && !hasJsonArrayEnvelope(raw)
630
+ && Buffer.byteLength(raw, 'utf8') > MAX_CLI_LOOKUP_BYTES) {
631
+ throw new TypeError('Source reliability input requires a supported bounded CLI Lookup document.');
632
+ }
589
633
  const values = parseValues(raw);
590
634
  const store = new Map();
591
635
  const reportInputs = values.every((value) => value.schema === SOURCE_RELIABILITY_REPORT_SCHEMA);
@@ -616,14 +660,9 @@ export function buildSourceReliabilityReport(raw, generatedAt = new Date().toISO
616
660
  for (const document of documents) {
617
661
  const mode = document.mode === 'fast' || document.mode === 'deep' ? document.mode : 'unknown';
618
662
  lookupModes[mode] += 1;
619
- if (typeof document.generatedAt === 'string') {
620
- try {
621
- sampleDates.push(reportTimestamp(document.generatedAt));
622
- }
623
- catch {
624
- // Invalid or missing document dates remain unmeasured.
625
- }
626
- }
663
+ const sampleDate = lookupDocumentTimestamp(document);
664
+ if (sampleDate)
665
+ sampleDates.push(sampleDate);
627
666
  traverse(document, store);
628
667
  }
629
668
  }
@@ -669,7 +708,7 @@ export function buildSourceReliabilityReport(raw, generatedAt = new Date().toISO
669
708
  return Object.freeze({
670
709
  schema: SOURCE_RELIABILITY_REPORT_SCHEMA,
671
710
  version: SOURCE_RELIABILITY_REPORT_VERSION,
672
- generatedAt: new Date(timestamp).toISOString(),
711
+ generatedAt: normalizedGeneratedAt,
673
712
  mode: 'offline_local',
674
713
  reportsMerged,
675
714
  documentsReviewed,
@@ -2,20 +2,40 @@ const MIN_TERMINAL_WIDTH = 40;
2
2
  const MAX_TERMINAL_WIDTH = 160;
3
3
  const DEFAULT_TERMINAL_WIDTH = 100;
4
4
  const ANSI_RESET = '\u001b[0m';
5
- const ANSI_BY_TONE = Object.freeze({
6
- accent: '\u001b[1;36m',
7
- danger: '\u001b[31m',
8
- dim: '\u001b[2m',
9
- info: '\u001b[36m',
10
- success: '\u001b[32m',
11
- warning: '\u001b[33m',
5
+ const ANSI_BY_PALETTE = Object.freeze({
6
+ auto: Object.freeze({
7
+ accent: '\u001b[1;36m',
8
+ danger: '\u001b[31m',
9
+ dim: '\u001b[2m',
10
+ info: '\u001b[36m',
11
+ success: '\u001b[32m',
12
+ warning: '\u001b[33m',
13
+ }),
14
+ light: Object.freeze({
15
+ accent: '\u001b[1;34m',
16
+ danger: '\u001b[31m',
17
+ dim: '\u001b[2;30m',
18
+ info: '\u001b[34m',
19
+ success: '\u001b[32m',
20
+ warning: '\u001b[33m',
21
+ }),
22
+ dark: Object.freeze({
23
+ accent: '\u001b[1;96m',
24
+ danger: '\u001b[91m',
25
+ dim: '\u001b[2m',
26
+ info: '\u001b[96m',
27
+ success: '\u001b[92m',
28
+ warning: '\u001b[93m',
29
+ }),
12
30
  });
13
31
  const SECTION_LABELS = new Set([
14
32
  'Collection',
33
+ 'DNS',
15
34
  'Discover',
16
35
  'Integrity and calibration',
17
36
  'Investigate',
18
37
  'Network',
38
+ 'Mail',
19
39
  'Quick start',
20
40
  'Registration',
21
41
  'Result',
@@ -24,6 +44,7 @@ const SECTION_LABELS = new Set([
24
44
  'Source health',
25
45
  'Target',
26
46
  'Terminal',
47
+ 'TLS and certificate',
27
48
  'Website and security',
28
49
  ]);
29
50
  function boundedTerminalWidth(value) {
@@ -32,7 +53,7 @@ function boundedTerminalWidth(value) {
32
53
  return DEFAULT_TERMINAL_WIDTH;
33
54
  return Math.max(MIN_TERMINAL_WIDTH, Math.min(MAX_TERMINAL_WIDTH, width));
34
55
  }
35
- function terminalPresentation(stream, requestedColor = true, environment = process.env) {
56
+ function terminalPresentation(stream, requestedColor = true, environment = process.env, palette = 'auto') {
36
57
  const interactive = stream?.isTTY === true
37
58
  && environment.TERM !== 'dumb'
38
59
  && !environment.CI;
@@ -42,11 +63,15 @@ function terminalPresentation(stream, requestedColor = true, environment = proce
42
63
  return Object.freeze({
43
64
  color,
44
65
  interactive,
66
+ palette,
45
67
  width: interactive ? boundedTerminalWidth(stream?.columns) : null,
46
68
  });
47
69
  }
48
- function tone(value, selectedTone, enabled) {
49
- return enabled ? `${ANSI_BY_TONE[selectedTone]}${value}${ANSI_RESET}` : value;
70
+ function tone(value, selectedTone, presentation) {
71
+ if (presentation === false)
72
+ return value;
73
+ const palette = presentation === true ? 'auto' : presentation;
74
+ return `${ANSI_BY_PALETTE[palette][selectedTone]}${value}${ANSI_RESET}`;
50
75
  }
51
76
  function statusTone(value) {
52
77
  const normalized = value.trim().toLowerCase();
@@ -107,32 +132,32 @@ function wrapTerminalOutput(value, width) {
107
132
  const lines = value.replace(/\n$/u, '').split('\n').flatMap((line) => wrapLine(line, width));
108
133
  return `${lines.join('\n')}${trailingNewline ? '\n' : ''}`;
109
134
  }
110
- function colorizeLine(line) {
135
+ function colorizeLine(line, palette) {
111
136
  const trimmed = line.trim();
112
137
  if (!trimmed)
113
138
  return line;
114
139
  const section = trimmed.endsWith(':') ? trimmed.slice(0, -1) : trimmed;
115
140
  if (SECTION_LABELS.has(section)) {
116
141
  const prefixLength = line.indexOf(trimmed);
117
- return `${line.slice(0, prefixLength)}${tone(trimmed, 'accent', true)}${line.slice(prefixLength + trimmed.length)}`;
142
+ return `${line.slice(0, prefixLength)}${tone(trimmed, 'accent', palette)}${line.slice(prefixLength + trimmed.length)}`;
118
143
  }
119
144
  if (trimmed.startsWith('✓'))
120
- return line.replace('✓', tone('✓', 'success', true));
145
+ return line.replace('✓', tone('✓', 'success', palette));
121
146
  if (trimmed.startsWith('!'))
122
- return line.replace('!', tone('!', 'danger', true));
147
+ return line.replace('!', tone('!', 'danger', palette));
123
148
  if (/^(?:Limitation|Access note|Boundary)\s{2,}/u.test(trimmed)) {
124
- return tone(line, 'dim', true);
149
+ return tone(line, 'dim', palette);
125
150
  }
126
151
  const bracket = line.match(/^(\s*)\[([^\]]+)\](.*)$/u);
127
152
  if (bracket) {
128
153
  const selectedTone = statusTone(bracket[2] || '') || 'info';
129
- return `${bracket[1]}${tone(`[${bracket[2]}]`, selectedTone, true)}${bracket[3]}`;
154
+ return `${bracket[1]}${tone(`[${bracket[2]}]`, selectedTone, palette)}${bracket[3]}`;
130
155
  }
131
156
  const keyValue = line.match(/^(\s*)(\S(?:.*?\S)?)(\s{2,})(\S.*)$/u);
132
157
  if (keyValue) {
133
158
  const value = keyValue[4] || '';
134
159
  const selectedTone = statusTone(value);
135
- return `${keyValue[1]}${tone(keyValue[2] || '', 'info', true)}${keyValue[3]}${selectedTone ? tone(value, selectedTone, true) : value}`;
160
+ return `${keyValue[1]}${tone(keyValue[2] || '', 'info', palette)}${keyValue[3]}${selectedTone ? tone(value, selectedTone, palette) : value}`;
136
161
  }
137
162
  return line;
138
163
  }
@@ -141,7 +166,7 @@ function presentTerminalOutput(value, presentation) {
141
166
  if (!presentation.color)
142
167
  return wrapped;
143
168
  const trailingNewline = wrapped.endsWith('\n');
144
- const lines = wrapped.replace(/\n$/u, '').split('\n').map(colorizeLine);
169
+ const lines = wrapped.replace(/\n$/u, '').split('\n').map((line) => colorizeLine(line, presentation.palette));
145
170
  return `${lines.join('\n')}${trailingNewline ? '\n' : ''}`;
146
171
  }
147
172
  export { presentTerminalOutput, terminalPresentation, tone, wrapTerminalOutput, };