@slicedearth/whoisleuth-cli 1.47.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -177
- package/SECURITY.md +6 -3
- package/bin/whoisleuth.mjs +2 -1
- package/cli/archive-inspect.mjs +28 -10
- package/cli/arguments.mjs +204 -174
- package/cli/artifact-structure.mjs +837 -163
- package/cli/artifact-verify.mjs +90 -61
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +33 -20
- package/cli/bulk.mjs +9 -2
- package/cli/case-pack.mjs +180 -576
- package/cli/ci-report.mjs +6 -1
- package/cli/collection-preflight.mjs +2 -2
- package/cli/command-catalogue.mjs +12 -3
- package/cli/command-reference.mjs +910 -92
- package/cli/completion.mjs +504 -226
- package/cli/config-profile.mjs +13 -3
- package/cli/discover.mjs +7 -1
- package/cli/discovery-observation-snapshot.mjs +24 -28
- package/cli/discovery-scan-command-runner.mjs +9 -9
- package/cli/discovery-scan.mjs +17 -3
- package/cli/discovery-snapshot.mjs +15 -7
- package/cli/domain-control-monitor.mjs +203 -30
- package/cli/domain-control-observations.mjs +15 -21
- package/cli/errors.mjs +8 -3
- package/cli/evidence-signing.mjs +10 -20
- package/cli/external-observation-mapping.mjs +3 -3
- package/cli/formatters/json.mjs +20 -32
- package/cli/formatters/terminal.mjs +583 -22
- package/cli/input.mjs +7 -2
- package/cli/interactive-launcher.mjs +226 -0
- package/cli/interchange-report.mjs +19 -19
- package/cli/investigation-manifest.mjs +1 -1
- package/cli/investigation-plan.mjs +167 -16
- package/cli/investigation-run.mjs +9 -1
- package/cli/lookalike-calibration.mjs +3 -1
- package/cli/lookup-brief.mjs +13 -8
- package/cli/lookup-browser.mjs +844 -0
- package/cli/lookup-command-runner.mjs +94 -31
- package/cli/lookup-diff.mjs +2 -2
- package/cli/lookup-plan.mjs +1 -0
- package/cli/mail-review.mjs +206 -94
- package/cli/manual.mjs +5 -5
- package/cli/offline-evidence-review.mjs +18 -7
- package/cli/open-asset-model-bridge.mjs +1 -1
- package/cli/output-file.mjs +70 -14
- package/cli/page-compare.mjs +11 -6
- package/cli/progress-events.mjs +2 -1
- package/cli/progress.mjs +3 -3
- package/cli/registry-cohort.mjs +9 -51
- package/cli/registry-doctor.mjs +67 -28
- package/cli/retained-artifact-diff.mjs +7 -8
- package/cli/risk-calibration.mjs +85 -38
- package/cli/runner.mjs +189 -75
- package/cli/saved-lookup.mjs +424 -57
- package/cli/sharing-review.mjs +6 -1
- package/cli/source-reliability.mjs +75 -36
- package/cli/terminal-presentation.mjs +43 -18
- package/docs/cli-reference.md +125 -1708
- package/docs/cli.md +86 -254
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -145
- package/frontend/src/lib/analysis/domain-control-records.js +3 -88
- package/lib/availability.mjs +58 -40
- package/lib/bounded-contract-normalizers.mjs +2 -4
- package/lib/bounded-file.mjs +78 -4
- package/lib/bounded-json.mjs +51 -13
- package/lib/browser-library-profile.mjs +1 -1
- package/lib/classify.mjs +56 -5
- package/lib/client-behavior-profile.mjs +1 -1
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/cryptographic-assurance.mjs +35 -17
- package/lib/ct-query.mjs +3 -3
- package/lib/ct-search.mjs +37 -21
- package/lib/distributed-operation-budget.mjs +4 -2
- package/lib/dns-delegation-health.mjs +9 -6
- package/lib/dns-intelligence.mjs +1 -1
- package/lib/dnssec-chain-validation.mjs +2 -3
- package/lib/dnssec-evidence-validation.mjs +13 -7
- package/lib/domain-assurance.mjs +12 -12
- package/lib/domain-change-packet.mjs +1 -1
- package/lib/domain-control-flight-recorder.mjs +442 -78
- package/lib/domain-control-manifest.mjs +314 -59
- package/lib/domain-posture-analysis.mjs +5 -2
- package/lib/encrypted-dns-contract.mjs +2 -3
- package/lib/evidence-export.mjs +518 -36
- package/lib/external-intelligence-risk.mjs +26 -10
- package/lib/feature-policy.mjs +1 -1
- package/lib/homepage-metadata-contract.mjs +290 -0
- package/lib/hostname.mjs +1 -1
- package/lib/html-signals.mjs +89 -5
- package/lib/http-intelligence.mjs +331 -2
- package/lib/interchange-fidelity-registry.mjs +28 -26
- package/lib/lookup-cache.mjs +6 -4
- package/lib/lookup.mjs +25 -2
- package/lib/observed-network-context.mjs +1 -1
- package/lib/page-fingerprints.mjs +2 -2
- package/lib/page-role-profile.mjs +1 -1
- package/lib/portable-generator.mjs +8 -0
- package/lib/provider-policy-admission.mjs +3 -1
- package/lib/rdap-bootstrap.mjs +76 -16
- package/lib/rdap-client.mjs +20 -12
- package/lib/rdap-normalization.mjs +306 -75
- package/lib/rdap-transport.mjs +2 -2
- package/lib/rdap-validation.mjs +65 -14
- package/lib/rdap.mjs +2 -2
- package/lib/registrable-domain.mjs +36 -0
- package/lib/registry-insights.mjs +7 -0
- package/lib/response-policy.mjs +27 -4
- package/lib/risk-calibration-summary.mjs +35 -33
- package/lib/risk-scoring.mjs +4 -3
- package/lib/safe-fetch.mjs +62 -40
- package/lib/security-txt.mjs +1 -1
- package/lib/smtp-transport-review.mjs +8 -5
- package/lib/sslbl-intelligence.mjs +6 -8
- package/lib/static-html-analysis.mjs +301 -4
- package/lib/structured-data-identity.mjs +51 -19
- package/lib/threat-intelligence-fixtures.mjs +2 -4
- package/lib/threat-intelligence-manifests.mjs +2 -7
- package/lib/threat-intelligence-runtime.mjs +3 -5
- package/lib/threat-intelligence-types.mjs +3 -0
- package/lib/threatfox-intelligence.mjs +7 -3
- package/lib/tls-intelligence.mjs +4 -4
- package/lib/urlhaus-intelligence.mjs +4 -2
- package/lib/urlscan-intelligence.mjs +6 -5
- package/lib/website-security-posture.mjs +1 -1
- package/lib/website-technology.mjs +1 -1
- package/lib/whois-authority.mjs +43 -8
- package/lib/whois-chain.mjs +25 -3
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-transport.mjs +12 -3
- package/lib/zone-intent-review.mjs +1 -1
- package/package.json +12 -4
- package/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
- package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
- package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
- package/packages/cases/case-migration-model.mjs +476 -0
- package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
- package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -21
- package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
- package/packages/cases/case-record-model.mjs +9 -0
- package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +72 -29
- package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
- package/packages/cases/case-response-model.mjs +1604 -0
- package/packages/cases/case-response-packet.mjs +1312 -0
- package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
- package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
- package/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
- package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
- package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
- package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -0
- package/packages/comparison/favicon-similarity.mjs +78 -0
- package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
- package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
- package/packages/contracts/analyst-review-state-contract.mjs +61 -0
- package/packages/contracts/analyst-review-state.mjs +50 -0
- package/packages/contracts/case-portability.mjs +1310 -0
- package/packages/contracts/cli-command-catalogue.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +394 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
- package/packages/contracts/domain-control-manifest.mjs +1027 -0
- package/packages/contracts/domain-control-monitor.mjs +310 -0
- package/packages/contracts/domain-control-review.mjs +828 -0
- package/packages/contracts/domain-name.mjs +4 -0
- package/packages/contracts/external-observation-interchange.mjs +224 -0
- package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
- package/packages/contracts/investigation-portability.mjs +331 -0
- package/packages/contracts/offline-comparison.mjs +160 -0
- package/packages/contracts/risk-calibration.mjs +1288 -0
- package/packages/contracts/schema-compatibility.mjs +71 -0
- package/packages/contracts/schema-lifecycle.mjs +1702 -0
- package/packages/contracts/sslbl-snapshot.mjs +17 -0
- package/packages/contracts/workspace-portability.mjs +1347 -0
- package/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
- package/packages/evidence/decision-fact.mjs +735 -0
- package/packages/evidence/domain-control-runtime.mjs +547 -0
- package/packages/evidence/domain-name.mjs +41 -0
- package/packages/evidence/observation.mjs +384 -0
- package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +24 -22
- package/packages/interchange/retained-text.mjs +8 -0
- package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
- package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
- package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
- package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
- package/packages/investigation/bulk-review-policy.mjs +1 -0
- package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
- package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
- package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
- package/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
- package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
- package/packages/investigation/lookup-investigation-brief.mjs +172 -0
- package/packages/monitoring/analyst-review-state.mjs +680 -0
- package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +70 -47
- package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
- package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
- package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -5
- package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
- package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
- package/packages/workspace/hostile-input.mjs +172 -0
- package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +8 -45
- package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
- package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
- package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
- package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
- package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
- package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
- package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +13 -13
- package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
- package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +110 -89
- package/third-party-notices.txt +14 -14
- package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
- package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
- package/frontend/src/lib/analysis/case-migration-model.js +0 -305
- package/frontend/src/lib/analysis/case-record-model.js +0 -9
- package/frontend/src/lib/analysis/case-response-model.js +0 -728
- package/frontend/src/lib/analysis/case-response-packet.js +0 -583
- package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
- package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
- package/frontend/src/lib/analysis/lookup-response.js +0 -2
- package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
- package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
- package/frontend/src/lib/analysis/scoring.js +0 -55
- package/frontend/src/lib/analysis/utils.js +0 -255
- package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
- package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
- package/frontend/src/lib/analysis/visualization-matrix.js +0 -131
- package/frontend/src/lib/analysis/visualization-models.js +0 -338
- package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
- package/lib/bounded-json-response.mjs +0 -170
- package/lib/lookup-response-contract.mjs +0 -1034
- package/lib/observation.mjs +0 -115
- /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
- /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
- /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
- /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
package/README.md
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# WHOISleuth CLI
|
|
2
2
|
|
|
3
|
-
WHOISleuth's command-line interface runs
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
WHOISleuth's command-line interface runs local WHOIS, RDAP, DNS, HTTP, TLS,
|
|
4
|
+
certificate, posture and lookalike investigation commands. Networked commands
|
|
5
|
+
run from the operator's machine; offline commands read only selected local input.
|
|
6
|
+
It does not use the hosted WHOISleuth login or workspace.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
|
-
WHOISleuth requires Node.js 24 or later. Run the
|
|
11
|
-
a global installation:
|
|
10
|
+
WHOISleuth requires Node.js 24 or later. Run the public package directly:
|
|
12
11
|
|
|
13
12
|
```bash
|
|
14
13
|
npm exec --yes --ignore-scripts --package=@slicedearth/whoisleuth-cli -- whoisleuth --help
|
|
@@ -21,189 +20,87 @@ npm install --global --ignore-scripts @slicedearth/whoisleuth-cli
|
|
|
21
20
|
whoisleuth doctor
|
|
22
21
|
```
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
The package has no dependency lifecycle scripts. Its version follows the
|
|
24
|
+
application version.
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
Inspect the installed command set and plan a Lookup:
|
|
25
29
|
|
|
26
30
|
```bash
|
|
27
|
-
|
|
28
|
-
whoisleuth --
|
|
31
|
+
whoisleuth --help
|
|
32
|
+
whoisleuth commands --common
|
|
33
|
+
whoisleuth lookup example.test --plan --json
|
|
29
34
|
```
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
Run common commands:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
whoisleuth example.test
|
|
40
|
+
whoisleuth lookup example.test --deep --summary
|
|
41
|
+
whoisleuth bulk domains.txt --csv
|
|
42
|
+
whoisleuth discover example.test --preset common --jsonl
|
|
43
|
+
whoisleuth compare lookup.json --json
|
|
44
|
+
whoisleuth verify-artifact lookup.json --json --strict-exit
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
One strict domain, IP address or ASN can occupy command position as Lookup
|
|
48
|
+
shorthand. URL-like or ambiguous input requires the explicit `lookup` command;
|
|
49
|
+
only `bulk` accepts multiple targets.
|
|
50
|
+
|
|
51
|
+
## Command groups
|
|
52
|
+
|
|
53
|
+
| Group | Common commands |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| Investigate | `lookup`, `bulk`, `discover`, `ct-search`, `posture`, `http`, `tls`, `compare`, `brief` |
|
|
56
|
+
| Respond | `case-pack`, `change-packet`, `sharing-review`, `export` |
|
|
57
|
+
| Assure | `dnssec-validate`, `mail-transport`, `domain-control`, `assurance`, `workflow-plan`, `diff`, `inspect-archive`, `verify-artifact` |
|
|
58
|
+
| Utilities | `doctor`, `commands`, `completion`, `manual`, `registry-scaffold` |
|
|
59
|
+
|
|
60
|
+
Use the installed registry for the complete inventory:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
whoisleuth commands --group investigate
|
|
64
|
+
whoisleuth commands --group respond --mode offline
|
|
65
|
+
whoisleuth commands --json
|
|
66
|
+
whoisleuth manual | man -l -
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`registry-scaffold` has a separate fixture contract: its `--profile` selects a
|
|
70
|
+
fixed fixture profile and it rejects shared `--config` profiles. It creates
|
|
71
|
+
sanitised local fixture material.
|
|
72
|
+
|
|
73
|
+
## Collection and output
|
|
74
|
+
|
|
75
|
+
Fast Lookup is registration-first. Deep Lookup explicitly adds the applicable
|
|
76
|
+
WHOIS, DNS, website, TLS, registrar-RDAP and observed-network sources. Compact
|
|
77
|
+
Deep Bulk retains only the fields needed for comparison. Authorised active
|
|
78
|
+
commands require their dedicated command and per-run acknowledgement.
|
|
79
|
+
|
|
80
|
+
Focused `whoisleuth <command> --help` states the exact target, request, input and
|
|
81
|
+
output limits. `lookup --plan` lists planned sources before collection.
|
|
82
|
+
|
|
83
|
+
Terminal text is the default. Commands expose JSON, JSONL, CSV, Markdown, HTML
|
|
84
|
+
or domain-only output only where declared. `--output` writes a private local
|
|
85
|
+
file atomically and refuses an existing path unless `--force` is selected.
|
|
86
|
+
`--strict-exit` and `--fail-on` expose selected evidence states to automation.
|
|
36
87
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
not upload them or invoke a hosted WHOISleuth deployment. The installation
|
|
41
|
-
examples disable lifecycle scripts for the package and its dependency tree;
|
|
42
|
-
WHOISleuth does not require them.
|
|
88
|
+
Exit codes are 0 for completion, 2 for invalid input, 3 for collection or
|
|
89
|
+
comparison failure, 4 for a partial result or unmet selected evidence policy,
|
|
90
|
+
70 for internal bootstrap failure, 130 for cancellation and 143 for SIGTERM.
|
|
43
91
|
|
|
44
92
|
## Repository checkout
|
|
45
93
|
|
|
46
|
-
Contributors can run the
|
|
94
|
+
Contributors can run the source command without installing the package:
|
|
47
95
|
|
|
48
96
|
```bash
|
|
49
97
|
node bin/whoisleuth.mts --help
|
|
50
|
-
node bin/whoisleuth.mts lookup example.test
|
|
51
|
-
|
|
52
|
-
node bin/whoisleuth.mts lookup example.test --deep --plan --json
|
|
53
|
-
node bin/whoisleuth.mts lookup example.test --deep --observer workstation-a --vantage office-egress --json
|
|
54
|
-
node bin/whoisleuth.mts lookup example.test --deep --markdown --output lookup.md
|
|
55
|
-
node bin/whoisleuth.mts bulk domains.txt --deep --checkpoint bulk-checkpoint.json
|
|
56
|
-
node bin/whoisleuth.mts bulk domains.txt --csv --registered-only
|
|
57
|
-
node bin/whoisleuth.mts bulk domains.txt --queries --errors-only
|
|
58
|
-
node bin/whoisleuth.mts discover example.test --dictionary private-terms.txt --snapshot discovery-state.json --json
|
|
59
|
-
node bin/whoisleuth.mts discover-scan example.test --scan-limit 50 --checkpoint candidate-scan.json --json
|
|
60
|
-
node bin/whoisleuth.mts page-compare official.json candidate.json --json
|
|
61
|
-
node bin/whoisleuth.mts mail-review bulk.json --json
|
|
62
|
-
node bin/whoisleuth.mts dnssec-validate example.test --resolver "$PUBLIC_RESOLVER_IP" --trust-anchor anchor.json --owned-or-authorized --json
|
|
63
|
-
node bin/whoisleuth.mts mail-transport selected-mx.json --resolver "$PUBLIC_RESOLVER_IP" --trust-anchor anchor.json --owned-or-authorized --active-probe --json
|
|
64
|
-
node bin/whoisleuth.mts domain-control domain-control-input.json --json
|
|
65
|
-
node bin/whoisleuth.mts assurance domain-assurance-input.json --json
|
|
66
|
-
node bin/whoisleuth.mts sharing-review response-packet.json --marking amber --recipient-scope organization --purpose 'Reviewed handoff' --human-reviewed --personal-data-reviewed --redactions-confirmed --json
|
|
67
|
-
node bin/whoisleuth.mts workflow-plan domain-triage example.test --json
|
|
68
|
-
node bin/whoisleuth.mts diff first-lookup.json second-lookup.json --json
|
|
69
|
-
node bin/whoisleuth.mts reconcile office.json mobile.json external.json --json
|
|
70
|
-
node bin/whoisleuth.mts timeline first-observation.json second-observation.json latest-observation.json --json
|
|
71
|
-
node bin/whoisleuth.mts registry-support example.test --json
|
|
72
|
-
node bin/whoisleuth.mts registry-doctor lookup.json --json
|
|
73
|
-
node bin/whoisleuth.mts registry-cohort saved-lookups.jsonl --json
|
|
74
|
-
node bin/whoisleuth.mts brief lookup.json
|
|
75
|
-
node bin/whoisleuth.mts case-pack cases.json --audience trusted --reviewed --json
|
|
76
|
-
node bin/whoisleuth.mts monitor-once control-manifest.json --json
|
|
77
|
-
node bin/whoisleuth.mts workflow-run domain-triage example.test --approve-network --json --output run.json
|
|
78
|
-
node bin/whoisleuth.mts lookalike-calibrate reviewed-candidates.json --json
|
|
79
|
-
node bin/whoisleuth.mts doctor
|
|
80
|
-
node bin/whoisleuth.mts commands --json
|
|
81
|
-
node bin/whoisleuth.mts completion zsh
|
|
82
|
-
node bin/whoisleuth.mts completion powershell
|
|
83
|
-
node bin/whoisleuth.mts manual | man -l -
|
|
98
|
+
node bin/whoisleuth.mts lookup example.test --plan --json
|
|
99
|
+
npm run cli:package:check
|
|
84
100
|
```
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
explicit and are never interpreted as evidence of safety or absence.
|
|
90
|
-
`lookup --plan` classifies a target and explains the selected source families
|
|
91
|
-
and disclosures without making a network request. `commands --json` exposes the
|
|
92
|
-
installed command contracts for local wrappers without running collection.
|
|
93
|
-
The isolated `dnssec-validate` and `mail-transport` actions are not part of
|
|
94
|
-
Lookup, Bulk, monitoring, or recipes. Each run requires explicit
|
|
95
|
-
owned-or-authorised acknowledgement; mail transport also requires a separate
|
|
96
|
-
active-probe acknowledgement and never sends a message, authenticates, tests
|
|
97
|
-
relay, or enumerates recipients or mailboxes. Its output distinguishes selected,
|
|
98
|
-
public-revalidated, and connected addresses and explicitly reports that A, AAAA,
|
|
99
|
-
and CNAME authentication was not evaluated after a candidate is retained, or
|
|
100
|
-
that address authentication is unavailable when no candidate exists; only
|
|
101
|
-
confirmed connections can form address relationship leads. If a DANE-TA TLSA
|
|
102
|
-
usage 2 association is published, active collection retains only the observed
|
|
103
|
-
leaf certificate and leaves that comparison partial without certificate-path
|
|
104
|
-
construction and trust-anchor path validation. SMTP relay PKIX-TA usage 0 and
|
|
105
|
-
PKIX-EE usage 1 records are retained as unsupported and cannot complete SMTP
|
|
106
|
-
DANE assurance; a separate usage 3 match remains eligible. Saved
|
|
107
|
-
Lookup JSON is byte-, nesting-, entry-, and per-container-bounded before parsing and portable export.
|
|
108
|
-
Focused command help and the generated manual label every operation as offline
|
|
109
|
-
or networked and state its target, input, and concurrency boundaries. The
|
|
110
|
-
packaged [dual-use disclosure](https://github.com/slicedearth/whoisleuth/blob/main/DISCLOSURE)
|
|
111
|
-
defines the supported defensive use of those capabilities.
|
|
112
|
-
|
|
113
|
-
Interactive output uses restrained semantic colour, width-aware wrapping, and
|
|
114
|
-
stderr-only progress for slower collection. Redirected and machine-readable
|
|
115
|
-
output stays free of ANSI and progress text. Lookup also provides `--summary`
|
|
116
|
-
and `--verbose` terminal views without changing what is collected. `doctor` is
|
|
117
|
-
offline unless `--network` is explicitly supplied, and completion scripts are
|
|
118
|
-
printed without modifying shell configuration. Commands can use atomic private
|
|
119
|
-
`--output` files, Lookup can emit strict automation exits and target-free
|
|
120
|
-
versioned progress events, and Bulk can resume an exact validated compact
|
|
121
|
-
checkpoint. Checkpoint schema 2 preserves each current row's observation time;
|
|
122
|
-
legacy schema-1 rows remain readable with unknown observation time rather than
|
|
123
|
-
being restamped on resume. Bulk machine output schema 3 distinguishes current
|
|
124
|
-
and resumed rows. A checkpoint write failure preserves completed output and returns
|
|
125
|
-
the partial-result exit code. Ctrl-C suppresses partial final output and
|
|
126
|
-
returns exit code 130. SIGTERM uses the same graceful cleanup path and returns
|
|
127
|
-
143; a second interrupt performs best-effort temporary-file
|
|
128
|
-
cleanup before exiting immediately.
|
|
129
|
-
Strict local profiles can supply only safe presentation, Fast-mode,
|
|
130
|
-
concurrency, and observer defaults; command-line options override them.
|
|
131
|
-
Supported automation commands can emit JUnit or SARIF and apply explicit
|
|
132
|
-
failure policies without changing their collection result.
|
|
133
|
-
|
|
134
|
-
The offline `timeline` command orders 2 to 20 saved Lookup documents for one
|
|
135
|
-
domain and compares each adjacent observation. It retains observation times and
|
|
136
|
-
normalised field states but no input filenames or raw registry payloads.
|
|
137
|
-
Observed differences remain separate from missing, unavailable, and changed
|
|
138
|
-
collection conditions.
|
|
139
|
-
|
|
140
|
-
Optional bounded observer and vantage labels let analysts distinguish saved
|
|
141
|
-
collection contexts without changing collection. `reconcile` compares 2 to 5
|
|
142
|
-
same-domain observations while keeping agreement, disagreement, and
|
|
143
|
-
non-comparable evidence separate; the labels are context, not proof of
|
|
144
|
-
independence. `registry-doctor`, `assurance`, `sharing-review`, and
|
|
145
|
-
`lookalike-calibrate` add offline compatibility, change/recovery, sharing, and
|
|
146
|
-
review-yield workflows without contacting the hosted application.
|
|
147
|
-
`registry-cohort` emits target-free publication-quality timelines from either
|
|
148
|
-
saved Lookups or one unmixed family of retained version-1/version-2 cohort
|
|
149
|
-
reports; it never sums overlapping retained samples into independent
|
|
150
|
-
consistency. `registry-scaffold` produces a sanitised synthetic contributor
|
|
151
|
-
fixture. `diff` preserves saved-Lookup output and also emits a bounded
|
|
152
|
-
comparison ledger for explicitly paired Bulk sessions or domain portfolios.
|
|
153
|
-
`verify-artifact --manifest ... --manifest-entry ...` separates exact retained
|
|
154
|
-
byte identity from canonical-only or mismatched identity. `brief` and
|
|
155
|
-
`case-pack` create bounded local handoffs, `monitor-once` runs one explicit
|
|
156
|
-
control review rather than a daemon, and `workflow-run` executes only installed
|
|
157
|
-
fixed-recipe steps with per-run network approval and analyst-selection pauses.
|
|
158
|
-
The current schema-26 JSON produced by `export` is accepted directly by
|
|
159
|
-
`verify-artifact` and reported as `structure_valid`. It has no embedded checksum
|
|
160
|
-
or signature, so exact retained bytes require a verified investigation manifest
|
|
161
|
-
entry. The export can contain a bounded privacy-projected registry RDAP
|
|
162
|
-
publication, normalised WHOIS values, and bounded contacts and should be
|
|
163
|
-
reviewed before sharing. Request and response headers, cookies, session and
|
|
164
|
-
credential fields, and credential-bearing or query-bearing URLs are excluded.
|
|
165
|
-
Schema 25 remains readable through its historical wrapper contract: retained
|
|
166
|
-
diagnostics are authoritative, unavailable wrapper data is suppressed during
|
|
167
|
-
replay, and other contradictions fail closed. Verification and browser replay share a 5 MiB,
|
|
168
|
-
20,000-entry, 24-level portable boundary.
|
|
169
|
-
|
|
170
|
-
Lookup can also export a version-1 `whoisleuth.lookup-claim-passport` for one
|
|
171
|
-
selected readiness statement. `verify-artifact` checks its strict bounded
|
|
172
|
-
structure and sorted-json-v2 digest; `interchange-report` describes the exact
|
|
173
|
-
source-state fields it preserves and the raw payload, contact, page-value,
|
|
174
|
-
request-path, credential, browser-store, and signer-authentication fields it
|
|
175
|
-
excludes.
|
|
176
|
-
|
|
177
|
-
Current `case-pack` output keeps its version-1 envelope. Trusted and internal
|
|
178
|
-
audiences preserve exact analyst-selected Case-to-Brand Profile identifiers;
|
|
179
|
-
public output clears them from cases and embedded reports and discloses the
|
|
180
|
-
bounded `brandProfileReferencesOmitted` count. Verification also enforces the
|
|
181
|
-
actual audience projection after digest validation, rejects re-signed
|
|
182
|
-
sensitive-field leaks at expected or unexpected nested paths, and binds each
|
|
183
|
-
report to its canonical top-level Case and exact manifest counts. Current
|
|
184
|
-
schema-12 input must survive bounded normalisation exactly, provide safe unique
|
|
185
|
-
Case identities and a valid unique at-most-eight identifier list, and use
|
|
186
|
-
report v8. Schema 11 uses report v7; valid collections through schema 10 remain
|
|
187
|
-
compatible with supported reports through v6 and without later branch,
|
|
188
|
-
reference, or omission-count fields.
|
|
189
|
-
|
|
190
|
-
Bulk also supports fixed-column CSV, domain-only or exact-query output,
|
|
191
|
-
registered, inconclusive, and hard-failure output filters, and bounded A, AAAA,
|
|
192
|
-
NS, and MX summaries. Discover
|
|
193
|
-
supports local custom dictionaries and private snapshot comparison suitable
|
|
194
|
-
for an operator-managed scheduler. The separate networked `discover-scan`
|
|
195
|
-
command can collect a deterministic bounded subset in chunks, retain mutation
|
|
196
|
-
provenance, apply a non-destructive analyst allowlist, group exact shared DNS
|
|
197
|
-
observations, and compare a private component-aware material-change snapshot.
|
|
198
|
-
The full allowlist is not emitted, but matching scanned candidates are labelled
|
|
199
|
-
as suppressed so the review decision remains explainable. Saved Deep
|
|
200
|
-
lookups can be compared across static page, parse5-tokenized DOM structure,
|
|
201
|
-
favicon, technology, and TLS evidence, while saved Bulk output can be reviewed
|
|
202
|
-
for passive MX, null MX, SPF, DMARC, and shared-provider context.
|
|
203
|
-
|
|
204
|
-
See `docs/cli.md` in the package for installation and first use, and
|
|
205
|
-
`docs/cli-reference.md` for the complete command, privacy, output, and
|
|
206
|
-
evidence-contract reference. Report suspected vulnerabilities through the
|
|
207
|
-
private channel described in `SECURITY.md`.
|
|
102
|
+
The package includes `docs/cli.md` for usage and `docs/cli-reference.md` for
|
|
103
|
+
compatibility, privacy and evidence contracts. Report suspected vulnerabilities
|
|
104
|
+
through the private channel in `SECURITY.md`.
|
|
208
105
|
|
|
209
106
|
Copyright 2026 slicedearth. Licensed under AGPL-3.0-only.
|
package/SECURITY.md
CHANGED
|
@@ -26,9 +26,12 @@ weakness can use the public issue tracker.
|
|
|
26
26
|
|
|
27
27
|
Reports are assessed against WHOISleuth's documented trust boundaries,
|
|
28
28
|
including its local-first workspace, bounded network collection, shared
|
|
29
|
-
deployment password, and optional hosted monitoring.
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
deployment password, and optional hosted monitoring. The accepted
|
|
30
|
+
[product boundary](https://github.com/slicedearth/whoisleuth/blob/main/docs/product-boundary.md) and
|
|
31
|
+
[threat model](https://github.com/slicedearth/whoisleuth/blob/main/docs/threat-model.md) describe those boundaries, principal abuse
|
|
32
|
+
cases, controls, residual risks, and deliberate non-goals. A source outage,
|
|
33
|
+
partial registry response, or heuristic disagreement is not by itself a
|
|
34
|
+
security vulnerability, but unsafe handling of those states may be.
|
|
32
35
|
|
|
33
36
|
Confirmed reports will be handled through a private advisory until a fix and
|
|
34
37
|
coordinated disclosure are ready. No bounty or guaranteed response time is
|
package/bin/whoisleuth.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import EXIT_CODES from "../cli/exit-codes.mjs";
|
|
4
|
+
import { metaActionDefinition } from "../cli/command-reference.mjs";
|
|
4
5
|
for (const stream of [process.stdout, process.stderr]) {
|
|
5
6
|
stream.on('error', (error) => {
|
|
6
7
|
if (error.code === 'EPIPE')
|
|
@@ -9,7 +10,7 @@ for (const stream of [process.stdout, process.stderr]) {
|
|
|
9
10
|
});
|
|
10
11
|
}
|
|
11
12
|
const argv = process.argv.slice(2);
|
|
12
|
-
if (argv.length === 1 && (
|
|
13
|
+
if (argv.length === 1 && metaActionDefinition('version').aliases.includes(argv[0] ?? '')) {
|
|
13
14
|
const require = createRequire(import.meta.url);
|
|
14
15
|
const metadata = require('../package.json');
|
|
15
16
|
if (typeof metadata.version !== 'string' || !metadata.version) {
|
package/cli/archive-inspect.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { domainToASCII } from 'node:url';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { scanBoundedJson } from "../lib/bounded-json.mjs";
|
|
5
|
+
import { decryptWorkspaceArchive, isEncryptedWorkspaceArchive, } from "../packages/workspace/workspace-archive-crypto.mjs";
|
|
6
|
+
import { WORKSPACE_ARCHIVE_SCHEMA, readWorkspaceArchive, } from "../packages/workspace/workspace-archive.mjs";
|
|
7
|
+
import { canonicalArtifactJson } from "../packages/evidence/artifact-integrity.mjs";
|
|
7
8
|
import { MAX_OFFLINE_ARTIFACT_BYTES, verifyOfflineArtifact, } from "./artifact-verify.mjs";
|
|
9
|
+
import { safeTerminalValue } from "./formatters/terminal.mjs";
|
|
8
10
|
export const ARCHIVE_INSPECTION_SCHEMA = 'whoisleuth.workspace-archive-inspection';
|
|
9
11
|
export const ARCHIVE_INSPECTION_VERSION = 2;
|
|
10
12
|
export const MAX_ARCHIVE_SEARCH_LENGTH = 253;
|
|
@@ -35,10 +37,11 @@ function parseJson(raw) {
|
|
|
35
37
|
}
|
|
36
38
|
let parsed;
|
|
37
39
|
try {
|
|
40
|
+
scanBoundedJson(raw);
|
|
38
41
|
parsed = JSON.parse(raw);
|
|
39
42
|
}
|
|
40
43
|
catch {
|
|
41
|
-
throw new TypeError('Archive input must be valid JSON.');
|
|
44
|
+
throw new TypeError('Archive input must be valid bounded JSON without duplicate keys.');
|
|
42
45
|
}
|
|
43
46
|
const value = record(parsed);
|
|
44
47
|
if (!value)
|
|
@@ -96,8 +99,12 @@ function searchSection(section, value, search, reveal, results, state) {
|
|
|
96
99
|
const stack = [{ value, depth: 0 }];
|
|
97
100
|
while (stack.length && results.length < MAX_ARCHIVE_SEARCH_MATCHES) {
|
|
98
101
|
const current = stack.pop();
|
|
99
|
-
if (!current
|
|
102
|
+
if (!current)
|
|
100
103
|
continue;
|
|
104
|
+
if (current.depth > 16) {
|
|
105
|
+
state.truncated = true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
101
108
|
if (state.visited >= MAX_ARCHIVE_SEARCH_NODES) {
|
|
102
109
|
state.truncated = true;
|
|
103
110
|
return;
|
|
@@ -105,7 +112,10 @@ function searchSection(section, value, search, reveal, results, state) {
|
|
|
105
112
|
state.visited += 1;
|
|
106
113
|
const object = record(current.value);
|
|
107
114
|
if (object) {
|
|
108
|
-
|
|
115
|
+
const entries = Object.entries(object);
|
|
116
|
+
if (entries.length > 300)
|
|
117
|
+
state.truncated = true;
|
|
118
|
+
for (const [field, child] of entries.slice(0, 300)) {
|
|
109
119
|
if (SEARCHABLE_FIELDS.has(field) && typeof child === 'string') {
|
|
110
120
|
const normalized = canonicalSearchValue(child);
|
|
111
121
|
if (normalized === search) {
|
|
@@ -116,7 +126,7 @@ function searchSection(section, value, search, reveal, results, state) {
|
|
|
116
126
|
...(reveal ? { value: child.slice(0, MAX_ARCHIVE_SEARCH_LENGTH) } : {}),
|
|
117
127
|
}));
|
|
118
128
|
if (results.length >= MAX_ARCHIVE_SEARCH_MATCHES) {
|
|
119
|
-
state.truncated =
|
|
129
|
+
state.truncated = true;
|
|
120
130
|
return;
|
|
121
131
|
}
|
|
122
132
|
}
|
|
@@ -126,6 +136,8 @@ function searchSection(section, value, search, reveal, results, state) {
|
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
else if (Array.isArray(current.value)) {
|
|
139
|
+
if (current.value.length > 5_000)
|
|
140
|
+
state.truncated = true;
|
|
129
141
|
for (const child of current.value.slice(0, 5_000)) {
|
|
130
142
|
if (child && typeof child === 'object')
|
|
131
143
|
stack.push({ value: child, depth: current.depth + 1 });
|
|
@@ -165,6 +177,9 @@ export async function inspectWorkspaceArchive(raw, options = {}) {
|
|
|
165
177
|
}
|
|
166
178
|
}
|
|
167
179
|
if (options.requireMatch === true && search && results.length === 0) {
|
|
180
|
+
if (searchState.truncated) {
|
|
181
|
+
throw new TypeError('Archive search reached a configured bound before an exact canonical match could be established.');
|
|
182
|
+
}
|
|
168
183
|
throw new TypeError('Archive search found no exact canonical match.');
|
|
169
184
|
}
|
|
170
185
|
return Object.freeze({
|
|
@@ -207,6 +222,9 @@ export async function inspectWorkspaceArchive(raw, options = {}) {
|
|
|
207
222
|
...(reveal
|
|
208
223
|
? ['Search values were revealed only because the operator supplied --reveal; handle the output as sensitive evidence.']
|
|
209
224
|
: ['Search results contain field names and digests only. A digest does not authenticate or establish the accuracy of a retained value.']),
|
|
225
|
+
...(searchState.truncated
|
|
226
|
+
? ['Archive search reached a configured traversal or result boundary; zero retained matches is inconclusive.']
|
|
227
|
+
: []),
|
|
210
228
|
]),
|
|
211
229
|
});
|
|
212
230
|
}
|
|
@@ -219,15 +237,15 @@ export function formatArchiveInspection(report) {
|
|
|
219
237
|
`Content digest: ${report.summary.contentDigestSha256}`,
|
|
220
238
|
];
|
|
221
239
|
for (const section of report.sections) {
|
|
222
|
-
lines.push(`${section.label}: ${section.recordCount} records · ${section.status} · ${section.bytes} bytes`);
|
|
240
|
+
lines.push(`${safeTerminalValue(section.label)}: ${section.recordCount} records · ${safeTerminalValue(section.status)} · ${section.bytes} bytes`);
|
|
223
241
|
}
|
|
224
242
|
if (report.search.requested) {
|
|
225
243
|
lines.push(`Search matches: ${report.search.matchCount}${report.search.truncated ? ' (truncated)' : ''}`);
|
|
226
244
|
for (const match of report.search.results) {
|
|
227
|
-
lines.push(`${match.section}.${match.field}: ${match.value ?? `sha256:${match.valueDigestSha256}`}`);
|
|
245
|
+
lines.push(`${safeTerminalValue(match.section)}.${safeTerminalValue(match.field)}: ${safeTerminalValue(match.value ?? `sha256:${match.valueDigestSha256}`)}`);
|
|
228
246
|
}
|
|
229
247
|
}
|
|
230
248
|
for (const limitation of report.limitations)
|
|
231
|
-
lines.push(`Limitation: ${limitation}`);
|
|
249
|
+
lines.push(`Limitation: ${safeTerminalValue(limitation)}`);
|
|
232
250
|
return `${lines.join('\n')}\n`;
|
|
233
251
|
}
|