@slicedearth/whoisleuth-cli 2.6.0 → 2.7.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.
- package/DISCLOSURE +8 -5
- package/NOTICE +3 -2
- package/README.md +60 -67
- package/cli/artifact-validation/case-response.mjs +6 -857
- package/cli/artifact-validation/lookup-evidence.mjs +15 -2
- package/cli/artifact-verify.mjs +32 -1
- package/cli/case-command.mjs +17 -17
- package/cli/case-pack.mjs +7 -1
- package/cli/ci-report.mjs +17 -5
- package/cli/command-definition.mjs +5 -5
- package/cli/doctor.mjs +37 -3
- package/cli/formatters/html.mjs +3 -0
- package/cli/formatters/json.mjs +8 -1
- package/cli/formatters/terminal-lookup.mjs +24 -1
- package/cli/history-command-definitions.mjs +1 -1
- package/cli/investigation-plan.mjs +15 -0
- package/cli/lookup-brief.mjs +11 -5
- package/cli/lookup-command-runner.mjs +20 -13
- package/cli/review-command-definitions.mjs +11 -11
- package/cli/saved-lookup.mjs +22 -7
- package/cli/source-reliability.mjs +12 -10
- package/cli/support-arguments.mjs +1 -1
- package/cli/support-command-definitions.mjs +5 -4
- package/cli/support-command-runner.mjs +6 -4
- package/cli/workflow-command-definitions.mjs +2 -1
- package/docs/cli-reference.md +49 -11
- package/docs/cli.md +109 -32
- package/docs/contextual-reviews.md +10 -0
- package/docs/infrastructure-observations.md +9 -0
- package/lib/dmarc-discovery.mjs +4 -2
- package/lib/dns-inheritance-review.mjs +14 -1
- package/lib/evidence-export.mjs +10 -4
- package/lib/evidence-report.mjs +2 -1
- package/lib/generated/cisa-kev-catalog.mjs +17 -4
- package/lib/generated/registrar-standing-catalogue.mjs +7 -7
- package/lib/generated/retire-browser-catalog.mjs +178 -5
- package/lib/idn-confusables.mjs +5 -1
- package/lib/lookup-collection-receipt.mjs +52 -0
- package/lib/lookup-diagnostics.mjs +2 -12
- package/lib/lookup-progress.mjs +29 -31
- package/lib/lookup-response-contract.mjs +9 -0
- package/lib/lookup-response.mjs +15 -14
- package/lib/lookup-source-progress.mjs +4 -2
- package/lib/lookup.mjs +33 -2
- package/lib/rdap-capabilities.mjs +43 -9
- package/lib/registry-insights.mjs +17 -6
- package/lib/sslbl-certificates.generated.mjs +143 -142
- package/lib/whois-authority.mjs +56 -1
- package/lib/whois-chain.mjs +2 -1
- package/lib/whois-contracts.mjs +2 -0
- package/lib/whois-normalization.mjs +4 -4
- package/package.json +6 -6
- package/packages/analysis/bounded-json.mjs +12 -2
- package/packages/cases/case-attachment-model.mjs +36 -1
- package/packages/cases/case-checkpoint-receipts.mjs +68 -0
- package/packages/cases/case-evidence-links.mjs +28 -0
- package/packages/cases/case-evidence-model.mjs +39 -6
- package/packages/cases/case-migration-model.mjs +9 -1
- package/packages/cases/case-object-coverage.mjs +106 -20
- package/packages/cases/case-record-operations.mjs +13 -0
- package/packages/cases/case-record-projection.mjs +28 -2
- package/packages/cases/case-report.mjs +5 -4
- package/packages/cases/case-response-packet-validation.mjs +880 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +12 -1
- package/packages/cases/case-response-packet.mjs +2 -15
- package/packages/cases/case-response-review-inputs.mjs +18 -1
- package/packages/cases/case-type-presentation.mjs +10 -0
- package/packages/cases/case-workflow-metadata.mjs +0 -8
- package/packages/comparison/capture-context.mjs +3 -2
- package/packages/contracts/analyst-review-state-contract.mjs +1 -1
- package/packages/contracts/case-portability.mjs +59 -28
- package/packages/contracts/cli-doctor.mjs +4 -0
- package/packages/contracts/cli-lookup.mjs +57 -46
- package/packages/contracts/lookup-evidence.mjs +12 -2
- package/packages/contracts/registry-insights.mjs +6 -0
- package/packages/evidence/dmarc-policy-context.mjs +80 -0
- package/packages/evidence/lookup-collection-receipt.mjs +140 -0
- package/packages/evidence/lookup-collection-review.mjs +74 -0
- package/packages/interchange/external-findings-import.mjs +8 -4
- package/packages/investigation/bulk-mail-exposure.mjs +2 -2
- package/packages/investigation/domain-history-review.mjs +7 -1
- package/packages/workspace/brand-keyword-campaign.mjs +11 -1
- package/packages/workspace/bulk-session-model.mjs +8 -0
- package/third-party-notices.txt +29 -29
package/docs/cli.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# WHOISleuth CLI guide
|
|
2
2
|
|
|
3
|
-
`review-evidence` also reviews bounded source-qualified infrastructure snapshots and explicit earlier/later comparison inputs entirely offline. See [infrastructure observations](infrastructure-observations.md) for the exact schemas, outcome semantics and retained-evidence limitations.
|
|
4
|
-
|
|
5
3
|
The first-party CLI runs on the operator's machine and does not call the hosted
|
|
6
4
|
WHOISleuth deployment. Use this guide for installation, common commands,
|
|
7
5
|
collection boundaries and output. Installed `whoisleuth --help`, focused
|
|
@@ -14,6 +12,11 @@ tasks, then offers searchable commands with examples, option explanations and
|
|
|
14
12
|
defaults. Command-section links can be bookmarked. Its option guidance and
|
|
15
13
|
installed help derive from the same command definitions.
|
|
16
14
|
|
|
15
|
+
Not sure which interface to use? The [installation-mode comparison](https://www.whoisleuth.com/cli#choose-mode)
|
|
16
|
+
covers the hosted console, filesystem-local application, CLI and capture companion,
|
|
17
|
+
including request origin, storage, offline use and distribution. The optional
|
|
18
|
+
application and capture archives are separate from the public CLI package.
|
|
19
|
+
|
|
17
20
|
The generated [privacy and data-flow catalogue](https://github.com/slicedearth/whoisleuth/blob/main/docs/privacy-data-flow-catalogue.md)
|
|
18
21
|
lists the network, recipient, retention and export boundary for every command.
|
|
19
22
|
|
|
@@ -77,22 +80,35 @@ private document from the interactive view:
|
|
|
77
80
|
|
|
78
81
|
```bash
|
|
79
82
|
whoisleuth lookup example.test --deep --browse --save-lookup lookup.json
|
|
83
|
+
whoisleuth verify-artifact lookup.json --json
|
|
84
|
+
whoisleuth export lookup.json --output evidence.json
|
|
85
|
+
whoisleuth verify-artifact evidence.json --json
|
|
80
86
|
```
|
|
81
87
|
|
|
82
|
-
|
|
83
|
-
its digest and source states,
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
Keep `lookup.json` as the private CLI original. In Lookup in the console, open **Replay
|
|
89
|
+
exported evidence**, select `evidence.json`, check its digest and source states,
|
|
90
|
+
then create or update a browser-local case. The private CLI document is not a
|
|
91
|
+
browser replay format; the separate evidence export excludes raw registration
|
|
92
|
+
payloads and expanded contacts. Neither file is uploaded. Case classification, exact incident links and response
|
|
93
|
+
actions remain deliberate analyst steps. Ordinary case files also support the
|
|
86
94
|
offline CLI workflow below.
|
|
87
|
-
When several incident
|
|
88
|
-
retaining replay evidence. Case packs preserve each current
|
|
95
|
+
When several incident cases share a domain, select the intended case before
|
|
96
|
+
retaining replay evidence. Case packs preserve each current case ID separately;
|
|
89
97
|
trusted and public packs exclude analyst-entered incident titles.
|
|
90
98
|
Every audience retains tags, decision summaries and rationale as well as domain
|
|
91
|
-
evidence and
|
|
99
|
+
evidence and case, pin and decision IDs. Public does not mean anonymous.
|
|
92
100
|
Browser Lookup evidence can be verified or packaged in the CLI. `brief`,
|
|
93
101
|
`export`, `compare`, `registry-doctor` and `source-report` instead require a
|
|
94
102
|
saved CLI Lookup; they do not reconstruct it from a browser export.
|
|
95
103
|
|
|
104
|
+
Registration source states are independent: `unsupported` means no RDAP or
|
|
105
|
+
WHOIS service is published for the target, while `skipped` means no query was
|
|
106
|
+
issued in that mode or under the registry access policy. Neither is a transport
|
|
107
|
+
failure or a domain-absence finding. Standard terminal output explains these
|
|
108
|
+
non-query outcomes; JSON and evidence exports retain their separate states.
|
|
109
|
+
`--strict-exit` does not fail solely because a protocol is unsupported or skipped,
|
|
110
|
+
but continues to report genuine failed or partial sources.
|
|
111
|
+
|
|
96
112
|
Process selected local input:
|
|
97
113
|
|
|
98
114
|
```bash
|
|
@@ -146,6 +162,8 @@ For an explicit inherited-policy and direct parent-delegation review, use
|
|
|
146
162
|
option, posture collection keeps its existing exact-name scope. The extra
|
|
147
163
|
results distinguish recursive policy publication from sampled parent referrals;
|
|
148
164
|
they do not establish name existence, message authentication or DNSSEC validity.
|
|
165
|
+
The machine-readable posture document keeps its existing versioned fields;
|
|
166
|
+
the website's transient structured policy scope is not included in that export.
|
|
149
167
|
|
|
150
168
|
| Mode | Behaviour |
|
|
151
169
|
| --- | --- |
|
|
@@ -160,6 +178,16 @@ their focused help. They do not use the hosted login or hosted usage controls.
|
|
|
160
178
|
`lookup --plan` lists planned source families and disclosure targets before
|
|
161
179
|
collection. `doctor` is offline unless `--network` is selected.
|
|
162
180
|
|
|
181
|
+
For troubleshooting that you intend to share, use
|
|
182
|
+
`whoisleuth doctor --summary-json > diagnostics.json`. This summary keeps numeric
|
|
183
|
+
CLI/Node versions, fixed check codes and public format identifiers from the command
|
|
184
|
+
catalogue. Those identifiers describe declared formats, not a compatibility test.
|
|
185
|
+
Targets, URLs, filenames, paths, user names, credentials, headers, timestamps and
|
|
186
|
+
free-form diagnostic details are omitted. Version prerelease/build metadata is
|
|
187
|
+
also omitted. Normal terminal and `--json` diagnostics can include error details;
|
|
188
|
+
inspect them before sharing. The summary does not enable network checks or upload
|
|
189
|
+
anything, and it has the same success/partial exit behaviour as the detailed report.
|
|
190
|
+
|
|
163
191
|
Deep Lookup reports IPv4 and IPv6 results separately, including empty answers,
|
|
164
192
|
NODATA, resolver-reported missing names and query failures. These outcomes remain
|
|
165
193
|
distinct in evidence exports; none alone establishes domain removal.
|
|
@@ -265,6 +293,8 @@ metadata or freshness admission; terminal output identifies that limitation.
|
|
|
265
293
|
|
|
266
294
|
### Contextual reviews
|
|
267
295
|
|
|
296
|
+
`review-evidence` reviews bounded source-qualified infrastructure snapshots and explicit earlier/later comparisons offline. See [infrastructure observations](infrastructure-observations.md) for supported inputs and how to interpret their outcomes.
|
|
297
|
+
|
|
268
298
|
`review-evidence` also accepts [contextual review inputs](contextual-reviews.md)
|
|
269
299
|
for source-qualified incident sequences, retained domain history, platform-object continuity, authorised storefront
|
|
270
300
|
comparison and connector provenance. These workflows are offline; partial
|
|
@@ -278,11 +308,11 @@ reassessment; they do not establish ownership or discard prior evidence.
|
|
|
278
308
|
|
|
279
309
|
`review-evidence containment.json --json --strict-exit` accepts a
|
|
280
310
|
`whoisleuth.internal-containment.input` version-1 document containing a supported
|
|
281
|
-
|
|
311
|
+
case export, selected next-step assertion and linked pin IDs, internal or trusted
|
|
282
312
|
audience, recipient role and an explicit disclosure-review acknowledgement.
|
|
283
|
-
It exports only that selected handoff, never writes the
|
|
313
|
+
It exports only that selected handoff, never writes the case or performs a
|
|
284
314
|
control. Missing or unselected supporting context stays partial (exit 4 under
|
|
285
|
-
`--strict-exit`), and a resolved
|
|
315
|
+
`--strict-exit`), and a resolved case or provider outcome does not close an open
|
|
286
316
|
internal follow-up. See [internal containment](offline-intake-context.md#selected-internal-containment-handoff)
|
|
287
317
|
for the complete input and audience limitations.
|
|
288
318
|
|
|
@@ -335,19 +365,19 @@ selected file, including an envelope when supplied. No provider API is contacted
|
|
|
335
365
|
|
|
336
366
|
## Output and automation
|
|
337
367
|
|
|
338
|
-
### Local
|
|
368
|
+
### Local case files
|
|
339
369
|
|
|
340
370
|
Manual response scope is explicit and offline. `case incident-link` takes
|
|
341
371
|
`{"url":"https://example.test/reported-page"}`; use its retained ID to bind
|
|
342
372
|
`responseObjects` on `case action`. Each object contains `kind`, `identifier`
|
|
343
|
-
and `incidentTargetId` (null for the
|
|
373
|
+
and `incidentTargetId` (null for the case domain or an associated hostname).
|
|
344
374
|
New URL objects must match that exact retained link. Exact historical identities
|
|
345
375
|
already retained in response records remain selectable after a link edit or
|
|
346
376
|
removal; they are not rebound to its replacement. An action may bind up to 20
|
|
347
377
|
objects; missing historical scope stays unknown.
|
|
348
378
|
|
|
349
379
|
`case action-event` takes `{"id":"action-id","transition":{...}}`, using
|
|
350
|
-
the same reviewed, authorised and manually submitted workflow as
|
|
380
|
+
the same reviewed, authorised and manually submitted workflow as the console.
|
|
351
381
|
After submission, a typed `objectOutcome` such as `removed`, `restricted`,
|
|
352
382
|
`suspended`, `delisted`, `restored` or `disputed` requires explicit event
|
|
353
383
|
`responseObjects`: a partial receipt never applies to all objects by default.
|
|
@@ -372,11 +402,11 @@ Independent removal, restriction, suspension, delisting, transfer and restoratio
|
|
|
372
402
|
use those same comparison requirements. Disputes and warnings retain procedural
|
|
373
403
|
source attribution separately; provider claims are not independent observations.
|
|
374
404
|
`case close-object` requires one `responseObject` and the usual typed closure
|
|
375
|
-
reason/evidence links. It leaves the
|
|
405
|
+
reason/evidence links. It leaves the case and other objects unchanged.
|
|
376
406
|
All these JSON operations require `--input`, `--output`, deliberate file
|
|
377
407
|
replacement and the existing digest/lease checks. No report or request is sent.
|
|
378
408
|
|
|
379
|
-
Create a working file, inspect its
|
|
409
|
+
Create a working file, inspect its case IDs, then append a note:
|
|
380
410
|
|
|
381
411
|
```sh
|
|
382
412
|
whoisleuth case open --domain example.test --title "Review the selected form" --output cases.json
|
|
@@ -384,8 +414,8 @@ whoisleuth case show cases.json
|
|
|
384
414
|
whoisleuth case note cases.json --note-file note.txt --output cases.json --force
|
|
385
415
|
```
|
|
386
416
|
|
|
387
|
-
Use `--case-id` when the file contains several
|
|
388
|
-
|
|
417
|
+
Use `--case-id` when the file contains several cases. `open` reuses the selected
|
|
418
|
+
case; `--new-incident --title "Another incident"` creates a distinct ID for the
|
|
389
419
|
same domain. It does not open a browser or collect anything.
|
|
390
420
|
|
|
391
421
|
`pin`, `link`, `withdraw-link`, `assess` and `recheck` read a selected JSON file with `--input`:
|
|
@@ -442,23 +472,23 @@ A recheck input contains `state`, `observedAt`, `completeness`, `source` and
|
|
|
442
472
|
`comparisonSummary`, with optional limitations and follow-up time. For example,
|
|
443
473
|
use `state: "unavailable"` and `completeness: "partial"` when a supplied capture
|
|
444
474
|
did not complete. `not_reproduced` requires a saved recheck question from the
|
|
445
|
-
|
|
475
|
+
case's console workflow, its exact `recheck` context, the target
|
|
446
476
|
`observationHostname`, a complete later observation where a baseline exists,
|
|
447
477
|
and `conditionsMatch: "comparable"`. It never means global removal or takedown.
|
|
448
478
|
|
|
449
|
-
Mutations require an explicit output path and write the complete current
|
|
479
|
+
Mutations require an explicit output path and write the complete current case
|
|
450
480
|
export. They never prune old evidence to fit. Input formatting may use up to
|
|
451
|
-
16 MiB; the canonical store remains limited to 4 MiB and 500
|
|
452
|
-
can export a selected subset to a **different** path. Ordinary
|
|
481
|
+
16 MiB; the canonical store remains limited to 4 MiB and 500 cases. `show --json`
|
|
482
|
+
can export a selected subset to a **different** path. Ordinary case imports also
|
|
453
483
|
allow 16 MiB for formatting and export metadata; the stored-data limit remains
|
|
454
|
-
4 MiB. Case packs retain their separate 25-
|
|
484
|
+
4 MiB. Case packs retain their separate 25-case selection and 4-MiB source-file
|
|
455
485
|
limits, and include generated reports within the bounded import allowance.
|
|
456
486
|
|
|
457
487
|
`show` reports the exact file digest. Add `--expect-file-digest sha256:<digest>`
|
|
458
488
|
to reject a file changed since that review. Source/output locks and atomic
|
|
459
489
|
replacement also detect changes during execution. After an interrupted process,
|
|
460
490
|
inspect its adjacent `.workflow.lock` before deliberately removing it. Validation
|
|
461
|
-
or conflict failures leave the
|
|
491
|
+
or conflict failures leave the case file unchanged and return 2. Filesystem
|
|
462
492
|
publication failures return 3.
|
|
463
493
|
|
|
464
494
|
Working files contain private analyst content and file references, not attachment
|
|
@@ -478,19 +508,19 @@ whoisleuth manifest evidence.json screenshot.png --workflow "Evidence review" \
|
|
|
478
508
|
whoisleuth verify-artifact --folder ./evidence-project --json --strict-exit
|
|
479
509
|
```
|
|
480
510
|
|
|
481
|
-
Packages admit the 128-file, 64-MiB original selection plus one bounded
|
|
511
|
+
Packages admit the 128-file, 64-MiB original selection plus one bounded case
|
|
482
512
|
export, with a separate manifest allowance. Original-file limits remain unchanged.
|
|
483
|
-
`verify-artifact` uses the same exact ordinary
|
|
513
|
+
`verify-artifact` uses the same exact ordinary case reader as `case show`,
|
|
484
514
|
without repairing content. Package verification also reports original-reference
|
|
485
|
-
completeness for each
|
|
486
|
-
An ordinary
|
|
515
|
+
completeness for each case file; `--strict-exit` returns 4 when a referenced original is missing.
|
|
516
|
+
An ordinary case JSON file alone provides structural validity, not a checksum.
|
|
487
517
|
They use generated entry names, not original paths. Ordinary ZIPs and folders
|
|
488
518
|
are private and unencrypted; packaging does not redact selected files. The report distinguishes
|
|
489
519
|
file identity, supported source formats, opaque content, exact capsule/source
|
|
490
520
|
links, declared image derivation and capture-manifest attachment matches.
|
|
491
521
|
Image derivation reports an immediate-parent digest and byte length, editing
|
|
492
522
|
method and operation kinds, not proof of transformation. Parent inclusion matches
|
|
493
|
-
another selected manifest entry by exact byte digest and size; an unselected
|
|
523
|
+
another selected manifest entry by exact byte digest and size; an unselected case
|
|
494
524
|
attachment reference does not count. Parent pixels are optional; absent declarations
|
|
495
525
|
leave editing history unknown. Include the capture manifest and
|
|
496
526
|
its screenshot/DOM-digest files together to check their declared bytes; original
|
|
@@ -551,6 +581,35 @@ trees and unlisted files. Its package digest and byte count describe the
|
|
|
551
581
|
canonical stored-ZIP representation, not filesystem metadata. These are
|
|
552
582
|
selected evidence exports, not complete workspace backups.
|
|
553
583
|
|
|
584
|
+
### Exporting a fixed workflow for review
|
|
585
|
+
|
|
586
|
+
Terminal plans and recipe explanations distinguish networked steps, offline
|
|
587
|
+
steps and declared reuse of earlier outputs. These are step and handoff counts,
|
|
588
|
+
not request totals or confirmation that a saved file is available. The planner
|
|
589
|
+
does not inspect files or caches; collection and evidence selection remain
|
|
590
|
+
deliberate actions. JSON exports keep their existing versioned fields.
|
|
591
|
+
|
|
592
|
+
Select one installed recipe and save its declared steps for review:
|
|
593
|
+
|
|
594
|
+
```sh
|
|
595
|
+
whoisleuth workflow-plan --explain domain-triage --json --output recipe.json
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
The document includes the subject requirement, command and argument examples,
|
|
599
|
+
network modes, required approvals, output artefact families and completion guidance.
|
|
600
|
+
To prepare the same recipe for a specific subject:
|
|
601
|
+
|
|
602
|
+
```sh
|
|
603
|
+
whoisleuth workflow-plan domain-triage example.test --json --output plan.json
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
Both commands plan offline. The files contain declarative steps rather than
|
|
607
|
+
shell history or executable scripts. Argument examples and approval fields
|
|
608
|
+
describe requirements; they do not record a completed review or grant approval.
|
|
609
|
+
These are review documents, not `workflow-run` checkpoints or custom recipe
|
|
610
|
+
imports. Run an installed recipe deliberately with its required inputs and
|
|
611
|
+
fresh approvals.
|
|
612
|
+
|
|
554
613
|
### Resuming a fixed workflow
|
|
555
614
|
|
|
556
615
|
Inspect a paused run without executing any step or rewriting its checkpoint:
|
|
@@ -583,7 +642,7 @@ whoisleuth workflow-run domain-triage example.test --approve-network \
|
|
|
583
642
|
```
|
|
584
643
|
|
|
585
644
|
Bindings accept Lookup outputs for export, diff, timeline, comparison, source
|
|
586
|
-
reports and briefs; evidence exports for verification; and
|
|
645
|
+
reports and briefs; evidence exports for verification; and case-pack output
|
|
587
646
|
for sharing review. Remaining placeholders use `--select` in order;
|
|
588
647
|
for example, `--select diff=previous.json` keeps the current observation as the
|
|
589
648
|
second input. Supply every input for an uncompleted step to use files instead,
|
|
@@ -698,6 +757,9 @@ and machine output contains no ANSI or transient progress text.
|
|
|
698
757
|
- `lookup --browse` provides an interactive terminal view; press `?` for keys.
|
|
699
758
|
- `--events` writes versioned lifecycle events to stderr.
|
|
700
759
|
- `--strict-exit` and `--fail-on` expose selected evidence states to automation.
|
|
760
|
+
- `lookup --junit` reports retained diagnostic source states, not version or
|
|
761
|
+
timing metadata. Nested diagnostic failures remain failures; target-bearing
|
|
762
|
+
diagnostic paths are not copied into the XML. This is not a truth verdict.
|
|
701
763
|
- `--output` writes a private local file atomically and refuses an existing path
|
|
702
764
|
unless `--force` is also selected.
|
|
703
765
|
|
|
@@ -708,7 +770,22 @@ options override profile defaults.
|
|
|
708
770
|
|
|
709
771
|
`source-report` summarises collector-owned health envelopes in saved Lookup
|
|
710
772
|
and Bulk output. Raw publications and extension fields are not interpreted as
|
|
711
|
-
diagnostics, and missing health metadata remains unmeasured.
|
|
773
|
+
diagnostics, and missing health metadata remains unmeasured. Its text view groups
|
|
774
|
+
collection states, sample counts and durations under each source. Observation
|
|
775
|
+
durations and Lookup timings stay separate; merged-report distributions describe
|
|
776
|
+
retained report statistics, not a new measurement of individual requests.
|
|
777
|
+
|
|
778
|
+
After verifying a domain-control or Lookup-claim passport, `verify-artifact`
|
|
779
|
+
suggests the compatible offline `interchange-report` command. Suggestions use
|
|
780
|
+
example filenames and never run automatically. Other artefact families receive
|
|
781
|
+
guidance only when the suggested reader supports that format.
|
|
782
|
+
|
|
783
|
+
The text output of `brief` groups selected facts in an indented source tree.
|
|
784
|
+
Each source keeps its collection state and observation time, including sources
|
|
785
|
+
with no fact selected for the brief; that does not mean the source was empty.
|
|
786
|
+
Conflicting values remain separate and include both the value state and source
|
|
787
|
+
state. The brief uses retained evidence only. Use `--json` for the
|
|
788
|
+
structured report, including its facts, source health, unknowns and action plan.
|
|
712
789
|
|
|
713
790
|
`mail-review` keeps failed input targets, incomplete DNS coverage and original
|
|
714
791
|
collection times visible. Source-output and review-generation times do not
|
|
@@ -71,6 +71,16 @@ to the retained concern. Incomplete comparisons remain visible. A changed creati
|
|
|
71
71
|
date asks for reassessment of earlier decisions without asserting a new owner,
|
|
72
72
|
resetting a baseline or discarding history; expiry alone never establishes deletion.
|
|
73
73
|
|
|
74
|
+
Open **Retirement review checklist** for DNS-alias, recovery-email and retained
|
|
75
|
+
integration-reference prompts. Review the configuration you control, then use
|
|
76
|
+
the existing declaration form to record the affected hostnames, evidence family,
|
|
77
|
+
retirement time and source. The draft shows these details before you prepare a
|
|
78
|
+
review. Prefer a configuration or change-reference label; do not enter recovery
|
|
79
|
+
addresses, credentials or complete URLs. Reports with declared retired dependencies
|
|
80
|
+
include the same suggested next reviews in the browser and CLI. Suggestions do not
|
|
81
|
+
record checks as completed or establish takeover availability. Saving preserves
|
|
82
|
+
the source-qualified declaration, not confirmation that configuration was changed.
|
|
83
|
+
|
|
74
84
|
Schema: `whoisleuth.domain-history.input`. Its `evidence` contains:
|
|
75
85
|
|
|
76
86
|
- `caseExport`: an ordinary supported editable Case export, with `version` and
|
|
@@ -43,6 +43,15 @@ Topology search applies to the complete admitted one-hop relationship set, not
|
|
|
43
43
|
the current source page. Its diagram has a 50-relationship budget; the paginated
|
|
44
44
|
source list and exact snapshot review retain access to evidence beyond the diagram.
|
|
45
45
|
|
|
46
|
+
Follow a retained domain to an address or certificate, then open its supporting
|
|
47
|
+
Case. **Return to retained infrastructure** restores the exact query, filters,
|
|
48
|
+
page positions and selected identity. **Tab references → Navigation trail** also
|
|
49
|
+
lets you return to earlier successful pivots. This trail lives only in the
|
|
50
|
+
current tab and workspace; it creates no observed relationships. Sources are
|
|
51
|
+
read again rather than copied into navigation state. A removed identity stays
|
|
52
|
+
unavailable, and clearing references or locking the workspace expires the links.
|
|
53
|
+
Certificate identities here are leaf-certificate SHA-256, not SPKI digests.
|
|
54
|
+
|
|
46
55
|
Lookup links fill the selected target for review without submitting it. Explicitly
|
|
47
56
|
selected hostnames can be prepared in existing Bulk. That handoff does not scan,
|
|
48
57
|
recursively expand targets, change a watchlist or enrol a host in monitoring.
|
package/lib/dmarc-discovery.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isValidAsciiHostname } from "../packages/contracts/domain-name.mjs";
|
|
2
2
|
import { normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
|
|
3
3
|
import { parseDmarcRecords } from "./domain-posture-parsers.mjs";
|
|
4
|
+
import { MAX_DMARC_DISCOVERY_OWNERS } from "../packages/evidence/dmarc-policy-context.mjs";
|
|
4
5
|
export function requireDmarcDomain(domain) {
|
|
5
6
|
if (typeof domain !== 'string' || domain.length > 253 || !isValidAsciiHostname(domain) || domain !== domain.toLowerCase()) {
|
|
6
7
|
throw new TypeError('The additional DNS review requires a normalised domain.');
|
|
@@ -29,7 +30,8 @@ export function dmarcPolicyObservation(owner, observation) {
|
|
|
29
30
|
: state;
|
|
30
31
|
const time = typeof observation.observedAt === 'string' && observation.observedAt.length <= 64
|
|
31
32
|
? normalizeExplicitIsoTimestamp(observation.observedAt) : null;
|
|
32
|
-
return { owner, parsed, psd, valid, unavailable, incomplete,
|
|
33
|
+
return { owner, parsed, psd, valid, unavailable, incomplete, observedAt: time,
|
|
34
|
+
state: unavailable ? 'unavailable' : valid ? 'observed' : parsed.records.length ? 'invalid' : 'not_found',
|
|
33
35
|
record: `Recursive DNS TXT _dmarc.${owner} · ${time ?? 'time unavailable'} · ${description}` };
|
|
34
36
|
}
|
|
35
37
|
/** RFC 9989 sections 4.10–4.10.2: at most eight owners, never PSL inference. */
|
|
@@ -38,7 +40,7 @@ export async function walkDmarcTree(domain, exact, resolve, purpose) {
|
|
|
38
40
|
const rows = [dmarcPolicyObservation(domain, exact)];
|
|
39
41
|
const first = rows[0];
|
|
40
42
|
const labels = domain.split('.');
|
|
41
|
-
let next = labels.length >=
|
|
43
|
+
let next = labels.length >= MAX_DMARC_DISCOVERY_OWNERS ? labels.slice(-(MAX_DMARC_DISCOVERY_OWNERS - 1)) : labels.slice(1);
|
|
42
44
|
const policyOnly = purpose === 'policy' && first.valid;
|
|
43
45
|
if (!policyOnly && !first.unavailable && !(first.valid && ['n', 'y'].includes(first.psd))) {
|
|
44
46
|
while (next.length) {
|
|
@@ -5,6 +5,7 @@ import { requireDmarcDomain, walkDmarcTree } from "./dmarc-discovery.mjs";
|
|
|
5
5
|
import { buildDnssecQuery, DNS_TYPE_NS, normalizeResolverEndpoint, parseDnssecResponse } from "./dnssec-chain-validation.mjs";
|
|
6
6
|
import { defaultTcpExchange } from "./service-binding-dns.mjs";
|
|
7
7
|
import { isValidAsciiHostname } from "../packages/contracts/domain-name.mjs";
|
|
8
|
+
import { normalizeDmarcPolicyContext } from "../packages/evidence/dmarc-policy-context.mjs";
|
|
8
9
|
const MAX_PARENT_SERVERS = 2;
|
|
9
10
|
const MAX_NS = 16;
|
|
10
11
|
const QUERY_TIMEOUT_MS = 2200;
|
|
@@ -35,7 +36,19 @@ export async function reviewInheritedDmarc(domain, exact, resolve) {
|
|
|
35
36
|
? `Published policy: ${selected.parsed.policy}. Test mode: ${selected.parsed.testMode ? 'yes' : 'no'}.`
|
|
36
37
|
: `For an existing name: ${selected.parsed.subdomainPolicy}. For a nonexistent name: ${selected.parsed.nonexistentSubdomainPolicy}. Test mode: ${selected.parsed.testMode ? 'yes' : 'no'}. Name existence is not inferred from this review.`
|
|
37
38
|
: 'Invalid or unavailable records do not establish absence or a usable inherited policy.';
|
|
38
|
-
|
|
39
|
+
const effective = !incomplete ? selected : null;
|
|
40
|
+
return {
|
|
41
|
+
...check('dmarc_inheritance', 'Inherited DMARC policy', incomplete || !selected ? 'warning' : 'info', summary, `${detail} Discovery follows RFC 9989 with at most eight owners. This is publication evidence, not message authentication or a guarantee of receiver behaviour.`, rows.map(row => row.record)),
|
|
42
|
+
dmarcPolicyContext: normalizeDmarcPolicyContext({
|
|
43
|
+
version: 1, source: 'recursive_dns_txt', complete: !incomplete,
|
|
44
|
+
relation: effective ? effective.owner === domain ? 'exact' : 'inherited' : 'undetermined',
|
|
45
|
+
owner: effective?.owner ?? null, observedAt: effective?.observedAt ?? null,
|
|
46
|
+
policy: effective?.parsed.policy ?? null, subdomainPolicy: effective?.parsed.subdomainPolicy ?? null,
|
|
47
|
+
nonexistentPolicy: effective?.parsed.nonexistentSubdomainPolicy ?? null,
|
|
48
|
+
psd: effective?.psd ?? null, testMode: effective?.parsed.testMode ?? null,
|
|
49
|
+
discovery: rows.map(row => ({ owner: row.owner, state: row.state, observedAt: row.observedAt })),
|
|
50
|
+
}, domain),
|
|
51
|
+
};
|
|
39
52
|
}
|
|
40
53
|
function nameservers(value) {
|
|
41
54
|
if (!Array.isArray(value) || value.length > MAX_NS)
|
package/lib/evidence-export.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { buildRegistryInsights } from "./registry-insights.mjs";
|
|
|
6
6
|
import { buildPortableGeneratorMetadata } from "../packages/analysis/portable-generator.mjs";
|
|
7
7
|
import { assertBoundedJsonStructure } from "../packages/analysis/bounded-json.mjs";
|
|
8
8
|
import { normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
|
|
9
|
-
import { LOOKUP_EVIDENCE_SCHEMA, LOOKUP_EVIDENCE_SCHEMA_VERSION, } from "../packages/contracts/lookup-evidence.mjs";
|
|
9
|
+
import { LOOKUP_EVIDENCE_SCHEMA, LOOKUP_EVIDENCE_SCHEMA_VERSION, AUTHORITY_AWARE_LOOKUP_EVIDENCE_SCHEMA_VERSION, } from "../packages/contracts/lookup-evidence.mjs";
|
|
10
10
|
export * from "../packages/contracts/lookup-evidence.mjs";
|
|
11
11
|
import { isValidAsciiHostname } from "../packages/contracts/domain-name.mjs";
|
|
12
12
|
import { validLookupObservationScope } from "../packages/evidence/lookup-target.mjs";
|
|
@@ -705,6 +705,7 @@ const LOOKUP_WHOIS_PORTABLE_SCALARS = Object.freeze({
|
|
|
705
705
|
failedHop: nullableText(253),
|
|
706
706
|
conflictingHop: nullableText(253),
|
|
707
707
|
});
|
|
708
|
+
const WHOIS_REGISTRATION_STATUS = enumeration(new Set(['registered', 'not_found', 'inconclusive']));
|
|
708
709
|
const LOOKUP_REGISTRY_INSIGHT_PORTABLE_KEYS = new Set([
|
|
709
710
|
'version', 'contactDisclosure', 'lifecycle', 'reconciliation', 'publications',
|
|
710
711
|
'rdapCapabilities',
|
|
@@ -815,11 +816,16 @@ export function projectLookupEvidenceRdapPublication(value) {
|
|
|
815
816
|
return output;
|
|
816
817
|
}
|
|
817
818
|
/** Positive portable projection for normalized WHOIS publication fields. */
|
|
818
|
-
export function projectLookupEvidenceWhoisPublication(value) {
|
|
819
|
+
export function projectLookupEvidenceWhoisPublication(value, schemaVersion = LOOKUP_EVIDENCE_SCHEMA_VERSION) {
|
|
819
820
|
const input = recordOrNull(value);
|
|
820
821
|
if (!input)
|
|
821
822
|
return null;
|
|
822
823
|
const output = projectedKnownScalarRecord(input, LOOKUP_WHOIS_PORTABLE_SCALARS) || {};
|
|
824
|
+
if (schemaVersion >= AUTHORITY_AWARE_LOOKUP_EVIDENCE_SCHEMA_VERSION && Object.hasOwn(input, 'registrationStatus')) {
|
|
825
|
+
const status = WHOIS_REGISTRATION_STATUS(input.registrationStatus);
|
|
826
|
+
if (status !== undefined)
|
|
827
|
+
output.registrationStatus = status;
|
|
828
|
+
}
|
|
823
829
|
if (Object.hasOwn(input, 'lifecycle')) {
|
|
824
830
|
output.lifecycle = projectedKnownScalarRecord(input.lifecycle, LOOKUP_RDAP_LIFECYCLE_SCALARS);
|
|
825
831
|
}
|
|
@@ -884,7 +890,7 @@ function projectedWhoisChainHop(value) {
|
|
|
884
890
|
};
|
|
885
891
|
}
|
|
886
892
|
/** Exact positive projection for one current-schema WHOIS source wrapper. */
|
|
887
|
-
export function projectLookupEvidenceWhoisSourcePublication(value) {
|
|
893
|
+
export function projectLookupEvidenceWhoisSourcePublication(value, schemaVersion = LOOKUP_EVIDENCE_SCHEMA_VERSION) {
|
|
888
894
|
const source = recordOrNull(value);
|
|
889
895
|
if (!source)
|
|
890
896
|
return null;
|
|
@@ -895,7 +901,7 @@ export function projectLookupEvidenceWhoisSourcePublication(value) {
|
|
|
895
901
|
error: boundedSourceError(source.error, 'WHOIS source reported an error.'),
|
|
896
902
|
};
|
|
897
903
|
const hasPublication = status === 'complete' || status === 'partial';
|
|
898
|
-
const parsed = hasPublication ? projectLookupEvidenceWhoisPublication(source.parsed) : null;
|
|
904
|
+
const parsed = hasPublication ? projectLookupEvidenceWhoisPublication(source.parsed, schemaVersion) : null;
|
|
899
905
|
if (status === 'complete' && parsed === null)
|
|
900
906
|
return null;
|
|
901
907
|
return {
|
package/lib/evidence-report.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registryAccessProfileLabel } from "./registry-access.mjs";
|
|
2
|
+
import { isSupportedRegistryInsightsVersion } from "../packages/contracts/registry-insights.mjs";
|
|
2
3
|
import { dnsAddressFamilyEvidence } from "../packages/evidence/dns-query-outcome.mjs";
|
|
3
4
|
import { buildPortableGeneratorMetadata, portableGeneratorAttribution, } from "../packages/analysis/portable-generator.mjs";
|
|
4
5
|
import { technologyEvidenceRoles, } from "./technology-evidence-role.mjs";
|
|
@@ -386,7 +387,7 @@ function buildLookupEvidenceReport(document, options = {}) {
|
|
|
386
387
|
reportField('Standing limitations', listText(registrarStanding.limitations)),
|
|
387
388
|
reportField('Review steps', listText(registrarStanding.nextActions)),
|
|
388
389
|
] : [],
|
|
389
|
-
registryInterpretation: registryInsights.version
|
|
390
|
+
registryInterpretation: isSupportedRegistryInsightsVersion(registryInsights.version) ? [
|
|
390
391
|
reportField('Lifecycle', displayLabel(registryLifecycle.label)),
|
|
391
392
|
reportField('Raw lifecycle statuses', listText(registryLifecycle.rawStatuses)),
|
|
392
393
|
reportField('Client registration lock observed', yesNoUnknown(registryLocks.client)),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Generated by tools/cisa-kev-catalog.mts from the pinned CISA KEV feed.
|
|
2
2
|
// United States Government work; do not edit by hand.
|
|
3
3
|
const CISA_KEV_CATALOG = Object.freeze({
|
|
4
|
-
catalogVersion: "2026.10.
|
|
5
|
-
releasedAt: "2026-10-
|
|
6
|
-
sourceSha256: "
|
|
4
|
+
catalogVersion: "2026.10.15",
|
|
5
|
+
releasedAt: "2026-10-15T17:00:11.2795Z",
|
|
6
|
+
sourceSha256: "0a4807c44c302e6c3f8ba5adbcd45f8163514f52059f636ad9048728cf0c57b8",
|
|
7
7
|
sourceUrl: "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
|
|
8
8
|
identifiers: Object.freeze([
|
|
9
9
|
"CVE-2002-0367",
|
|
@@ -194,6 +194,7 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
194
194
|
"CVE-2015-3043",
|
|
195
195
|
"CVE-2015-3113",
|
|
196
196
|
"CVE-2015-3246",
|
|
197
|
+
"CVE-2015-3306",
|
|
197
198
|
"CVE-2015-4068",
|
|
198
199
|
"CVE-2015-4495",
|
|
199
200
|
"CVE-2015-4852",
|
|
@@ -203,6 +204,7 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
203
204
|
"CVE-2015-5123",
|
|
204
205
|
"CVE-2015-5287",
|
|
205
206
|
"CVE-2015-5317",
|
|
207
|
+
"CVE-2015-5477",
|
|
206
208
|
"CVE-2015-6175",
|
|
207
209
|
"CVE-2015-7450",
|
|
208
210
|
"CVE-2015-7645",
|
|
@@ -229,6 +231,7 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
229
231
|
"CVE-2016-20017",
|
|
230
232
|
"CVE-2016-2386",
|
|
231
233
|
"CVE-2016-2388",
|
|
234
|
+
"CVE-2016-3081",
|
|
232
235
|
"CVE-2016-3088",
|
|
233
236
|
"CVE-2016-3235",
|
|
234
237
|
"CVE-2016-3298",
|
|
@@ -842,6 +845,7 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
842
845
|
"CVE-2021-31955",
|
|
843
846
|
"CVE-2021-31956",
|
|
844
847
|
"CVE-2021-31979",
|
|
848
|
+
"CVE-2021-3199",
|
|
845
849
|
"CVE-2021-32030",
|
|
846
850
|
"CVE-2021-32648",
|
|
847
851
|
"CVE-2021-33044",
|
|
@@ -1080,6 +1084,7 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1080
1084
|
"CVE-2023-22515",
|
|
1081
1085
|
"CVE-2023-22518",
|
|
1082
1086
|
"CVE-2023-22527",
|
|
1087
|
+
"CVE-2023-22894",
|
|
1083
1088
|
"CVE-2023-22952",
|
|
1084
1089
|
"CVE-2023-23376",
|
|
1085
1090
|
"CVE-2023-23397",
|
|
@@ -1573,10 +1578,14 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1573
1578
|
"CVE-2025-9377",
|
|
1574
1579
|
"CVE-2026-0257",
|
|
1575
1580
|
"CVE-2026-0300",
|
|
1581
|
+
"CVE-2026-0322",
|
|
1576
1582
|
"CVE-2026-0770",
|
|
1577
1583
|
"CVE-2026-102489",
|
|
1578
1584
|
"CVE-2026-102490",
|
|
1579
1585
|
"CVE-2026-104286",
|
|
1586
|
+
"CVE-2026-104935",
|
|
1587
|
+
"CVE-2026-105133",
|
|
1588
|
+
"CVE-2026-105134",
|
|
1580
1589
|
"CVE-2026-10520",
|
|
1581
1590
|
"CVE-2026-11645",
|
|
1582
1591
|
"CVE-2026-12569",
|
|
@@ -1616,6 +1625,7 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1616
1625
|
"CVE-2026-21519",
|
|
1617
1626
|
"CVE-2026-21525",
|
|
1618
1627
|
"CVE-2026-21533",
|
|
1628
|
+
"CVE-2026-21589",
|
|
1619
1629
|
"CVE-2026-21643",
|
|
1620
1630
|
"CVE-2026-21962",
|
|
1621
1631
|
"CVE-2026-22719",
|
|
@@ -1711,6 +1721,7 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1711
1721
|
"CVE-2026-76460",
|
|
1712
1722
|
"CVE-2026-76461",
|
|
1713
1723
|
"CVE-2026-76504",
|
|
1724
|
+
"CVE-2026-76519",
|
|
1714
1725
|
"CVE-2026-8037",
|
|
1715
1726
|
"CVE-2026-81578",
|
|
1716
1727
|
"CVE-2026-81963",
|
|
@@ -1733,12 +1744,14 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1733
1744
|
"CVE-2026-87902",
|
|
1734
1745
|
"CVE-2026-88771",
|
|
1735
1746
|
"CVE-2026-88772",
|
|
1747
|
+
"CVE-2026-88779",
|
|
1736
1748
|
"CVE-2026-9082",
|
|
1737
1749
|
"CVE-2026-9198",
|
|
1738
1750
|
"CVE-2026-93616",
|
|
1739
1751
|
"CVE-2026-93952",
|
|
1740
1752
|
"CVE-2026-94127",
|
|
1741
|
-
"CVE-2026-9586"
|
|
1753
|
+
"CVE-2026-9586",
|
|
1754
|
+
"CVE-2026-96951"
|
|
1742
1755
|
]),
|
|
1743
1756
|
});
|
|
1744
1757
|
export { CISA_KEV_CATALOG };
|