@slicedearth/whoisleuth-cli 2.4.0 → 2.6.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 (340) hide show
  1. package/DISCLOSURE +1 -1
  2. package/NOTICE +2 -2
  3. package/cli/argument-values.mjs +55 -0
  4. package/cli/arguments.mjs +24 -470
  5. package/cli/artifact-validation/case-response.mjs +67 -9
  6. package/cli/artifact-validation/lookup-evidence.mjs +18 -4
  7. package/cli/artifact-validation/signed-review.mjs +61 -64
  8. package/cli/artifact-verify.mjs +11 -2
  9. package/cli/assurance-arguments.mjs +56 -0
  10. package/cli/assurance-command-definitions.mjs +179 -0
  11. package/cli/assurance-command-runner.mjs +1 -1
  12. package/cli/bounded-json.mjs +1 -1
  13. package/cli/bulk-checkpoint.mjs +1 -1
  14. package/cli/bulk-command-runner.mjs +6 -8
  15. package/cli/bulk.mjs +2 -3
  16. package/cli/case-command.mjs +99 -6
  17. package/cli/case-pack.mjs +19 -4
  18. package/cli/ci-report.mjs +6 -3
  19. package/cli/collection-arguments.mjs +123 -0
  20. package/cli/collection-command-definitions.mjs +330 -0
  21. package/cli/collection-preflight.mjs +8 -1
  22. package/cli/command-argument-grammar.mjs +8 -210
  23. package/cli/command-definition.mjs +330 -0
  24. package/cli/command-reference.mjs +55 -1388
  25. package/cli/command-seeds.mjs +18 -0
  26. package/cli/compare.mjs +1 -1
  27. package/cli/config-profile.mjs +1 -1
  28. package/cli/ct-event-intake.mjs +4 -4
  29. package/cli/discover.mjs +2 -2
  30. package/cli/discovery-observation-snapshot.mjs +2 -2
  31. package/cli/discovery-scan-command-runner.mjs +7 -9
  32. package/cli/discovery-scan.mjs +1 -1
  33. package/cli/discovery-snapshot.mjs +2 -2
  34. package/cli/discovery-workflow.mjs +4 -4
  35. package/cli/domain-control-monitor.mjs +6 -6
  36. package/cli/domain-control-observations.mjs +2 -1
  37. package/cli/domain-feed.mjs +61 -0
  38. package/cli/errors.mjs +5 -6
  39. package/cli/evidence-arguments.mjs +46 -0
  40. package/cli/evidence-command-definitions.mjs +69 -0
  41. package/cli/evidence-command-runner.mjs +2 -3
  42. package/cli/evidence-signing.mjs +3 -2
  43. package/cli/external-observation-mapping.mjs +2 -2
  44. package/cli/formatters/json.mjs +1 -13
  45. package/cli/formatters/terminal-command-formats.mjs +6 -0
  46. package/cli/formatters/terminal-lookup.mjs +6 -0
  47. package/cli/history-arguments.mjs +39 -0
  48. package/cli/history-command-definitions.mjs +88 -0
  49. package/cli/indicator-set-command.mjs +32 -0
  50. package/cli/inline-command-families.mjs +6 -47
  51. package/cli/inline-command-runner.mjs +4 -31
  52. package/cli/input.mjs +2 -2
  53. package/cli/intake-command.mjs +147 -0
  54. package/cli/interchange-report.mjs +4 -2
  55. package/cli/investigation-artifacts.mjs +3 -4
  56. package/cli/investigation-package-review.mjs +2 -0
  57. package/cli/investigation-plan.mjs +32 -154
  58. package/cli/investigation-recipes.mjs +177 -0
  59. package/cli/investigation-run.mjs +78 -12
  60. package/cli/local-mmdb-review.mjs +220 -17
  61. package/cli/lookalike-calibration.mjs +1 -1
  62. package/cli/lookup-brief.mjs +2 -2
  63. package/cli/lookup-command-runner.mjs +19 -35
  64. package/cli/lookup-diff.mjs +10 -9
  65. package/cli/lookup-plan.mjs +10 -14
  66. package/cli/lookup-reconcile.mjs +6 -5
  67. package/cli/lookup-timeline.mjs +7 -6
  68. package/cli/mail-header-review.mjs +24 -247
  69. package/cli/mail-review.mjs +9 -7
  70. package/cli/network-arguments.mjs +51 -0
  71. package/cli/network-command-definitions.mjs +130 -0
  72. package/cli/network-command-runner.mjs +43 -31
  73. package/cli/offline-evidence-review.mjs +85 -6
  74. package/cli/posture.mjs +1 -1
  75. package/cli/registry-fixture-scaffold.mjs +1 -1
  76. package/cli/retained-artifact-diff.mjs +8 -7
  77. package/cli/review-arguments.mjs +132 -0
  78. package/cli/review-command-definitions.mjs +279 -0
  79. package/cli/review-command-runner.mjs +72 -159
  80. package/cli/risk-calibration.mjs +14 -12
  81. package/cli/runner.mjs +20 -32
  82. package/cli/saved-lookup.mjs +2 -2
  83. package/cli/selected-input-worker.mjs +73 -0
  84. package/cli/sharing-review.mjs +5 -64
  85. package/cli/source-reliability.mjs +2 -2
  86. package/cli/support-arguments.mjs +49 -0
  87. package/cli/support-command-definitions.mjs +209 -0
  88. package/cli/support-command-runner.mjs +4 -4
  89. package/cli/watchlist-review.mjs +43 -0
  90. package/cli/workflow-arguments.mjs +100 -0
  91. package/cli/workflow-command-definitions.mjs +103 -0
  92. package/cli/workflow-command-runner.mjs +40 -1
  93. package/docs/cli-reference.md +27 -0
  94. package/docs/cli.md +322 -4
  95. package/docs/contextual-reviews.md +178 -0
  96. package/docs/infrastructure-observations.md +126 -0
  97. package/docs/offline-intake-context.md +213 -0
  98. package/lib/analyst-taxonomy.mjs +2 -40
  99. package/lib/application-version.mjs +1 -1
  100. package/lib/availability.mjs +13 -789
  101. package/lib/bounded-contract-normalizers.mjs +4 -0
  102. package/lib/bounded-json.mjs +2 -330
  103. package/lib/browser-library-profile.mjs +62 -84
  104. package/lib/candidate-provenance-bounds.mjs +2 -0
  105. package/lib/classify.mjs +1 -1
  106. package/lib/credential-form-attribution.mjs +136 -0
  107. package/lib/credential-surface-profile.mjs +6 -2
  108. package/lib/ct-query.mjs +2 -27
  109. package/lib/ct-response-bounds.mjs +2 -9
  110. package/lib/ct-search.mjs +83 -47
  111. package/lib/dmarc-discovery.mjs +62 -0
  112. package/lib/dns-convergence-review.mjs +21 -8
  113. package/lib/dns-delegation-health.mjs +35 -15
  114. package/lib/dns-inheritance-review.mjs +53 -63
  115. package/lib/dns-intelligence.mjs +27 -11
  116. package/lib/dns-query-coverage.mjs +40 -0
  117. package/lib/domain-assurance.mjs +1 -1
  118. package/lib/domain-change-packet.mjs +12 -13
  119. package/lib/domain-change-review.mjs +32 -14
  120. package/lib/domain-control-flight-recorder.mjs +103 -27
  121. package/lib/domain-control-manifest.mjs +25 -17
  122. package/lib/domain-posture-analysis.mjs +75 -7
  123. package/lib/domain-posture-parsers.mjs +9 -0
  124. package/lib/domain-posture.mjs +45 -25
  125. package/lib/domain-registration.mjs +392 -0
  126. package/lib/evidence-export-privacy.mjs +27 -17
  127. package/lib/evidence-export.mjs +3 -3
  128. package/lib/evidence-report.mjs +5 -1
  129. package/lib/external-intelligence-risk.mjs +2 -124
  130. package/lib/favicon.mjs +13 -4
  131. package/lib/generated/cisa-kev-catalog.mjs +41 -3
  132. package/lib/generated/registrar-standing-catalogue.mjs +18 -10
  133. package/lib/generated/retire-browser-catalog.mjs +332 -19
  134. package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
  135. package/lib/html-signals.mjs +13 -2
  136. package/lib/http-evidence-bounds.mjs +2 -8
  137. package/lib/http-intelligence.mjs +13 -1
  138. package/lib/idn-confusable-policy.mjs +6 -4
  139. package/lib/idn-confusables.mjs +1 -1
  140. package/lib/interchange-fidelity-registry.mjs +10 -0
  141. package/lib/lookup-child-profile-contract.mjs +28 -11
  142. package/lib/lookup-contract-primitives.mjs +1 -1
  143. package/lib/lookup-diagnostics.mjs +1 -23
  144. package/lib/lookup-network-evidence-bounds.mjs +1 -10
  145. package/lib/lookup-progress.mjs +50 -1
  146. package/lib/lookup-response-contract.mjs +70 -40
  147. package/lib/lookup-response.mjs +5 -15
  148. package/lib/lookup-source-progress.mjs +8 -22
  149. package/lib/lookup.mjs +1 -1
  150. package/lib/operation-budget.mjs +4 -0
  151. package/lib/opportunity-scoring.mjs +2 -107
  152. package/lib/outbound-identity.mjs +1 -1
  153. package/lib/page-fingerprints.mjs +18 -1
  154. package/lib/perceptual-hash-comparison.mjs +2 -34
  155. package/lib/perceptual-hash.mjs +36 -10
  156. package/lib/portable-generator.mjs +2 -29
  157. package/lib/project-metadata.mjs +2 -8
  158. package/lib/rdap-client.mjs +11 -7
  159. package/lib/registrable-domain.mjs +2 -56
  160. package/lib/registrar-standing-contract.mjs +1 -1
  161. package/lib/registrar-standing.mjs +9 -13
  162. package/lib/registry-capabilities.mjs +2 -2
  163. package/lib/registry-capability-catalogue.mjs +27 -19
  164. package/lib/risk-calibration-summary.mjs +2 -352
  165. package/lib/risk-scoring.mjs +2 -345
  166. package/lib/scoring-evidence-quality.mjs +2 -107
  167. package/lib/semantic-version.mjs +2 -54
  168. package/lib/service-binding-dns.mjs +46 -2
  169. package/lib/smtp-transport-review.mjs +48 -19
  170. package/lib/sslbl-certificates.generated.mjs +142 -139
  171. package/lib/static-html-analysis.mjs +32 -4
  172. package/lib/structured-data-identity.mjs +1 -1
  173. package/lib/technology-evidence-role.mjs +1 -51
  174. package/lib/threat-intelligence-manifests.mjs +1 -1
  175. package/lib/threat-intelligence-runtime.mjs +1 -1
  176. package/lib/threat-intelligence-targets.mjs +1 -1
  177. package/lib/threat-intelligence-types.mjs +2 -75
  178. package/lib/tls-intelligence.mjs +30 -5
  179. package/lib/typosquat-generator.mjs +11 -2
  180. package/lib/website-enrichment.mjs +404 -0
  181. package/lib/whois-authority.mjs +43 -5
  182. package/lib/whois-chain.mjs +12 -3
  183. package/lib/whois-common-formats.mjs +3 -1
  184. package/lib/whois-contacts.mjs +13 -2
  185. package/lib/whois-normalization.mjs +3 -1
  186. package/lib/whois-parser.mjs +5 -3
  187. package/package.json +15 -14
  188. package/packages/analysis/analyst-taxonomy.mjs +40 -0
  189. package/packages/analysis/bounded-json.mjs +330 -0
  190. package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
  191. package/packages/analysis/cli-argument-grammar.mjs +216 -0
  192. package/packages/analysis/ct-query.mjs +27 -0
  193. package/packages/analysis/ct-response-bounds.mjs +9 -0
  194. package/packages/analysis/external-intelligence-risk.mjs +124 -0
  195. package/packages/analysis/http-evidence-bounds.mjs +8 -0
  196. package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
  197. package/packages/analysis/opportunity-scoring.mjs +107 -0
  198. package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
  199. package/packages/analysis/portable-generator.mjs +29 -0
  200. package/packages/analysis/project-metadata.mjs +8 -0
  201. package/packages/analysis/registrable-domain.mjs +56 -0
  202. package/packages/analysis/risk-calibration-summary.mjs +352 -0
  203. package/packages/analysis/risk-scoring.mjs +348 -0
  204. package/packages/analysis/scoring-evidence-quality.mjs +107 -0
  205. package/packages/analysis/semantic-version.mjs +54 -0
  206. package/packages/analysis/threat-intelligence-types.mjs +75 -0
  207. package/packages/cases/case-containment-handoff.mjs +195 -0
  208. package/packages/cases/case-evidence-links.mjs +151 -0
  209. package/packages/cases/case-evidence-model.mjs +89 -12
  210. package/packages/cases/case-evidence-quality.mjs +5 -0
  211. package/packages/cases/case-export-input.mjs +7 -5
  212. package/packages/cases/case-incident-context.mjs +52 -0
  213. package/packages/cases/case-markdown.mjs +11 -0
  214. package/packages/cases/case-migration-model.mjs +40 -24
  215. package/packages/cases/case-object-coverage.mjs +33 -0
  216. package/packages/cases/case-packet-correction.mjs +106 -0
  217. package/packages/cases/case-recheck-model.mjs +124 -36
  218. package/packages/cases/case-record-contracts.mjs +1 -1
  219. package/packages/cases/case-record-core.mjs +1 -1
  220. package/packages/cases/case-record-operations.mjs +111 -118
  221. package/packages/cases/case-record-projection.mjs +23 -4
  222. package/packages/cases/case-record-version-input.mjs +41 -0
  223. package/packages/cases/case-report.mjs +93 -27
  224. package/packages/cases/case-requested-evidence.mjs +146 -0
  225. package/packages/cases/case-response-actions.mjs +159 -1
  226. package/packages/cases/case-response-model.mjs +16 -1
  227. package/packages/cases/case-response-object.mjs +119 -0
  228. package/packages/cases/case-response-outcomes.mjs +108 -20
  229. package/packages/cases/case-response-packet-render.mjs +35 -12
  230. package/packages/cases/case-response-packet.mjs +55 -13
  231. package/packages/cases/case-response-records.mjs +0 -1
  232. package/packages/cases/case-response-review-inputs.mjs +60 -8
  233. package/packages/cases/case-storage-model.mjs +1 -1
  234. package/packages/cases/case-workflow-metadata.mjs +217 -0
  235. package/packages/cases/case-workflow-migration.mjs +43 -0
  236. package/packages/cases/http-summary.mjs +6 -1
  237. package/packages/comparison/favicon-similarity.mjs +1 -1
  238. package/packages/comparison/page-similarity.mjs +1 -1
  239. package/packages/comparison/relationship-evidence.mjs +21 -9
  240. package/packages/comparison/relationship-provenance.mjs +7 -1
  241. package/packages/contracts/analyst-interchange.mjs +35 -15
  242. package/packages/contracts/candidate-watch-review.mjs +54 -0
  243. package/packages/contracts/case-portability.mjs +360 -49
  244. package/packages/contracts/cli-command-semantics.mjs +5 -0
  245. package/packages/contracts/cli-grammar.mjs +6 -0
  246. package/packages/contracts/cli-lookup-brief.mjs +2 -0
  247. package/packages/contracts/context-review.mjs +25 -0
  248. package/packages/contracts/document-intake.mjs +11 -0
  249. package/packages/contracts/domain-control-flight-recorder.mjs +30 -14
  250. package/packages/contracts/domain-control-manifest.mjs +1 -1
  251. package/packages/contracts/domain-control-monitor.mjs +13 -2
  252. package/packages/contracts/domain-control-review.mjs +36 -15
  253. package/packages/contracts/external-observation-interchange.mjs +96 -28
  254. package/packages/contracts/har-review.mjs +2 -0
  255. package/packages/contracts/identity-events.mjs +4 -0
  256. package/packages/contracts/investigation-run.mjs +2 -0
  257. package/packages/contracts/ip-address.mjs +10 -0
  258. package/packages/contracts/lookup-evidence.mjs +3 -2
  259. package/packages/contracts/mail-authentication.mjs +5 -0
  260. package/packages/contracts/message-intake.mjs +37 -0
  261. package/packages/contracts/offline-comparison.mjs +4 -1
  262. package/packages/contracts/passive-mail.mjs +10 -0
  263. package/packages/contracts/risk-calibration.mjs +44 -29
  264. package/packages/contracts/schema-lifecycle.mjs +3 -1
  265. package/packages/contracts/web-capture.mjs +2 -2
  266. package/packages/contracts/workspace-portability.mjs +144 -28
  267. package/packages/evidence/collection-quality.mjs +67 -0
  268. package/packages/evidence/credential-form-attribution.mjs +60 -0
  269. package/packages/evidence/dns-query-outcome.mjs +72 -0
  270. package/packages/evidence/email-recipient.mjs +18 -0
  271. package/packages/evidence/image-regions.mjs +15 -0
  272. package/packages/evidence/lookup-target.mjs +5 -1
  273. package/packages/evidence/observation.mjs +2 -0
  274. package/packages/evidence/png-pixels.mjs +117 -0
  275. package/packages/evidence/public-address-policy.mjs +83 -0
  276. package/packages/evidence/record-identity.mjs +60 -16
  277. package/packages/evidence/technology-evidence-role.mjs +51 -0
  278. package/packages/interchange/defensive-indicator-export.mjs +264 -0
  279. package/packages/interchange/external-findings-import.mjs +36 -29
  280. package/packages/interchange/json-output.mjs +14 -0
  281. package/packages/interchange/managed-indicator-export.mjs +36 -0
  282. package/packages/interchange/managed-indicator-set.mjs +223 -0
  283. package/packages/interchange/misp-indicator-export.mjs +93 -0
  284. package/packages/interchange/sharing-policy.mjs +56 -0
  285. package/packages/interchange/stix-indicator-export.mjs +129 -0
  286. package/packages/interchange/web-capture-import.mjs +22 -15
  287. package/packages/investigation/acquisition-decision-packet.mjs +79 -1
  288. package/packages/investigation/bulk-mail-exposure.mjs +7 -13
  289. package/packages/investigation/capture-coverage.mjs +57 -0
  290. package/packages/investigation/connector-provenance-review.mjs +100 -0
  291. package/packages/investigation/context-review.mjs +35 -0
  292. package/packages/investigation/document-intake.mjs +87 -0
  293. package/packages/investigation/docx-intake.mjs +133 -0
  294. package/packages/investigation/domain-history-review.mjs +132 -0
  295. package/packages/investigation/domain-transition-review.mjs +49 -0
  296. package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
  297. package/packages/investigation/har-intake.mjs +68 -0
  298. package/packages/investigation/identity-event-intake.mjs +131 -0
  299. package/packages/investigation/identity-incident-review.mjs +39 -0
  300. package/packages/investigation/incident-sequence-review.mjs +58 -0
  301. package/packages/investigation/infrastructure-observation.mjs +234 -0
  302. package/packages/investigation/intake-context.mjs +284 -0
  303. package/packages/investigation/intake-indicators.mjs +116 -0
  304. package/packages/investigation/intake-phones.mjs +155 -0
  305. package/packages/investigation/intake-report.mjs +20 -0
  306. package/packages/investigation/investigation-manifest.mjs +35 -9
  307. package/packages/investigation/link-intake.mjs +209 -0
  308. package/packages/investigation/lookup-asset-graph.mjs +1 -1
  309. package/packages/investigation/mail-authentication-review.mjs +222 -0
  310. package/packages/investigation/mail-header-identity.mjs +209 -0
  311. package/packages/investigation/message-intake.mjs +235 -0
  312. package/packages/investigation/page-behaviour.mjs +113 -0
  313. package/packages/investigation/pdf-intake.mjs +155 -0
  314. package/packages/investigation/platform-continuity-review.mjs +60 -0
  315. package/packages/investigation/qr-intake.mjs +56 -0
  316. package/packages/investigation/requested-action-hints.mjs +11 -0
  317. package/packages/investigation/selected-input-review.mjs +17 -0
  318. package/packages/investigation/storefront-review.mjs +70 -0
  319. package/packages/monitoring/domain-feed.mjs +321 -0
  320. package/packages/workspace/brand-candidate-workflow.mjs +351 -0
  321. package/packages/workspace/brand-keyword-campaign.mjs +231 -0
  322. package/packages/workspace/brand-profile-model.mjs +48 -4
  323. package/packages/workspace/bulk-review-model.mjs +28 -26
  324. package/packages/workspace/bulk-session-model.mjs +27 -13
  325. package/packages/workspace/campaign-model.mjs +1 -1
  326. package/packages/workspace/candidate-watch-handoff.mjs +195 -0
  327. package/packages/workspace/detection-rule-model.mjs +11 -3
  328. package/packages/workspace/hostile-input.mjs +1 -1
  329. package/packages/workspace/investigation-guide.mjs +2 -2
  330. package/packages/workspace/investigation-template-model.mjs +26 -2
  331. package/packages/workspace/page-baseline.mjs +2 -2
  332. package/packages/workspace/relationship-observation-model.mjs +17 -13
  333. package/packages/workspace/shortlist-model.mjs +24 -7
  334. package/packages/workspace/watchlist-history.mjs +59 -16
  335. package/packages/workspace/watchlist-store.mjs +152 -11
  336. package/packages/workspace/website-snapshot-model.mjs +28 -3
  337. package/packages/workspace/workspace-archive-crypto.mjs +1 -1
  338. package/packages/workspace/workspace-archive.mjs +5 -4
  339. package/third-party-notices.txt +549 -16
  340. package/lib/hostname.mjs +0 -2
