@slicedearth/whoisleuth-cli 1.45.0 → 1.47.4

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 (133) hide show
  1. package/DISCLOSURE +7 -3
  2. package/NOTICE +5 -4
  3. package/README.md +59 -6
  4. package/bin/whoisleuth.mjs +5 -3
  5. package/cli/arguments.mjs +256 -9
  6. package/cli/artifact-structure.mjs +1833 -0
  7. package/cli/artifact-verify.mjs +242 -49
  8. package/cli/bounded-json.mjs +1 -0
  9. package/cli/bulk-checkpoint.mjs +2 -3
  10. package/cli/bulk-command-runner.mjs +2 -5
  11. package/cli/case-pack.mjs +819 -34
  12. package/cli/command-reference.mjs +48 -24
  13. package/cli/completion.mjs +16 -8
  14. package/cli/ct-event-intake.mjs +32 -2
  15. package/cli/discovery-scan-command-runner.mjs +4 -5
  16. package/cli/discovery-workflow.mjs +2 -3
  17. package/cli/doctor.mjs +2 -2
  18. package/cli/domain-control-monitor.mjs +21 -15
  19. package/cli/domain-control-observations.mjs +25 -2
  20. package/cli/evidence-command-runner.mjs +17 -11
  21. package/cli/evidence-signing.mjs +73 -45
  22. package/cli/export-evidence.mjs +11 -4
  23. package/cli/formatters/html.mjs +3 -3
  24. package/cli/formatters/terminal.mjs +3 -0
  25. package/cli/input.mjs +57 -0
  26. package/cli/interchange-report.mjs +226 -0
  27. package/cli/investigation-manifest.mjs +16 -17
  28. package/cli/investigation-run.mjs +71 -23
  29. package/cli/local-mmdb-review.mjs +45 -14
  30. package/cli/lookup-command-runner.mjs +2 -2
  31. package/cli/lookup-diff.mjs +9 -0
  32. package/cli/network-command-runner.mjs +78 -0
  33. package/cli/offline-evidence-review.mjs +28 -8
  34. package/cli/page-compare.mjs +8 -5
  35. package/cli/registry-cohort.mjs +385 -61
  36. package/cli/registry-support.mjs +2 -1
  37. package/cli/retained-artifact-diff.mjs +376 -0
  38. package/cli/risk-calibration.mjs +7 -5
  39. package/cli/runner.mjs +147 -185
  40. package/cli/saved-lookup.mjs +26 -3
  41. package/cli/sharing-review.mjs +12 -6
  42. package/docs/cli-reference.md +444 -70
  43. package/docs/cli.md +24 -5
  44. package/frontend/src/lib/analysis/acquisition-decision-packet.js +4 -4
  45. package/frontend/src/lib/analysis/artifact-integrity.js +42 -0
  46. package/frontend/src/lib/analysis/brand-profile-model.js +29 -5
  47. package/frontend/src/lib/analysis/bulk-domain-comparison.js +25 -5
  48. package/frontend/src/lib/analysis/bulk-mail-exposure.js +66 -15
  49. package/frontend/src/lib/analysis/bulk-retry-plan.js +1 -1
  50. package/frontend/src/lib/analysis/bulk-review-export.js +7 -6
  51. package/frontend/src/lib/analysis/bulk-review-model.js +3 -2
  52. package/frontend/src/lib/analysis/bulk-session-model.js +192 -26
  53. package/frontend/src/lib/analysis/bulk-sort.js +4 -0
  54. package/frontend/src/lib/analysis/case-brand-profile-references.js +85 -0
  55. package/frontend/src/lib/analysis/case-evidence-model.js +8 -0
  56. package/frontend/src/lib/analysis/case-investigation-branch-model.js +134 -0
  57. package/frontend/src/lib/analysis/case-migration-model.js +83 -31
  58. package/frontend/src/lib/analysis/case-record-contracts.js +4 -2
  59. package/frontend/src/lib/analysis/case-record-model.js +2 -0
  60. package/frontend/src/lib/analysis/case-record-operations.js +42 -12
  61. package/frontend/src/lib/analysis/case-report.js +39 -6
  62. package/frontend/src/lib/analysis/case-response-model.js +66 -7
  63. package/frontend/src/lib/analysis/case-response-packet.js +15 -5
  64. package/frontend/src/lib/analysis/comparison-ledger-bulk.js +366 -0
  65. package/frontend/src/lib/analysis/comparison-ledger-contract.js +241 -0
  66. package/frontend/src/lib/analysis/comparison-ledger-json.js +79 -0
  67. package/frontend/src/lib/analysis/comparison-ledger-serialization.js +51 -0
  68. package/frontend/src/lib/analysis/domain-control-manifest-core.js +145 -0
  69. package/frontend/src/lib/analysis/domain-control-records.js +88 -0
  70. package/frontend/src/lib/analysis/external-findings-import.js +70 -6
  71. package/frontend/src/lib/analysis/investigation-capsule.js +47 -13
  72. package/frontend/src/lib/analysis/investigation-guide.js +52 -10
  73. package/frontend/src/lib/analysis/investigation-template-model.js +9 -6
  74. package/frontend/src/lib/analysis/lookup-claim-passport.js +236 -0
  75. package/frontend/src/lib/analysis/lookup-claim-readiness.js +280 -0
  76. package/frontend/src/lib/analysis/lookup-decision-support.js +61 -6
  77. package/frontend/src/lib/analysis/lookup-presentation.js +62 -0
  78. package/frontend/src/lib/analysis/opaque-reference-id.js +9 -0
  79. package/frontend/src/lib/analysis/utils.js +26 -3
  80. package/frontend/src/lib/analysis/visualization-matrix.js +71 -28
  81. package/frontend/src/lib/analysis/visualization-models.js +63 -94
  82. package/frontend/src/lib/analysis/visualization-time-series.js +53 -12
  83. package/frontend/src/lib/analysis/website-snapshot-model.js +6 -0
  84. package/frontend/src/lib/analysis/workspace-archive.js +104 -14
  85. package/lib/application-version.mjs +3 -0
  86. package/lib/availability.mjs +14 -12
  87. package/lib/bounded-file.mjs +13 -0
  88. package/lib/bounded-json-response.mjs +17 -4
  89. package/lib/bounded-json.mjs +249 -0
  90. package/lib/certificate-extension-profile.mjs +2 -1
  91. package/lib/cryptographic-assurance.mjs +155 -0
  92. package/lib/ct-search.mjs +87 -47
  93. package/lib/dns-intelligence.mjs +3 -2
  94. package/lib/dns-resolver-selection.mjs +6 -4
  95. package/lib/dnssec-chain-validation.mjs +1053 -0
  96. package/lib/dnssec-evidence-validation.mjs +10 -3
  97. package/lib/domain-change-packet.mjs +7 -5
  98. package/lib/domain-control-manifest.mjs +17 -129
  99. package/lib/domain-posture.mjs +7 -4
  100. package/lib/evidence-export.mjs +751 -78
  101. package/lib/evidence-report-markdown.mjs +3 -0
  102. package/lib/evidence-report.mjs +5 -0
  103. package/lib/favicon.mjs +8 -5
  104. package/lib/http-evidence-bounds.mjs +3 -0
  105. package/lib/http-intelligence.mjs +2 -5
  106. package/lib/interchange-fidelity-registry.mjs +122 -0
  107. package/lib/local-geoip-evidence.mjs +2 -1
  108. package/lib/lookup-network-evidence-bounds.mjs +9 -0
  109. package/lib/lookup-response-contract.mjs +691 -3
  110. package/lib/lookup.mjs +2 -2
  111. package/lib/observation.mjs +7 -6
  112. package/lib/outbound-identity.mjs +2 -3
  113. package/lib/perceptual-hash.mjs +12 -4
  114. package/lib/portable-generator.mjs +21 -0
  115. package/lib/provider-policy-admission.mjs +9 -4
  116. package/lib/rdap-normalization.mjs +18 -5
  117. package/lib/rdap-registrar.mjs +5 -1
  118. package/lib/registry-capabilities.mjs +3 -0
  119. package/lib/registry-capability-catalogue.mjs +12 -2
  120. package/lib/registry-comparison.mjs +28 -11
  121. package/lib/risk-calibration-summary.mjs +338 -0
  122. package/lib/rpki-evidence.mjs +9 -8
  123. package/lib/safe-fetch.mjs +59 -10
  124. package/lib/security-txt.mjs +26 -3
  125. package/lib/service-binding-dns.mjs +6 -1
  126. package/lib/smtp-transport-review.mjs +784 -0
  127. package/lib/sslbl-certificates.generated.mjs +136 -134
  128. package/lib/threat-intelligence-runtime.mjs +3 -11
  129. package/lib/threat-intelligence-types.mjs +18 -1
  130. package/lib/tls-intelligence.mjs +11 -5
  131. package/lib/tlsa-evidence.mjs +37 -7
  132. package/package.json +1 -1
  133. package/third-party-notices.txt +95 -5817
