@slicedearth/whoisleuth-cli 2.3.0 → 2.4.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 (264) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +19 -15
  3. package/bin/whoisleuth.mjs +8 -1
  4. package/cli/archive-content-digest.mjs +14 -0
  5. package/cli/archive-inspect.mjs +20 -53
  6. package/cli/arguments.mjs +68 -6
  7. package/cli/artifact-validation/case-response.mjs +47 -41
  8. package/cli/artifact-validation/investigation-capsule.mjs +52 -31
  9. package/cli/artifact-validation/lookup-evidence.mjs +12 -6
  10. package/cli/artifact-validation/signed-review.mjs +83 -61
  11. package/cli/artifact-validation/structure-primitives.mjs +1 -105
  12. package/cli/artifact-verify.mjs +90 -24
  13. package/cli/assurance-command-runner.mjs +39 -4
  14. package/cli/bagit-folder.mjs +57 -0
  15. package/cli/bagit-review.mjs +23 -0
  16. package/cli/bounded-json.mjs +1 -1
  17. package/cli/bulk-command-runner.mjs +2 -0
  18. package/cli/bulk-output.mjs +29 -3
  19. package/cli/case-command.mjs +195 -0
  20. package/cli/case-pack.mjs +39 -31
  21. package/cli/command-argument-grammar.mjs +4 -0
  22. package/cli/command-reference.mjs +145 -75
  23. package/cli/completion.mjs +298 -79
  24. package/cli/config-profile.mjs +7 -5
  25. package/cli/ct-event-intake.mjs +12 -12
  26. package/cli/discover.mjs +4 -3
  27. package/cli/discovery-observation-snapshot.mjs +44 -16
  28. package/cli/discovery-scan-command-runner.mjs +2 -0
  29. package/cli/discovery-scan.mjs +7 -4
  30. package/cli/discovery-workflow.mjs +1 -1
  31. package/cli/domain-control-monitor.mjs +50 -20
  32. package/cli/domain-control-observations.mjs +149 -102
  33. package/cli/errors.mjs +33 -1
  34. package/cli/evidence-command-runner.mjs +16 -5
  35. package/cli/evidence-signing.mjs +2 -2
  36. package/cli/external-observation-mapping.mjs +7 -8
  37. package/cli/formatters/terminal-lookup.mjs +18 -46
  38. package/cli/formatters/terminal-shared.mjs +5 -3
  39. package/cli/formatters/terminal-technology.mjs +114 -0
  40. package/cli/inline-command-families.mjs +1 -0
  41. package/cli/interactive-launcher.mjs +2 -160
  42. package/cli/interchange-report.mjs +4 -8
  43. package/cli/investigation-artifacts.mjs +176 -0
  44. package/cli/investigation-checkpoint.mjs +10 -0
  45. package/cli/investigation-folder.mjs +114 -0
  46. package/cli/investigation-manifest.mjs +1 -112
  47. package/cli/investigation-package-review.mjs +103 -0
  48. package/cli/investigation-plan.mjs +32 -19
  49. package/cli/investigation-run.mjs +188 -33
  50. package/cli/local-document-checkpoint.mjs +170 -0
  51. package/cli/lookup-browser.mjs +5 -12
  52. package/cli/lookup-command-runner.mjs +17 -6
  53. package/cli/lookup-diff.mjs +37 -3
  54. package/cli/lookup-plan.mjs +11 -5
  55. package/cli/lookup-reconcile.mjs +10 -8
  56. package/cli/mail-review.mjs +66 -14
  57. package/cli/manual.mjs +1 -0
  58. package/cli/network-command-runner.mjs +2 -0
  59. package/cli/output-file.mjs +31 -5
  60. package/cli/registry-doctor.mjs +3 -1
  61. package/cli/registry-support.mjs +1 -1
  62. package/cli/retained-artifact-diff.mjs +2 -2
  63. package/cli/review-command-runner.mjs +45 -0
  64. package/cli/runner.mjs +55 -6
  65. package/cli/sharing-review.mjs +21 -34
  66. package/cli/signer-trust.mjs +104 -0
  67. package/cli/source-reliability.mjs +74 -83
  68. package/cli/terminal-input.mjs +168 -0
  69. package/cli/workflow-command-runner.mjs +68 -13
  70. package/docs/cli-reference.md +76 -30
  71. package/docs/cli.md +289 -25
  72. package/lib/abort.mjs +4 -1
  73. package/lib/availability.mjs +161 -112
  74. package/lib/bounded-file.mjs +3 -1
  75. package/lib/bounded-html-document.mjs +125 -0
  76. package/lib/bounded-json.mjs +43 -3
  77. package/lib/browser-library-profile.mjs +42 -51
  78. package/lib/browser-library-worker.mjs +112 -0
  79. package/lib/client-behavior-profile.mjs +3 -3
  80. package/lib/ct-search.mjs +11 -4
  81. package/lib/dns-delegation-health.mjs +24 -14
  82. package/lib/dns-inheritance-review.mjs +217 -0
  83. package/lib/dns-intelligence.mjs +36 -23
  84. package/lib/dns-mx.mjs +1 -1
  85. package/lib/dnssec-evidence-validation.mjs +11 -3
  86. package/lib/domain-change-packet.mjs +7 -2
  87. package/lib/domain-change-review.mjs +14 -8
  88. package/lib/domain-control-flight-recorder.mjs +219 -118
  89. package/lib/domain-control-manifest.mjs +117 -57
  90. package/lib/domain-posture-analysis.mjs +20 -7
  91. package/lib/domain-posture-parsers.mjs +88 -6
  92. package/lib/domain-posture.mjs +113 -26
  93. package/lib/evidence-export-privacy.mjs +561 -0
  94. package/lib/evidence-export.mjs +11 -592
  95. package/lib/evidence-report.mjs +3 -1
  96. package/lib/favicon.mjs +117 -41
  97. package/lib/generated/cisa-kev-catalog.mjs +12 -4
  98. package/lib/generated/registrar-standing-catalogue.mjs +7 -7
  99. package/lib/generated/retire-browser-catalog.mjs +2335 -940
  100. package/lib/hostname.mjs +2 -38
  101. package/lib/html-signals.mjs +82 -85
  102. package/lib/http-evidence-bounds.mjs +2 -1
  103. package/lib/http-intelligence.mjs +39 -26
  104. package/lib/lookup-cache.mjs +34 -11
  105. package/lib/lookup-child-profile-contract.mjs +24 -15
  106. package/lib/lookup-diagnostics.mjs +24 -1
  107. package/lib/lookup-progress.mjs +30 -13
  108. package/lib/lookup-response-contract.mjs +1048 -0
  109. package/lib/lookup-response.mjs +7 -10
  110. package/lib/lookup-source-progress.mjs +17 -14
  111. package/lib/lookup.mjs +48 -27
  112. package/lib/observed-network-context.mjs +43 -27
  113. package/lib/outbound-request-bounds.mjs +4 -1
  114. package/lib/page-fingerprints.mjs +69 -133
  115. package/lib/page-role-profile.mjs +12 -8
  116. package/lib/perceptual-hash.mjs +43 -14
  117. package/lib/rdap-bootstrap.mjs +72 -22
  118. package/lib/rdap-client.mjs +15 -7
  119. package/lib/rdap-normalization.mjs +33 -18
  120. package/lib/rdap-search-workbench.mjs +1 -1
  121. package/lib/rdap-transport.mjs +47 -20
  122. package/lib/rdap-types.mjs +2 -1
  123. package/lib/rdap.mjs +18 -3
  124. package/lib/registrable-domain.mjs +21 -1
  125. package/lib/registrar-standing-contract.mjs +1 -0
  126. package/lib/registry-capabilities.mjs +5 -1
  127. package/lib/registry-capability-catalogue.mjs +2 -1
  128. package/lib/registry-dates.mjs +2 -190
  129. package/lib/registry-insights.mjs +56 -18
  130. package/lib/response-policy.mjs +86 -48
  131. package/lib/risk-calibration-summary.mjs +17 -3
  132. package/lib/rpki-evidence.mjs +8 -5
  133. package/lib/safe-fetch.mjs +54 -47
  134. package/lib/security-txt.mjs +10 -7
  135. package/lib/service-binding-dns.mjs +8 -0
  136. package/lib/smtp-transport-review.mjs +27 -15
  137. package/lib/sslbl-certificates.generated.mjs +136 -135
  138. package/lib/static-html-analysis.mjs +117 -146
  139. package/lib/structured-data-identity.mjs +1 -0
  140. package/lib/technology-markup.mjs +100 -0
  141. package/lib/threat-intelligence-manifests.mjs +11 -139
  142. package/lib/threat-intelligence-runtime.mjs +24 -52
  143. package/lib/threat-intelligence-targets.mjs +6 -34
  144. package/lib/threat-intelligence-types.mjs +39 -0
  145. package/lib/typosquat-generator.mjs +19 -15
  146. package/lib/urlhaus-intelligence.mjs +8 -3
  147. package/lib/urlscan-intelligence.mjs +1 -1
  148. package/lib/website-security-posture.mjs +30 -6
  149. package/lib/website-technology.mjs +111 -71
  150. package/lib/whois-authority.mjs +14 -2
  151. package/lib/whois-chain.mjs +9 -2
  152. package/lib/whois-common-formats.mjs +5 -5
  153. package/lib/whois-contracts.mjs +2 -1
  154. package/lib/whois-dialects.mjs +2 -2
  155. package/lib/whois-parser-support.mjs +8 -1
  156. package/lib/whois-transport.mjs +23 -7
  157. package/package.json +2 -1
  158. package/packages/cases/case-attachment-model.mjs +83 -0
  159. package/packages/cases/case-evidence-model.mjs +156 -62
  160. package/packages/cases/case-export-input.mjs +53 -0
  161. package/packages/cases/case-migration-model.mjs +59 -25
  162. package/packages/cases/case-model.mjs +1 -0
  163. package/packages/cases/case-recheck-model.mjs +91 -0
  164. package/packages/cases/case-record-core.mjs +3 -2
  165. package/packages/cases/case-record-operations.mjs +89 -27
  166. package/packages/cases/case-record-projection.mjs +23 -20
  167. package/packages/cases/case-report.mjs +63 -52
  168. package/packages/cases/case-response-actions.mjs +654 -0
  169. package/packages/cases/case-response-model.mjs +41 -1129
  170. package/packages/cases/case-response-outcomes.mjs +317 -0
  171. package/packages/cases/case-response-packet-render.mjs +153 -0
  172. package/packages/cases/case-response-packet-types.mjs +1 -0
  173. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  174. package/packages/cases/case-response-packet.mjs +38 -587
  175. package/packages/cases/case-response-records.mjs +94 -0
  176. package/packages/cases/case-response-review-inputs.mjs +371 -0
  177. package/packages/cases/case-response-values.mjs +85 -0
  178. package/packages/cases/case-review-package.mjs +45 -0
  179. package/packages/cases/case-selection.mjs +46 -0
  180. package/packages/cases/case-storage-model.mjs +25 -0
  181. package/packages/cases/response-route-freshness.mjs +16 -0
  182. package/packages/comparison/capture-context.mjs +61 -0
  183. package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
  184. package/packages/comparison/page-similarity.mjs +2 -1
  185. package/packages/comparison/relationship-evidence.mjs +85 -8
  186. package/packages/comparison/relationship-provenance.mjs +93 -0
  187. package/packages/contracts/analyst-interchange.mjs +216 -0
  188. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  189. package/packages/contracts/analyst-review-state.mjs +5 -4
  190. package/packages/contracts/case-portability.mjs +279 -116
  191. package/packages/contracts/case-views-contract.mjs +12 -0
  192. package/packages/contracts/case-views.mjs +26 -0
  193. package/packages/contracts/cli-command-semantics.mjs +1 -0
  194. package/packages/contracts/cli-lookup.mjs +0 -1
  195. package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
  196. package/packages/contracts/domain-control-manifest.mjs +127 -73
  197. package/packages/contracts/domain-control-monitor.mjs +48 -45
  198. package/packages/contracts/domain-control-review.mjs +179 -121
  199. package/packages/contracts/domain-name.mjs +21 -0
  200. package/packages/contracts/external-observation-interchange.mjs +2 -1
  201. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  202. package/packages/contracts/http-url.mjs +2 -0
  203. package/packages/contracts/investigation-package-limits.mjs +24 -0
  204. package/packages/contracts/investigation-portability.mjs +56 -27
  205. package/packages/contracts/investigation-run.mjs +16 -0
  206. package/packages/contracts/ip-address.mjs +34 -0
  207. package/packages/contracts/lookup-evidence.mjs +35 -0
  208. package/packages/contracts/offline-comparison.mjs +1 -2
  209. package/packages/contracts/page-fingerprints.mjs +15 -0
  210. package/packages/contracts/risk-calibration.mjs +0 -1
  211. package/packages/contracts/schema-compatibility.mjs +1 -1
  212. package/packages/contracts/schema-lifecycle.mjs +83 -153
  213. package/packages/contracts/selected-file-limits.mjs +6 -0
  214. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  215. package/packages/contracts/web-capture.mjs +10 -0
  216. package/packages/contracts/workspace-portability.mjs +160 -225
  217. package/packages/evidence/artifact-integrity.mjs +8 -2
  218. package/packages/evidence/artifact-structure.mjs +105 -0
  219. package/packages/evidence/decision-fact.mjs +4 -1
  220. package/packages/evidence/domain-control-runtime.mjs +155 -27
  221. package/packages/evidence/domain-posture-context.mjs +79 -0
  222. package/packages/evidence/image-regions.mjs +56 -0
  223. package/packages/evidence/latest-observations.mjs +28 -0
  224. package/packages/evidence/lookup-target.mjs +104 -0
  225. package/packages/evidence/observation.mjs +16 -1
  226. package/packages/evidence/passphrase-encryption.mjs +44 -0
  227. package/packages/evidence/record-identity.mjs +72 -0
  228. package/packages/evidence/registry-dates.mjs +190 -0
  229. package/packages/evidence/retained-file.mjs +69 -0
  230. package/packages/evidence/review-age.mjs +14 -0
  231. package/packages/interchange/bagit.mjs +342 -0
  232. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  233. package/packages/interchange/crc32.mjs +17 -0
  234. package/packages/interchange/external-findings-import.mjs +89 -49
  235. package/packages/interchange/web-capture-import.mjs +382 -0
  236. package/packages/investigation/bulk-mail-exposure.mjs +2 -1
  237. package/packages/investigation/investigation-bagit.mjs +14 -0
  238. package/packages/investigation/investigation-capsule.mjs +33 -5
  239. package/packages/investigation/investigation-manifest.mjs +200 -0
  240. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  241. package/packages/investigation/investigation-package.mjs +200 -0
  242. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  243. package/packages/investigation/lookup-asset-graph.mjs +161 -141
  244. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  245. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  246. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  247. package/packages/monitoring/analyst-review-state.mjs +38 -90
  248. package/packages/workspace/brand-profile-model.mjs +158 -20
  249. package/packages/workspace/bulk-columns.mjs +17 -0
  250. package/packages/workspace/bulk-review-model.mjs +11 -5
  251. package/packages/workspace/bulk-session-model.mjs +95 -20
  252. package/packages/workspace/bulk-sort.mjs +1 -0
  253. package/packages/workspace/case-views.mjs +102 -0
  254. package/packages/workspace/hostile-input.mjs +29 -8
  255. package/packages/workspace/investigation-template-model.mjs +19 -4
  256. package/packages/workspace/page-baseline.mjs +18 -15
  257. package/packages/workspace/relationship-observation-model.mjs +41 -26
  258. package/packages/workspace/watchlist-history.mjs +2 -2
  259. package/packages/workspace/watchlist-store.mjs +8 -2
  260. package/packages/workspace/website-snapshot-model.mjs +70 -30
  261. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  262. package/packages/workspace/workspace-archive.mjs +85 -21
  263. package/third-party-notices.txt +29 -2
  264. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -1,19 +1,21 @@
