@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/docs/cli.md
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# WHOISleuth CLI guide
|
|
2
2
|
|
|
3
|
-
The first-party CLI runs
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[
|
|
8
|
-
artefact
|
|
3
|
+
The first-party CLI runs on the operator's machine and does not call the hosted
|
|
4
|
+
WHOISleuth deployment. Use this guide for installation, common commands,
|
|
5
|
+
collection boundaries and output. Installed `whoisleuth --help`, focused
|
|
6
|
+
`--help` and `whoisleuth commands` output are the authority for that installed
|
|
7
|
+
version. The [CLI reference](cli-reference.md) covers durable command and
|
|
8
|
+
artefact contracts.
|
|
9
|
+
|
|
10
|
+
The generated [privacy and data-flow catalogue](https://github.com/slicedearth/whoisleuth/blob/main/docs/privacy-data-flow-catalogue.md)
|
|
11
|
+
lists the network, recipient, retention and export boundary for every command.
|
|
9
12
|
|
|
10
13
|
## Installation
|
|
11
14
|
|
|
12
|
-
Public releases require Node.js 24 or later
|
|
13
|
-
command:
|
|
15
|
+
Public releases require Node.js 24 or later:
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
18
|
npm exec --yes --ignore-scripts --package=@slicedearth/whoisleuth-cli -- whoisleuth --help
|
|
@@ -26,296 +28,126 @@ whoisleuth --version
|
|
|
26
28
|
whoisleuth doctor
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
The
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
From a repository checkout, replace `whoisleuth` with
|
|
35
|
-
`node bin/whoisleuth.mts`. Maintainers can assemble, install, and smoke-test the
|
|
36
|
-
exact compiled package closure without publishing it:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npm run cli:package:check
|
|
40
|
-
```
|
|
31
|
+
The package and application share one semantic version. The package requires no
|
|
32
|
+
dependency lifecycle scripts. From a repository checkout, replace `whoisleuth`
|
|
33
|
+
with `node bin/whoisleuth.mts`; maintainers can verify the exact package closure
|
|
34
|
+
with `npm run cli:package:check`.
|
|
41
35
|
|
|
42
36
|
## First commands
|
|
43
37
|
|
|
44
|
-
Inspect the installed
|
|
38
|
+
Inspect the installed command set and plan one Lookup before collecting:
|
|
45
39
|
|
|
46
40
|
```bash
|
|
47
41
|
whoisleuth --help
|
|
48
|
-
whoisleuth commands
|
|
42
|
+
whoisleuth commands --common
|
|
49
43
|
whoisleuth doctor
|
|
50
|
-
whoisleuth lookup example.
|
|
44
|
+
whoisleuth lookup example.test --plan --json
|
|
51
45
|
```
|
|
52
46
|
|
|
53
|
-
Run
|
|
47
|
+
Run Fast or Deep Lookup:
|
|
54
48
|
|
|
55
49
|
```bash
|
|
56
|
-
whoisleuth
|
|
57
|
-
whoisleuth lookup example.
|
|
58
|
-
whoisleuth lookup example.
|
|
59
|
-
whoisleuth lookup example.
|
|
50
|
+
whoisleuth example.test
|
|
51
|
+
whoisleuth lookup example.test --deep
|
|
52
|
+
whoisleuth lookup example.test --deep --summary
|
|
53
|
+
whoisleuth lookup example.test --deep --browse
|
|
54
|
+
whoisleuth lookup example.test --deep --markdown --output lookup.md
|
|
60
55
|
```
|
|
61
56
|
|
|
62
|
-
Process
|
|
63
|
-
saved result without sending it anywhere:
|
|
57
|
+
Process selected local input:
|
|
64
58
|
|
|
65
59
|
```bash
|
|
66
60
|
whoisleuth bulk domains.txt --csv
|
|
67
|
-
whoisleuth discover example.
|
|
61
|
+
whoisleuth discover example.test --preset common --jsonl
|
|
68
62
|
whoisleuth verify-artifact lookup.json --json --strict-exit
|
|
69
|
-
whoisleuth verify-artifact report.json --manifest manifest.json --manifest-entry artifact-2 --json --strict-exit
|
|
70
|
-
whoisleuth interchange-report workspace.json --json
|
|
71
63
|
whoisleuth compare lookup.json --json
|
|
72
|
-
whoisleuth diff earlier-bulk.json later-bulk.json --left-session session-1 --right-session session-2 --json
|
|
73
64
|
whoisleuth brief lookup.json
|
|
74
|
-
whoisleuth registry-doctor lookup.json --json
|
|
75
|
-
whoisleuth registry-cohort retained-cohorts.jsonl --json
|
|
76
65
|
```
|
|
77
66
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
67
|
+
One strict domain, IP address or ASN can occupy command position as Lookup
|
|
68
|
+
shorthand. URL-like or ambiguous input requires the explicit `lookup` command.
|
|
69
|
+
Only `bulk` accepts multiple targets.
|
|
81
70
|
|
|
82
|
-
##
|
|
71
|
+
## Command groups
|
|
83
72
|
|
|
84
|
-
|
|
85
|
-
|
|
73
|
+
The installed registry groups commands under the same analyst vocabulary as the
|
|
74
|
+
application:
|
|
86
75
|
|
|
87
|
-
|
|
|
76
|
+
| Group | Common commands |
|
|
88
77
|
| --- | --- |
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
| Isolated authorised action | Runs only when its dedicated command and per-run acknowledgement flags are supplied; it is never added to Lookup, Bulk, monitoring, or recipes. |
|
|
94
|
-
|
|
95
|
-
Networked commands run from the local machine and contact the sources declared
|
|
96
|
-
by their profile. They do not use the hosted login or hosted usage controls;
|
|
97
|
-
upstream services can see and rate-limit the local network address. The CLI
|
|
98
|
-
does not implicitly enable the browser Console's optional external intelligence
|
|
99
|
-
actions.
|
|
100
|
-
|
|
101
|
-
`lookup --plan` performs classification and emits a versioned preflight without
|
|
102
|
-
starting collection. It lists planned source families and disclosure targets,
|
|
103
|
-
but cannot predict cache state, live configuration, redirects, referrals,
|
|
104
|
-
source availability, or exact request count.
|
|
105
|
-
|
|
106
|
-
`doctor` is offline by default. `doctor --network` adds three independent,
|
|
107
|
-
bounded checks against fixed diagnostic infrastructure and retains only state,
|
|
108
|
-
HTTP status, and a bounded explanation.
|
|
109
|
-
|
|
110
|
-
## Command map
|
|
111
|
-
|
|
112
|
-
| Goal | Commands |
|
|
113
|
-
| --- | --- |
|
|
114
|
-
| Investigate a target | `lookup`, `http`, `tls`, `dnssec-validate`, `mail-transport`, `posture`, `registry-support` |
|
|
115
|
-
| Review many targets | `bulk`, `discover`, `discover-scan`, `ct-search`, `ct-intake` |
|
|
116
|
-
| Compare saved evidence | `compare`, `page-compare`, `diff`, `reconcile`, `timeline`, `mail-review` |
|
|
117
|
-
| Review supplied evidence | `brief`, `review-evidence`, `registry-doctor`, `registry-cohort`, `source-report`, `sharing-review`, `map-observations` |
|
|
118
|
-
| Plan and assure changes | `domain-control`, `monitor-once`, `assurance`, `change-packet`, `workflow-plan`, `workflow-run` |
|
|
119
|
-
| Verify and package evidence | `case-pack`, `manifest`, `oam-export`, `verify-artifact`, `interchange-report`, `inspect-archive`, `sign-artifact`, `verify-signature`, `export` |
|
|
120
|
-
| Calibrate offline | `risk-calibrate`, `lookalike-calibrate` |
|
|
121
|
-
| Operate the CLI | `doctor`, `registry-scaffold`, `commands`, `completion`, `manual` |
|
|
78
|
+
| Investigate | `lookup`, `bulk`, `discover`, `ct-search`, `posture`, `http`, `tls`, `compare`, `brief` |
|
|
79
|
+
| Respond | `case-pack`, `change-packet`, `sharing-review`, `export` |
|
|
80
|
+
| Assure | `dnssec-validate`, `mail-transport`, `domain-control`, `assurance`, `workflow-plan`, `diff`, `inspect-archive`, `verify-artifact` |
|
|
81
|
+
| Utilities | `doctor`, `commands`, `completion`, `manual`, `registry-scaffold` |
|
|
122
82
|
|
|
123
|
-
|
|
83
|
+
Filter the canonical index without running the selected commands:
|
|
124
84
|
|
|
125
85
|
```bash
|
|
126
|
-
whoisleuth
|
|
127
|
-
whoisleuth
|
|
86
|
+
whoisleuth commands --group investigate
|
|
87
|
+
whoisleuth commands --group respond --mode offline
|
|
128
88
|
whoisleuth commands --json
|
|
129
89
|
whoisleuth manual | man -l -
|
|
130
90
|
```
|
|
131
91
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
restrained semantic colour and transient progress on stderr. Redirected output,
|
|
136
|
-
JSON, and JSONL contain no ANSI sequences or progress text. State remains
|
|
137
|
-
explicit in text, so colour is never the only distinction.
|
|
138
|
-
|
|
139
|
-
- `--json` and `--jsonl` provide bounded machine-readable documents.
|
|
140
|
-
- `--summary` and `--verbose` change human detail without changing collection.
|
|
141
|
-
- `--no-color` or `NO_COLOR` disables colour.
|
|
142
|
-
- `WHOISLEUTH_NO_PROGRESS=1` disables the transient progress indicator.
|
|
143
|
-
- `--events` keeps the final document on stdout and writes versioned JSONL
|
|
144
|
-
lifecycle events to stderr.
|
|
145
|
-
- `--strict-exit` treats failures in explicitly requested source families as a
|
|
146
|
-
partial failure while keeping skipped or unsupported sources neutral.
|
|
147
|
-
- `--fail-on` applies explicit `source-failure`, `inconclusive`, `danger`, or
|
|
148
|
-
`material-drift` automation policy where the selected command supports it.
|
|
149
|
-
- `--junit` emits bounded CI test cases for Lookup, Bulk, and one-shot control
|
|
150
|
-
reviews; owned-domain Posture can emit `--sarif` only with
|
|
151
|
-
`--owned-domain`.
|
|
152
|
-
|
|
153
|
-
`--config <file> --profile <name>` applies a strict version-1 local profile.
|
|
154
|
-
The default path is `$XDG_CONFIG_HOME/whoisleuth/config.json`. Profiles can set
|
|
155
|
-
only presentation mode, Fast collection, bounded concurrency, and observer or
|
|
156
|
-
vantage labels. They cannot add targets, enable Deep collection, select output
|
|
157
|
-
paths, approve network work, or set failure policy. Explicit command options
|
|
158
|
-
override defaults from the same option group.
|
|
159
|
-
|
|
160
|
-
Every command accepts `--output <file>`. WHOISleuth buffers bounded output,
|
|
161
|
-
creates a private temporary file beside the destination, syncs it, and publishes
|
|
162
|
-
it atomically with mode `0600`. Existing destinations are refused unless
|
|
163
|
-
`--force` is deliberately combined with `--output`. Failed or cancelled
|
|
164
|
-
commands do not publish partial files. Output is capped at 32 MiB.
|
|
165
|
-
|
|
166
|
-
## Exit codes
|
|
167
|
-
|
|
168
|
-
| Code | Meaning |
|
|
169
|
-
| ---: | --- |
|
|
170
|
-
| 0 | Command completed. Individual sources can still be partial or inconclusive; inspect diagnostics. |
|
|
171
|
-
| 2 | Invalid command, option, input, or stdin shape. |
|
|
172
|
-
| 3 | The requested lookup, collection, or comparison could not run. |
|
|
173
|
-
| 4 | A bounded operation completed partially. |
|
|
174
|
-
| 70 | Unexpected CLI bootstrap failure. |
|
|
175
|
-
| 130 | The analyst cancelled the command; no partial final result was emitted. |
|
|
176
|
-
| 143 | The process received SIGTERM; no partial final result was emitted. |
|
|
177
|
-
|
|
178
|
-
## Detailed command reference
|
|
179
|
-
|
|
180
|
-
The headings below preserve the established guide anchors while moving their
|
|
181
|
-
full contracts into the packaged reference.
|
|
182
|
-
|
|
183
|
-
### Registry capability coverage
|
|
184
|
-
|
|
185
|
-
[Read the complete registry capability reference.](cli-reference.md#registry-capability-coverage)
|
|
186
|
-
|
|
187
|
-
### Offline Risk calibration
|
|
188
|
-
|
|
189
|
-
[Read the calibration dataset and replay contract.](cli-reference.md#offline-risk-calibration)
|
|
190
|
-
|
|
191
|
-
### Lookalike review-yield calibration
|
|
192
|
-
|
|
193
|
-
[Read the reviewed candidate calibration contract.](cli-reference.md#lookalike-review-yield-calibration)
|
|
194
|
-
|
|
195
|
-
### Offline artefact verification
|
|
196
|
-
|
|
197
|
-
[Read the archive, saved Lookup, Lookup-evidence, and signed-artefact verification contract.](cli-reference.md#offline-artefact-verification)
|
|
198
|
-
|
|
199
|
-
### Privacy-safe source reliability report
|
|
200
|
-
|
|
201
|
-
[Read the target-free source diagnostic contract.](cli-reference.md#privacy-safe-source-reliability-report)
|
|
202
|
-
|
|
203
|
-
### Workspace archive inspection
|
|
204
|
-
|
|
205
|
-
[Read the redacted archive summary, stable content-digest, transfer-check, and
|
|
206
|
-
search contract.](cli-reference.md#workspace-archive-inspection)
|
|
207
|
-
|
|
208
|
-
### Optional evidence-package signing
|
|
209
|
-
|
|
210
|
-
[Read the local signing and verification contract.](cli-reference.md#optional-evidence-package-signing)
|
|
211
|
-
|
|
212
|
-
### Reproducible investigation manifest
|
|
213
|
-
|
|
214
|
-
[Read the ordered, path-free artefact digest contract.](cli-reference.md#reproducible-investigation-manifest)
|
|
215
|
-
|
|
216
|
-
### Bulk lookup
|
|
217
|
-
|
|
218
|
-
[Read Bulk bounds, filters, formats, checkpoints, and cancellation behaviour.](cli-reference.md#bulk-lookup)
|
|
219
|
-
|
|
220
|
-
### Certificate Transparency search
|
|
221
|
-
|
|
222
|
-
[Read the CT query and source-limit contract.](cli-reference.md#certificate-transparency-search)
|
|
92
|
+
`registry-scaffold` has a separate fixture bootstrap contract: its `--profile`
|
|
93
|
+
selects one fixed fixture profile and it rejects shared `--config` profiles.
|
|
94
|
+
It creates sanitised local fixtures and does not contact a registry.
|
|
223
95
|
|
|
224
|
-
|
|
96
|
+
## Collection boundaries
|
|
225
97
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
[Read candidate generation, dictionaries, snapshots, and output behaviour.](cli-reference.md#lookalike-discovery)
|
|
235
|
-
|
|
236
|
-
### Supervised candidate scan
|
|
237
|
-
|
|
238
|
-
[Read the bounded candidate collection workflow.](cli-reference.md#supervised-candidate-scan)
|
|
239
|
-
|
|
240
|
-
### Domain posture audit
|
|
241
|
-
|
|
242
|
-
[Read the DNS, mail, and certificate posture contract.](cli-reference.md#domain-posture-audit)
|
|
243
|
-
|
|
244
|
-
### HTTP intelligence
|
|
245
|
-
|
|
246
|
-
[Read the bounded HTTP collection contract.](cli-reference.md#http-intelligence)
|
|
247
|
-
|
|
248
|
-
### TLS intelligence
|
|
249
|
-
|
|
250
|
-
[Read the bounded TLS collection contract.](cli-reference.md#tls-intelligence)
|
|
251
|
-
|
|
252
|
-
### Isolated cryptographic and mail transport review
|
|
253
|
-
|
|
254
|
-
[Read the explicitly authorised DNSSEC, TLSA/DANE, STARTTLS, and SMTP transport contract.](cli-reference.md#isolated-cryptographic-and-mail-transport-review)
|
|
255
|
-
The mail report keeps address selection, public revalidation, confirmed
|
|
256
|
-
connection, and cryptographic address authentication as separate provenance;
|
|
257
|
-
address authentication is unavailable when no address candidate was retained.
|
|
258
|
-
If a DANE-TA TLSA usage 2 association is published, active collection retains
|
|
259
|
-
only the observed leaf certificate and leaves that comparison partial without
|
|
260
|
-
certificate-path construction and trust-anchor path validation. SMTP relay
|
|
261
|
-
PKIX-TA usage 0 and PKIX-EE usage 1 records are retained as unsupported and
|
|
262
|
-
cannot complete SMTP DANE assurance; a separate usage 3 match remains eligible.
|
|
263
|
-
|
|
264
|
-
### Registry-source comparison
|
|
265
|
-
|
|
266
|
-
[Read the normalised RDAP and WHOIS comparison contract.](cli-reference.md#registry-source-comparison)
|
|
267
|
-
|
|
268
|
-
### Static page comparison
|
|
269
|
-
|
|
270
|
-
[Read saved page-identity comparison behaviour.](cli-reference.md#static-page-comparison)
|
|
271
|
-
|
|
272
|
-
### Passive mail exposure review
|
|
273
|
-
|
|
274
|
-
[Read the saved-evidence mail review contract.](cli-reference.md#passive-mail-exposure-review)
|
|
275
|
-
|
|
276
|
-
### Offline supplied-evidence review
|
|
277
|
-
|
|
278
|
-
[Read the separate DNSSEC, route-origin, DANE/TLSA, zone-intent, portfolio, domain-change, and supplied-observation review contract.](cli-reference.md#offline-supplied-evidence-review)
|
|
279
|
-
|
|
280
|
-
### Domain control manifests
|
|
281
|
-
|
|
282
|
-
[Read desired-state manifest and drift-review behaviour.](cli-reference.md#domain-control-manifests)
|
|
283
|
-
|
|
284
|
-
### Domain assurance review
|
|
285
|
-
|
|
286
|
-
[Read planned-change, recovery-dependency, and retirement contracts.](cli-reference.md#domain-assurance-review)
|
|
287
|
-
|
|
288
|
-
### Domain change assurance packet
|
|
289
|
-
|
|
290
|
-
[Read the bounded pre-change, post-change, planning, and integrity contract.](cli-reference.md#domain-change-assurance-packet)
|
|
291
|
-
|
|
292
|
-
### Pre-sharing review
|
|
98
|
+
| Mode | Behaviour |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| Fast Lookup | Registration-first triage without WHOIS or deeper website and TLS collection. |
|
|
101
|
+
| Deep Lookup | Explicit RDAP, WHOIS, DNS, website, TLS, registrar-RDAP and observed-network collection where applicable. |
|
|
102
|
+
| Compact Deep Bulk | Comparison fields from shared Lookup orchestration, omitting full-only collectors and response fields. |
|
|
103
|
+
| Offline | Reads only the named local artefact, stdin or built-in catalogue. |
|
|
104
|
+
| Authorised active | Requires its dedicated command and per-run owned-or-authorised acknowledgement. |
|
|
293
105
|
|
|
294
|
-
|
|
106
|
+
Networked commands run from the local machine and contact the sources named in
|
|
107
|
+
their focused help. They do not use the hosted login or hosted usage controls.
|
|
108
|
+
`lookup --plan` lists planned source families and disclosure targets before
|
|
109
|
+
collection. `doctor` is offline unless `--network` is selected.
|
|
295
110
|
|
|
296
|
-
|
|
111
|
+
## Output and automation
|
|
297
112
|
|
|
298
|
-
|
|
113
|
+
Terminal text is the default. Commands expose JSON, JSONL, CSV, Markdown, HTML
|
|
114
|
+
or domain-only output only where declared by the installed registry. Redirected
|
|
115
|
+
and machine output contains no ANSI or transient progress text.
|
|
299
116
|
|
|
300
|
-
|
|
117
|
+
- `--summary` and `--verbose` change presentation, not collection.
|
|
118
|
+
- `lookup --browse` provides an interactive terminal view; press `?` for keys.
|
|
119
|
+
- `--events` writes versioned lifecycle events to stderr.
|
|
120
|
+
- `--strict-exit` and `--fail-on` expose selected evidence states to automation.
|
|
121
|
+
- `--output` writes a private local file atomically and refuses an existing path
|
|
122
|
+
unless `--force` is also selected.
|
|
301
123
|
|
|
302
|
-
|
|
124
|
+
Local configuration profiles can set presentation, Fast collection, bounded
|
|
125
|
+
concurrency and observer labels. They cannot add targets, enable Deep
|
|
126
|
+
collection, choose output paths or approve network work. Explicit command
|
|
127
|
+
options override profile defaults.
|
|
303
128
|
|
|
304
|
-
|
|
129
|
+
## Exit codes
|
|
305
130
|
|
|
306
|
-
|
|
131
|
+
| Code | Meaning |
|
|
132
|
+
| ---: | --- |
|
|
133
|
+
| 0 | The command completed. |
|
|
134
|
+
| 2 | Invalid command, option or input. |
|
|
135
|
+
| 3 | Collection, lookup or comparison failed. |
|
|
136
|
+
| 4 | The result was partial or a selected evidence policy was not met. |
|
|
137
|
+
| 70 | Internal CLI bootstrap failure. |
|
|
138
|
+
| 130 | The analyst cancelled the command. |
|
|
139
|
+
| 143 | The process received SIGTERM. |
|
|
307
140
|
|
|
308
|
-
|
|
141
|
+
## Command details
|
|
309
142
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
143
|
+
Run `whoisleuth <command> --help` for exact arguments, input ceilings, network
|
|
144
|
+
effects and output formats. The packaged [CLI reference](cli-reference.md) gives
|
|
145
|
+
the longer-lived compatibility and evidence contracts, while the generated
|
|
146
|
+
[online command reference](https://whoisleuth.com/cli#commands) provides a searchable index.
|
|
313
147
|
|
|
314
148
|
## Safety and limitations
|
|
315
149
|
|
|
316
150
|
WHOISleuth is a defensive investigation tool, not an authorisation mechanism.
|
|
317
|
-
Operators remain responsible for applicable law
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
Offline analyst assertions remain separately attributed and are not converted
|
|
321
|
-
into observed facts.
|
|
151
|
+
Operators remain responsible for permission, applicable law and provider terms.
|
|
152
|
+
Incomplete evidence remains explicitly qualified, and analyst assertions remain
|
|
153
|
+
separate from observed facts.
|
|
@@ -1,145 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export const DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA = 'whoisleuth.domain-control-manifest-input';
|
|
5
|
-
export const DOMAIN_CONTROL_PASSPORT_SCHEMA = 'whoisleuth.domain-control-manifest';
|
|
6
|
-
export const DOMAIN_CONTROL_PASSPORT_VERSION = 1;
|
|
7
|
-
export const DOMAIN_CONTROL_MANIFEST_VERSION = 2;
|
|
8
|
-
export const MAX_DOMAIN_CONTROL_PASSPORT_ENTRIES = 100;
|
|
9
|
-
export const DOMAIN_CONTROL_PASSPORT_LIMITATIONS = Object.freeze([
|
|
10
|
-
'This analyst-authored manifest records intended domain-control state. It does not collect evidence or change registrar, DNS, mail, or certificate configuration.',
|
|
11
|
-
'Empty desired fields are unconfigured rather than claims that a record should be absent.',
|
|
12
|
-
]);
|
|
13
|
-
const INPUT_KEYS = new Set(['schema', 'version', 'expiresAt', 'entries']);
|
|
14
|
-
const MANIFEST_KEYS = new Set(['schema', 'version', 'generatedAt', 'expiresAt', 'entries', 'limitations', 'integrity']);
|
|
15
|
-
const ENTRY_KEYS = new Set(['domain', 'nameservers', 'ds', 'mx', 'caa', 'tlsIssuer', 'tlsSpkiSha256', 'registrarLock', 'renewalReviewAt', 'note']);
|
|
16
|
-
const INTEGRITY_KEYS = new Set(['algorithm', 'canonicalization', 'digestSha256']);
|
|
17
|
-
const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
|
|
18
|
-
function record(value) {
|
|
19
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
20
|
-
? value
|
|
21
|
-
: null;
|
|
22
|
-
}
|
|
23
|
-
function exactKeys(value, allowed, label) {
|
|
24
|
-
const unknown = Object.keys(value).find((key) => !allowed.has(key));
|
|
25
|
-
if (unknown)
|
|
26
|
-
throw new TypeError(`${label} contains unknown field: ${unknown}.`);
|
|
27
|
-
}
|
|
28
|
-
function text(value, maximum = 300) {
|
|
29
|
-
if (typeof value !== 'string' || value.length > maximum * 4 || CONTROL_RE.test(value))
|
|
30
|
-
return null;
|
|
31
|
-
return value.replace(/\s+/gu, ' ').trim().slice(0, maximum) || null;
|
|
32
|
-
}
|
|
33
|
-
function timestamp(value) {
|
|
34
|
-
const candidate = text(value, 64);
|
|
35
|
-
if (!candidate)
|
|
36
|
-
return null;
|
|
37
|
-
const parsed = Date.parse(candidate);
|
|
38
|
-
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
|
|
39
|
-
}
|
|
40
|
-
function hostnames(value) {
|
|
41
|
-
if (!Array.isArray(value))
|
|
42
|
-
return [];
|
|
43
|
-
return [...new Set(value.slice(0, 128).flatMap((item) => {
|
|
44
|
-
const normalized = normalizeDomain(item);
|
|
45
|
-
return normalized ? [normalized] : [];
|
|
46
|
-
}))].sort().slice(0, 32);
|
|
47
|
-
}
|
|
48
|
-
function digest(value) {
|
|
49
|
-
const candidate = text(value, 64)?.toLowerCase() ?? '';
|
|
50
|
-
return /^[a-f0-9]{64}$/u.test(candidate) ? candidate : null;
|
|
51
|
-
}
|
|
52
|
-
function normalizeEntry(value) {
|
|
53
|
-
const source = record(value);
|
|
54
|
-
if (!source)
|
|
55
|
-
return null;
|
|
56
|
-
exactKeys(source, ENTRY_KEYS, 'Domain control manifest entry');
|
|
57
|
-
const domain = normalizeDomain(source.domain);
|
|
58
|
-
if (!domain)
|
|
59
|
-
return null;
|
|
60
|
-
return Object.freeze({
|
|
61
|
-
domain,
|
|
62
|
-
nameservers: Object.freeze(hostnames(source.nameservers)),
|
|
63
|
-
ds: Object.freeze(canonicalDomainControlRecordList(source.ds, 'ds')),
|
|
64
|
-
mx: Object.freeze(canonicalDomainControlRecordList(source.mx, 'mx')),
|
|
65
|
-
caa: Object.freeze(canonicalDomainControlRecordList(source.caa, 'caa')),
|
|
66
|
-
tlsIssuer: text(source.tlsIssuer, 300)?.toLowerCase() ?? null,
|
|
67
|
-
tlsSpkiSha256: digest(source.tlsSpkiSha256),
|
|
68
|
-
registrarLock: source.registrarLock === 'required' || source.registrarLock === 'not_required'
|
|
69
|
-
? source.registrarLock
|
|
70
|
-
: null,
|
|
71
|
-
renewalReviewAt: timestamp(source.renewalReviewAt),
|
|
72
|
-
note: text(source.note, 500),
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
export function buildUnsignedDomainControlPassport(input, generatedAtValue) {
|
|
76
|
-
const source = record(input);
|
|
77
|
-
if (!source || source.schema !== DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA || source.version !== DOMAIN_CONTROL_PASSPORT_VERSION) {
|
|
78
|
-
throw new TypeError(`Domain control manifest input must use ${DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA} version ${DOMAIN_CONTROL_PASSPORT_VERSION}.`);
|
|
79
|
-
}
|
|
80
|
-
exactKeys(source, INPUT_KEYS, 'Domain control manifest input');
|
|
81
|
-
const generatedAt = timestamp(generatedAtValue);
|
|
82
|
-
const expiresAt = timestamp(source.expiresAt);
|
|
83
|
-
if (!generatedAt || !expiresAt || Date.parse(expiresAt) <= Date.parse(generatedAt)) {
|
|
84
|
-
throw new TypeError('Domain control manifest expiry must be a valid time after generation.');
|
|
85
|
-
}
|
|
86
|
-
if (!Array.isArray(source.entries) || source.entries.length < 1 || source.entries.length > MAX_DOMAIN_CONTROL_PASSPORT_ENTRIES) {
|
|
87
|
-
throw new TypeError(`Domain control manifest input must contain between 1 and ${MAX_DOMAIN_CONTROL_PASSPORT_ENTRIES} entries.`);
|
|
88
|
-
}
|
|
89
|
-
const entries = source.entries.map(normalizeEntry);
|
|
90
|
-
if (entries.some((entry) => entry === null))
|
|
91
|
-
throw new TypeError('Domain control manifest contains an invalid entry.');
|
|
92
|
-
const normalizedEntries = entries;
|
|
93
|
-
if (new Set(normalizedEntries.map((entry) => entry.domain)).size !== normalizedEntries.length) {
|
|
94
|
-
throw new TypeError('Domain control manifest entries must use unique domains.');
|
|
95
|
-
}
|
|
96
|
-
return Object.freeze({
|
|
97
|
-
schema: DOMAIN_CONTROL_PASSPORT_SCHEMA,
|
|
98
|
-
version: DOMAIN_CONTROL_MANIFEST_VERSION,
|
|
99
|
-
generatedAt,
|
|
100
|
-
expiresAt,
|
|
101
|
-
entries: Object.freeze([...normalizedEntries].sort((left, right) => left.domain.localeCompare(right.domain))),
|
|
102
|
-
limitations: DOMAIN_CONTROL_PASSPORT_LIMITATIONS,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
export function normalizeDomainControlPassportDocument(value) {
|
|
106
|
-
const source = record(value);
|
|
107
|
-
const integrity = record(source?.integrity);
|
|
108
|
-
if (!source
|
|
109
|
-
|| source.schema !== DOMAIN_CONTROL_PASSPORT_SCHEMA
|
|
110
|
-
|| !integrity
|
|
111
|
-
|| integrity.algorithm !== 'SHA-256'
|
|
112
|
-
|| typeof integrity.digestSha256 !== 'string'
|
|
113
|
-
|| !/^sha256:[a-f0-9]{64}$/u.test(integrity.digestSha256)) {
|
|
114
|
-
throw new TypeError('Domain control manifest has an unsupported or malformed structure.');
|
|
115
|
-
}
|
|
116
|
-
exactKeys(source, MANIFEST_KEYS, 'Domain control manifest');
|
|
117
|
-
exactKeys(integrity, INTEGRITY_KEYS, 'Domain control manifest integrity');
|
|
118
|
-
const canonicalization = resolveArtifactCanonicalization(source.version, integrity.canonicalization, [
|
|
119
|
-
{ version: DOMAIN_CONTROL_PASSPORT_VERSION, canonicalization: SORTED_JSON_V1, explicit: true },
|
|
120
|
-
{ version: DOMAIN_CONTROL_MANIFEST_VERSION, canonicalization: SORTED_JSON_V2, explicit: true },
|
|
121
|
-
], 'Domain control manifest');
|
|
122
|
-
if (!Array.isArray(source.limitations)
|
|
123
|
-
|| source.limitations.length !== DOMAIN_CONTROL_PASSPORT_LIMITATIONS.length
|
|
124
|
-
|| source.limitations.some((item, index) => item !== DOMAIN_CONTROL_PASSPORT_LIMITATIONS[index])) {
|
|
125
|
-
throw new TypeError('Domain control manifest has an unsupported or malformed structure.');
|
|
126
|
-
}
|
|
127
|
-
const baseUnsigned = buildUnsignedDomainControlPassport({
|
|
128
|
-
schema: DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA,
|
|
129
|
-
version: DOMAIN_CONTROL_PASSPORT_VERSION,
|
|
130
|
-
expiresAt: source.expiresAt,
|
|
131
|
-
entries: source.entries,
|
|
132
|
-
}, source.generatedAt);
|
|
133
|
-
const unsigned = Object.freeze({ ...baseUnsigned, version: source.version });
|
|
134
|
-
const { integrity: _integrity, ...suppliedUnsigned } = source;
|
|
135
|
-
const canonicalUnsigned = canonicalArtifactJsonFor(unsigned, canonicalization);
|
|
136
|
-
if (canonicalArtifactJsonFor(suppliedUnsigned, canonicalization) !== canonicalUnsigned) {
|
|
137
|
-
throw new TypeError('Domain control manifest must use its canonical normalised content.');
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
manifest: source,
|
|
141
|
-
unsigned,
|
|
142
|
-
canonicalUnsigned,
|
|
143
|
-
canonicalization,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
1
|
+
// Historical browser-safe import facade. The pure domain-control manifest
|
|
2
|
+
// contract is shared by browser, CLI, and Node adapters.
|
|
3
|
+
export { DOMAIN_CONTROL_MANIFEST_VERSION, DOMAIN_CONTROL_PASSPORT_INPUT_SCHEMA, DOMAIN_CONTROL_PASSPORT_LIMITATIONS, DOMAIN_CONTROL_PASSPORT_SCHEMA, DOMAIN_CONTROL_PASSPORT_VERSION, MAX_DOMAIN_CONTROL_MANIFEST_BYTES, MAX_DOMAIN_CONTROL_PASSPORT_BYTES, MAX_DOMAIN_CONTROL_PASSPORT_ENTRIES, assertDomainControlPassportByteBudget, buildUnsignedDomainControlPassport, domainControlPassportSerialisedBytes, normalizeDomainControlPassportDocument, } from "../../../../packages/evidence/domain-control-runtime.mjs";
|
|
@@ -1,88 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
5
|
-
? value
|
|
6
|
-
: {};
|
|
7
|
-
}
|
|
8
|
-
function text(value, maximum = 500) {
|
|
9
|
-
if (typeof value !== 'string' || value.length > maximum * 4 || /[\u0000-\u001f\u007f]/u.test(value))
|
|
10
|
-
return '';
|
|
11
|
-
return value.replace(/\s+/gu, ' ').trim().slice(0, maximum).trim();
|
|
12
|
-
}
|
|
13
|
-
function integer(value, maximum) {
|
|
14
|
-
const candidate = typeof value === 'number' ? value : typeof value === 'string' && /^\d+$/u.test(value.trim()) ? Number(value) : NaN;
|
|
15
|
-
return Number.isSafeInteger(candidate) && candidate >= 0 && candidate <= maximum ? candidate : null;
|
|
16
|
-
}
|
|
17
|
-
function exchange(value) {
|
|
18
|
-
const candidate = text(value, 253).toLowerCase();
|
|
19
|
-
if (candidate === '.')
|
|
20
|
-
return '.';
|
|
21
|
-
return normalizeDomain(candidate) ?? '';
|
|
22
|
-
}
|
|
23
|
-
export function canonicalMxRecord(value) {
|
|
24
|
-
const item = record(value);
|
|
25
|
-
if (Object.keys(item).length) {
|
|
26
|
-
const host = exchange(item.exchange ?? item.host ?? item.value);
|
|
27
|
-
const priority = integer(item.priority ?? item.preference, 65_535);
|
|
28
|
-
return host && priority !== null ? `${priority} ${host}` : '';
|
|
29
|
-
}
|
|
30
|
-
const candidate = text(value, 500).toLowerCase();
|
|
31
|
-
const match = /^(\d{1,5})\s+(.+)$/u.exec(candidate);
|
|
32
|
-
if (match) {
|
|
33
|
-
const priority = integer(match[1], 65_535);
|
|
34
|
-
const host = exchange(match[2]);
|
|
35
|
-
return priority !== null && host ? `${priority} ${host}` : '';
|
|
36
|
-
}
|
|
37
|
-
return exchange(candidate);
|
|
38
|
-
}
|
|
39
|
-
function caaValue(value) {
|
|
40
|
-
return text(value, 500).replace(/^(?:"([\s\S]*)"|'([\s\S]*)')$/u, '$1$2').trim().toLowerCase();
|
|
41
|
-
}
|
|
42
|
-
export function canonicalCaaRecord(value) {
|
|
43
|
-
const item = record(value);
|
|
44
|
-
if (Object.keys(item).length) {
|
|
45
|
-
const flags = integer(item.critical ?? item.flags ?? 0, 255);
|
|
46
|
-
const tag = text(item.tag, 32).toLowerCase();
|
|
47
|
-
const payload = caaValue(item.value);
|
|
48
|
-
return flags !== null && /^(?:issue|issuewild|iodef)$/u.test(tag) && payload
|
|
49
|
-
? `${flags} ${tag} ${payload}`
|
|
50
|
-
: '';
|
|
51
|
-
}
|
|
52
|
-
const candidate = text(value, 600);
|
|
53
|
-
const match = /^(\d{1,3})\s+(issue|issuewild|iodef)\s+(.+)$/iu.exec(candidate);
|
|
54
|
-
if (!match)
|
|
55
|
-
return '';
|
|
56
|
-
const flags = integer(match[1], 255);
|
|
57
|
-
const tag = match[2]?.toLowerCase() ?? '';
|
|
58
|
-
const payload = caaValue(match[3]);
|
|
59
|
-
return flags !== null && payload ? `${flags} ${tag} ${payload}` : '';
|
|
60
|
-
}
|
|
61
|
-
export function canonicalDsRecord(value) {
|
|
62
|
-
const item = record(value);
|
|
63
|
-
if (Object.keys(item).length) {
|
|
64
|
-
const keyTag = integer(item.keyTag ?? item.key_tag, 65_535);
|
|
65
|
-
const algorithm = integer(item.algorithm, 255);
|
|
66
|
-
const digestType = integer(item.digestType ?? item.digest_type, 255);
|
|
67
|
-
const digest = text(item.digest, 1024).replace(/\s+/gu, '').toLowerCase();
|
|
68
|
-
return keyTag !== null && algorithm !== null && digestType !== null && /^[a-f0-9]{2,1024}$/u.test(digest)
|
|
69
|
-
? `${keyTag} ${algorithm} ${digestType} ${digest}`
|
|
70
|
-
: '';
|
|
71
|
-
}
|
|
72
|
-
const candidate = text(value, 1200);
|
|
73
|
-
const match = /^(\d{1,5})\s+(\d{1,3})\s+(\d{1,3})\s+([a-f0-9\s]+)$/iu.exec(candidate);
|
|
74
|
-
if (!match)
|
|
75
|
-
return '';
|
|
76
|
-
return canonicalDsRecord({ keyTag: match[1], algorithm: match[2], digestType: match[3], digest: match[4] });
|
|
77
|
-
}
|
|
78
|
-
export function canonicalDomainControlRecordList(value, kind) {
|
|
79
|
-
if (!Array.isArray(value))
|
|
80
|
-
return [];
|
|
81
|
-
const normalizer = kind === 'mx' ? canonicalMxRecord : kind === 'caa' ? canonicalCaaRecord : canonicalDsRecord;
|
|
82
|
-
return [...new Set(value
|
|
83
|
-
.slice(0, MAX_CANONICAL_DOMAIN_CONTROL_RECORDS * 4)
|
|
84
|
-
.map(normalizer)
|
|
85
|
-
.filter(Boolean))]
|
|
86
|
-
.sort()
|
|
87
|
-
.slice(0, MAX_CANONICAL_DOMAIN_CONTROL_RECORDS);
|
|
88
|
-
}
|
|
1
|
+
// Historical browser-safe import facade. Domain-control record normalisation
|
|
2
|
+
// is owned by the pure evidence package and shared unchanged across runtimes.
|
|
3
|
+
export { MAX_CANONICAL_DOMAIN_CONTROL_RECORDS, canonicalCaaRecord, canonicalDomainControlRecordList, canonicalDsRecord, canonicalMxRecord, } from "../../../../packages/evidence/domain-control-runtime.mjs";
|