@@ -63,7 +63,7 @@ node bin/whoisleuth.mts ct-search 'example brand' --json
63
63
  node bin/whoisleuth.mts discover example.com --preset common --jsonl
64
64
  node bin/whoisleuth.mts discover example.test --dictionary private-terms.txt --snapshot discovery-state.json --json
65
65
  node bin/whoisleuth.mts discover-scan example.test --scan-limit 50 --checkpoint candidate-scan.json --json
66
- node bin/whoisleuth.mts discover-scan example.test --deep --scan-limit 20 --resolver 192.0.2.53 --observation-snapshot observed-candidates.json --csv
66
+ node bin/whoisleuth.mts discover-scan example.test --deep --scan-limit 20 --resolver <public-resolver-ip> --observation-snapshot observed-candidates.json --csv
67
67
  node bin/whoisleuth.mts posture example.com --selectors selector1 --retired-selectors selector0 --mail-profile defensive-no-mail --json
68
68
  node bin/whoisleuth.mts http example.com --json
69
69
  node bin/whoisleuth.mts tls example.com --json
@@ -72,9 +72,10 @@ node bin/whoisleuth.mts registry-doctor lookup.json --json
72
72
  node bin/whoisleuth.mts registry-cohort saved-lookups.jsonl --json
73
73
  node bin/whoisleuth.mts registry-scaffold --profile example-profile --suffix test --scenario registered
74
74
  node bin/whoisleuth.mts risk-calibrate calibration.json --json
75
+ node bin/whoisleuth.mts risk-calibrate calibration.json --summary-json
75
76
  node bin/whoisleuth.mts lookalike-calibrate reviewed-candidates.json --json
76
77
  node bin/whoisleuth.mts verify-artifact workspace.json --json
77
- node bin/whoisleuth.mts verify-artifact lookup.json --json
78
+ node bin/whoisleuth.mts verify-artifact lookup.json --json --strict-exit
78
79
  node bin/whoisleuth.mts verify-artifact workspace-encrypted.json --passphrase-file passphrase.txt --json
79
80
  node bin/whoisleuth.mts source-report lookup.json --json
80
81
  node bin/whoisleuth.mts inspect-archive workspace.json --json
@@ -89,6 +90,8 @@ node bin/whoisleuth.mts lookup example.com --deep --json > lookup.json
89
90
  node bin/whoisleuth.mts compare lookup.json --json
90
91
  node bin/whoisleuth.mts page-compare official.json candidate.json --json
91
92
  node bin/whoisleuth.mts mail-review bulk.json --json
93
+ node bin/whoisleuth.mts dnssec-validate example.test --resolver "$PUBLIC_RESOLVER_IP" --trust-anchor anchor.json --owned-or-authorized --json
94
+ node bin/whoisleuth.mts mail-transport selected-mx.json --resolver "$PUBLIC_RESOLVER_IP" --trust-anchor anchor.json --owned-or-authorized --active-probe --json
92
95
  node bin/whoisleuth.mts review-evidence dnssec-evidence.json --json
93
96
  node bin/whoisleuth.mts review-evidence trust-store-comparison.json --json
94
97
  node bin/whoisleuth.mts domain-control domain-control-input.json --json
@@ -201,7 +204,7 @@ ordinary package-check candidate remain private. Explicitly reviewed CLI
201
204
  release candidates can be published under the scoped public package described
202
205
  in the installation section.
203
206
 
204
- Commands that query RDAP, WHOIS, DNS, HTTP, TLS, or Certificate Transparency do
207
+ Commands that query RDAP, WHOIS, DNS, HTTP, TLS, SMTP, or Certificate Transparency do
205
208
  so directly from the machine running the CLI. They do not use the hosted login,
206
209
  hosted session, or deployment usage controls; upstream providers can see and
207
210
  rate-limit the local machine's network address. Offline `discover`, `compare`,
@@ -213,6 +216,12 @@ rate-limit the local machine's network address. Offline `discover`, `compare`,
213
216
  `commands`, `completion`, and `manual` operations make no network requests. Commands write
214
217
  to stdout unless the analyst deliberately selects a local output file.
215
218
 
219
+ Path operands are opened through one bounded regular-file boundary. Named
220
+ pipes, devices and directories are refused without waiting for a writer;
221
+ symbolic links to regular analyst-selected files remain supported. Standard
222
+ input is the only intentional streaming input and observes the same command
223
+ byte limit and cancellation signal.
224
+
216
225
  ## Output
217
226
 
218
227
  Human-readable terminal output is the default. On an interactive terminal it
@@ -252,7 +261,10 @@ normal output is emitted.
252
261
  `lookup --markdown` and `lookup --html` build the existing normalised evidence
253
262
  report directly after one completed domain lookup. They do not start a second
254
263
  collection. These report formats reject IP and ASN inputs before collection;
255
- JSON and terminal output remain available for those query types.
264
+ JSON and terminal output remain available for those query types. Both readable
265
+ formats include a fixed local generator footer by default. `--no-attribution`
266
+ removes that presentation-only footer without changing evidence-source
267
+ attribution, generator metadata in JSON, or collection behaviour.
256
268
 
257
269
  Lookup terminal output supports three detail levels. The standard view keeps
258
270
  the normal bounded evidence summary. `--summary` keeps the source states and
@@ -361,11 +373,16 @@ completed final result, reports the checkpoint limitation, and exits with code
361
373
  4. The last successfully published checkpoint remains available, but it may
362
374
  not contain the final completed items.
363
375
 
364
- `diff <left.json> <right.json>` compares two different saved domain Lookup
365
- documents entirely offline. It reuses the bounded Bulk comparison model for
366
- registration, DNS, page identity, mail, certificate, and relationship fields,
367
- keeps missing and unavailable evidence distinct, and makes no ownership or
368
- maliciousness inference. The output records both original observation times.
376
+ `diff <left.json> <right.json>` compares two compatible retained artefacts
377
+ entirely offline. Saved Lookup documents keep their version-1 lookup-diff
378
+ output. Portable Bulk-session exports and domain-portfolio reviews emit a
379
+ `whoisleuth.cli.comparison-ledger` version-1 document with a value-free bounded
380
+ index and at most 256 exact detail rows. Bulk exports containing more than one
381
+ session require `--left-session <id>` or `--right-session <id>` as applicable.
382
+ The command keeps missing, unavailable, equal, added, and different evidence
383
+ separate, treats later portfolio omission as incomplete rather than removal,
384
+ and retains no input path. It makes no ownership, safety, or maliciousness
385
+ inference.
369
386
 
370
387
  `lookup --observer <label> --vantage <label>` can add two bounded analyst labels
371
388
  to a saved Lookup document. The labels identify the person or process that ran
@@ -409,7 +426,7 @@ machine access is not evidence that a domain is unregistered or safe.
409
426
  | 143 | The process received SIGTERM. No partial final result was emitted. |
410
427
 
411
428
  This release supports `lookup`, `bulk`, `ct-search`, `ct-intake`, `map-observations`, `oam-export`, `discover`, `discover-scan`, `posture`,
