@slicedearth/whoisleuth-cli 1.32.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/LICENSE +661 -0
- package/LICENSES/Retire.js-Apache-2.0.txt +199 -0
- package/NOTICE +76 -0
- package/README.md +84 -0
- package/TRADEMARKS.md +41 -0
- package/bin/whoisleuth.mjs +35 -0
- package/cli/archive-inspect.mjs +198 -0
- package/cli/arguments.mjs +1022 -0
- package/cli/artifact-verify.mjs +210 -0
- package/cli/bulk-checkpoint.mjs +218 -0
- package/cli/bulk-command-runner.mjs +109 -0
- package/cli/bulk-output.mjs +112 -0
- package/cli/bulk.mjs +133 -0
- package/cli/compare.mjs +238 -0
- package/cli/completion.mjs +175 -0
- package/cli/csv.mjs +1 -0
- package/cli/discover.mjs +50 -0
- package/cli/discovery-command-runner.mjs +36 -0
- package/cli/discovery-observation-snapshot.mjs +315 -0
- package/cli/discovery-scan-command-runner.mjs +141 -0
- package/cli/discovery-scan.mjs +325 -0
- package/cli/discovery-snapshot.mjs +133 -0
- package/cli/discovery-workflow.mjs +73 -0
- package/cli/doctor.mjs +137 -0
- package/cli/errors.mjs +21 -0
- package/cli/evidence-command-arguments.mjs +145 -0
- package/cli/evidence-command-runner.mjs +106 -0
- package/cli/evidence-signing.mjs +215 -0
- package/cli/exit-codes.mjs +9 -0
- package/cli/export-evidence.mjs +28 -0
- package/cli/formatters/evidence-report.mjs +3 -0
- package/cli/formatters/html.mjs +69 -0
- package/cli/formatters/json.mjs +135 -0
- package/cli/formatters/markdown.mjs +3 -0
- package/cli/formatters/terminal.mjs +658 -0
- package/cli/http.mjs +27 -0
- package/cli/lookup-command-runner.mjs +87 -0
- package/cli/lookup-diff.mjs +171 -0
- package/cli/mail-review.mjs +204 -0
- package/cli/manual.mjs +34 -0
- package/cli/network-command-runner.mjs +88 -0
- package/cli/output-file.mjs +97 -0
- package/cli/page-compare.mjs +123 -0
- package/cli/posture.mjs +20 -0
- package/cli/progress-events.mjs +53 -0
- package/cli/progress.mjs +56 -0
- package/cli/registry-access.mjs +3 -0
- package/cli/registry-support.mjs +120 -0
- package/cli/risk-calibration.mjs +294 -0
- package/cli/runner-types.mjs +1 -0
- package/cli/runner.mjs +726 -0
- package/cli/saved-lookup.mjs +87 -0
- package/cli/source-reliability.mjs +727 -0
- package/cli/strict-exit.mjs +55 -0
- package/cli/terminal-presentation.mjs +147 -0
- package/docs/cli.md +827 -0
- package/frontend/src/lib/analysis/acquisition-decision-packet.js +93 -0
- package/frontend/src/lib/analysis/acquisition-due-diligence.js +391 -0
- package/frontend/src/lib/analysis/artifact-integrity.js +21 -0
- package/frontend/src/lib/analysis/brand-profile-model.js +421 -0
- package/frontend/src/lib/analysis/bulk-domain-comparison.js +246 -0
- package/frontend/src/lib/analysis/bulk-mail-exposure.js +192 -0
- package/frontend/src/lib/analysis/bulk-retry-plan.js +86 -0
- package/frontend/src/lib/analysis/bulk-review-export.js +62 -0
- package/frontend/src/lib/analysis/bulk-review-model.js +266 -0
- package/frontend/src/lib/analysis/bulk-session-model.js +515 -0
- package/frontend/src/lib/analysis/bulk-sort.js +49 -0
- package/frontend/src/lib/analysis/campaign-model.js +265 -0
- package/frontend/src/lib/analysis/case-evidence-model.js +642 -0
- package/frontend/src/lib/analysis/case-migration-model.js +248 -0
- package/frontend/src/lib/analysis/case-model.js +5 -0
- package/frontend/src/lib/analysis/case-record-contracts.js +44 -0
- package/frontend/src/lib/analysis/case-record-core.js +232 -0
- package/frontend/src/lib/analysis/case-record-model.js +7 -0
- package/frontend/src/lib/analysis/case-record-operations.js +224 -0
- package/frontend/src/lib/analysis/case-response-model.js +640 -0
- package/frontend/src/lib/analysis/case-response-packet.js +573 -0
- package/frontend/src/lib/analysis/case-storage-model.js +93 -0
- package/frontend/src/lib/analysis/detection-rule-model.js +306 -0
- package/frontend/src/lib/analysis/dns-record-normalization.js +11 -0
- package/frontend/src/lib/analysis/http-summary.js +165 -0
- package/frontend/src/lib/analysis/investigation-guide.js +500 -0
- package/frontend/src/lib/analysis/investigation-template-model.js +144 -0
- package/frontend/src/lib/analysis/page-baseline.js +272 -0
- package/frontend/src/lib/analysis/page-similarity.js +179 -0
- package/frontend/src/lib/analysis/relationship-evidence.js +265 -0
- package/frontend/src/lib/analysis/relationship-observation-model.js +423 -0
- package/frontend/src/lib/analysis/scoring.js +135 -0
- package/frontend/src/lib/analysis/shortlist-model.js +209 -0
- package/frontend/src/lib/analysis/utils.js +265 -0
- package/frontend/src/lib/analysis/watchlist-history.js +643 -0
- package/frontend/src/lib/analysis/watchlist-store.js +127 -0
- package/frontend/src/lib/analysis/website-snapshot-model.js +302 -0
- package/frontend/src/lib/analysis/workspace-archive-crypto.js +282 -0
- package/frontend/src/lib/analysis/workspace-archive.js +472 -0
- package/lib/abort.mjs +26 -0
- package/lib/abusech-auth.mjs +19 -0
- package/lib/availability.mjs +716 -0
- package/lib/bounded-contract-normalizers.mjs +61 -0
- package/lib/browser-library-profile.mjs +310 -0
- package/lib/bulk-comparison-evidence.mjs +83 -0
- package/lib/classify.mjs +93 -0
- package/lib/client-behavior-profile.mjs +83 -0
- package/lib/credential-surface-profile.mjs +66 -0
- package/lib/csv.mjs +23 -0
- package/lib/ct-query.mjs +27 -0
- package/lib/ct-search.mjs +393 -0
- package/lib/distributed-operation-budget.mjs +461 -0
- package/lib/dns-delegation-health.mjs +344 -0
- package/lib/dns-intelligence.mjs +739 -0
- package/lib/dns-mx.mjs +24 -0
- package/lib/dns-resolver-selection.mjs +33 -0
- package/lib/domain-posture-analysis.mjs +254 -0
- package/lib/domain-posture-parsers.mjs +358 -0
- package/lib/domain-posture.mjs +745 -0
- package/lib/error-detail.mjs +10 -0
- package/lib/evidence-export.mjs +431 -0
- package/lib/evidence-report-markdown.mjs +71 -0
- package/lib/evidence-report.mjs +391 -0
- package/lib/external-intelligence-risk.mjs +108 -0
- package/lib/favicon.mjs +146 -0
- package/lib/feature-policy.mjs +70 -0
- package/lib/generated/retire-browser-catalog.mjs +8724 -0
- package/lib/generated/unicode-confusables-17.mjs +93 -0
- package/lib/hostname.mjs +38 -0
- package/lib/html-signals.mjs +680 -0
- package/lib/http-intelligence.mjs +232 -0
- package/lib/idn-confusable-policy.mjs +86 -0
- package/lib/idn-confusables.mjs +465 -0
- package/lib/lookup-cache.mjs +130 -0
- package/lib/lookup-progress.mjs +291 -0
- package/lib/lookup-source-progress.mjs +109 -0
- package/lib/lookup-threat-provider-inventory.mjs +13 -0
- package/lib/lookup.mjs +592 -0
- package/lib/observation.mjs +114 -0
- package/lib/observed-network-context.mjs +355 -0
- package/lib/operation-budget.mjs +489 -0
- package/lib/page-fingerprints.mjs +481 -0
- package/lib/page-role-profile.mjs +117 -0
- package/lib/perceptual-hash.mjs +451 -0
- package/lib/rdap-attempts.mjs +34 -0
- package/lib/rdap-bootstrap.mjs +173 -0
- package/lib/rdap-capabilities.mjs +292 -0
- package/lib/rdap-client.mjs +125 -0
- package/lib/rdap-normalization.mjs +691 -0
- package/lib/rdap-registrar.mjs +196 -0
- package/lib/rdap-transport.mjs +39 -0
- package/lib/rdap-types.mjs +1 -0
- package/lib/rdap-validation.mjs +94 -0
- package/lib/rdap.mjs +16 -0
- package/lib/registry-access.mjs +32 -0
- package/lib/registry-capabilities.mjs +117 -0
- package/lib/registry-capability-catalogue.mjs +1206 -0
- package/lib/registry-comparison.mjs +318 -0
- package/lib/registry-dates.mjs +190 -0
- package/lib/registry-insights.mjs +325 -0
- package/lib/response-policy.mjs +256 -0
- package/lib/risk-scoring.mjs +164 -0
- package/lib/safe-fetch.mjs +446 -0
- package/lib/security-txt.mjs +359 -0
- package/lib/service-binding-dns.mjs +608 -0
- package/lib/sslbl-certificates.generated.mjs +143 -0
- package/lib/sslbl-intelligence.mjs +241 -0
- package/lib/static-html-analysis.mjs +340 -0
- package/lib/structured-data-identity.mjs +323 -0
- package/lib/threat-intelligence-contract.mjs +5 -0
- package/lib/threat-intelligence-definition-registry.mjs +34 -0
- package/lib/threat-intelligence-fixtures.mjs +82 -0
- package/lib/threat-intelligence-manifests.mjs +512 -0
- package/lib/threat-intelligence-runtime.mjs +662 -0
- package/lib/threat-intelligence-targets.mjs +168 -0
- package/lib/threat-intelligence-types.mjs +3 -0
- package/lib/threatfox-intelligence.mjs +307 -0
- package/lib/tls-intelligence.mjs +814 -0
- package/lib/typosquat-generator.mjs +884 -0
- package/lib/urlhaus-intelligence.mjs +361 -0
- package/lib/urlscan-intelligence.mjs +349 -0
- package/lib/website-security-posture.mjs +325 -0
- package/lib/website-technology.mjs +437 -0
- package/lib/whois-authority.mjs +98 -0
- package/lib/whois-chain.mjs +261 -0
- package/lib/whois-contacts.mjs +163 -0
- package/lib/whois-contracts.mjs +3 -0
- package/lib/whois-normalization.mjs +27 -0
- package/lib/whois-transport.mjs +100 -0
- package/lib/whois-values.mjs +92 -0
- package/lib/whois.mjs +1187 -0
- package/package.json +55 -0
- package/third-party-notices.txt +5571 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// Bounded normalized provenance for the homepage response already collected
|
|
2
|
+
// by deep availability analysis. This module does not perform network work; it
|
|
3
|
+
// converts the shared safe-fetch trace and selected response metadata into an
|
|
4
|
+
// additive evidence object suitable for API responses and exports.
|
|
5
|
+
import { createObservation } from "./observation.mjs";
|
|
6
|
+
const MAX_HTTP_PROVENANCE_URL = 2048;
|
|
7
|
+
const MAX_HTTP_REDIRECTS = 5;
|
|
8
|
+
const MAX_HTTP_ATTEMPTS = 2;
|
|
9
|
+
const MAX_HTTP_ERROR_LENGTH = 180;
|
|
10
|
+
const MAX_HTTP_HEADER_LENGTH = 1024;
|
|
11
|
+
const MAX_CAPTURED_BODY_BYTES = 5 * 1024 * 1024;
|
|
12
|
+
const OBSERVED_HEADER_MARKER = 'observed';
|
|
13
|
+
const SECURITY_HEADERS = Object.freeze({
|
|
14
|
+
'strict-transport-security': 'strictTransportSecurity',
|
|
15
|
+
'content-security-policy': 'contentSecurityPolicy',
|
|
16
|
+
'x-frame-options': 'xFrameOptions',
|
|
17
|
+
'x-content-type-options': 'xContentTypeOptions',
|
|
18
|
+
'referrer-policy': 'referrerPolicy',
|
|
19
|
+
});
|
|
20
|
+
function boundedString(value, maxLength) {
|
|
21
|
+
if (typeof value !== 'string')
|
|
22
|
+
return null;
|
|
23
|
+
const normalized = value.trim().replace(/[\u0000-\u001f\u007f]/g, ' ');
|
|
24
|
+
return normalized ? normalized.slice(0, maxLength) : null;
|
|
25
|
+
}
|
|
26
|
+
function normalizeProvenanceUrl(value) {
|
|
27
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > 4096)
|
|
28
|
+
return null;
|
|
29
|
+
try {
|
|
30
|
+
const parsed = new URL(value);
|
|
31
|
+
if (!['http:', 'https:'].includes(parsed.protocol) || parsed.username || parsed.password || !parsed.hostname)
|
|
32
|
+
return null;
|
|
33
|
+
const queryOmitted = Boolean(parsed.search);
|
|
34
|
+
parsed.search = '';
|
|
35
|
+
parsed.hash = '';
|
|
36
|
+
let url = parsed.toString();
|
|
37
|
+
let pathTruncated = false;
|
|
38
|
+
if (url.length > MAX_HTTP_PROVENANCE_URL) {
|
|
39
|
+
parsed.pathname = '/';
|
|
40
|
+
url = parsed.toString();
|
|
41
|
+
pathTruncated = true;
|
|
42
|
+
}
|
|
43
|
+
return { url, queryOmitted, pathTruncated };
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function boundedDuration(value) {
|
|
50
|
+
const number = Number(value);
|
|
51
|
+
return Number.isFinite(number) ? Math.max(0, Math.min(120_000, Math.round(number))) : null;
|
|
52
|
+
}
|
|
53
|
+
function boundedHttpStatus(value) {
|
|
54
|
+
const number = Number(value);
|
|
55
|
+
return Number.isInteger(number) && number >= 100 && number <= 599 ? number : null;
|
|
56
|
+
}
|
|
57
|
+
function headerValue(headers, name, maxLength = MAX_HTTP_HEADER_LENGTH) {
|
|
58
|
+
if (!headers || typeof headers.get !== 'function')
|
|
59
|
+
return null;
|
|
60
|
+
return boundedString(headers.get(name), maxLength);
|
|
61
|
+
}
|
|
62
|
+
function declaredContentLength(headers) {
|
|
63
|
+
const raw = headerValue(headers, 'content-length', 32);
|
|
64
|
+
if (!raw || !/^\d+$/.test(raw))
|
|
65
|
+
return null;
|
|
66
|
+
const parsed = Number(raw);
|
|
67
|
+
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
68
|
+
}
|
|
69
|
+
function normalizeAttempts(attempts) {
|
|
70
|
+
return (Array.isArray(attempts) ? attempts : []).slice(0, MAX_HTTP_ATTEMPTS).map((attempt) => {
|
|
71
|
+
const record = attempt && typeof attempt === 'object' ? attempt : {};
|
|
72
|
+
const url = normalizeProvenanceUrl(record.url);
|
|
73
|
+
const error = boundedString(record.error, MAX_HTTP_ERROR_LENGTH);
|
|
74
|
+
const httpStatus = boundedHttpStatus(record.httpStatus);
|
|
75
|
+
return {
|
|
76
|
+
url: url ? url.url : null,
|
|
77
|
+
queryOmitted: url ? url.queryOmitted : false,
|
|
78
|
+
outcome: error ? 'error' : httpStatus !== null ? 'response' : 'unknown',
|
|
79
|
+
httpStatus,
|
|
80
|
+
error,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function normalizeRedirects(hops) {
|
|
85
|
+
return (Array.isArray(hops) ? hops : [])
|
|
86
|
+
.filter((hop) => Boolean(hop && typeof hop === 'object' && hop.location))
|
|
87
|
+
.slice(0, MAX_HTTP_REDIRECTS)
|
|
88
|
+
.map((hop) => {
|
|
89
|
+
const record = hop;
|
|
90
|
+
const from = normalizeProvenanceUrl(record.url);
|
|
91
|
+
const to = normalizeProvenanceUrl(record.location);
|
|
92
|
+
if (!from || !to)
|
|
93
|
+
return null;
|
|
94
|
+
return {
|
|
95
|
+
from: from.url,
|
|
96
|
+
to: to.url,
|
|
97
|
+
status: boundedHttpStatus(record.status),
|
|
98
|
+
durationMs: boundedDuration(record.durationMs),
|
|
99
|
+
queryOmitted: from.queryOmitted || to.queryOmitted,
|
|
100
|
+
};
|
|
101
|
+
})
|
|
102
|
+
.filter((item) => item !== null);
|
|
103
|
+
}
|
|
104
|
+
function securityHeaders(headers) {
|
|
105
|
+
return Object.fromEntries(Object.entries(SECURITY_HEADERS).map(([header, field]) => [
|
|
106
|
+
field,
|
|
107
|
+
headerValue(headers, header) ? OBSERVED_HEADER_MARKER : null,
|
|
108
|
+
]));
|
|
109
|
+
}
|
|
110
|
+
function normalizedSha256(value) {
|
|
111
|
+
return typeof value === 'string' && /^[0-9a-f]{64}$/i.test(value) ? value.toLowerCase() : null;
|
|
112
|
+
}
|
|
113
|
+
function buildHttpObservation(detail, options = {}) {
|
|
114
|
+
const response = detail && detail.response;
|
|
115
|
+
const requested = normalizeProvenanceUrl(detail && detail.requestedUrl);
|
|
116
|
+
const final = normalizeProvenanceUrl(detail && detail.finalUrl);
|
|
117
|
+
const redirects = normalizeRedirects(detail && detail.hops);
|
|
118
|
+
const bodyTruncated = options.bodyTruncated === true;
|
|
119
|
+
const redirectLimitReached = detail && detail.redirectLimitReached === true;
|
|
120
|
+
const capturedBodyBytes = Number.isFinite(Number(options.capturedBodyBytes))
|
|
121
|
+
? Math.max(0, Math.min(MAX_CAPTURED_BODY_BYTES, Math.round(Number(options.capturedBodyBytes))))
|
|
122
|
+
: 0;
|
|
123
|
+
const bodyInspected = options.bodyInspected === true;
|
|
124
|
+
const bodySha256 = bodyInspected ? normalizedSha256(options.bodySha256) : null;
|
|
125
|
+
const limitations = [];
|
|
126
|
+
if (bodyTruncated)
|
|
127
|
+
limitations.push('Homepage body capture reached its byte limit; response metadata and the retained prefix remain usable.');
|
|
128
|
+
if (redirectLimitReached)
|
|
129
|
+
limitations.push('Redirect following reached the configured hop limit; the final destination was not requested.');
|
|
130
|
+
if ((requested && requested.pathTruncated) || (final && final.pathTruncated))
|
|
131
|
+
limitations.push('An overlong URL path was omitted from retained provenance.');
|
|
132
|
+
if ((requested && requested.queryOmitted) || (final && final.queryOmitted) || redirects.some((item) => item.queryOmitted)) {
|
|
133
|
+
limitations.push('URL query strings were omitted from retained provenance.');
|
|
134
|
+
}
|
|
135
|
+
const httpsDowngrade = redirects.some((item) => item.from.startsWith('https:') && item.to.startsWith('http:'));
|
|
136
|
+
const crossOriginRedirect = redirects.some((item) => new URL(item.from).origin !== new URL(item.to).origin);
|
|
137
|
+
const incomplete = bodyTruncated || redirectLimitReached;
|
|
138
|
+
const status = boundedHttpStatus(response && response.status);
|
|
139
|
+
const attempts = normalizeAttempts([
|
|
140
|
+
...(Array.isArray(options.previousAttempts) ? options.previousAttempts : []),
|
|
141
|
+
{ url: detail && detail.requestedUrl, httpStatus: status },
|
|
142
|
+
]);
|
|
143
|
+
return {
|
|
144
|
+
...createObservation({
|
|
145
|
+
status: incomplete ? 'partial' : 'success',
|
|
146
|
+
observedAt: options.observedAt,
|
|
147
|
+
scanMode: 'deep',
|
|
148
|
+
source: 'http',
|
|
149
|
+
durationMs: detail && detail.durationMs,
|
|
150
|
+
complete: !incomplete,
|
|
151
|
+
truncated: incomplete,
|
|
152
|
+
limitations,
|
|
153
|
+
diagnostics: {
|
|
154
|
+
redirectCount: redirects.length,
|
|
155
|
+
httpStatus: status,
|
|
156
|
+
},
|
|
157
|
+
}),
|
|
158
|
+
requestUrl: requested ? requested.url : null,
|
|
159
|
+
finalUrl: final ? final.url : null,
|
|
160
|
+
transportSecurity: final ? final.url.split(':', 1)[0] : null,
|
|
161
|
+
redirectCount: redirects.length,
|
|
162
|
+
redirectLimitReached,
|
|
163
|
+
redirects,
|
|
164
|
+
crossOriginRedirect,
|
|
165
|
+
httpsDowngrade,
|
|
166
|
+
attempts,
|
|
167
|
+
response: {
|
|
168
|
+
status,
|
|
169
|
+
contentType: headerValue(response && response.headers, 'content-type', 200),
|
|
170
|
+
contentLanguage: headerValue(response && response.headers, 'content-language', 120),
|
|
171
|
+
server: headerValue(response && response.headers, 'server', 200),
|
|
172
|
+
declaredContentLength: declaredContentLength(response && response.headers),
|
|
173
|
+
capturedBodyBytes,
|
|
174
|
+
bodyInspected,
|
|
175
|
+
bodyTruncated,
|
|
176
|
+
bodyHash: bodySha256 ? {
|
|
177
|
+
algorithm: 'sha256',
|
|
178
|
+
value: bodySha256,
|
|
179
|
+
scope: bodyTruncated ? 'captured-prefix' : 'complete-body',
|
|
180
|
+
bytes: capturedBodyBytes,
|
|
181
|
+
} : null,
|
|
182
|
+
securityHeaders: securityHeaders(response && response.headers),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function failedHttpObservation(attempts, options = {}) {
|
|
187
|
+
const normalizedAttempts = normalizeAttempts(attempts);
|
|
188
|
+
return {
|
|
189
|
+
...createObservation({
|
|
190
|
+
status: 'error',
|
|
191
|
+
observedAt: options.observedAt,
|
|
192
|
+
scanMode: 'deep',
|
|
193
|
+
source: 'http',
|
|
194
|
+
durationMs: options.durationMs,
|
|
195
|
+
complete: false,
|
|
196
|
+
limitations: ['HTTP activity could not be confirmed; network, DNS, TLS, timeout, and policy failures are not proof that no site exists.'],
|
|
197
|
+
diagnostics: { attemptCount: normalizedAttempts.length },
|
|
198
|
+
}),
|
|
199
|
+
requestUrl: normalizedAttempts[0]?.url || null,
|
|
200
|
+
finalUrl: null,
|
|
201
|
+
transportSecurity: null,
|
|
202
|
+
redirectCount: 0,
|
|
203
|
+
redirectLimitReached: false,
|
|
204
|
+
redirects: [],
|
|
205
|
+
crossOriginRedirect: false,
|
|
206
|
+
httpsDowngrade: false,
|
|
207
|
+
attempts: normalizedAttempts,
|
|
208
|
+
response: null,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function skippedHttpObservation() {
|
|
212
|
+
return {
|
|
213
|
+
...createObservation({
|
|
214
|
+
status: 'skipped',
|
|
215
|
+
scanMode: 'deep',
|
|
216
|
+
source: 'http',
|
|
217
|
+
complete: false,
|
|
218
|
+
limitations: ['Website probing is disabled by deployment policy.'],
|
|
219
|
+
}),
|
|
220
|
+
requestUrl: null,
|
|
221
|
+
finalUrl: null,
|
|
222
|
+
transportSecurity: null,
|
|
223
|
+
redirectCount: 0,
|
|
224
|
+
redirectLimitReached: false,
|
|
225
|
+
redirects: [],
|
|
226
|
+
crossOriginRedirect: false,
|
|
227
|
+
httpsDowngrade: false,
|
|
228
|
+
attempts: [],
|
|
229
|
+
response: null,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
export { MAX_HTTP_PROVENANCE_URL, MAX_HTTP_REDIRECTS, MAX_HTTP_ATTEMPTS, normalizeProvenanceUrl, buildHttpObservation, failedHttpObservation, skippedHttpObservation, };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Versioned policy for projecting the Unicode UTS #39 confusables data into
|
|
2
|
+
// WHOISleuth's intentionally small domain-label mapping. The full upstream
|
|
3
|
+
// table is never shipped to browsers or consulted at runtime.
|
|
4
|
+
export const UNICODE_CONFUSABLE_DATA_VERSION = '17.0.0';
|
|
5
|
+
export const UNICODE_CONFUSABLE_SOURCE_URL = 'https://www.unicode.org/Public/17.0.0/security/confusables.txt';
|
|
6
|
+
export const UNICODE_CONFUSABLE_SOURCE_SHA256 = '091c7f82fc39ef208faf8f94d29c244de99254675e09de163160c810d13ef22a';
|
|
7
|
+
export const UNICODE_CONFUSABLE_LICENSE = 'Unicode-3.0';
|
|
8
|
+
export const CONFUSABLE_PROJECTION_SCHEMA = 'whoisleuth.unicode-confusable-projection';
|
|
9
|
+
export const CONFUSABLE_PROJECTION_VERSION = 1;
|
|
10
|
+
export const MAX_CONFUSABLE_SOURCE_BYTES = 1_000_000;
|
|
11
|
+
export const MAX_CONFUSABLE_SOURCE_LINES = 12_000;
|
|
12
|
+
export const MAX_CONFUSABLE_SOURCE_LINE_LENGTH = 768;
|
|
13
|
+
export const MAX_CONFUSABLE_SOURCE_CODEPOINTS = 1;
|
|
14
|
+
export const MAX_CONFUSABLE_TARGET_CODEPOINTS = 18;
|
|
15
|
+
export const MAX_CONFUSABLE_ASCII_TARGETS = 26;
|
|
16
|
+
export const MAX_SKELETON_CONFUSABLES_PER_ASCII = 12;
|
|
17
|
+
export const MAX_GENERATION_CONFUSABLES_PER_ASCII = 8;
|
|
18
|
+
export const MAX_PROJECTED_CONFUSABLES = MAX_CONFUSABLE_ASCII_TARGETS * MAX_SKELETON_CONFUSABLES_PER_ASCII;
|
|
19
|
+
export const ACCEPTED_CONFUSABLE_SCRIPTS = Object.freeze([
|
|
20
|
+
'Latin',
|
|
21
|
+
'Cyrillic',
|
|
22
|
+
'Greek',
|
|
23
|
+
'Armenian',
|
|
24
|
+
'Coptic',
|
|
25
|
+
'Deseret',
|
|
26
|
+
'Lisu',
|
|
27
|
+
]);
|
|
28
|
+
// Existing reviewed mappings are priority seeds, not an independent data
|
|
29
|
+
// source. Keeping their order prevents a Unicode refresh from unexpectedly
|
|
30
|
+
// displacing already-calibrated characters when a per-letter cap is reached.
|
|
31
|
+
export const REVIEWED_SKELETON_CONFUSABLES = Object.freeze({
|
|
32
|
+
a: 'аαɑ',
|
|
33
|
+
b: 'ьƄ',
|
|
34
|
+
c: 'сϲⅽᴄⲥ𐐽',
|
|
35
|
+
d: 'ԁⅾꓒ',
|
|
36
|
+
e: 'еεҽꬲ',
|
|
37
|
+
f: 'ϝꞙƒ',
|
|
38
|
+
g: 'ɡ',
|
|
39
|
+
h: 'һհ',
|
|
40
|
+
i: 'іιıɪɩⲓꙇ',
|
|
41
|
+
j: 'јϳ',
|
|
42
|
+
k: 'κк',
|
|
43
|
+
l: 'ӏⅼǀꓲ',
|
|
44
|
+
m: 'мⅿ',
|
|
45
|
+
n: 'ոռ',
|
|
46
|
+
o: 'оοօᴏⲟ',
|
|
47
|
+
p: 'рρϱⲣ',
|
|
48
|
+
q: 'ԛգզ',
|
|
49
|
+
r: 'гꭇ',
|
|
50
|
+
s: 'ѕꜱƽ',
|
|
51
|
+
t: 'тτ',
|
|
52
|
+
u: 'υսᴜꭎ',
|
|
53
|
+
v: 'ѵνᴠ',
|
|
54
|
+
w: 'ԝաᴡш',
|
|
55
|
+
x: 'хχ',
|
|
56
|
+
y: 'уүʏγⲩ',
|
|
57
|
+
z: 'ᴢ',
|
|
58
|
+
});
|
|
59
|
+
export const REVIEWED_GENERATION_CONFUSABLES = Object.freeze({
|
|
60
|
+
a: 'аαɑ',
|
|
61
|
+
b: 'ь',
|
|
62
|
+
c: 'сᴄⲥ𐐽',
|
|
63
|
+
d: 'ԁꓒ',
|
|
64
|
+
e: 'еεҽꬲ',
|
|
65
|
+
f: 'ϝꞙƒ',
|
|
66
|
+
g: 'ɡ',
|
|
67
|
+
h: 'һհ',
|
|
68
|
+
i: 'іιıɪɩⲓꙇ',
|
|
69
|
+
j: 'јϳ',
|
|
70
|
+
k: 'κк',
|
|
71
|
+
l: 'ӏǀꓲ',
|
|
72
|
+
m: 'м',
|
|
73
|
+
n: 'ոռ',
|
|
74
|
+
o: 'оοօᴏⲟ',
|
|
75
|
+
p: 'рρⲣ',
|
|
76
|
+
q: 'ԛգզ',
|
|
77
|
+
r: 'гꭇ',
|
|
78
|
+
s: 'ѕꜱƽ',
|
|
79
|
+
t: 'тτ',
|
|
80
|
+
u: 'υսᴜꭎ',
|
|
81
|
+
v: 'ѵνᴠ',
|
|
82
|
+
w: 'ԝաᴡш',
|
|
83
|
+
x: 'хχ',
|
|
84
|
+
y: 'уүʏγⲩ',
|
|
85
|
+
z: 'ᴢ',
|
|
86
|
+
});
|