@@ -1,4 +1,5 @@
1
1
  import { DOMAIN_CONTROL_MANIFEST_COMPATIBILITY } from "../packages/contracts/domain-control-manifest.mjs";
2
+ import { MANAGED_INDICATOR_SET_COMPATIBILITY } from "../packages/contracts/analyst-interchange.mjs";
2
3
  import { CLI_CASE_PACK_COMPATIBILITY, ENCRYPTED_WORKSPACE_ARCHIVE_COMPATIBILITY, WORKSPACE_ARCHIVE_COMPATIBILITY, } from "../packages/contracts/case-portability.mjs";
3
4
  import { LOOKUP_CLAIM_PASSPORT_COMPATIBILITY } from "../packages/contracts/investigation-portability.mjs";
4
5
  import { BRAND_PROFILE_EXPORT_COMPATIBILITY } from "../packages/contracts/workspace-portability.mjs";
@@ -16,6 +17,15 @@ function compatibilityReference(descriptor) {
16
17
  });
17
18
  }
18
19
  export const INTERCHANGE_ARTIFACT_CONTRACTS = Object.freeze([
20
+ Object.freeze({
21
+ id: 'managed_indicators', ...compatibilityReference(MANAGED_INDICATOR_SET_COMPATIBILITY),
22
+ versionField: 'version', nestedSchemaPath: Object.freeze([]),
23
+ browser: Object.freeze({ import: 'supported', export: 'supported' }),
24
+ cli: Object.freeze({ read: 'supported', write: 'supported', verify: 'supported' }),
25
+ fidelity: 'semantic_exact_after_normalisation', requiredAssurance: 'whole_integrity',
26
+ preservedFieldGroups: Object.freeze(['stable_indicator_identities', 'original_observations', 'review_windows', 'withdrawal_records', 'preceding_revision_digest']),
27
+ excludedFieldGroups: Object.freeze(['raw_responses', 'browser_local_records', 'credentials', 'remote_publication', 'author_authentication', 'unavailable_revision_history']),
28
+ }),
19
29
  Object.freeze({
20
30
  id: 'lookup_claim_passport',
21
31
  ...compatibilityReference(LOOKUP_CLAIM_PASSPORT_COMPATIBILITY),
@@ -8,6 +8,7 @@ import { MAX_LOOKUP_TLS_ALT_NAMES, MAX_LOOKUP_TLS_CERTIFICATE_POLICIES, MAX_LOOK
8
8
  import { isJsonObject, normalizedDomain, validBoundedString, validHttpProvenanceUrl, validObservationDiagnostics, validOptionalNullableText, validStringArray, validUint, } from "./lookup-contract-primitives.mjs";
9
9
  import { TECHNOLOGY_EVIDENCE_ROLE_ORDER } from "./technology-evidence-role.mjs";
10
10
  import { MAX_HOMEPAGE_BYTES } from "./outbound-request-bounds.mjs";
11
+ import { CREDENTIAL_CATEGORY_NAMES, MAX_CREDENTIAL_FORMS, MAX_CREDENTIAL_INPUTS, externalPasswordFormObservation, validCredentialFormAttribution } from "../packages/evidence/credential-form-attribution.mjs";
11
12
  export const TECHNOLOGY_PROFILE_VERSION = 12;
12
13
  export const SUPPORTED_TECHNOLOGY_PROFILE_VERSIONS = Object.freeze([10, 11, TECHNOLOGY_PROFILE_VERSION]);
13
14
  export const MAX_TECHNOLOGY_FINDINGS = 24;
@@ -17,7 +18,7 @@ export const BROWSER_LIBRARY_PROFILE_VERSION = 2;
17
18
  export const MAX_LIBRARY_FINDINGS = 16;
18
19
  export const WEBSITE_SECURITY_POSTURE_VERSION = 3;
19
20
  export const MAX_SECURITY_POSTURE_FINDINGS = 32;
20
- export const CREDENTIAL_SURFACE_PROFILE_VERSION = 1;
21
+ export const CREDENTIAL_SURFACE_PROFILE_VERSION = 2;
21
22
  export const STRUCTURED_DATA_IDENTITY_VERSION = 1;
22
23
  export const MAX_STRUCTURED_DATA_ENTITIES = 16;
23
24
  export const MAX_STRUCTURED_DATA_SAME_AS_HOSTS = 12;
@@ -66,7 +67,7 @@ const SIMHASH64_RE = /^[a-f0-9]{16}$/u;
66
67
  const OID_RE = /^\d{1,10}(?:\.\d{1,10}){1,31}$/u;
67
68
  const CREDENTIAL_METHOD_KEYS = Object.freeze(['missing', 'get', 'post', 'dialog', 'other']);
68
69
  const CREDENTIAL_ACTION_KEYS = Object.freeze(['sameOrigin', 'external', 'missing', 'cleartext', 'unclassified']);
69
- const CREDENTIAL_CATEGORY_KEYS = Object.freeze(['password', 'email', 'username', 'one_time_code', 'payment']);
70
+ const CREDENTIAL_CATEGORY_KEYS = CREDENTIAL_CATEGORY_NAMES;
70
71
  const TLS_NAME_KEYS = Object.freeze([
71
72
  'commonNames', 'organizations', 'organizationalUnits', 'countries', 'localities', 'states',
72
73
  ]);
@@ -683,28 +684,37 @@ function securityPostureContractState(value) {
683
684
  return 'supported';
684
685
  }
685
686
  function credentialSurfaceContractState(value) {
686
- const versionState = childVersionState(value, 'credentialSurfaceVersion', [CREDENTIAL_SURFACE_PROFILE_VERSION]);
687
+ const versionState = childVersionState(value, 'credentialSurfaceVersion', [1, CREDENTIAL_SURFACE_PROFILE_VERSION]);
687
688
  if (versionState !== 'supported')
688
689
  return versionState;
689
690
  const profile = value;
690
691
  const forms = profile.forms;
691
692
  const inputs = profile.inputs;
692
- if (!hasExactKeys(profile, [...OBSERVATION_FIELDS, 'credentialSurfaceVersion', 'forms', 'inputs'])
693
+ if (!hasExactKeys(profile, [...OBSERVATION_FIELDS, 'credentialSurfaceVersion', 'forms', 'inputs', ...(profile.credentialSurfaceVersion === 2 ? ['formAttribution'] : [])])
693
694
  || !validProfileObservation(profile, new Set(['success', 'partial']), new Set(['html']))
694
695
  || !isJsonObject(forms)
695
696
  || !hasExactKeys(forms, ['count', 'methods', 'actions'])
696
- || !validUint(forms.count, 50)
697
- || !validCredentialCountRecord(forms.methods, CREDENTIAL_METHOD_KEYS, 50)
698
- || !validCredentialCountRecord(forms.actions, CREDENTIAL_ACTION_KEYS, 50)
697
+ || !validUint(forms.count, MAX_CREDENTIAL_FORMS)
698
+ || !validCredentialCountRecord(forms.methods, CREDENTIAL_METHOD_KEYS, MAX_CREDENTIAL_FORMS)
699
+ || !validCredentialCountRecord(forms.actions, CREDENTIAL_ACTION_KEYS, MAX_CREDENTIAL_FORMS)
699
700
  || CREDENTIAL_METHOD_KEYS.reduce((sum, key) => sum + Number(forms.methods[key]), 0) !== forms.count
700
701
  || CREDENTIAL_ACTION_KEYS.reduce((sum, key) => sum + Number(forms.actions[key]), 0) !== forms.count
701
702
  || !isJsonObject(inputs)
702
703
  || !hasExactKeys(inputs, ['count', 'classifiedCount', 'categories'])
703
- || !validUint(inputs.count, 500)
704
- || !validUint(inputs.classifiedCount, 500)
704
+ || !validUint(inputs.count, MAX_CREDENTIAL_INPUTS)
705
+ || !validUint(inputs.classifiedCount, MAX_CREDENTIAL_INPUTS)
705
706
  || Number(inputs.classifiedCount) > Number(inputs.count)
706
- || !validCredentialCountRecord(inputs.categories, CREDENTIAL_CATEGORY_KEYS, 500))
707
+ || !validCredentialCountRecord(inputs.categories, CREDENTIAL_CATEGORY_KEYS, MAX_CREDENTIAL_INPUTS))
707
708
  return 'invalid';
709
+ if (profile.credentialSurfaceVersion === 2 && (!validCredentialFormAttribution(profile.formAttribution)
710
+ || profile.formAttribution.forms.length !== forms.count
711
+ || (profile.complete === true && !profile.formAttribution.complete)))
712
+ return 'invalid';
713
+ if (profile.credentialSurfaceVersion === 2 && validCredentialFormAttribution(profile.formAttribution)) {
714
+ const attribution = profile.formAttribution;
715
+ if (CREDENTIAL_CATEGORY_KEYS.some(key => attribution.forms.reduce((sum, form) => sum + form.categories[key], 0) > Number(inputs.categories[key])))
716
+ return 'invalid';
717
+ }
708
718
  return 'supported';
709
719
  }
710
720
  function structuredDataContractState(value) {
@@ -819,6 +829,13 @@ function sanitizeLookupChildProfiles(value) {
819
829
  replaceChild('tls', 'TLS profile', 'tls', tlsProfileContractState);
820
830
  replaceChild('securityPosture', 'Security-posture profile', 'derived', securityPostureContractState);
821
831
  replaceChild('credentialSurfaceProfile', 'Credential-surface profile', 'html', credentialSurfaceContractState);
832
+ if (availability.hasExternalPasswordForm !== undefined && availability.credentialSurfaceProfile != null) {
833
+ const credential = availability.credentialSurfaceProfile;
834
+ if (!isJsonObject(credential) || credential.credentialSurfaceVersion !== 2
835
+ || availability.hasExternalPasswordForm !== externalPasswordFormObservation(credential.formAttribution)) {
836
+ mutableAvailability().hasExternalPasswordForm = null;
837
+ }
838
+ }
822
839
  replaceChild('structuredDataIdentity', 'Structured-data identity profile', 'html', structuredDataContractState);
823
840
  replaceChild('pageRoleProfile', 'Page-role profile', 'derived', pageRoleContractState);
824
841
  replaceChild('clientBehaviorProfile', 'Client-behaviour profile', 'derived', clientBehaviorContractState);
@@ -864,4 +881,4 @@ function sanitizeLookupChildProfiles(value) {
864
881
  }
865
882
  return output;
866
883
  }
867
- export { sanitizeLookupChildProfiles, technologyProfileContractState, validSecurityPostureFinding, validTlsChainCertificate, validTlsFinding, };
884
+ export { sanitizeLookupChildProfiles, credentialSurfaceContractState, technologyProfileContractState, validSecurityPostureFinding, validTlsChainCertificate, validTlsFinding, };
@@ -2,7 +2,7 @@
2
2
  // child-profile contracts. These checks validate without normalising or
3
3
  // copying untrusted values.
4
4
  import { MAX_OBSERVATION_DIAGNOSTICS } from "../packages/evidence/observation.mjs";
5
- import { MAX_HTTP_PROVENANCE_URL } from "./http-evidence-bounds.mjs";
5
+ import { MAX_HTTP_PROVENANCE_URL } from "../packages/analysis/http-evidence-bounds.mjs";
6
6
  const LOOKUP_CONTROL_CHAR_RE = /[\u0000-\u001f\u007f-\u009f]|\p{Default_Ignorable_Code_Point}/u;
7
7
  const MAX_LOOKUP_CONTRACT_HOST_LENGTH = 253;
8
8
  function isJsonObject(value) {
@@ -1,31 +1,9 @@
1
1
  // Lookup diagnostics, bounded source errors and optional deep-lookup timing.
2
2
  import { OPERATION_BUDGET_ERROR_CODE } from "./operation-budget.mjs";
3
3
  import { FEATURE_DISABLED_ERROR_CODE } from "./feature-policy.mjs";
4
+ import { LOOKUP_SOURCE_STATES, lookupDiagnosticStates } from "./lookup-progress.mjs";
4
5
  const LOOKUP_DIAGNOSTICS_VERSION = 8;
5
6
  const LOOKUP_LEGACY_DIAGNOSTICS_VERSION = 7;
6
- const LOOKUP_SOURCE_STATES = Object.freeze([
7
- 'success', 'partial', 'not_found', 'skipped', 'error', 'unsupported',
8
- 'not_applicable', 'unavailable', 'rate_limited', 'complete', 'disabled', 'stale',
9
- ]);
10
- const SOURCE_STATES = new Set(LOOKUP_SOURCE_STATES);
11
- function lookupDiagnosticStates(value) {
12
- const record = (input) => input && typeof input === 'object' && !Array.isArray(input)
13
- ? input : {};
14
- const diagnostics = record(value);
15
- const candidates = {
16
- rdap: record(diagnostics.rdap).status,
17
- registrar_rdap: record(record(diagnostics.rdap).registrar).status,
18
- whois: record(diagnostics.whois).status,
19
- availability: record(diagnostics.availability).status,
20
- reverse_dns: record(diagnostics.reverseDns).status,
21
- network_context: record(diagnostics.network).status,
22
- security_txt: record(diagnostics.securityTxt).status,
23
- sslbl: record(diagnostics.sslbl).status,
24
- };
25
- return Object.freeze(Object.fromEntries(Object.entries(candidates).map(([source, state]) => [
26
- source, typeof state === 'string' && SOURCE_STATES.has(state) ? state : null,
27
- ])));
28
- }
29
7
  const LOOKUP_TIMING_VERSION = 1;
30
8
  const MAX_LOOKUP_TIMING_MS = 120_000;
31
9
  const LOOKUP_TIMING_SOURCE_ORDER = Object.freeze([
@@ -1,10 +1 @@
1
- // Browser-safe collection bounds shared by the network evidence producers,
2
- // the Lookup response trust boundary, and the display projection.
3
- export const MAX_LOOKUP_DNS_RECORDS_PER_TYPE = 16;
4
- export const MAX_LOOKUP_REVERSE_DNS_PTR_RECORDS = 8;
5
- export const TLS_PROFILE_VERSION = 3;
6
- export const MAX_LOOKUP_TLS_CHAIN_CERTIFICATES = 8;
7
- export const MAX_LOOKUP_TLS_ALT_NAMES = 50;
8
- export const MAX_LOOKUP_TLS_NAME_VALUES = 4;
9
- export const MAX_LOOKUP_TLS_FINDINGS = 5;
10
- export const MAX_LOOKUP_TLS_CERTIFICATE_POLICIES = 16;
1
+ export * from "../packages/analysis/lookup-network-evidence-bounds.mjs";
@@ -1,7 +1,7 @@
1
1
  // Portable bounded NDJSON Lookup progress contract. Partial events are
2
2
  // presentation-only; only a validated final ordinary Lookup result may cross
3
3
  // the persistence boundary.
4
- import { scanBoundedJson, MAX_BOUNDED_JSON_DEPTH, MAX_BOUNDED_JSON_KEYS, MAX_BOUNDED_JSON_VALUES } from "./bounded-json.mjs";
4
+ import { scanBoundedJson, MAX_BOUNDED_JSON_DEPTH, MAX_BOUNDED_JSON_KEYS, MAX_BOUNDED_JSON_VALUES } from "../packages/analysis/bounded-json.mjs";
5
5
  export const LOOKUP_PROGRESS_SCHEMA = 'whoisleuth.lookup-progress';
6
6
  export const LOOKUP_PROGRESS_VERSION = 1;
7
7
  export const MAX_LOOKUP_PROGRESS_SOURCES = 16;
@@ -32,7 +32,56 @@ const SOURCE_STATES = new Set([
32
32
  'unavailable',
33
33
  'rate_limited',
34
34
  ]);
35
+ // Final diagnostic states are distinct from the provisional progress subset.
36
+ export const LOOKUP_SOURCE_STATES = Object.freeze([
37
+ 'success', 'partial', 'not_found', 'skipped', 'error', 'unsupported',
38
+ 'not_applicable', 'unavailable', 'rate_limited', 'complete', 'disabled', 'stale',
39
+ ]);
40
+ const FINAL_SOURCE_STATES = new Set(LOOKUP_SOURCE_STATES);
41
+ export function lookupDiagnosticStates(value) {
42
+ const record = (input) => input && typeof input === 'object' && !Array.isArray(input)
43
+ ? input : {};
44
+ const diagnostics = record(value);
45
+ const candidates = {
46
+ rdap: record(diagnostics.rdap).status,
47
+ registrar_rdap: record(record(diagnostics.rdap).registrar).status,
48
+ whois: record(diagnostics.whois).status,
49
+ availability: record(diagnostics.availability).status,
50
+ reverse_dns: record(diagnostics.reverseDns).status,
51
+ network_context: record(diagnostics.network).status,
52
+ security_txt: record(diagnostics.securityTxt).status,
53
+ sslbl: record(diagnostics.sslbl).status,
54
+ };
55
+ return Object.freeze(Object.fromEntries(Object.entries(candidates).map(([source, state]) => [
56
+ source, typeof state === 'string' && FINAL_SOURCE_STATES.has(state) ? state : null,
57
+ ])));
58
+ }
35
59
  const SOURCE_ID_RE = /^[a-z][a-z0-9_]{0,39}$/u;
60
+ /** The existing Lookup recipe, shared by offline plans and source progress.
61
+ * This describes admitted families, not provider health or request counts. */
62
+ export function plannedLookupSources(targetType, mode, options = {}) {
63
+ const sources = ['rdap'];
64
+ if (mode === 'fast') {
65
+ if (targetType === 'domain')
66
+ sources.push('domain_evidence');
67
+ return Object.freeze(sources);
68
+ }
69
+ sources.push('whois');
70
+ if (targetType === 'domain') {
71
+ sources.push('domain_evidence', 'registrar_rdap', 'network_context');
72
+ if (options.securityTxt)
73
+ sources.push('security_txt');
74
+ if (options.externalIntelligence)
75
+ sources.push('external_intelligence');
76
+ if (options.malwareHostIntelligence)
77
+ sources.push('malware_host_intelligence');
78
+ if (options.malwareIocIntelligence)
79
+ sources.push('malware_ioc_intelligence');
80
+ }
81
+ else if (targetType === 'ipv4' || targetType === 'ipv6')
82
+ sources.push('reverse_dns');
83
+ return Object.freeze(sources);
84
+ }
36
85
  const TEXT_ENCODER = new TextEncoder();
37
86
  function byteLength(value) {
38
87
  return TEXT_ENCODER.encode(value).byteLength;
@@ -6,12 +6,13 @@
6
6
  // relies on before any evidence is derived or rendered. Nested source records
7
7
  // stay separately attributed and additive; their own normalizers remain the
8
8
  // authority for source-specific fields.
9
- import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_CATEGORIES, THREAT_INTELLIGENCE_CONFIDENCES, THREAT_INTELLIGENCE_ENVELOPE_VERSION, THREAT_INTELLIGENCE_RESULT_STATES, THREAT_INTELLIGENCE_SCHEMA, THREAT_INTELLIGENCE_SEVERITIES, } from "./threat-intelligence-types.mjs";
10
- import { MAX_HTTP_ATTEMPTS, MAX_HTTP_ERROR_LENGTH, MAX_HTTP_EVIDENCE_REDIRECTS, } from "./http-evidence-bounds.mjs";
9
+ import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_CATEGORIES, THREAT_INTELLIGENCE_CONFIDENCES, THREAT_INTELLIGENCE_ENVELOPE_VERSION, THREAT_INTELLIGENCE_RESULT_STATES, THREAT_INTELLIGENCE_SCHEMA, THREAT_INTELLIGENCE_SEVERITIES, } from "../packages/analysis/threat-intelligence-types.mjs";
10
+ import { MAX_HTTP_ATTEMPTS, MAX_HTTP_ERROR_LENGTH, MAX_HTTP_EVIDENCE_REDIRECTS, } from "../packages/analysis/http-evidence-bounds.mjs";
11
11
  import { MAX_OBSERVATION_LIMITATIONS, MAX_OBSERVATION_LIMITATION_LENGTH, normalizeExplicitIsoTimestamp, } from "../packages/evidence/observation.mjs";
12
- import { assertBoundedJsonStructure } from "./bounded-json.mjs";
13
- import { canonicalRegistrableDomain } from "./registrable-domain.mjs";
12
+ import { assertBoundedJsonStructure } from "../packages/analysis/bounded-json.mjs";
13
+ import { canonicalRegistrableDomain } from "../packages/analysis/registrable-domain.mjs";
14
14
  import { validLookupObservationScope } from "../packages/evidence/lookup-target.mjs";
15
+ import { isWebCollectionQuality } from "../packages/evidence/collection-quality.mjs";
15
16
  import { MAX_LOOKUP_DNS_RECORDS_PER_TYPE, MAX_LOOKUP_REVERSE_DNS_PTR_RECORDS, MAX_LOOKUP_TLS_ALT_NAMES, MAX_LOOKUP_TLS_CERTIFICATE_POLICIES, MAX_LOOKUP_TLS_CHAIN_CERTIFICATES, MAX_LOOKUP_TLS_FINDINGS, MAX_LOOKUP_TLS_NAME_VALUES, } from "./lookup-network-evidence-bounds.mjs";
16
17
  import { MAX_SECURITY_POSTURE_FINDINGS, sanitizeLookupChildProfiles, validSecurityPostureFinding, validTlsChainCertificate, validTlsFinding, } from "./lookup-child-profile-contract.mjs";
17
18
  import { validHttpDeliveryMetadata, validPagePublicationMetadata, } from "./homepage-metadata-contract.mjs";
@@ -136,22 +137,28 @@ function normalizeThreatFinding(value, providerId) {
136
137
  }
137
138
  function normalizeThreatProvider(value, expectedDomain) {
138
139
  const input = record(value);
140
+ const identity = record(input.provider);
141
+ const providerId = boundedThreatText(identity.id, 80);
142
+ const providerDefinition = providerId && Object.hasOwn(THREAT_INTELLIGENCE_PROVIDERS, providerId)
143
+ ? THREAT_INTELLIGENCE_PROVIDERS[providerId] : null;
144
+ const withheld = (reason) => ({ provider: null, withheld: {
145
+ providerId: providerDefinition ? providerId : null,
146
+ label: providerDefinition ? `${providerDefinition.label} record` : 'Unrecognised provider record',
147
+ count: 1, reason,
148
+ } });
139
149
  if (input.schema !== THREAT_INTELLIGENCE_SCHEMA
140
150
  || input.version !== THREAT_INTELLIGENCE_CONTRACT_VERSION)
141
- return null;
151
+ return withheld('Unsupported provider format; evidence was withheld.');
142
152
  const target = record(input.target);
143
153
  if (target.type !== 'domain'
144
154
  || target.exposure !== 'registrable_domain'
145
155
  || target.value !== expectedDomain)
146
- return null;
147
- const identity = record(input.provider);
148
- const providerId = boundedThreatText(identity.id, 80);
149
- const providerDefinition = providerId ? THREAT_INTELLIGENCE_PROVIDERS[providerId] : null;
156
+ return withheld('The provider target did not match this Lookup; evidence was withheld.');
150
157
  if (!providerId || !providerDefinition)
151
- return null;
158
+ return withheld('The provider identity was not recognised; evidence was withheld.');
152
159
  const state = boundedThreatText(input.state, 32);
153
160
  if (!state || !THREAT_INTELLIGENCE_STATES.has(state))
154
- return null;
161
+ return withheld('The provider result state was not supported; evidence was withheld.');
155
162
  const observationInput = record(input.observation);
156
163
  const limitations = Array.isArray(observationInput.limitations)
157
164
  ? observationInput.limitations
@@ -177,35 +184,40 @@ function normalizeThreatProvider(value, expectedDomain) {
177
184
  ? `${omitted} invalid or over-limit provider finding${omitted === 1 ? ' was' : 's were'} omitted from this view.`
178
185
  : 'Provider findings were not a supported array and were withheld from this view.');
179
186
  }
180
- return {
181
- schema: THREAT_INTELLIGENCE_SCHEMA,
182
- version: THREAT_INTELLIGENCE_CONTRACT_VERSION,
183
- provider: {
184
- id: providerId,
185
- label: providerDefinition.label,
186
- },
187
- target: {
188
- type: 'domain',
189
- value: expectedDomain,
190
- exposure: 'registrable_domain',
191
- },
192
- state: projectionIncomplete && ['success', 'not_found'].includes(state) ? 'partial' : state,
193
- detail: boundedThreatText(input.detail),
194
- findings,
195
- observation: {
196
- observedAt: threatTimestamp(observationInput.observedAt),
197
- limitations: limitations.slice(0, MAX_THREAT_INTELLIGENCE_LIMITATIONS),
198
- complete: projectionIncomplete ? false : typeof observationInput.complete === 'boolean' ? observationInput.complete : null,
199
- truncated: projectionTruncated ? true : typeof observationInput.truncated === 'boolean' ? observationInput.truncated : null,
200
- },
201
- };
187
+ return { withheld: null, provider: {
188
+ schema: THREAT_INTELLIGENCE_SCHEMA,
189
+ version: THREAT_INTELLIGENCE_CONTRACT_VERSION,
190
+ provider: {
191
+ id: providerId,
192
+ label: providerDefinition.label,
193
+ },
194
+ target: {
195
+ type: 'domain',
196
+ value: expectedDomain,
197
+ exposure: 'registrable_domain',
198
+ },
199
+ state: projectionIncomplete && ['success', 'not_found'].includes(state) ? 'partial' : state,
200
+ detail: boundedThreatText(input.detail),
201
+ findings,
202
+ observation: {
203
+ observedAt: threatTimestamp(observationInput.observedAt),
204
+ limitations: limitations.slice(0, MAX_THREAT_INTELLIGENCE_LIMITATIONS),
205
+ complete: projectionIncomplete ? false : typeof observationInput.complete === 'boolean' ? observationInput.complete : null,
206
+ truncated: projectionTruncated ? true : typeof observationInput.truncated === 'boolean' ? observationInput.truncated : null,
207
+ },
208
+ } };
202
209
  }
203
210
  function projectThreatProviders(values, expectedDomain) {
204
211
  const grouped = new Map();
212
+ const withheld = [];
213
+ const unexamined = Math.max(0, values.length - MAX_THREAT_INTELLIGENCE_PROVIDERS * 2);
205
214
  for (const value of values.slice(0, MAX_THREAT_INTELLIGENCE_PROVIDERS * 2)) {
206
- const provider = normalizeThreatProvider(value, expectedDomain);
207
- if (!provider)
215
+ const projection = normalizeThreatProvider(value, expectedDomain);
216
+ if (projection.withheld) {
217
+ withheld.push(projection.withheld);
208
218
  continue;
219
+ }
220
+ const provider = projection.provider;
209
221
  const providerId = String(record(provider.provider).id || '');
210
222
  if (!providerId)
211
223
  continue;
@@ -218,20 +230,25 @@ function projectThreatProviders(values, expectedDomain) {
218
230
  grouped.set(providerId, { first: provider, signatures: new Set([signature]) });
219
231
  }
220
232
  }
221
- return [...grouped.values()].slice(0, MAX_THREAT_INTELLIGENCE_PROVIDERS).map(({ first, signatures }) => (signatures.size === 1
233
+ if (unexamined)
234
+ withheld.push({ providerId: null, label: 'Additional provider records', count: unexamined,
235
+ reason: 'The provider-record review bound was exceeded; these records were not examined.' });
236
+ const withheldIds = new Set(withheld.map((item) => item.providerId).filter(Boolean));
237
+ const providers = [...grouped.values()].slice(0, MAX_THREAT_INTELLIGENCE_PROVIDERS).map(({ first, signatures }) => (signatures.size === 1 && !withheldIds.has(String(record(first.provider).id)) && unexamined === 0
222
238
  ? first
223
239
  : {
224
240
  ...first,
225
241
  state: 'partial',
226
- detail: 'Conflicting records were returned for this provider; no conclusive provider projection is available.',
242
+ detail: 'Conflicting or unvalidated provider records prevent a conclusive provider projection.',
227
243
  findings: [],
228
244
  observation: {
229
245
  observedAt: null,
230
- limitations: ['Conflicting records for the same provider identity were withheld rather than resolved by array order.'],
246
+ limitations: ['Conflicting or unvalidated records were withheld rather than resolved by array order.'],
231
247
  complete: false,
232
248
  truncated: null,
233
249
  },
234
250
  }));
251
+ return { providers, withheld };
235
252
  }
236
253
  function optionalBoundedText(value, maxLength) {
237
254
  return value === undefined || (typeof value === 'string'
@@ -624,10 +641,12 @@ function validAvailabilityScalars(value) {
624
641
  && validOptionalNullableText(value.pageTitle, MAX_LOOKUP_RESPONSE_PAGE_TITLE_LENGTH)
625
642
  && validOptionalNullableText(value.dnssec, 40)
626
643
  && (value.deepScanComplete === undefined || typeof value.deepScanComplete === 'boolean')
644
+ && (value.webCollectionQuality === undefined || isWebCollectionQuality(value.webCollectionQuality))
627
645
  && (value.hasMx === undefined || value.hasMx === null || typeof value.hasMx === 'boolean')
628
646
  && (value.hasNullMx === undefined || value.hasNullMx === null || typeof value.hasNullMx === 'boolean')
629
647
  && (value.hasSpf === undefined || value.hasSpf === null || typeof value.hasSpf === 'boolean')
630
648
  && (value.hasDmarc === undefined || value.hasDmarc === null || typeof value.hasDmarc === 'boolean')
649
+ && (value.hasExternalPasswordForm === undefined || value.hasExternalPasswordForm === null || typeof value.hasExternalPasswordForm === 'boolean')
631
650
  && validOptionalStringArray(value.mxHosts, MAX_LOOKUP_DNS_RECORDS_PER_TYPE, 253);
632
651
  }
633
652
  function validLookupDomainIdentity(value) {
@@ -860,6 +879,7 @@ function parseCompactLookupHttpResponse(value, expectedDomain) {
860
879
  || typeof availability.confidence !== 'string'
861
880
  || !COMPACT_CONFIDENCE_LEVELS.has(availability.confidence)
862
881
  || (availability.deepScanComplete !== undefined && typeof availability.deepScanComplete !== 'boolean')
882
+ || (availability.webCollectionQuality !== undefined && !isWebCollectionQuality(availability.webCollectionQuality))
863
883
  || (availability.bulkComparison !== undefined
864
884
  && !validCompactBulkComparison(availability.bulkComparison))
865
885
  || compactPageIdentity !== null && Object.hasOwn(compactPageIdentity, 'publicationMetadata')
@@ -968,11 +988,20 @@ function createLookupViewModel(response) {
968
988
  ? canonicalRegistrableDomain(response.registrableDomain)
969
989
  ?? canonicalRegistrableDomain(availability.domain)
970
990
  : null;
971
- const providers = expectedThreatDomain
991
+ const threatProjection = expectedThreatDomain
972
992
  && rawThreatIntelligence.version === THREAT_INTELLIGENCE_ENVELOPE_VERSION
973
993
  && Array.isArray(rawThreatIntelligence.providers)
974
994
  ? projectThreatProviders(rawThreatIntelligence.providers, expectedThreatDomain)
975
- : [];
995
+ : { providers: [], withheld: Object.keys(rawThreatIntelligence).length ? [{
996
+ providerId: null,
997
+ label: 'External-intelligence data',
998
+ count: Array.isArray(rawThreatIntelligence.providers) ? rawThreatIntelligence.providers.length : null,
999
+ reason: !expectedThreatDomain ? 'External intelligence was not bound to a domain Lookup and was withheld.'
1000
+ : rawThreatIntelligence.version !== THREAT_INTELLIGENCE_ENVELOPE_VERSION
1001
+ ? 'The external-intelligence envelope uses an unsupported version; its records were withheld.'
1002
+ : 'External-intelligence records were not a supported array and were withheld.',
1003
+ }] : [] };
1004
+ const { providers, withheld } = threatProjection;
976
1005
  const threatIntelligence = providers.length
977
1006
  ? { version: THREAT_INTELLIGENCE_ENVELOPE_VERSION, providers }
978
1007
  : {};
@@ -1008,6 +1037,7 @@ function createLookupViewModel(response) {
1008
1037
  sslbl,
1009
1038
  threatIntelligence,
1010
1039
  threatIntelligenceProviders: providers,
1040
+ threatIntelligenceWithheld: withheld,
1011
1041
  dnsEvidence,
1012
1042
  dnsRecords: record(dnsEvidence.records),
1013
1043
  httpEvidence,
@@ -1,11 +1,12 @@
1
1
  // Lookup result shaping after the source plan has settled. This module starts no
2
2
  // collection and preserves compact, diagnostics and enrichment response shapes.
3
+ import { withoutHttpDeliveryMetadata } from "./http-intelligence.mjs";
3
4
  import { buildBulkComparisonEvidence } from "./bulk-comparison-evidence.mjs";
4
5
  import { failedReverseDnsIntelligence, skippedReverseDnsIntelligence, } from "./dns-intelligence.mjs";
5
6
  import { LOOKUP_DIAGNOSTICS_VERSION, LOOKUP_ERROR_CODES, LOOKUP_LEGACY_DIAGNOSTICS_VERSION, boundedSourceDetail, } from "./lookup-diagnostics.mjs";
6
7
  import { createThreatIntelligenceResult } from "./threat-intelligence-contract.mjs";
7
8
  import { THREATFOX_PROVIDER, URLHAUS_PROVIDER, URLSCAN_PROVIDER, } from "./lookup-threat-provider-inventory.mjs";
8
- import { THREAT_INTELLIGENCE_ENVELOPE_VERSION } from "./threat-intelligence-types.mjs";
9
+ import { THREAT_INTELLIGENCE_ENVELOPE_VERSION } from "../packages/analysis/threat-intelligence-types.mjs";
9
10
  import { registryAccessDiagnosticFor } from "./registry-capabilities.mjs";
10
11
  import { buildRegistryInsights } from "./registry-insights.mjs";
11
12
  import { buildRegistrarStanding } from "./registrar-standing.mjs";
@@ -20,17 +21,6 @@ function withoutNestedPublicationMetadata(value) {
20
21
  delete output.publicationMetadata;
21
22
  return output;
22
23
  }
23
- function withoutNestedDeliveryMetadata(value) {
24
- if (!value || typeof value !== 'object' || Array.isArray(value))
25
- return value;
26
- const output = { ...value };
27
- if (output.response && typeof output.response === 'object' && !Array.isArray(output.response)) {
28
- const response = { ...output.response };
29
- delete response.deliveryMetadata;
30
- output.response = response;
31
- }
32
- return output;
33
- }
34
24
  async function buildUnifiedLookupResponse(context) {
35
25
  const { availabilityEnabled, availabilityPromise, classified, compact, dnsIntelligenceEnabled, fast, inspectCertificateWarningData, networkContextPromise, options, rdapEnabled, rdapPromise, registrarRdapPromise, reverseDnsEligible, reverseDnsPromise, securityTxtPromise, skipWhois, threatfoxIntelligencePromise, timing, urlhausIntelligencePromise, urlscanIntelligencePromise, whoisEnabled, whoisPromise, } = context;
36
26
  const [rdapResult, whoisResult, availabilityResult, reverseDnsResult, registrarRdapResult, networkContextResult, securityTxtResult, urlscanIntelligenceResult, urlhausIntelligenceResult, threatfoxIntelligenceResult] = await Promise.allSettled([
@@ -100,7 +90,7 @@ async function buildUnifiedLookupResponse(context) {
100
90
  whois = { skipped: true, detail: 'WHOIS is omitted in fast RDAP-only mode.' };
101
91
  }
102
92
  else if (Array.isArray(whoisChain)) {
103
- whois = { chain: whoisChain, parsed: parseWhoisChain(whoisChain) };
93
+ whois = { chain: whoisChain, parsed: parseWhoisChain(whoisChain, classified.type === 'domain' ? classified.registrableDomain ?? classified.value : undefined) };
104
94
  }
105
95
  else {
106
96
  whois = {
@@ -154,7 +144,7 @@ async function buildUnifiedLookupResponse(context) {
154
144
  ? 'skipped'
155
145
  : whoisResult.status === 'rejected'
156
146
  ? 'error'
157
- : whoisCollectionStatus(whoisChain);
147
+ : whoisCollectionStatus(whoisChain, classified.type === 'domain' ? classified.registrableDomain ?? classified.value : undefined);
158
148
  const availabilityStatus = classified.type !== 'domain'
159
149
  ? 'not_applicable'
160
150
  : !availabilityEnabled ? 'disabled'
@@ -298,7 +288,7 @@ async function buildUnifiedLookupResponse(context) {
298
288
  availability: {
299
289
  ...compactAvailability,
300
290
  ...(richPageIdentity !== undefined ? { pageIdentity: withoutNestedPublicationMetadata(richPageIdentity) } : {}),
301
- ...(richHttp !== undefined ? { http: withoutNestedDeliveryMetadata(richHttp) } : {}),
291
+ ...(richHttp !== undefined ? { http: withoutHttpDeliveryMetadata(richHttp) } : {}),
302
292
  ...(bulkComparison ? { bulkComparison } : {}),
303
293
  },
304
294
  diagnostics,
@@ -3,7 +3,8 @@
3
3
  // provider payloads, and request errors. The ordinary final Lookup response
4
4
  // remains the only authoritative and persistable result.
5
5
  import { whoisCollectionStatus } from "./whois-authority.mjs";
6
- import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_RESULT_STATES, THREAT_INTELLIGENCE_SCHEMA, } from "./threat-intelligence-types.mjs";
6
+ import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_RESULT_STATES, THREAT_INTELLIGENCE_SCHEMA, } from "../packages/analysis/threat-intelligence-types.mjs";
7
+ import { plannedLookupSources } from "./lookup-progress.mjs";
7
8
  const RESULT_STATES = new Set([
8
9
  'available',
9
10
  'expiring',
@@ -38,28 +39,13 @@ function sourceTruncated(value) {
38
39
  .some(([key, field]) => field === true && (key === 'truncated' || key.endsWith('Truncated'))));
39
40
  }
40
41
  function plannedLookupProgressSources(classified, options = {}) {
41
- const sources = ['rdap', 'whois'];
42
- if (classified.type === 'domain') {
43
- sources.push('domain_evidence', 'registrar_rdap', 'network_context');
44
- if (options.securityTxt)
45
- sources.push('security_txt');
46
- if (options.externalIntelligence)
47
- sources.push('external_intelligence');
48
- if (options.malwareHostIntelligence)
49
- sources.push('malware_host_intelligence');
50
- if (options.malwareIocIntelligence)
51
- sources.push('malware_ioc_intelligence');
52
- }
53
- else if (classified.type === 'ipv4' || classified.type === 'ipv6') {
54
- sources.push('reverse_dns');
55
- }
56
- return Object.freeze(sources);
42
+ return plannedLookupSources(classified.type, 'deep', options);
57
43
  }
58
- function normalizedState(source, outcome, value) {
44
+ function normalizedState(source, outcome, value, requestedDomain, requested = true) {
59
45
  if (outcome === 'rejected')
60
46
  return 'error';
61
47
  if (value === null || value === undefined)
62
- return 'skipped';
48
+ return source === 'rdap' && requested ? 'unsupported' : 'skipped';
63
49
  if (source === 'rdap') {
64
50
  const rdap = record(value);
65
51
  if (rdap.upstreamStatus === 404)
@@ -69,7 +55,7 @@ function normalizedState(source, outcome, value) {
69
55
  return sourceTruncated(rdap) ? 'partial' : 'success';
70
56
  }
71
57
  if (source === 'whois') {
72
- const status = whoisCollectionStatus(value);
58
+ const status = whoisCollectionStatus(value, requestedDomain);
73
59
  return status === 'complete' ? 'success' : status;
74
60
  }
75
61
  if (source === 'domain_evidence') {
@@ -94,8 +80,8 @@ function normalizedState(source, outcome, value) {
94
80
  return 'skipped';
95
81
  return 'error';
96
82
  }
97
- function normalizeLookupSourceSettlement(source, outcome, value) {
98
- const state = normalizedState(source, outcome, value);
83
+ function normalizeLookupSourceSettlement(source, outcome, value, requestedDomain, requested = true) {
84
+ const state = normalizedState(source, outcome, value, requestedDomain, requested);
99
85
  const sourceRecord = record(value);
100
86
  const threatObservation = THREAT_INTELLIGENCE_SOURCES.has(source)
101
87
  ? record(sourceRecord.observation)
package/lib/lookup.mjs CHANGED
@@ -170,7 +170,7 @@ async function runUnifiedLookup(classified, options = {}) {
170
170
  ]);
171
171
  const notify = (source, outcome, value) => {
172
172
  try {
173
- options.onSourceSettled?.(normalizeLookupSourceSettlement(source, outcome, value));
173
+ options.onSourceSettled?.(normalizeLookupSourceSettlement(source, outcome, value, classified.type === 'domain' ? classified.registrableDomain ?? classified.value : undefined, source !== 'rdap' || rdapEnabled));
174
174
  }
175
175
  catch {
176
176
  // Presentation callbacks must never change evidence collection.
@@ -34,6 +34,7 @@ const OPERATION_FEATURES = Object.freeze({
34
34
  AVAILABILITY_FAST: 'availability_fast',
35
35
  AVAILABILITY_DEEP: 'availability_deep',
36
36
  CERTIFICATE_TRANSPARENCY: 'certificate_transparency',
37
+ DOMAIN_FEED_SEARCH: 'domain_feed_search',
37
38
  DOMAIN_POSTURE: 'domain_posture',
38
39
  });
39
40
  const OPERATION_FEATURE_CLASSES = Object.freeze({
@@ -47,6 +48,7 @@ const OPERATION_FEATURE_CLASSES = Object.freeze({
47
48
  [OPERATION_FEATURES.AVAILABILITY_FAST]: OPERATION_CLASSES.REGISTRY_LIGHT,
48
49
  [OPERATION_FEATURES.AVAILABILITY_DEEP]: OPERATION_CLASSES.REGISTRY_DEEP,
49
50
  [OPERATION_FEATURES.CERTIFICATE_TRANSPARENCY]: OPERATION_CLASSES.CERTIFICATE_SEARCH,
51
+ [OPERATION_FEATURES.DOMAIN_FEED_SEARCH]: OPERATION_CLASSES.CERTIFICATE_SEARCH,
50
52
  [OPERATION_FEATURES.DOMAIN_POSTURE]: OPERATION_CLASSES.POSTURE_AUDIT,
51
53
  });
52
54
  // The browser currently runs at most 12 fast Bulk workers, 4 deep Bulk
@@ -87,6 +89,8 @@ function operationFeatureFor(feature, { fast = false, compact = false } = {}) {
87
89
  return OPERATION_FEATURES.WHOIS;
88
90
  if (feature === 'certificate_transparency')
89
91
  return OPERATION_FEATURES.CERTIFICATE_TRANSPARENCY;
92
+ if (feature === 'domain_feed_search')
93
+ return OPERATION_FEATURES.DOMAIN_FEED_SEARCH;
90
94
  if (feature === 'domain_posture')
91
95
  return OPERATION_FEATURES.DOMAIN_POSTURE;
92
96
  return null;