412
- `http`, `tls`, `registry-support`, `registry-doctor`, `registry-cohort`, `registry-scaffold`, `risk-calibrate`,
429
+ `http`, `tls`, `dnssec-validate`, `mail-transport`, `registry-support`, `registry-doctor`, `registry-cohort`, `registry-scaffold`, `risk-calibrate`,
413
430
  `lookalike-calibrate`, `verify-artifact`,
414
431
  `inspect-archive`, `manifest`, `sign-artifact`, `verify-signature`, `source-report`,
415
432
  `compare`, `page-compare`, `mail-review`, `review-evidence`, `brief`, `case-pack`, `domain-control`,
@@ -425,15 +442,18 @@ catalogue for one domain or suffix. It distinguishes an explicit
425
442
  fixture-backed or access-documented suffix profile from the generic IANA
426
443
  discovery profile and reports the RDAP and WHOIS discovery/access paths,
427
444
  WHOIS query and parser profiles, fixture scenarios,
428
- verification references, documentation references, and the catalogue's
445
+ verification references, documentation references, an optional reviewed
446
+ official browser-lookup URL, and the catalogue's
429
447
  limitation. The same document includes a dated, official-source aggregate of
430
448
  generic, generic-restricted, sponsored, and infrastructure RDAP coverage. That
431
449
  snapshot is separate from the suffix profile and from live reachability.
432
450
 
433
- Terminal output is bounded and control-safe. `--json` emits version 3 of the
451
+ Terminal output is bounded and control-safe. `--json` emits version 4 of the
434
452
  `whoisleuth.cli.registry-support` schema. Unknown but syntactically valid
435
453
  suffixes retain the generic `discovery_only` profile; malformed input exits
436
454
  with code 2. The command never probes a registry or tests current reachability.
455
+ An official lookup URL is emitted only for an individually reviewed HTTPS
456
+ destination. The CLI does not open it or append the investigated domain.
437
457
  Coverage is context only and cannot decide registration, availability,
438
458
  ownership, safety, or maliciousness.
439
459
 
@@ -446,12 +466,15 @@ and event consistency. A missing identifier or metadata field is a publication
446
466
  omission, not a failed lookup. The command is offline, makes no retry, and
447
467
  cannot establish current registry reachability.
448
468
 
449
- `registry-cohort [lookups.json|lookups.jsonl]` aggregates from 1 to 500 saved
450
- Lookups into suffix and capability-profile cohorts. It retains only target-free
451
- counts for source alignment and publication-quality states. A cohort with
452
- fewer than five samples is labelled insufficient rather than treated as a
453
- quality result, and repeated observations from one environment are not assumed
454
- representative.
469
+ `registry-cohort [lookups-or-reports.json|jsonl]` accepts either 1 to 500 saved
470
+ Lookups or an unmixed collection of retained `whoisleuth.cli.registry-cohort`
471
+ version-1/version-2 reports. It emits version 2 with target-free suffix and
472
+ capability-profile timelines, an explicit sample window, retained-report count,
473
+ and at most 50 visible points per cohort. A version-1 report becomes one
474
+ retained point. A cohort with fewer than five samples remains insufficient,
475
+ and overlapping retained samples are never summed or promoted to independent
476
+ consistency. Domains, queries, filenames, and raw registry evidence are not
477
+ retained.
455
478
 
456
479
  `registry-scaffold --profile <id> --suffix <suffix> --scenario <state>` emits
457
480
  one bounded, sanitised synthetic fixture scaffold from the installed registry
@@ -473,7 +496,9 @@ pinned official RDAP extension fixture with the reviewed local interpretation
473
496
  catalogue entirely offline. `--live` performs one bounded manual fetch from the
474
497
  fixed official CSV URL and reports added, removed, renamed, status-changed,
475
498
  unrecognized, and local-only identifiers. `--json` emits
476
- `whoisleuth.rdap-extension-drift-audit` version 1. Drift exits with status 1 and
499
+ `whoisleuth.rdap-extension-drift-audit` version 1. The manual live fetch keeps one
500
+ 10-second deadline armed through the capped 128 KiB body read and fails closed if
501
+ the official CSV is truncated. Drift exits with status 1 and
477
502
  requires specification and fixture review; the command never enables an
478
503
  extension, changes authority or availability logic, or issues reverse search.
479
504
 
@@ -495,7 +520,18 @@ thresholds from 40 through 90 and identifies 70 as the current review band. It
495
520
  also reports F1, balanced accuracy, Wilson 95% intervals, deliberately small
496
521
  scan-depth and review-reason strata, and a current-versus-previous model replay.
497
522
  Strata with fewer than 20 included labels are marked insufficient rather than
498
- presented as reliable performance estimates.
523
+ presented as reliable performance estimates. Report version 3 distinguishes
524
+ the ordinary `detailed` output from the aggregate `summary` projection.
525
+
526
+ `--json` emits the bounded detailed report, including record identifiers,
527
+ domains, analyst dispositions, scores and factor summaries. Use
528
+ `--summary-json` for the target-free aggregate accepted by Monitor's local
529
+ calibration explorer. That projection retains only dataset and model versions,
530
+ aggregate class and score-band counts, fixed-threshold confusion metrics and
531
+ Wilson intervals, bounded strata, model-comparison counts, and an explicit
532
+ zero-target privacy declaration. It contains no record array, identifier,
533
+ domain, evidence field, factor, note or provider payload. Both forms remain
534
+ offline and are written only when the analyst deliberately selects an output.
499
535
 
500
536
  The JSON report derives a bounded `interoperabilityTags` list from the
501
537
  analyst-owned disposition and review reason. The mapping uses reviewed MISP
@@ -532,7 +568,7 @@ and bounded before scoring. The accepted dataset envelope is:
532
568
  and `expected` are negative labels. `unreviewed`, `suspicious`, and
533
569
  `closed_no_action` remain contextual and are excluded from threshold quality
534
570
  metrics, as are records for which Risk is not applicable. Terminal output caps
535
- its record list at 100 while `--json` retains the complete bounded report,
571
+ its record list at 100 while `--json` retains the complete bounded detailed report,
536
572
  factor breakdowns, score bands, confidence intervals, bounded strata, model
537
573
  comparison counts, and per-threshold confusion metrics. Analyst
538
574
  dispositions and heuristic scores are review context: neither proves
@@ -571,16 +607,29 @@ establish that a current candidate is malicious or safe.
571
607
  `verify-artifact` validates a supported local artefact without printing its
572
608
  evidence contents. Input is capped at 15 MiB. It currently recognises ordinary
573
609
  and encrypted workspace archives, case-response packets, acquisition-decision
574
- exports, domain-comparison exports, Bulk mail-exposure exports, and Bulk review
610
+ exports, supported Lookup-evidence exports, Lookup claim passports, domain-comparison exports, Bulk mail-exposure exports, and Bulk review
575
611
  manifests. Reviewed CLI case packs are checked against their canonical digest,
576
612
  bounded browser-importable case collection, audience, and review marker. The
577
- command also verifies the internal digests of a complete investigation
578
- capsule, including its evidence, brief, graph, and optional analyst-record
579
- projections, and that capsule can be passed to `sign-artifact`. It also validates the bounded versioned structure of saved CLI
580
- Lookup JSON. Because saved Lookup documents do not embed a checksum or
581
- signature, that result is reported as `structure_valid` with content integrity
582
- explicitly unchecked. Saved Lookup validation retains its stricter 8 MiB
583
- document ceiling.
613
+ command verifies both the embedded projections and whole-capsule digest of an
614
+ investigation capsule version 2, including its metadata and linked
615
+ source-contract digests, and reports `verified`. The non-embedded Lookup
616
+ evidence must still be retained separately. Legacy capsule version 1 covers
617
+ only the embedded brief, graph, and optional analyst-record projections, so it
618
+ remains `integrity_valid` rather than whole-file `verified`. It also validates
619
+ the bounded versioned structure of saved CLI Lookup JSON and current
620
+ Lookup-evidence schemas 25 and 26. Because neither saved Lookup documents nor full
621
+ Lookup-evidence exports embed a checksum or signature, those results are
622
+ reported as `structure_valid` with content integrity explicitly unchecked.
623
+ Lookup-evidence verification closes the supported export envelopes and source
624
+ wrappers, enforces the same 5 MiB, 20,000-entry, and 24-level limits as browser
625
+ replay, and does not claim that
626
+ an observation is accurate, current, or unchanged. Exact retained-file identity
627
+ requires a verified investigation manifest and matching `--manifest-entry`;
628
+ the evidence file can contain a bounded privacy-projected registry RDAP
629
+ publication, normalised WHOIS values, and bounded contacts, so review it before
630
+ sharing. Request and response headers, cookies, session and credential fields,
631
+ and URL credentials, queries, and fragments are excluded. Saved Lookup validation retains its
632
+ stricter 8 MiB document ceiling.
584
633
 