1
1
  // Browser-safe version and collection boundaries shared by Lookup child
2
2
  // profile producers and the client response trust boundary.
3
3
  import { MAX_OBSERVATION_LIMITATIONS, MAX_OBSERVATION_LIMITATION_LENGTH, OBSERVATION_VERSION, normalizeExplicitIsoTimestamp, } from "../packages/evidence/observation.mjs";
4
- import { PAGE_FINGERPRINT_VERSION, PAGE_IDENTITY_VERSION, } from "../packages/contracts/workspace-portability.mjs";
4
+ import { PAGE_FINGERPRINT_PARSERS, PAGE_IDENTITY_VERSION, } from "../packages/contracts/workspace-portability.mjs";
5
5
  import { validPagePublicationMetadata } from "./homepage-metadata-contract.mjs";
6
+ import { PAGE_FINGERPRINT_TOKEN_LIMITS } from "../packages/contracts/page-fingerprints.mjs";
6
7
  import { MAX_LOOKUP_TLS_ALT_NAMES, MAX_LOOKUP_TLS_CERTIFICATE_POLICIES, MAX_LOOKUP_TLS_CHAIN_CERTIFICATES, MAX_LOOKUP_TLS_FINDINGS, MAX_LOOKUP_TLS_NAME_VALUES, TLS_PROFILE_VERSION, } from "./lookup-network-evidence-bounds.mjs";
