@slicedearth/whoisleuth-cli 1.45.0 → 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/DISCLOSURE +7 -3
- package/NOTICE +5 -4
- package/README.md +74 -124
- package/SECURITY.md +6 -3
- package/bin/whoisleuth.mjs +7 -4
- package/cli/archive-inspect.mjs +28 -10
- package/cli/arguments.mjs +450 -173
- package/cli/artifact-structure.mjs +2507 -0
- package/cli/artifact-verify.mjs +286 -64
- package/cli/bounded-json.mjs +1 -0
- package/cli/bulk-checkpoint.mjs +35 -23
- package/cli/bulk-command-runner.mjs +2 -5
- package/cli/bulk.mjs +9 -2
- package/cli/case-pack.mjs +435 -46
- 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 +948 -106
- package/cli/completion.mjs +504 -218
- package/cli/config-profile.mjs +13 -3
- package/cli/ct-event-intake.mjs +32 -2
- package/cli/discover.mjs +7 -1
- package/cli/discovery-observation-snapshot.mjs +24 -28
- package/cli/discovery-scan-command-runner.mjs +13 -14
- package/cli/discovery-scan.mjs +17 -3
- package/cli/discovery-snapshot.mjs +15 -7
- package/cli/discovery-workflow.mjs +2 -3
- package/cli/doctor.mjs +2 -2
- package/cli/domain-control-monitor.mjs +215 -36
- package/cli/domain-control-observations.mjs +34 -17
- package/cli/errors.mjs +8 -3
- package/cli/evidence-command-runner.mjs +17 -11
- package/cli/evidence-signing.mjs +68 -50
- package/cli/export-evidence.mjs +11 -4
- package/cli/external-observation-mapping.mjs +3 -3
- package/cli/formatters/html.mjs +3 -3
- package/cli/formatters/json.mjs +20 -32
- package/cli/formatters/terminal.mjs +586 -22
- package/cli/input.mjs +62 -0
- package/cli/interactive-launcher.mjs +226 -0
- package/cli/interchange-report.mjs +226 -0
- package/cli/investigation-manifest.mjs +16 -17
- package/cli/investigation-plan.mjs +167 -16
- package/cli/investigation-run.mjs +80 -24
- package/cli/local-mmdb-review.mjs +45 -14
- 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 +96 -33
- package/cli/lookup-diff.mjs +11 -2
- package/cli/lookup-plan.mjs +1 -0
- package/cli/mail-review.mjs +206 -94
- package/cli/manual.mjs +5 -5
- package/cli/network-command-runner.mjs +78 -0
- package/cli/offline-evidence-review.mjs +45 -14
- package/cli/open-asset-model-bridge.mjs +1 -1
- package/cli/output-file.mjs +70 -14
- package/cli/page-compare.mjs +18 -10
- package/cli/progress-events.mjs +2 -1
- package/cli/progress.mjs +3 -3
- package/cli/registry-cohort.mjs +343 -61
- package/cli/registry-doctor.mjs +67 -28
- package/cli/registry-support.mjs +2 -1
- package/cli/retained-artifact-diff.mjs +375 -0
- package/cli/risk-calibration.mjs +89 -40
- package/cli/runner.mjs +322 -246
- package/cli/saved-lookup.mjs +439 -49
- package/cli/sharing-review.mjs +18 -7
- package/cli/source-reliability.mjs +75 -36
- package/cli/terminal-presentation.mjs +43 -18
- package/docs/cli-reference.md +125 -1334
- package/docs/cli.md +87 -236
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -0
- package/frontend/src/lib/analysis/domain-control-records.js +3 -0
- package/lib/application-version.mjs +3 -0
- package/lib/availability.mjs +72 -52
- package/lib/bounded-contract-normalizers.mjs +2 -4
- package/lib/bounded-file.mjs +91 -4
- package/lib/bounded-json.mjs +287 -0
- package/lib/browser-library-profile.mjs +1 -1
- package/lib/certificate-extension-profile.mjs +2 -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 +173 -0
- package/lib/ct-query.mjs +3 -3
- package/lib/ct-search.mjs +120 -64
- package/lib/distributed-operation-budget.mjs +4 -2
- package/lib/dns-delegation-health.mjs +9 -6
- package/lib/dns-intelligence.mjs +4 -3
- package/lib/dns-resolver-selection.mjs +6 -4
- package/lib/dnssec-chain-validation.mjs +1052 -0
- package/lib/dnssec-evidence-validation.mjs +23 -10
- package/lib/domain-assurance.mjs +12 -12
- package/lib/domain-change-packet.mjs +7 -5
- package/lib/domain-control-flight-recorder.mjs +442 -78
- package/lib/domain-control-manifest.mjs +314 -171
- package/lib/domain-posture-analysis.mjs +5 -2
- package/lib/domain-posture.mjs +7 -4
- package/lib/encrypted-dns-contract.mjs +2 -3
- package/lib/evidence-export.mjs +1243 -88
- package/lib/evidence-report-markdown.mjs +3 -0
- package/lib/evidence-report.mjs +5 -0
- package/lib/external-intelligence-risk.mjs +26 -10
- package/lib/favicon.mjs +8 -5
- 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-evidence-bounds.mjs +3 -0
- package/lib/http-intelligence.mjs +332 -6
- package/lib/interchange-fidelity-registry.mjs +124 -0
- package/lib/local-geoip-evidence.mjs +2 -1
- package/lib/lookup-cache.mjs +6 -4
- package/lib/lookup-network-evidence-bounds.mjs +9 -0
- package/lib/lookup.mjs +27 -4
- package/lib/observed-network-context.mjs +1 -1
- package/lib/outbound-identity.mjs +2 -3
- package/lib/page-fingerprints.mjs +2 -2
- package/lib/page-role-profile.mjs +1 -1
- package/lib/perceptual-hash.mjs +12 -4
- package/lib/portable-generator.mjs +29 -0
- package/lib/provider-policy-admission.mjs +11 -4
- package/lib/rdap-bootstrap.mjs +76 -16
- package/lib/rdap-client.mjs +20 -12
- package/lib/rdap-normalization.mjs +324 -80
- package/lib/rdap-registrar.mjs +5 -1
- 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-capabilities.mjs +3 -0
- package/lib/registry-capability-catalogue.mjs +12 -2
- package/lib/registry-comparison.mjs +28 -11
- package/lib/registry-insights.mjs +7 -0
- package/lib/response-policy.mjs +27 -4
- package/lib/risk-calibration-summary.mjs +340 -0
- package/lib/risk-scoring.mjs +4 -3
- package/lib/rpki-evidence.mjs +9 -8
- package/lib/safe-fetch.mjs +121 -50
- package/lib/security-txt.mjs +27 -4
- package/lib/service-binding-dns.mjs +6 -1
- package/lib/smtp-transport-review.mjs +787 -0
- package/lib/sslbl-certificates.generated.mjs +136 -134
- 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 +5 -15
- package/lib/threat-intelligence-types.mjs +21 -1
- package/lib/threatfox-intelligence.mjs +7 -3
- package/lib/tls-intelligence.mjs +15 -9
- package/lib/tlsa-evidence.mjs +37 -7
- 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/packages/cases/case-brand-profile-references.mjs +85 -0
- package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +43 -14
- package/packages/cases/case-investigation-branch-model.mjs +132 -0
- 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 -19
- 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} +106 -33
- package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +143 -16
- 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/packages/cases/opaque-reference-id.mjs +9 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +366 -0
- package/packages/comparison/comparison-ledger-contract.mjs +241 -0
- package/packages/comparison/comparison-ledger-json.mjs +80 -0
- package/packages/comparison/comparison-ledger-serialization.mjs +52 -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/packages/evidence/artifact-integrity.mjs +65 -0
- 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} +86 -20
- package/packages/interchange/retained-text.mjs +8 -0
- package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +11 -8
- package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +28 -9
- package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +67 -18
- package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +10 -9
- package/packages/investigation/bulk-review-policy.mjs +1 -0
- package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +50 -18
- 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/packages/investigation/lookup-claim-passport.mjs +232 -0
- package/packages/investigation/lookup-claim-readiness.mjs +287 -0
- 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} +97 -50
- package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +15 -15
- package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +179 -39
- package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +12 -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} +51 -46
- package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +15 -15
- 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} +18 -12
- 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} +195 -84
- package/third-party-notices.txt +112 -5834
- package/frontend/src/lib/analysis/artifact-integrity.js +0 -21
- 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 -253
- package/frontend/src/lib/analysis/case-record-model.js +0 -7
- package/frontend/src/lib/analysis/case-response-model.js +0 -669
- package/frontend/src/lib/analysis/case-response-packet.js +0 -573
- package/frontend/src/lib/analysis/lookup-decision-support.js +0 -487
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
- package/frontend/src/lib/analysis/lookup-presentation.js +0 -58
- 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 -232
- 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 -88
- package/frontend/src/lib/analysis/visualization-models.js +0 -369
- package/frontend/src/lib/analysis/visualization-time-series.js +0 -144
- package/lib/bounded-json-response.mjs +0 -157
- package/lib/lookup-response-contract.mjs +0 -346
- package/lib/observation.mjs +0 -114
- /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/DISCLOSURE
CHANGED
|
@@ -8,9 +8,13 @@ can have both legitimate and potentially harmful uses.
|
|
|
8
8
|
WHOISleuth is intended for authorised defensive investigation, abuse response,
|
|
9
9
|
threat research, domain acquisition due diligence, asset review, and education.
|
|
10
10
|
Network collection is initiated explicitly by the operator, uses bounded
|
|
11
|
-
requests, and keeps source provenance and incomplete results visible.
|
|
12
|
-
does not exploit targets, bypass access controls,
|
|
13
|
-
remote page scripts, or automate enforcement
|
|
11
|
+
requests, and keeps source provenance and incomplete results visible. Hosted
|
|
12
|
+
and distributable collection does not exploit targets, bypass access controls,
|
|
13
|
+
submit credentials, execute remote page scripts, or automate enforcement
|
|
14
|
+
actions. The separate repo-local rendered-capture package is an explicit
|
|
15
|
+
authorised exception: it executes page JavaScript in a disposable,
|
|
16
|
+
network-bounded browser and writes only a local screenshot plus sanitised
|
|
17
|
+
digest and manifest artefacts for operator-controlled review and deletion.
|
|
14
18
|
|
|
15
19
|
Operators are responsible for using WHOISleuth only on systems and data they
|
|
16
20
|
are authorised to examine and in accordance with applicable laws, provider
|
package/NOTICE
CHANGED
|
@@ -32,10 +32,11 @@ warning-lists are available under CC0 1.0 Universal or the BSD 2-Clause
|
|
|
32
32
|
"Simplified" License. Source:
|
|
33
33
|
https://github.com/MISP/misp-warninglists
|
|
34
34
|
|
|
35
|
-
The exact locked production-dependency
|
|
36
|
-
distributed
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
The exact locked production-dependency inventories and licence documents are
|
|
36
|
+
distributed as third-party-notices.txt. The website inventory is served at
|
|
37
|
+
/third-party-notices.txt; the installed CLI contains its own notice file for
|
|
38
|
+
only the CLI runtime dependency closure. Both are generated from the reviewed
|
|
39
|
+
lockfile, and the website inventory is checked for lockfile drift in CI.
|
|
39
40
|
|
|
40
41
|
The generated Unicode confusable projection is derived from Unicode UTS #39
|
|
41
42
|
data file confusables.txt, Unicode version 17.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,136 +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 domain-control domain-control-input.json --json
|
|
63
|
-
node bin/whoisleuth.mts assurance domain-assurance-input.json --json
|
|
64
|
-
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
|
|
65
|
-
node bin/whoisleuth.mts workflow-plan domain-triage example.test --json
|
|
66
|
-
node bin/whoisleuth.mts diff first-lookup.json second-lookup.json --json
|
|
67
|
-
node bin/whoisleuth.mts reconcile office.json mobile.json external.json --json
|
|
68
|
-
node bin/whoisleuth.mts timeline first-observation.json second-observation.json latest-observation.json --json
|
|
69
|
-
node bin/whoisleuth.mts registry-support example.test --json
|
|
70
|
-
node bin/whoisleuth.mts registry-doctor lookup.json --json
|
|
71
|
-
node bin/whoisleuth.mts registry-cohort saved-lookups.jsonl --json
|
|
72
|
-
node bin/whoisleuth.mts brief lookup.json
|
|
73
|
-
node bin/whoisleuth.mts case-pack cases.json --audience trusted --reviewed --json
|
|
74
|
-
node bin/whoisleuth.mts monitor-once control-manifest.json --json
|
|
75
|
-
node bin/whoisleuth.mts workflow-run domain-triage example.test --approve-network --json --output run.json
|
|
76
|
-
node bin/whoisleuth.mts lookalike-calibrate reviewed-candidates.json --json
|
|
77
|
-
node bin/whoisleuth.mts doctor
|
|
78
|
-
node bin/whoisleuth.mts commands --json
|
|
79
|
-
node bin/whoisleuth.mts completion zsh
|
|
80
|
-
node bin/whoisleuth.mts completion powershell
|
|
81
|
-
node bin/whoisleuth.mts manual | man -l -
|
|
98
|
+
node bin/whoisleuth.mts lookup example.test --plan --json
|
|
99
|
+
npm run cli:package:check
|
|
82
100
|
```
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
explicit and are never interpreted as evidence of safety or absence.
|
|
88
|
-
`lookup --plan` classifies a target and explains the selected source families
|
|
89
|
-
and disclosures without making a network request. `commands --json` exposes the
|
|
90
|
-
installed command contracts for local wrappers without running collection.
|
|
91
|
-
Focused command help and the generated manual label every operation as offline
|
|
92
|
-
or networked and state its target, input, and concurrency boundaries. The
|
|
93
|
-
packaged [dual-use disclosure](https://github.com/slicedearth/whoisleuth/blob/main/DISCLOSURE)
|
|
94
|
-
defines the supported defensive use of those capabilities.
|
|
95
|
-
|
|
96
|
-
Interactive output uses restrained semantic colour, width-aware wrapping, and
|
|
97
|
-
stderr-only progress for slower collection. Redirected and machine-readable
|
|
98
|
-
output stays free of ANSI and progress text. Lookup also provides `--summary`
|
|
99
|
-
and `--verbose` terminal views without changing what is collected. `doctor` is
|
|
100
|
-
offline unless `--network` is explicitly supplied, and completion scripts are
|
|
101
|
-
printed without modifying shell configuration. Commands can use atomic private
|
|
102
|
-
`--output` files, Lookup can emit strict automation exits and target-free
|
|
103
|
-
versioned progress events, and Bulk can resume an exact validated compact
|
|
104
|
-
checkpoint. Checkpoint schema 2 preserves each current row's observation time;
|
|
105
|
-
legacy schema-1 rows remain readable with unknown observation time rather than
|
|
106
|
-
being restamped on resume. Bulk machine output schema 3 distinguishes current
|
|
107
|
-
and resumed rows. A checkpoint write failure preserves completed output and returns
|
|
108
|
-
the partial-result exit code. Ctrl-C suppresses partial final output and
|
|
109
|
-
returns exit code 130. SIGTERM uses the same graceful cleanup path and returns
|
|
110
|
-
143; a second interrupt performs best-effort temporary-file
|
|
111
|
-
cleanup before exiting immediately.
|
|
112
|
-
Strict local profiles can supply only safe presentation, Fast-mode,
|
|
113
|
-
concurrency, and observer defaults; command-line options override them.
|
|
114
|
-
Supported automation commands can emit JUnit or SARIF and apply explicit
|
|
115
|
-
failure policies without changing their collection result.
|
|
116
|
-
|
|
117
|
-
The offline `timeline` command orders 2 to 20 saved Lookup documents for one
|
|
118
|
-
domain and compares each adjacent observation. It retains observation times and
|
|
119
|
-
normalised field states but no input filenames or raw registry payloads.
|
|
120
|
-
Observed differences remain separate from missing, unavailable, and changed
|
|
121
|
-
collection conditions.
|
|
122
|
-
|
|
123
|
-
Optional bounded observer and vantage labels let analysts distinguish saved
|
|
124
|
-
collection contexts without changing collection. `reconcile` compares 2 to 5
|
|
125
|
-
same-domain observations while keeping agreement, disagreement, and
|
|
126
|
-
non-comparable evidence separate; the labels are context, not proof of
|
|
127
|
-
independence. `registry-doctor`, `assurance`, `sharing-review`, and
|
|
128
|
-
`lookalike-calibrate` add offline compatibility, change/recovery, sharing, and
|
|
129
|
-
review-yield workflows without contacting the hosted application.
|
|
130
|
-
`registry-cohort` aggregates target-free publication-quality counts from saved
|
|
131
|
-
Lookups, while `registry-scaffold` produces a sanitised synthetic contributor
|
|
132
|
-
fixture. `brief` and `case-pack` create bounded local handoffs, `monitor-once`
|
|
133
|
-
runs one explicit control review rather than a daemon, and `workflow-run`
|
|
134
|
-
executes only installed fixed-recipe steps with per-run network approval and
|
|
135
|
-
analyst-selection pauses.
|
|
136
|
-
|
|
137
|
-
Bulk also supports fixed-column CSV, domain-only or exact-query output,
|
|
138
|
-
registered, inconclusive, and hard-failure output filters, and bounded A, AAAA,
|
|
139
|
-
NS, and MX summaries. Discover
|
|
140
|
-
supports local custom dictionaries and private snapshot comparison suitable
|
|
141
|
-
for an operator-managed scheduler. The separate networked `discover-scan`
|
|
142
|
-
command can collect a deterministic bounded subset in chunks, retain mutation
|
|
143
|
-
provenance, apply a non-destructive analyst allowlist, group exact shared DNS
|
|
144
|
-
observations, and compare a private component-aware material-change snapshot.
|
|
145
|
-
The full allowlist is not emitted, but matching scanned candidates are labelled
|
|
146
|
-
as suppressed so the review decision remains explainable. Saved Deep
|
|
147
|
-
lookups can be compared across static page, parse5-tokenized DOM structure,
|
|
148
|
-
favicon, technology, and TLS evidence, while saved Bulk output can be reviewed
|
|
149
|
-
for passive MX, null MX, SPF, DMARC, and shared-provider context.
|
|
150
|
-
|
|
151
|
-
See `docs/cli.md` in the package for installation and first use, and
|
|
152
|
-
`docs/cli-reference.md` for the complete command, privacy, output, and
|
|
153
|
-
evidence-contract reference. Report suspected vulnerabilities through the
|
|
154
|
-
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`.
|
|
155
105
|
|
|
156
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) {
|
|
@@ -28,12 +29,14 @@ else {
|
|
|
28
29
|
import("../cli/runner.mjs"),
|
|
29
30
|
]);
|
|
30
31
|
let resolvedArgv;
|
|
32
|
+
let profileResolutionFailed = false;
|
|
31
33
|
try {
|
|
32
34
|
resolvedArgv = await configModule.resolveCliProfileArguments(argv);
|
|
33
35
|
}
|
|
34
36
|
catch (error) {
|
|
35
37
|
process.stderr.write(`Usage error: ${errorsModule.boundedCliErrorMessage(error, 'Invalid CLI profile')}\n`);
|
|
36
38
|
process.exitCode = EXIT_CODES.USAGE;
|
|
39
|
+
profileResolutionFailed = true;
|
|
37
40
|
resolvedArgv = [];
|
|
38
41
|
}
|
|
39
42
|
const cancellation = new AbortController();
|
|
@@ -54,9 +57,9 @@ else {
|
|
|
54
57
|
const onSigterm = () => interrupt('SIGTERM');
|
|
55
58
|
process.on('SIGINT', onSigint);
|
|
56
59
|
process.on('SIGTERM', onSigterm);
|
|
57
|
-
const execution =
|
|
58
|
-
?
|
|
59
|
-
:
|
|
60
|
+
const execution = profileResolutionFailed
|
|
61
|
+
? Promise.resolve(process.exitCode || EXIT_CODES.USAGE)
|
|
62
|
+
: runnerModule.runCli(resolvedArgv, { signal: cancellation.signal });
|
|
60
63
|
execution.then((code) => {
|
|
61
64
|
if (code === 130) {
|
|
62
65
|
process.removeListener('SIGINT', onSigint);
|
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
|
}
|