585
634
  For an ordinary workspace archive, the command validates the versioned
586
635
  structure, section byte counts, record counts, and per-section checksums. For a
@@ -588,6 +637,11 @@ supported signed review artefact, it validates the declared SHA-256 digest. An
588
637
  encrypted archive without `--passphrase-file` can establish only that the
589
638
  envelope is structurally valid; it reports `envelope_valid` and explicitly
590
639
  leaves authenticated encryption and inner checksums unchecked.
640
+ Checksum validity is reported separately from importability. A checksum-valid
641
+ archive can therefore list unsupported sections when it contains a section
642
+ schema this version cannot restore, or skipped and pruned records when a
643
+ supported section cannot be restored completely; inspect those counts before
644
+ importing.
591
645
 
592
646
  Supplying a separate passphrase file, capped at 1 KiB, allows authenticated
593
647
  decryption followed by the ordinary workspace checks. The passphrase is never
@@ -595,6 +649,83 @@ accepted as a command-line value, printed, or retained. Verification detects
595
649
  changes against the artefact's declared contract; it does not authenticate the
596
650
  analyst or establish that an observation was accurate or remains current.
597
651
 
652
+ `--manifest <manifest.json> --manifest-entry <artifact-N>` additionally verifies
653
+ the selected investigation manifest before comparing its declared entry with
654
+ the supplied artefact. `identity_verified` requires the exact UTF-8 byte length
655
+ and raw digest as well as canonical digest, schema, and version. A
656
+ `canonical_match_only` result means semantic JSON identity survived but the
657
+ retained bytes differ, so it is a partial result under `--strict-exit`.
658
+ `mismatch` likewise remains separate from the artefact's own structure or
659
+ integrity result. Manifest paths and artefact contents are not copied into the
660
+ verification report.
661
+
662
+ Verification reports use `whoisleuth.offline-artifact-verification` version 3
663
+ and do not expose an always-true validity shortcut. The explicit state and
664
+ checks distinguish structure, whole-file integrity, projection integrity, and
665
+ authenticated encryption. `--strict-exit` returns exit 0 for `verified` and
666
+ `structure_valid` when no manifest identity is requested or exact identity is
667
+ verified. It returns partial-failure exit 4 for `integrity_valid`,
668
+ `envelope_valid`, canonical-only identity, or identity mismatch; ordinary
669
+ verification errors retain their existing error codes. Raw JSON is scanned
670
+ before parsing to reject duplicate object keys and excessive nesting or
671
+ key/value counts.
672
+
673
+ Current integrity-bearing review outputs use `sorted-json-v2`, whose object-key
674
+ order is a locale-independent JavaScript code-unit order. Readers select the
675
+ canonical bytes from an exact artifact-version and canonicalization pair,
676
+ reject mixed or missing current declarations, and validate bounded structure
677
+ before any legacy hashing. Historical outputs keep their original
678
+ `sorted-json-v1` bytes and are accepted only through their legacy version.
679
+ Version 1 used the runtime's ambient `localeCompare` ordering, so historical
680
+ files containing non-ASCII object-key extensions require a compatible locale
681
+ and ICU ordering to verify; it is not a portable canonicalization guarantee.
682
+ The fixed keys emitted by the supported legacy builders are ASCII, but only
683
+ version 2 provides the cross-locale portability contract.
684
+
685
+ The current/legacy integrity pairs are: acquisition decision 2/1, Lookup claim
686
+ passport 1 (sorted-json-v2 only),
687
+ domain-comparison export 4/3 with nested comparison 3, Bulk mail-exposure
688
+ export 2/1 with nested report 1, Bulk review manifest 2/1, domain-control
689
+ manifest 2/1, domain-change packet 2/1 with input 1, investigation manifest
690
+ 2/1, case-response packet 6/5, investigation capsule 2/1, CLI case-pack packet
691
+ 2/1 with Case schema 12, and signed evidence package 2/1. Current outputs
692
+ declare `sorted-json-v2`; legacy declarations remain explicit only where their
693
+ historical contract included one.
694
+
695
+ ## Interchange fidelity report
696
+
697
+ `interchange-report [artifact.json]` explains what one recognised portable
698
+ artefact preserves, excludes, and supports in browser and CLI workflows. It
699
+ recognises supported Lookup-evidence exports, Lookup claim passports, domain-control passports, Brand Profile exports from versions 2
700
+ through 6, workspace archives from versions 1 through 5, encrypted workspace
701
+ envelopes, reviewed CLI case packs, and the retired one-way desired-baseline
702
+ export. The retired export is identified as unsupported rather than silently
703
+ treated as a current passport.
704
+
705
+ Output is metadata-only: registered schema and version, verification state,
706
+ bounded counts, browser import/export support, CLI read/write/verify support,
707
+ semantic fidelity, and fixed preserved or excluded field-group identifiers.
708
+ For workspace archives, checksum validity and complete browser importability are
709
+ reported separately. A checksum-valid archive with a newer inner section stays
710
+ verified for integrity while its fidelity is withheld and importability is
711
+ reported as partial. The same partial state applies when browser preview would
712
+ block a section, skip a malformed record, or prune data to a collection bound.
713
+ It never echoes a target, contact, note, evidence value, passphrase, arbitrary
714
+ unknown schema string, or encrypted content. A separate `--passphrase-file`
715
+ can authenticate an encrypted workspace; without it, only the envelope is
716
+ reported as valid and fidelity is withheld. Each registered interchange family
717
+ declares its exact assurance requirement: browser-importable structure is
718
+ sufficient for Brand Profile normalisation and the current bounded
719
+ Lookup-evidence contract, while checksummed formats
720
+ require whole-file integrity and encrypted workspaces additionally require
721
+ authenticated decryption. “Semantic exact after normalisation” does not mean byte
722
+ identity, current evidence, or a successful round trip through an unrelated
723
+ format.
724
+
725
+ For case packs, the fixed fidelity metadata explicitly lists public-audience
726
+ Brand Profile references as excluded and trusted/internal references as
727
+ preserved. The report never prints an identifier or other stored value.
728
+
598
729
  ## Privacy-safe source reliability report
599
730
 
600
731
  `source-report` summarises source states, durations, truncation, and rate-limit
@@ -655,22 +786,32 @@ node bin/whoisleuth.mts sign-artifact packet.json --private-key-file analyst-pri
655
786
  node bin/whoisleuth.mts verify-signature packet.signed.json --public-key-file analyst-public.pem