7
8
  import { isJsonObject, normalizedDomain, validBoundedString, validHttpProvenanceUrl, validObservationDiagnostics, validOptionalNullableText, validStringArray, validUint, } from "./lookup-contract-primitives.mjs";
8
9
  import { TECHNOLOGY_EVIDENCE_ROLE_ORDER } from "./technology-evidence-role.mjs";
9
- export const TECHNOLOGY_PROFILE_VERSION = 11;
10
- export const SUPPORTED_TECHNOLOGY_PROFILE_VERSIONS = Object.freeze([10, TECHNOLOGY_PROFILE_VERSION]);
10
+ import { MAX_HOMEPAGE_BYTES } from "./outbound-request-bounds.mjs";
11
+ export const TECHNOLOGY_PROFILE_VERSION = 12;
12
+ export const SUPPORTED_TECHNOLOGY_PROFILE_VERSIONS = Object.freeze([10, 11, TECHNOLOGY_PROFILE_VERSION]);
11
13
  export const MAX_TECHNOLOGY_FINDINGS = 24;
12
14
  export const MAX_EVIDENCE_PER_TECHNOLOGY = 4;
13
15
  export const MAX_TECHNOLOGY_EVIDENCE_DESCRIPTION_LENGTH = 180;
14
16
  export const BROWSER_LIBRARY_PROFILE_VERSION = 2;