656
787
  ```
657
788
 
658
- The signed package embeds the public key, key identifier, signature time, and
659
- original artefact. Verification first checks the exact package field set and
660
- signature, then the artefact's own checksums or manifest. A result is reported
661
- as `signature_valid`; signer trust is separately reported as `trusted_key` or
662
- `embedded_key_only`. Without a separately supplied trusted public key,
789
+ New signed packages use version 2 with `sorted-json-v2` canonicalisation and
790
+ embed the public key, key identifier, canonical UTC signature time, and
791
+ original artefact. Signing requires verified structure plus a verified
792
+ applicable integrity contract; a recomputed digest around an empty or malformed
793
+ document is rejected. Signature verification reports cryptographic status and
794
+ embedded-artifact assurance separately in
795
+ `whoisleuth.evidence-signature-verification` version 2. A result can therefore
796
+ remain `signature_valid` while embedded-artifact assurance is `not_verified`;
797
+ the signature authenticates those exact bytes without upgrading their
798
+ structure or inner integrity. Signer trust is separately reported as
799
+ `trusted_key` or `embedded_key_only`. Without a separately supplied trusted public key,
663
800
  verification establishes only that the package is internally self-consistent.
801
+ Historical version-1 packages remain verifiable with their explicit
802
+ `sorted-json-v1` signature declaration when their original locale-dependent
803
+ ordering is reproduced; version 2 is the portable signature contract.
664
804
  WHOISleuth does not generate, store, recover, rotate, publish, or establish
665
805
  trust in signing keys.
666
806
 
667
807
  ## Reproducible investigation manifest
668
808
 
669
809
  `manifest <artefact.json> [...] --workflow <label>` reads from 1 to 16 ordered
670
- JSON artefacts and emits `whoisleuth.investigation-manifest` version 1. Each
810
+ JSON artefacts and emits `whoisleuth.investigation-manifest` version 2. Each
671
811
  entry records its sequence, bounded schema and version metadata, exact UTF-8
672
- content digest, canonical JSON digest, and byte count. The manifest also records
673
- the installed CLI version and can include an externally calculated
812
+ content digest, `sorted-json-v2` canonical JSON digest, and byte count. Version
813
+ 1 manifests remain verifiable with their historical digest bytes. The manifest
814
+ also records the installed CLI version and can include an externally calculated
674
815
  `--configuration-digest sha256:...` without copying configuration values.
675
816
 
676
817
  Source paths, filenames, command-line arguments, and artefact contents are not
@@ -739,12 +880,17 @@ time, certificate SHA-256 digest, one or more DNS names, completeness, and
739
880
  optional issuer, expiry, and limitations. Unknown fields, malformed names,
740
881
  unsupported completeness values, and oversized batches are rejected.
741
882
 
742
- JSON output uses the existing `whoisleuth.external-findings` version 3 contract
883
+ JSON output uses the existing `whoisleuth.external-findings` version 4 contract
743
884
  and can be deliberately imported into the browser Console. Wildcard names are
744
885
  normalised to their base DNS name, exact event/domain duplicates are removed,
745
886
  and deterministic output is capped at 100 findings, 25 domains, and 20 findings
746
887
  per domain to match the browser import boundary. Omission is stated in each
747
888
  retained finding when the supplied batch exceeds those limits. The command
889
+ makes a deterministic event identifier and preserves the supplied log id,
890
+ certificate digest, issuer, expiry, original DNS-name count, and whether the
891
+ bounded import retained the complete supplied name set. This metadata lets the
892
+ browser replay reviewed issuer and certificate-name expectations without
893
+ mistaking an omitted name for a mismatch. The command
748
894
  makes no request and treats every certificate event as a review lead, not proof
749
895
  that the certificate was served, requested, or controlled by the named domain.
750
896
 
@@ -760,7 +906,7 @@ computed expressions, network endpoints, or executable callbacks. Prototype
760
906
  paths and unknown fields are rejected.
761
907
 
762
908
  The resulting JSON is the existing browser-compatible
763
- `whoisleuth.external-findings` version 3 contract. It retains source identity,
909
+ `whoisleuth.external-findings` version 4 contract. It retains source identity,
764
910
  observation time, completeness, and limitations, while stating that field
765
911
  semantics were not independently verified. Deterministic output is capped at
766
912
  100 findings, 25 domains, and 20 findings per domain. The command makes no
@@ -897,6 +1043,9 @@ and BIMI directly from the local machine. Literal SPF include and redirect
897
1043
  branches are expanded within fixed depth, policy-query, DNS-term, void-answer,
898
1044
  cycle, and time bounds. External DMARC reporting authorisation and external
899
1045
  nameserver, mail, SPF, and reporting dependencies are separately reported.
1046
+ The MTA-STS policy request uses the fixed HTTPS policy host and well-known path;
1047
+ HTTP redirects are not followed, as required by RFC 8461, and a redirect leaves
1048
+ that evidence unavailable rather than borrowing a policy body from another URL.
900
1049
  Supply up to ten known active and retired DKIM selectors in total with
901
1050
  `--selectors selector1,selector2` and `--retired-selectors selector0`;
902
1051
  selectors cannot be reliably discovered from DNS, so no-selector output
@@ -994,6 +1143,9 @@ complete supported static page-identity observations on both sides.
994
1143
 
995
1144
  Each page component remains independent. Exact, similar, overlapping,
996
1145
  different, unavailable, and partial evidence are not collapsed into a score.
1146
+ Version 3 also withholds technology-set equality and disjointness whenever
1147
+ either saved technology observation is partial or truncated; the retained
1148
+ identifiers remain visible only as incomplete evidence.
997
1149
  Matching components are investigative relationships rather than proof of
998
1150
  copying, common ownership, control, intent, safety, or maliciousness. Static
999
1151
  comparison does not execute JavaScript; use the optional local rendered
@@ -1015,14 +1167,126 @@ behaviour, SMTP banners, or whether a mail server is rogue, safe, or malicious.
1015
1167
  An analyst may add a bounded `tlsaEvidence` object to a version-2 Bulk item for
1016
1168
  offline DANE review. It must name the exact `_25._tcp.<mx-host>` service, and
1017
1169
  that host must occur in the same item's retained MX evidence. A certificate
1018
- association match is complete only when `dnssecState` is `validated`. TLSA
1019
- usages 0 and 1 additionally require `pkixValidationState: "validated"`.
1020
- Usages 0 and 2 compare against the bounded `authorityMaterials` array; usages
1021
- 1 and 3 compare against the supplied leaf certificate or SPKI. Mismatched
1170
+ association match is complete only when `dnssecState` is `validated`. For an
1171
+ SMTP relay service on `_25._tcp`, PKIX-TA usage 0 and PKIX-EE usage 1 are
1172
+ reported as unsupported and cannot complete SMTP DANE assurance because RFC
1173
+ 7672 leaves their SMTP treatment undefined. Usage 2 likewise
1174
+ cannot complete DANE-TA assurance because this offline action does not construct
1175
+ or validate a path from the observed leaf to the supplied trust-anchor material.
1176
+ For other service ports, usage 0 compares against the bounded
1177
+ `authorityMaterials` array and remains path-binding limited, while usage 1
1178
+ compares against the leaf and still requires independently validated PKIX.
1179
+ Usage 3 compares against the supplied leaf certificate or SPKI. Mismatched
1022
1180
  service names, missing certificate roles, unvalidated prerequisites, malformed
1023
1181
  material, and truncation remain invalid, partial, or untrusted rather than
1024
1182
  becoming a DANE match.
1025
1183
 
1184
+ ## Isolated cryptographic and mail transport review
1185
+
1186
+ These two network actions are disconnected from Lookup, Bulk, monitoring, and
1187
+ fixed recipes. They run only through their dedicated commands, require
1188
+ `--owned-or-authorized` on every invocation, use one caller-selected literal
1189
+ public resolver address, and require a local version-1
1190
+ `whoisleuth.dnssec-trust-anchor` JSON file. The resolver receives the DNS
1191
+ questions and can apply its own logging and retention. Resolver hostnames,
1192
+ private or reserved addresses, automatic resolver selection, and a non-default
1193
+ DNS port are refused.
1194
+
1195
+ Queries set RD, CD, and EDNS DO so the selected recursive resolver returns
1196
+ DNSSEC material for local validation; the resolver's AD bit is recorded only
1197
+ as response metadata and never substitutes for local cryptographic checks.
1198
+
1199
+ `dnssec-validate <domain> --resolver <public-IP> --trust-anchor <anchor.json>
1200
+ --owned-or-authorized` performs an isolated DNS-over-TCP validation from the
1201
+ supplied anchor. It validates supported DS, DNSKEY, and RRSIG relationships,
1202
+ and supported authenticated NSEC or exact NSEC3 denial evidence. NSEC3 opt-out
1203
+ spans and unknown flags remain explicitly unsupported. The action
1204
+ is capped at 32 DNS queries, eight delegations, 512 KiB of DNS
1205
+ response bytes, 2.5 seconds per query, and 15 seconds total, with no retry.
1206
+ Authenticated RRsets are capped at 32 records and 16 matching signatures, and
1207
+ denial review accepts at most 16 NSEC3 records per response.
1208
+ Canonical RRsets are deduplicated and ordered by canonical RDATA before the
1209
+ fixed owner, type, class, TTL, and length fields are constructed. NSEC Next
1210
+ Domain Name case is preserved as required for its canonical RDATA, while the
1211
+ RRSIG Signer's Name in signed data is canonical lowercase. A positive RRset
1212
+ whose RRSIG label count indicates wildcard expansion remains `unsupported`
1213
+ unless authenticated denial evidence proves the expansion; this version does
1214
+ not infer that proof from the signature alone.
1215
+ Supported signature algorithms are 5, 7, 8, 10, 13, 14, 15, and 16; supported
1216
+ DS digests are 1, 2, and 4; NSEC3 iteration counts above 500 remain
1217
+ unsupported. The output is `whoisleuth.dnssec-chain-validation` version 1 and
1218
+ keeps `secure`, `insecure`, `bogus`, `indeterminate`, `timed_out`,
1219
+ `unsupported`, and `invalid` separate. Resolver transport failures do not
1220
+ become cryptographic failures. The report retains the target, selected
1221
+ resolver, trust-anchor source and review time, validated zones, bounded counts,
1222
+ state, failure stage, and limitations. DNS wire responses, DNSKEY public keys,
1223
+ signatures, and transaction identifiers are not retained. `secure` describes
1224
+ the supported chain at that observation time; it is not an ownership,
1225
+ availability, safety, or maliciousness conclusion.
1226
+
1227
+ The trust-anchor document contains exactly `schema`, `version`, `zone`,
1228
+ `source`, `reviewedAt`, and one to eight `dsRecords`. Each DS entry contains
1229
+ `keyTag`, `algorithm`, `digestType`, and a correctly sized hexadecimal
1230
+ `digest`. The file is capped at 64 KiB. WHOISleuth does not download, refresh,
1231
+ select, or vouch for an anchor; maintaining its source and review time is the
1232
+ operator's responsibility.
1233
+
1234
+ `mail-transport [input.json] --resolver <public-IP> --trust-anchor
1235
+ <anchor.json> --owned-or-authorized --active-probe` additionally requires a
1236
+ separate active-probe acknowledgement on every run. Its version-1
1237
+ `whoisleuth.mail-transport.input` names one domain, one to three distinct
1238
+ selected MX hostnames, and optional bounded `policyContext.mtaSts` and
1239
+ `policyContext.tlsRpt` observations. Policy entries retain a supplied state,
1240
+ source, observation time, and `complete`, `partial`, or `unavailable`
1241
+ completeness; the command does not fetch either policy.
1242
+
1243
+ Selected hosts are processed sequentially. Each host is resolved through the
1244
+ chosen resolver, every returned connection candidate is checked as a public
1245
+ IP address, at most 16 candidates and four aliases are accepted per resolution,
1246
+ and the selected address must still occur in a fresh resolution immediately
1247
+ before one direct pinned port-25 connection. The client accepts only bounded
1248
+ printable-ASCII SMTP reply framing, sends exactly `EHLO whoisleuth.invalid`, and sends
1249
+ `STARTTLS` only when advertised. It never sends `AUTH`, `MAIL FROM`, `RCPT TO`,
1250
+ `DATA`, `VRFY`, `EXPN`, a message, or a retry. Per endpoint, SMTP response data
1251
+ is capped at 16 KiB, 64 lines, 1,000 bytes per line, and 32 retained capability
1252
+ names; one leaf certificate is accepted up to 256 KiB and the fixed endpoint
1253
+ timeout is eight seconds. Expiry aborts and destroys the connection. The entire
1254
+ sequential run is capped at 30 seconds and shares a
1255
+ 32-query, 512-KiB DNS budget.
1256
+
1257
+ Address provenance advances only through `selected`, `revalidated`, and
1258
+ `connected`; failures retain the strongest completed stage and never describe a
1259
+ DNS candidate as connection evidence. Successful fresh public-address checking
1260
+ uses `resolution.state=public_revalidated`. The separate
1261
+ `addressAuthentication.state` is `not_evaluated` after an address candidate is
1262
+ retained because the A or AAAA RRset and any CNAME chain are not
1263
+ cryptographically validated by this action; it is `unavailable` when no
1264
+ candidate was retained.
1265
+
1266
+ TLSA records for `_25._tcp.<selected-mx>` are compared only after their RRset
1267
+ or denial evidence validates through that endpoint's separately reported
1268
+ DNSSEC context. PKIX authorisation, certificate hostname identity,
1269
+ DNSSEC-chain validation, TLSA publication, DANE comparison, STARTTLS, SMTP
1270
+ transport, MTA-STS context, and TLS-RPT context retain separate states;
1271
+ missing or incomplete evidence in one family is never filled from another.
1272
+ If a DANE-TA TLSA usage 2 association is published, active STARTTLS review
1273
+ retains only the observed leaf certificate and leaves that comparison partial
1274
+ because this action does not construct or validate a certificate path to a
1275
+ TLSA trust anchor.
1276
+ The `whoisleuth.cli.mail-transport-review` version-1 output retains selected MX
1277
+ names, the bounded address value, family, highest proven address stage, and
1278
+ separate address-authentication state, greeting status and SHA-256 digest, capability
1279
+ names, certificate and SPKI SHA-256 digests, protocol and cipher labels,
1280
+ bounded certificate authorization or identity error text, source times, counts,
1281
+ completeness, and limitations. Raw greeting or reply
1282
+ text, certificate bytes, TLS session material, and DNS wire responses are not
1283
+ retained. Only address values whose stage is `connected` can form an exact
1284
+ shared-address lead. Address, greeting-digest, and certificate-digest matches
1285
+ are labelled review leads only; they do not establish a rogue endpoint, common
1286
+ ownership, coordination, intent, safety, or maliciousness. Neither action
1287
+ changes Risk, Opportunity, registration availability, or any stored browser
1288
+ record.
1289
+
1026
1290
  ## Offline supplied-evidence review
1027
1291
 
1028
1292
  `review-evidence [evidence.json]` accepts one version-1 JSON input and performs
@@ -1043,7 +1307,12 @@ Supported input schemas are:
1043
1307
  `_port._transport.hostname` service and compares supplied leaf or authority
1044
1308
  certificate/SPKI bytes. `matched` requires independently validated DNSSEC;
1045
1309
  PKIX-TA and PKIX-EE usages also require an independently validated PKIX path.
1046
- The command does not retrieve the certificate or negotiate STARTTLS.
1310
+ PKIX-TA usage 0 retains per-record authority comparisons but cannot complete
1311
+ PKIX-TA assurance because supplied authority material is not proven to belong
1312
+ to that validated path. DANE-TA usage 2 likewise cannot complete DANE-TA
1313
+ assurance because this offline action does not construct or validate a
1314
+ leaf-to-anchor certificate path. The command does not retrieve the certificate
1315
+ or negotiate STARTTLS.
1047
1316
  - `whoisleuth.rdap-search-input`: normalises a supplied RDAP search-help
1048
1317
  response and prepares an exact supported reverse-search request without
1049
1318
  sending it. If the document also contains a previously obtained `response`,
@@ -1052,6 +1321,13 @@ Supported input schemas are:
1052
1321
  the response as authoritative or complete.
1053
1322
  - `whoisleuth.rpki-route-input`: compares a route prefix and origin ASN with a
1054
1323
  bounded analyst-supplied VRP set.
1324
+ - `whoisleuth.cryptographic-assurance.input`: accepts separately wrapped,
1325
+ source-qualified DNSSEC, route-origin, and DANE/TLSA evidence and emits three
1326
+ independent cards. Every card retains its own authority, source, observation
1327
+ time, state, completeness, result, and limitations. An omitted family remains
1328
+ `unavailable`; the version-1 `whoisleuth.cryptographic-assurance.review`
1329
+ contains `combinedState: null` and never emits a scalar score or combined
1330
+ verdict.
1055
1331
  - `whoisleuth.local-geoip-query`: queries an analyst-supplied bounded prefix
1056
1332
  database whose source, version, and licence metadata remain in the result.
1057
1333
  - `whoisleuth.local-mmdb-query`: with an explicit `--mmdb <database-file>`,
@@ -1127,8 +1403,9 @@ fingerprint, registrar-lock preference, and a renewal-review date for up to 100
1127
1403
  domains. Empty fields mean unconfigured; they do not require a record to be
1128
1404
  absent.
1129
1405
 
1130
- The emitted `whoisleuth.domain-control-manifest` version 1 is normalised and
1131
- protected by a canonical SHA-256 digest. It can also be passed to
1406
+ The emitted `whoisleuth.domain-control-manifest` version 2 is normalised and
1407
+ protected by a `sorted-json-v2` SHA-256 digest. Version 1 remains readable with
1408
+ its historical canonicalization contract. Either version can also be passed to
1132
1409
  `sign-artifact` for an optional local Ed25519 signature. Integrity and
1133
1410
  signature verification detect changes and authenticate possession of the
1134
1411
  selected key; neither establishes that the desired state is correct. Manifest
@@ -1136,6 +1413,15 @@ input and verification reject unknown fields, and verification rejects
1136
1413
  non-canonical content rather than returning a shape that differs from the
1137
1414
  normalised contract covered by the digest.
1138
1415
 
1416
+ The same version-two manifest is the browser Console's domain-control passport
1417
+ format; its browser and CLI input envelope remains version 1. A deliberately
1418
+ selected passport can move the supported desired-state
1419
+ subset between Brand Profiles and the CLI without copying profile identity,
1420
+ notes, observations, change windows, suppressions, or other browser-only
1421
+ planning. Browser import verifies the digest and presents a field-level,
1422
+ non-destructive preview. A manifest remains an analyst-authored expectation in
1423
+ either runtime; interoperability does not make it observed evidence.
1424
+
1139
1425
  A review input uses `whoisleuth.domain-control-review-input` version 1 and
1140
1426
  contains one manifest plus separately attributed observations. Only a complete
1141
1427
  `observed` field may produce `drift`. Partial, unavailable, unsupported, and
@@ -1143,6 +1429,17 @@ missing observations remain inconclusive, and unrelated observations are
1143
1429
  counted but ignored. The review performs no DNS, RDAP, HTTP, TLS, SMTP, or
1144
1430
  registrar request.
1145
1431
 
1432
+ The saved-Lookup review envelope uses
1433
+ `whoisleuth.cli.domain-control-review-input` version 1 and accepts from 1 to
1434
+ 100 saved Lookup documents. The raw envelope is scanned before parsing for
1435
+ duplicate keys, the prototype-sensitive `__proto__` key, and a 50-level
1436
+ envelope depth that preserves each saved Lookup's standalone 48-level limit.
1437
+ Its aggregate scanner permits up to 5,050,000 keys and 10,100,000 values so
1438
+ the envelope does not silently narrow the per-document version-1 contract;
1439
+ no individual object or array may contain more than 10,000 items, and the
1440
+ command's byte ceiling remains authoritative. Each embedded Lookup is
1441
+ then independently revalidated against its ordinary saved-document bounds.
1442
+
1146
1443
  ## Domain assurance review
1147
1444
 
1148
1445
  `assurance [input.json]` reviews one bounded analyst-authored workflow entirely
@@ -1172,10 +1469,11 @@ no registrar, DNS, mail, certificate, or recovery configuration.
1172
1469
 
1173
1470
  `change-packet [input.json]` combines one pre-change domain review, one
1174
1471
  post-change domain review, and one planned-change assurance input for the same
1175
- domain. The input uses `whoisleuth.domain-change-packet.input` version 1. The
1472
+ domain. The input remains `whoisleuth.domain-change-packet.input` version 1. The
1176
1473
  command revalidates each nested contract, summarises changed authoritative
1177
- record sets, and emits `whoisleuth.domain-change-packet` version 1 with a
1178
- canonical SHA-256 integrity digest.
1474
+ record sets, and emits `whoisleuth.domain-change-packet` version 2 with a
1475
+ `sorted-json-v2` SHA-256 integrity digest. Historical packet version 1 remains
1476
+ verifiable with its original digest bytes.
1179
1477
 
1180
1478
  The packet is ready only when both supplied observation reviews and the change
1181
1479
  plan pass their own bounded gates. Partial or unavailable evidence remains a
@@ -1230,14 +1528,46 @@ canonical SHA-256 digest and redaction manifest. It refuses an invalid,
1230
1528
  duplicate, larger, or generated package above the browser's 2 MiB import limit
1231
1529
  rather than silently omitting records or evidence. Trusted output removes notes,
1232
1530
  recipient values, and manual-pivot targets. Public output additionally removes
1233
- actions and analyst assertions. The review flag records an explicit choice; it
1234
- does not prove recipient authorisation or factual correctness.
1531
+ actions, analyst assertions, named investigation branches, and every exact
1532
+ Brand Profile identifier from both the top-level cases and embedded Case report
1533
+ v8 projections. Its redaction manifest records the bounded
1534
+ `brandProfileReferencesOmitted` count, which cannot exceed 200. Trusted and
1535
+ internal output preserve the exact references and record zero omissions.
1536
+ Trusted output retains branch references while redacting action recipients.
1537
+ The review flag records an explicit choice; it does not prove recipient
1538
+ authorisation, profile ownership, attribution, or factual correctness.
1235
1539
 
1236
1540
  `verify-artifact` recognises the complete case-pack envelope before browser
1237
1541
  import. It checks the canonical digest, supported case-export schema, bounded
1238
1542
  case count, report count, audience and explicit review marker without printing
1239
- case contents. This detects a changed hand-off file but does not authenticate
1240
- the analyst, recipient, source observations, or review decision.
1543
+ case contents. For Case schema 12 it validates the opaque-reference syntax,
1544
+ requires public cases and reports to contain no references, and requires
1545
+ trusted or internal report references to match their top-level case. The
1546
+ case-pack envelope is version 2 with `sorted-json-v2` integrity; a valid legacy
1547
+ version-1 pack carrying a
1548
+ pre-v12 Case collection remains accepted without inventing the new field or
1549
+ redaction count. Current builders reject a schema-12 raw case unless its
1550
+ complete bounded Case projection, safe canonical identity, and unique
1551
+ at-most-eight reference list survive normalisation exactly; over-bound notes,
1552
+ actions, and other fields are not silently truncated. Legacy builder input
1553
+ remains lenient for later fields. Before digest calculation, the verifier binds
1554
+ each report's supported schema version, canonical Case identifier, domain, and
1555
+ audience-sensitive projection to its top-level case. It rejects re-signed
1556
+ public notes, actions, assertions, branches, recipients, manual-trail targets,
1557
+ or Brand Profile references, and the corresponding trusted-field leaks,
1558
+ including copies at unexpected nested paths. It also enforces the exact
1559
+ epoch-specific audience exclusion list, source-case count, and omission-count
1560
+ rules. Current Case schema 12 requires report v8, schema 11 requires report v7,
1561
+ and schemas through 10 accept supported reports through v6 without inventing
1562
+ later branch or Brand-reference fields. For every audience and Case schema 2
1563
+ through 11, retained Case and report nodes must have the same object, array,
1564
+ null, or scalar kind as their bounded normalised projection, and every nested
1565
+ key must belong to that versioned projection. Historical omissions remain
1566
+ valid, while re-signed scalar substitutions and undeclared private fields do
1567
+ not. These checks
1568
+ also govern offline verification and interchange-fidelity classification. This
1569
+ detects a changed or internally inconsistent hand-off file but does not
1570
+ authenticate the analyst, recipient, source observations, or review decision.
1241
1571
 
1242
1572
  `monitor-once [manifest.json]` performs one bounded Deep control review for at
1243
1573
  most 20 manifest domains with concurrency capped at three. An optional
@@ -1250,13 +1580,23 @@ failure policies are supported.
1250
1580
  installed fixed recipes. Network steps require `--approve-network` for that
1251
1581
  invocation. Analyst-selection placeholders always pause. A bounded `--resume`
1252
1582
  state must match the exact recipe and subject, cannot add commands or shell
1253
- syntax, and is not evidence that earlier results remain current.
1583
+ syntax, and is not evidence that earlier results remain current. Retained
1584
+ successful steps must form an exact contiguous prefix of the installed recipe,
1585
+ including command, ordered arguments, mode and output schema. A final failed
1586
+ step is retried rather than trusted as completed.
1587
+
1588
+ The `domain-triage` plan deliberately pairs `export` with `verify-artifact`.
1589
+ A fresh current Lookup-evidence export must pass the verifier as
1590
+ `structure_valid`; this establishes bounded contract compatibility, not a
1591
+ checksum, signature, source truth, or observation currency.
1254
1592
 
1255
1593
  ## Optional local rendered capture
1256
1594
 
1257
1595
  `packages/web-capture` is a private repo-local Playwright package, not part of
1258
1596
  the distributable core CLI or hosted application. It requires an explicit
1259
- authorisation flag and one new output directory:
1597
+ authorisation flag, one domain-hosted HTTP(S) target, and one new output
1598
+ directory. IP-literal targets are rejected so successful manifests remain
1599
+ compatible with the domain-only Cases importer:
1260
1600
 
1261
1601
  ```bash
1262
1602
  npm run capture:local -- https://example.test --output-dir ./capture-example --authorize-rendered-capture
@@ -1268,12 +1608,18 @@ digest containing hashes and bounded element counts rather than markup or page
1268
1608
  text, and a version-2 `whoisleuth.web-capture-manifest` compatible with the
1269
1609
  Cases importer. Version-1 manifests remain importable.
1270
1610
 
1271
- Rendered capture executes page JavaScript. It caps HTTP(S) requests and request
1272
- hostnames, blocks downloads, service workers, WebSockets, WebRTC, WebTransport,
1611
+ Rendered capture executes page JavaScript. Each admitted resource operator
1612
+ receives the exact requested URL, including path and query, and ordinary
1613
+ allowlisted request headers; structured manifest and digest fields retain only
1614
+ the target hostname, final HTTP(S) origin, admitted public resource hostnames,
1615
+ and one control-sanitised page title of up to 300 characters, not those paths or queries. The screenshot necessarily
1616
+ preserves visible rendered content and may include page text or a page-reflected
1617
+ path or query until the operator deletes it. It caps HTTP(S) requests and request
1618
+ hostnames, blocks downloads, service workers, dedicated and shared workers, WebSockets, WebRTC, WebTransport,
1273
1619
  non-read methods, credentials, non-default ports, and private or reserved