15
17
  export const MAX_LIBRARY_FINDINGS = 16;
16
- export const WEBSITE_SECURITY_POSTURE_VERSION = 2;
18
+ export const WEBSITE_SECURITY_POSTURE_VERSION = 3;
17
19
  export const MAX_SECURITY_POSTURE_FINDINGS = 32;
18
20
  export const CREDENTIAL_SURFACE_PROFILE_VERSION = 1;
19
21
  export const STRUCTURED_DATA_IDENTITY_VERSION = 1;
@@ -169,6 +171,7 @@ function validFingerprintIdentifier(value) {
169
171
  && /^[A-Z0-9-]{1,64}$/u.test(value.value);
170
172
  }
171
173
  function validPageFingerprintProfile(value) {
174
+ const tokenLimit = PAGE_FINGERPRINT_TOKEN_LIMITS[value.fingerprintVersion];
172
175
  const normalizedHtml = value.normalizedHtml;
173
176
  const visibleText = value.visibleText;
174
177
  const domStructure = value.domStructure;
@@ -181,7 +184,7 @@ function validPageFingerprintProfile(value) {
181
184
  || normalizedHtml.algorithm !== 'sha256'
182
185
  || typeof normalizedHtml.value !== 'string'
183
186
  || !SHA256_RE.test(normalizedHtml.value)
184
- || !validUint(normalizedHtml.tokenCount, 4_096)
187
+ || !validUint(normalizedHtml.tokenCount, tokenLimit)
185
188
  || typeof normalizedHtml.truncated !== 'boolean'
186
189
  || !(visibleText === null || isJsonObject(visibleText)
187
190
  && hasExactKeys(visibleText, ['algorithm', 'value', 'tokenCount', 'featureCount', 'truncated'])
@@ -197,16 +200,16 @@ function validPageFingerprintProfile(value) {
197
200
  || domStructure.algorithm !== 'sha256'
198
201
  || typeof domStructure.value !== 'string'
199
202
  || !SHA256_RE.test(domStructure.value)
200
- || !validUint(domStructure.nodeCount, 4_096)
201
- || domStructure.parser !== 'static-tag-sequence-v1'
203
+ || !validUint(domStructure.nodeCount, tokenLimit)
204
+ || domStructure.parser !== PAGE_FINGERPRINT_PARSERS[value.fingerprintVersion]
202
205
  || typeof domStructure.truncated !== 'boolean'
203
206
  || !(domStructure.similarity === undefined || domStructure.similarity === null || isJsonObject(domStructure.similarity)
204
207
  && hasExactKeys(domStructure.similarity, ['algorithm', 'value', 'tokenCount', 'featureCount', 'truncated'])
205
208
  && domStructure.similarity.algorithm === 'simhash64-v1'
206
209
  && typeof domStructure.similarity.value === 'string'
207
210
  && SIMHASH64_RE.test(domStructure.similarity.value)
208
- && validUint(domStructure.similarity.tokenCount, 4_096)
209
- && validUint(domStructure.similarity.featureCount, 4_096)
211
+ && validUint(domStructure.similarity.tokenCount, tokenLimit)
212
+ && validUint(domStructure.similarity.featureCount, tokenLimit)
210
213
  && typeof domStructure.similarity.truncated === 'boolean')
211
214
  || !(formStructure === null || isJsonObject(formStructure)
212
215
  && hasExactKeys(formStructure, ['algorithm', 'value', 'formCount', 'controlCount', 'truncated'])
@@ -461,7 +464,7 @@ function browserLibraryProfileContractState(value) {
461
464
  || !isJsonObject(knownExploitedCatalog)
462
465
  || !hasOnlyKeys(knownExploitedCatalog, ['name', 'version', 'releasedAt'])
463
466
  || !['name', 'version', 'releasedAt'].every((field) => validBoundedString(knownExploitedCatalog[field], 160))
464
- || normalizeExplicitIsoTimestamp(knownExploitedCatalog.releasedAt) !== knownExploitedCatalog.releasedAt
467
+ || normalizeExplicitIsoTimestamp(knownExploitedCatalog.releasedAt) === null
465
468
  || !Array.isArray(profile.findings)
466
469
  || profile.findings.length > MAX_LIBRARY_FINDINGS)
467
470
  return 'invalid';
@@ -488,6 +491,8 @@ function browserLibraryProfileContractState(value) {
488
491
  || !validExactStringArray(candidate.knownExploitedIdentifiers, 16, 80)
489
492
  || !validExactStringArray(candidate.weaknessClasses, 12, 80))
490
493
  return 'invalid';
494
+ // Published profiles could retain legacy upstream identifier spellings.
495
+ // Keep that reader boundary; current catalogue writers validate syntax.
491
496
  if (findingIds.has(candidate.id)
492
497
  || !candidate.advisoryIdentifiers.every((identifier) => typeof identifier === 'string'
493
498
  && /^(?:CVE-[0-9X-]+|GHSA-[A-Z0-9-]+)$/u.test(identifier))
@@ -495,7 +500,9 @@ function browserLibraryProfileContractState(value) {
495
500
  && /^CVE-[0-9X-]+$/u.test(identifier))
496
501
  || !candidate.weaknessClasses.every((identifier) => typeof identifier === 'string'
497
502
  && /^CWE-[0-9]+$/u.test(identifier))
498
- || Number(candidate.advisoryCount) < candidate.advisoryIdentifiers.length
503
+ // One advisory can have several identifiers, including CVE and GHSA
504
+ // aliases. Their count is not the number of matching advisory records.
505
+ || (Number(candidate.advisoryCount) === 0 && candidate.advisoryIdentifiers.length > 0)
499
506
  || Number(candidate.knownExploitedCount) < candidate.knownExploitedIdentifiers.length)
500
507
  return 'invalid';
501
508
  findingIds.add(candidate.id);
@@ -503,7 +510,7 @@ function browserLibraryProfileContractState(value) {
503
510
  return 'supported';
504
511
  }
505
512
  function pageFingerprintContractState(value) {
506
- const versionState = childVersionState(value, 'fingerprintVersion', [PAGE_FINGERPRINT_VERSION]);
513
+ const versionState = childVersionState(value, 'fingerprintVersion', Object.keys(PAGE_FINGERPRINT_PARSERS).map(Number));
507
514
  if (versionState !== 'supported')
508
515
  return versionState;
509
516
  const profile = value;
@@ -520,7 +527,9 @@ function pageFingerprintContractState(value) {
520
527
  || typeof profile.exact.value !== 'string'
521
528
  || !SHA256_RE.test(profile.exact.value)
522
529
  || !['complete-body', 'captured-prefix'].includes(String(profile.exact.scope))
523
- || !validUint(profile.exact.bytes, 300_000)
530
+ // The published first-generation writer had a fixed 300,000-byte ceiling.
531
+ // Current native fingerprints share the collector's source-size policy.
532
+ || !validUint(profile.exact.bytes, profile.fingerprintVersion === 1 ? 300_000 : MAX_HOMEPAGE_BYTES)
524
533
  || !['captured-response-bytes', 'decoded-markup'].includes(String(profile.exact.source))
525
534
  || !validPageFingerprintProfile(profile)
526
535
  || profile.complete !== (profile.truncated !== true))
@@ -647,7 +656,7 @@ function tlsProfileContractState(value) {
647
656
  return 'supported';
648
657
  }
649
658
  function securityPostureContractState(value) {
650
- const versionState = childVersionState(value, 'postureVersion', [WEBSITE_SECURITY_POSTURE_VERSION]);
659
+ const versionState = childVersionState(value, 'postureVersion', [2, WEBSITE_SECURITY_POSTURE_VERSION]);
651
660
  if (versionState !== 'supported')
652
661
  return versionState;
653
662
  const profile = value;
@@ -855,4 +864,4 @@ function sanitizeLookupChildProfiles(value) {
855
864
  }
856
865
  return output;
857
866
  }
858
- export { sanitizeLookupChildProfiles, validSecurityPostureFinding, validTlsChainCertificate, validTlsFinding, };
867
+ export { sanitizeLookupChildProfiles, technologyProfileContractState, validSecurityPostureFinding, validTlsChainCertificate, validTlsFinding, };
@@ -3,6 +3,29 @@ import { OPERATION_BUDGET_ERROR_CODE } from "./operation-budget.mjs";
3
3
  import { FEATURE_DISABLED_ERROR_CODE } from "./feature-policy.mjs";
4
4
  const LOOKUP_DIAGNOSTICS_VERSION = 8;
5
5
  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
+ }
6
29
  const LOOKUP_TIMING_VERSION = 1;
7
30
  const MAX_LOOKUP_TIMING_MS = 120_000;
8
31
  const LOOKUP_TIMING_SOURCE_ORDER = Object.freeze([
@@ -93,4 +116,4 @@ function createLookupTimingTracker(enabled, now) {
93
116
  },
94
117
  };
95
118
  }
96
- export { LOOKUP_DIAGNOSTICS_VERSION, LOOKUP_LEGACY_DIAGNOSTICS_VERSION, LOOKUP_TIMING_VERSION, MAX_LOOKUP_TIMING_MS, LOOKUP_ERROR_CODES, boundedSourceDetail, createLookupTimingTracker, errorMessage, };
119
+ export { LOOKUP_DIAGNOSTICS_VERSION, LOOKUP_LEGACY_DIAGNOSTICS_VERSION, LOOKUP_SOURCE_STATES, LOOKUP_TIMING_VERSION, MAX_LOOKUP_TIMING_MS, LOOKUP_ERROR_CODES, boundedSourceDetail, createLookupTimingTracker, errorMessage, lookupDiagnosticStates, };
@@ -1,6 +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
5
  export const LOOKUP_PROGRESS_SCHEMA = 'whoisleuth.lookup-progress';
5
6
  export const LOOKUP_PROGRESS_VERSION = 1;
6
7
  export const MAX_LOOKUP_PROGRESS_SOURCES = 16;
@@ -241,20 +242,25 @@ export function createLookupProgressNdjsonDecoder(onEvent) {
241
242
  throw new TypeError('NDJSON decoder requires an event consumer.');
242
243
  const decoder = new TextDecoder('utf-8', { fatal: true });
243
244
  let pending = '';
245
+ let pendingBytes = 0;
244
246
  let totalBytes = 0;
245
247
  let eventCount = 0;
246
248
  function consumeLine(line) {
247
249
  if (!line)
248
250
  return;
249
- if (byteLength(line) > MAX_LOOKUP_PROGRESS_FINAL_BYTES + MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES) {
250
- throw new TypeError('Lookup progress line exceeds its byte bound.');
251
- }
252
251
  eventCount += 1;
253
252
  if (eventCount > MAX_LOOKUP_PROGRESS_EVENTS) {
254
253
  throw new TypeError('Lookup progress stream contains too many events.');
255
254
  }
256
255
  let parsed;
257
256
  try {
257
+ // The ordinary result keeps its original limits after decoding. Allow
258
+ // only the fixed progress envelope overhead around that same document.
259
+ scanBoundedJson(line, {
260
+ maximumDepth: MAX_BOUNDED_JSON_DEPTH + 1,
261
+ maximumKeys: MAX_BOUNDED_JSON_KEYS + 8,
262
+ maximumValues: MAX_BOUNDED_JSON_VALUES + MAX_LOOKUP_PROGRESS_SOURCES + 16,
263
+ });
258
264
  parsed = JSON.parse(line);
259
265
  }
260
266
  catch {
@@ -269,16 +275,26 @@ export function createLookupProgressNdjsonDecoder(onEvent) {
269
275
  if (totalBytes > MAX_LOOKUP_PROGRESS_STREAM_BYTES) {
270
276
  throw new TypeError('Lookup progress stream exceeds its total byte bound.');
271
277
  }
272
- pending += decoder.decode(chunk, { stream: true });
273
- let newline = pending.indexOf('\n');
274
- while (newline >= 0) {
275
- const line = pending.slice(0, newline).replace(/\r$/u, '');
276
- pending = pending.slice(newline + 1);
277
- consumeLine(line);
278
- newline = pending.indexOf('\n');
279
- }
280
- if (byteLength(pending) > MAX_LOOKUP_PROGRESS_FINAL_BYTES + MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES) {
281
- throw new TypeError('Lookup progress line exceeds its byte bound.');
278
+ // Count incoming bytes once, before decoding or accumulation. LF cannot
279
+ // occur inside a valid UTF-8 sequence; the fatal decoder still receives
280
+ // every byte, including framing, so chunk boundaries cannot hide errors.
281
+ for (let start = 0; start < chunk.length;) {
282
+ const newline = chunk.indexOf(10, start);
283
+ const end = newline < 0 ? chunk.length : newline;
284
+ pendingBytes += end - start;
285
+ const maximum = MAX_LOOKUP_PROGRESS_FINAL_BYTES + MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES;
286
+ const trailingCr = end > start ? chunk[end - 1] === 13 : pending.endsWith('\r');
287
+ if (pendingBytes > maximum + (trailingCr ? 1 : 0)) {
288
+ throw new TypeError('Lookup progress line exceeds its byte bound.');
289
+ }
290
+ const decoded = decoder.decode(chunk.subarray(start, end + (newline < 0 ? 0 : 1)), { stream: true });
291
+ pending += newline < 0 ? decoded : decoded.slice(0, -1);
292
+ if (newline >= 0) {
293
+ consumeLine(pending.replace(/\r$/u, ''));
294
+ pending = '';
295
+ pendingBytes = 0;
296
+ }
297
+ start = end + 1;
282
298
  }
283
299
  }
284
300
  function finish() {
@@ -286,6 +302,7 @@ export function createLookupProgressNdjsonDecoder(onEvent) {
286
302
  if (pending)
287
303
  consumeLine(pending.replace(/\r$/u, ''));
288
304
  pending = '';
305
+ pendingBytes = 0;
289
306
  }
290
307
  return Object.freeze({ push, finish });
291
308
  }