1274
- addresses, and retains
1275
- no request path, query, headers, bodies, cookies, credentials, DOM markup, or
1276
- page text. Every browser request is intercepted, its hostname is resolved again,
1620
+ addresses, and retains no request path, query, headers, bodies, cookies,
1621
+ credentials, DOM markup, or body text in the structured manifest or digest
1622
+ beyond that bounded title. Every browser request is intercepted, its hostname is resolved again,
1277
1623
  and the bounded response is fetched through WHOISleuth's public-address-pinned
1278
1624
  transport. Individual responses are capped at 4 MiB and all fetched response
1279
1625
  bodies together are capped at 24 MiB. Redirect destinations pass through the
@@ -1286,9 +1632,14 @@ capture manifests. It first verifies each referenced screenshot and DOM digest
1286
1632
  against its declared size, SHA-256, screenshot dimensions, and perceptual hash;
1287
1633
  it also validates bounded source and limitation metadata and requires the DOM,
1288
1634
  manifest, and source collection times to agree. It then
1289
- compares screenshot distance, exact rendered DOM and visible-text digests,
1290
- bounded element counts, page identity, technologies, and request-domain sets.
1291
- It does not recrawl either target, reveal the input paths, or collapse the
1635
+ compares screenshot distance, exact equality of the bounded preorder
1636
+ element-tag sequence and body text-node sequence, bounded element counts, page
1637
+ identity, technologies, and request-domain sets. The tag sequence omits nesting
1638
+ and attributes. The version-1 DOM-digest schema retains the `visibleText` field
1639
+ name for compatibility, but it includes bounded body text nodes that CSS or
1640
+ non-rendered containers may hide and is not a visibility claim.
1641
+ Version-2 comparison output reports only whether the two bounded page titles
1642
+ match; it does not copy either title. It does not recrawl either target, reveal the input paths, or collapse the
1292
1643
  independent components into a similarity or maliciousness score. A missing
1293
1644
  perceptual hash remains unavailable rather than becoming a visual difference.
1294
1645
 
@@ -1301,13 +1652,36 @@ stdout, so use ordinary shell redirection when a file is wanted. Pretty JSON is
1301
1652
  the default; `--compact` emits one compact JSON line for pipeline use, while
1302
1653
  `--markdown` produces a readable source-attributed summary and `--html`
1303
1654
  produces a self-contained printable report. Compact, Markdown, and HTML output
1304
- are mutually exclusive.
1305
-
1306
- The saved input is capped at 8 MiB and revalidated using the same schema,
1307
- source-status, parsed-data, scalar, list, and event boundaries as `compare`.
1308
- The export retains query context, source diagnostics, normalised registry data,
1309
- raw registry RDAP JSON, the raw WHOIS referral chain, availability analysis,
1310
- and the shared registry-source comparison. Version 23 can add the exact local
1655
+ are mutually exclusive. Markdown and HTML include the fixed generator footer
1656
+ by default; `--no-attribution` removes only that footer and is rejected for
1657
+ JSON output.
1658
+
1659
+ The current schema-26 JSON output can be passed directly to
1660
+ `verify-artifact`. The verifier reports `structure_valid` because this format
1661
+ has no embedded checksum or signature. Use a verified investigation manifest
1662
+ and exact manifest-entry identity when byte-for-byte retention matters, and
1663
+ review the bounded privacy-projected registry RDAP publication, normalised
1664
+ WHOIS values, and bounded contacts before sharing. `interchange-report`
1665
+ recognises the current format and describes the
1666
+ browser replay as lossy by design because replay renders bounded normalised
1667
+ facts rather than raw payloads. Schema 25 remains readable when its retained
1668
+ historical wrappers differ from retained diagnostics in the ways emitted by
1669
+ the former producer. The diagnostics remain authoritative, unavailable wrapper
1670
+ data is suppressed during replay, and other contradictory legacy shapes fail
1671
+ closed rather than becoming positive observations.
1672
+
1673
+ The saved input is capped at 8 MiB and scanned before parsing for duplicate
1674
+ object keys, the prototype-sensitive `__proto__` key, more than 48 nesting levels, more than 50,000
1675
+ keys, more than 100,000 values, or more than 10,000 items in one container. It is then revalidated using the same schema, source-status,
1676
+ parsed-data, scalar, list, and event boundaries as `compare`. Portable
1677
+ projection stops at 24 levels and 20,000 entries before recursive copying, and
1678
+ the completed package must pass the same portable-tree assertion used by the
1679
+ browser replay and serializer.
1680
+ The export retains query context, explicitly projected source diagnostics,
1681
+ normalised registry data, a bounded privacy-projected registry RDAP
1682
+ publication, availability analysis, bounded WHOIS referral-hop
1683
+ metadata without response bodies, and the shared
1684
+ registry-source comparison. Version 23 can add the exact local
1311
1685
  SSLBL comparison already represented by a deep full Lookup. Version 21 adds the bounded registry
1312
1686
  lifecycle, disclosure, publication-quality, reconciliation, and abuse-routing
1313
1687
  interpretation derived from the already-collected sources. Registrar RDAP raw