@veryfront/ext-content-mdx 0.1.1190 → 0.1.1192
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/esm/deno.d.ts +6 -0
- package/esm/deno.js +13 -7
- package/esm/src/errors/error-registry/module.d.ts +4 -0
- package/esm/src/errors/error-registry/module.d.ts.map +1 -1
- package/esm/src/errors/error-registry/module.js +16 -0
- package/esm/src/errors/error-registry.d.ts +2 -0
- package/esm/src/errors/error-registry.d.ts.map +1 -1
- package/esm/src/errors/types.d.ts.map +1 -1
- package/esm/src/errors/types.js +5 -2
- package/esm/src/platform/compat/error-introspection.d.ts +12 -1
- package/esm/src/platform/compat/error-introspection.d.ts.map +1 -1
- package/esm/src/platform/compat/error-introspection.js +40 -5
- package/esm/src/platform/compat/path/basic-operations.d.ts.map +1 -1
- package/esm/src/platform/compat/path/basic-operations.js +6 -2
- package/esm/src/platform/compat/path/portable.d.ts.map +1 -1
- package/esm/src/platform/compat/path/portable.js +98 -54
- package/esm/src/platform/compat/path/resolution.d.ts.map +1 -1
- package/esm/src/platform/compat/path/resolution.js +14 -5
- package/esm/src/platform/compat/primordials/array.d.ts +8 -0
- package/esm/src/platform/compat/primordials/array.d.ts.map +1 -0
- package/esm/src/platform/compat/primordials/array.js +33 -0
- package/esm/src/transforms/import-rewriter/url-builder.d.ts +6 -0
- package/esm/src/transforms/import-rewriter/url-builder.d.ts.map +1 -1
- package/esm/src/transforms/import-rewriter/url-builder.js +96 -29
- package/esm/src/utils/base64url.d.ts.map +1 -1
- package/esm/src/utils/base64url.js +10 -4
- package/esm/src/utils/bundle-manifest.d.ts +19 -1
- package/esm/src/utils/bundle-manifest.d.ts.map +1 -1
- package/esm/src/utils/bundle-manifest.js +124 -27
- package/esm/src/utils/constants/limits.d.ts +1 -1
- package/esm/src/utils/constants/limits.d.ts.map +1 -1
- package/esm/src/utils/constants/limits.js +1 -1
- package/esm/src/utils/hash-utils.d.ts.map +1 -1
- package/esm/src/utils/hash-utils.js +32 -6
- package/esm/src/utils/id.d.ts +16 -0
- package/esm/src/utils/id.d.ts.map +1 -0
- package/esm/src/utils/id.js +69 -0
- package/esm/src/utils/import-lockfile.d.ts +52 -1
- package/esm/src/utils/import-lockfile.d.ts.map +1 -1
- package/esm/src/utils/import-lockfile.js +1042 -58
- package/esm/src/utils/index.d.ts +1 -1
- package/esm/src/utils/index.d.ts.map +1 -1
- package/esm/src/utils/index.js +1 -1
- package/esm/src/utils/logger/logger.d.ts +1 -0
- package/esm/src/utils/logger/logger.d.ts.map +1 -1
- package/esm/src/utils/logger/logger.js +359 -78
- package/esm/src/utils/logger/redact.d.ts +4 -2
- package/esm/src/utils/logger/redact.d.ts.map +1 -1
- package/esm/src/utils/logger/redact.js +300 -112
- package/esm/src/utils/logger/serialization.d.ts +3 -0
- package/esm/src/utils/logger/serialization.d.ts.map +1 -0
- package/esm/src/utils/logger/serialization.js +81 -0
- package/esm/src/utils/memoize.d.ts +1 -1
- package/esm/src/utils/memoize.d.ts.map +1 -1
- package/esm/src/utils/memoize.js +16 -5
- package/esm/src/utils/path-utils.d.ts.map +1 -1
- package/esm/src/utils/path-utils.js +4 -2
- package/esm/src/utils/sleep.d.ts +8 -0
- package/esm/src/utils/sleep.d.ts.map +1 -1
- package/esm/src/utils/sleep.js +11 -2
- package/esm/src/utils/timer.d.ts +10 -0
- package/esm/src/utils/timer.d.ts.map +1 -0
- package/esm/src/utils/timer.js +19 -0
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +2 -2
|
@@ -19,17 +19,85 @@
|
|
|
19
19
|
/** Replacement value substituted for any sensitive field. */
|
|
20
20
|
export const REDACTED = "[REDACTED]";
|
|
21
21
|
const apply = Reflect.apply;
|
|
22
|
+
const arrayPop = Array.prototype.pop;
|
|
23
|
+
const arrayPush = Array.prototype.push;
|
|
22
24
|
const NativeUint32Array = Uint32Array;
|
|
25
|
+
const arrayIsArray = Array.isArray;
|
|
26
|
+
const arrayPrototype = Array.prototype;
|
|
27
|
+
const bigIntToString = BigInt.prototype.toString;
|
|
28
|
+
const NativeMap = Map;
|
|
29
|
+
const mapDelete = Map.prototype.delete;
|
|
30
|
+
const mapGet = Map.prototype.get;
|
|
31
|
+
const mapKeys = Map.prototype.keys;
|
|
32
|
+
const mapSet = Map.prototype.set;
|
|
33
|
+
const objectDefineProperty = Object.defineProperty;
|
|
34
|
+
const objectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
35
|
+
const objectGetPrototypeOf = Object.getPrototypeOf;
|
|
36
|
+
const objectHasOwn = Object.hasOwn;
|
|
37
|
+
const objectPrototype = Object.prototype;
|
|
38
|
+
const NativeSet = Set;
|
|
39
|
+
const nativeDecodeURIComponent = decodeURIComponent;
|
|
40
|
+
const NativeURL = URL;
|
|
41
|
+
const numberIsFinite = Number.isFinite;
|
|
42
|
+
const numberIsInteger = Number.isInteger;
|
|
23
43
|
const regExpExec = RegExp.prototype.exec;
|
|
24
|
-
const
|
|
44
|
+
const regExpGlobalGetter = objectGetOwnPropertyDescriptor(RegExp.prototype, "global").get;
|
|
45
|
+
const regExpUnicodeGetter = objectGetOwnPropertyDescriptor(RegExp.prototype, "unicode").get;
|
|
25
46
|
const stringCharCodeAt = String.prototype.charCodeAt;
|
|
47
|
+
const stringIncludes = String.prototype.includes;
|
|
48
|
+
const stringIndexOf = String.prototype.indexOf;
|
|
26
49
|
const stringSlice = String.prototype.slice;
|
|
50
|
+
const stringStartsWith = String.prototype.startsWith;
|
|
27
51
|
const stringToLowerCase = String.prototype.toLowerCase;
|
|
52
|
+
const setAdd = Set.prototype.add;
|
|
53
|
+
const setDelete = Set.prototype.delete;
|
|
54
|
+
const setHas = Set.prototype.has;
|
|
55
|
+
const mapIteratorNext = objectGetPrototypeOf(new NativeMap().keys()).next;
|
|
56
|
+
const mapSizeGetter = objectGetOwnPropertyDescriptor(Map.prototype, "size").get;
|
|
57
|
+
const urlHostGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "host").get;
|
|
58
|
+
const urlOriginGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "origin").get;
|
|
59
|
+
const urlPasswordGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "password").get;
|
|
60
|
+
const urlPathnameGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "pathname").get;
|
|
61
|
+
const urlProtocolGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "protocol").get;
|
|
62
|
+
const urlUsernameGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "username").get;
|
|
28
63
|
const NON_ALPHANUMERIC_PATTERN = /[^a-z0-9]/g;
|
|
64
|
+
const CAMEL_CASE_BOUNDARY_PATTERN = /([a-z0-9])([A-Z])/g;
|
|
65
|
+
const ACRONYM_BOUNDARY_PATTERN = /([A-Z])([A-Z][a-z])/g;
|
|
66
|
+
const PROVIDER_CREDENTIAL_PATTERN = /\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\b/g;
|
|
67
|
+
function replaceWithCapturedExec(input, pattern, replacement) {
|
|
68
|
+
const global = apply(regExpGlobalGetter, pattern, []);
|
|
69
|
+
const unicode = apply(regExpUnicodeGetter, pattern, []);
|
|
70
|
+
let cursor = 0;
|
|
71
|
+
let matched = false;
|
|
72
|
+
let result = "";
|
|
73
|
+
pattern.lastIndex = 0;
|
|
74
|
+
try {
|
|
75
|
+
while (true) {
|
|
76
|
+
const match = apply(regExpExec, pattern, [input]);
|
|
77
|
+
if (match === null)
|
|
78
|
+
break;
|
|
79
|
+
const text = match[0];
|
|
80
|
+
const start = match.index;
|
|
81
|
+
result += sliceString(input, cursor, start);
|
|
82
|
+
result += typeof replacement === "string" ? replacement : replacement(match);
|
|
83
|
+
cursor = start + text.length;
|
|
84
|
+
matched = true;
|
|
85
|
+
if (!global)
|
|
86
|
+
break;
|
|
87
|
+
if (text.length === 0) {
|
|
88
|
+
pattern.lastIndex = advanceStringIndex(input, start, unicode);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
pattern.lastIndex = 0;
|
|
94
|
+
}
|
|
95
|
+
return matched ? result + sliceString(input, cursor) : input;
|
|
96
|
+
}
|
|
29
97
|
/** Strip all non-alphanumeric characters and lowercase, used for key normalization. */
|
|
30
98
|
function normalizeToAlphanumeric(s) {
|
|
31
99
|
const lowercase = apply(stringToLowerCase, s, []);
|
|
32
|
-
return
|
|
100
|
+
return replaceWithCapturedExec(lowercase, NON_ALPHANUMERIC_PATTERN, "");
|
|
33
101
|
}
|
|
34
102
|
const FORWARD_SLASH_CODE_UNIT = 47;
|
|
35
103
|
const BACKSLASH_CODE_UNIT = 92;
|
|
@@ -40,6 +108,16 @@ const ASCII_LOWERCASE_OFFSET = 32;
|
|
|
40
108
|
function stringCodeUnitAt(value, index) {
|
|
41
109
|
return apply(stringCharCodeAt, value, [index]);
|
|
42
110
|
}
|
|
111
|
+
function advanceStringIndex(value, index, unicode) {
|
|
112
|
+
const next = index + 1;
|
|
113
|
+
if (!unicode || next >= value.length)
|
|
114
|
+
return next;
|
|
115
|
+
const first = stringCodeUnitAt(value, index);
|
|
116
|
+
if (first < 0xd800 || first > 0xdbff)
|
|
117
|
+
return next;
|
|
118
|
+
const second = stringCodeUnitAt(value, next);
|
|
119
|
+
return second >= 0xdc00 && second <= 0xdfff ? index + 2 : next;
|
|
120
|
+
}
|
|
43
121
|
function sliceString(value, start, end) {
|
|
44
122
|
return end === undefined
|
|
45
123
|
? apply(stringSlice, value, [start])
|
|
@@ -55,6 +133,22 @@ function isAsciiLetterCodeUnit(codeUnit) {
|
|
|
55
133
|
: codeUnit;
|
|
56
134
|
return lowercase >= 97 && lowercase <= 122;
|
|
57
135
|
}
|
|
136
|
+
function splitIdentifierTokens(value) {
|
|
137
|
+
const tokens = [];
|
|
138
|
+
let tokenStart = 0;
|
|
139
|
+
for (let index = 0; index <= value.length; index++) {
|
|
140
|
+
const codeUnit = index === value.length ? -1 : stringCodeUnitAt(value, index);
|
|
141
|
+
const isIdentifierCodeUnit = (codeUnit >= 97 && codeUnit <= 122) ||
|
|
142
|
+
(codeUnit >= 48 && codeUnit <= 57);
|
|
143
|
+
if (isIdentifierCodeUnit)
|
|
144
|
+
continue;
|
|
145
|
+
if (index > tokenStart) {
|
|
146
|
+
tokens[tokens.length] = sliceString(value, tokenStart, index);
|
|
147
|
+
}
|
|
148
|
+
tokenStart = index + 1;
|
|
149
|
+
}
|
|
150
|
+
return tokens;
|
|
151
|
+
}
|
|
58
152
|
function isWindowsPath(path) {
|
|
59
153
|
if (path.length >= 2) {
|
|
60
154
|
const first = stringCodeUnitAt(path, 0);
|
|
@@ -133,10 +227,9 @@ export function redactPathFromText(input, path, replacement) {
|
|
|
133
227
|
* a lowercased, non-alphanumeric-stripped form of the key, so `API-Key`,
|
|
134
228
|
* `api_key`, and `apiKey` all collapse to `apikey` and match.
|
|
135
229
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* - short tokens like `"dsn"`/`"sas"` (would mask `feedsNamespace`, etc.).
|
|
230
|
+
* Bare `"auth"` is matched separately as an exact normalized key so `author`
|
|
231
|
+
* remains visible. Short tokens like `"dsn"`/`"sas"` are deliberately omitted
|
|
232
|
+
* to avoid masking keys such as `feedsNamespace`.
|
|
140
233
|
*/
|
|
141
234
|
const SENSITIVE_KEY_PATTERNS = [
|
|
142
235
|
"password",
|
|
@@ -150,6 +243,7 @@ const SENSITIVE_KEY_PATTERNS = [
|
|
|
150
243
|
"accesskey",
|
|
151
244
|
"privatekey",
|
|
152
245
|
"credential",
|
|
246
|
+
"authheader",
|
|
153
247
|
"authorization",
|
|
154
248
|
"cookie",
|
|
155
249
|
"bearer",
|
|
@@ -168,13 +262,17 @@ const SENSITIVE_KEY_PATTERNS = [
|
|
|
168
262
|
const SENSITIVE_KEY_CACHE_MAX_SIZE = 512;
|
|
169
263
|
/** Avoid retaining attacker-controlled, oversized property names in the cache. */
|
|
170
264
|
const SENSITIVE_KEY_CACHE_MAX_KEY_LENGTH = 128;
|
|
171
|
-
const sensitiveKeyCache = new
|
|
265
|
+
const sensitiveKeyCache = new NativeMap();
|
|
172
266
|
/** Stop traversing past this depth to keep the pass cheap and stack-safe. */
|
|
173
267
|
const MAX_DEPTH = 16;
|
|
174
268
|
/** Bound every individual array/object before allocating a redacted copy. */
|
|
175
269
|
const MAX_CONTAINER_ENTRIES = 1_024;
|
|
176
270
|
/** Bound aggregate work across an entire redaction call, not per branch. */
|
|
177
271
|
const MAX_TRAVERSAL_NODES = 4_096;
|
|
272
|
+
/** Bound hostile or cyclic prototype walks while looking for serializers. */
|
|
273
|
+
const MAX_SERIALIZATION_HOOK_PROTOTYPES = 64;
|
|
274
|
+
/** Bound the quadratic identifier-token scan used for free-text assignments. */
|
|
275
|
+
const MAX_ASSIGNMENT_KEY_LENGTH = 256;
|
|
178
276
|
/**
|
|
179
277
|
* Whether a context key names a credential and should have its value masked.
|
|
180
278
|
*
|
|
@@ -186,19 +284,24 @@ const MAX_TRAVERSAL_NODES = 4_096;
|
|
|
186
284
|
export function isSensitiveKey(key) {
|
|
187
285
|
const cacheable = key.length <= SENSITIVE_KEY_CACHE_MAX_KEY_LENGTH;
|
|
188
286
|
if (cacheable) {
|
|
189
|
-
const cached = sensitiveKeyCache
|
|
287
|
+
const cached = apply(mapGet, sensitiveKeyCache, [key]);
|
|
190
288
|
if (cached !== undefined)
|
|
191
289
|
return cached;
|
|
192
290
|
}
|
|
193
291
|
const normalized = normalizeToAlphanumeric(key);
|
|
194
|
-
|
|
292
|
+
let sensitive = normalized === "auth";
|
|
293
|
+
for (let index = 0; !sensitive && index < SENSITIVE_KEY_PATTERNS.length; index++) {
|
|
294
|
+
sensitive = apply(stringIncludes, normalized, [SENSITIVE_KEY_PATTERNS[index]]);
|
|
295
|
+
}
|
|
195
296
|
if (cacheable) {
|
|
196
|
-
|
|
197
|
-
|
|
297
|
+
const cacheSize = apply(mapSizeGetter, sensitiveKeyCache, []);
|
|
298
|
+
if (cacheSize >= SENSITIVE_KEY_CACHE_MAX_SIZE) {
|
|
299
|
+
const iterator = apply(mapKeys, sensitiveKeyCache, []);
|
|
300
|
+
const oldestKey = apply(mapIteratorNext, iterator, []).value;
|
|
198
301
|
if (oldestKey !== undefined)
|
|
199
|
-
sensitiveKeyCache
|
|
302
|
+
apply(mapDelete, sensitiveKeyCache, [oldestKey]);
|
|
200
303
|
}
|
|
201
|
-
sensitiveKeyCache
|
|
304
|
+
apply(mapSet, sensitiveKeyCache, [key, sensitive]);
|
|
202
305
|
}
|
|
203
306
|
return sensitive;
|
|
204
307
|
}
|
|
@@ -209,12 +312,48 @@ export function isSensitiveKey(key) {
|
|
|
209
312
|
*/
|
|
210
313
|
function classifyArray(value) {
|
|
211
314
|
try {
|
|
212
|
-
return
|
|
315
|
+
return arrayIsArray(value);
|
|
213
316
|
}
|
|
214
317
|
catch {
|
|
215
318
|
return null;
|
|
216
319
|
}
|
|
217
320
|
}
|
|
321
|
+
function hasSeenSerializationHookOwner(seenOwners, owner) {
|
|
322
|
+
for (let index = 0; index < seenOwners.length; index++) {
|
|
323
|
+
if (seenOwners[index] === owner)
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Read a deliberate serialization hook without consulting hooks installed on
|
|
330
|
+
* the intrinsic Object or Array prototypes. Custom and platform prototypes
|
|
331
|
+
* such as Date and URL remain supported through data-property methods.
|
|
332
|
+
*/
|
|
333
|
+
function readSerializationHook(value) {
|
|
334
|
+
let owner = value;
|
|
335
|
+
const seenOwners = [];
|
|
336
|
+
let prototypesVisited = 0;
|
|
337
|
+
while (owner !== null) {
|
|
338
|
+
if (owner === objectPrototype || owner === arrayPrototype)
|
|
339
|
+
return undefined;
|
|
340
|
+
if (prototypesVisited >= MAX_SERIALIZATION_HOOK_PROTOTYPES ||
|
|
341
|
+
hasSeenSerializationHookOwner(seenOwners, owner)) {
|
|
342
|
+
throw new TypeError("serialization hook prototype chain is cyclic or too deep");
|
|
343
|
+
}
|
|
344
|
+
prototypesVisited++;
|
|
345
|
+
apply(arrayPush, seenOwners, [owner]);
|
|
346
|
+
const descriptor = objectGetOwnPropertyDescriptor(owner, "toJSON");
|
|
347
|
+
if (descriptor !== undefined) {
|
|
348
|
+
if (!objectHasOwn(descriptor, "value")) {
|
|
349
|
+
throw new TypeError("serialization hooks must be data properties");
|
|
350
|
+
}
|
|
351
|
+
return descriptor.value;
|
|
352
|
+
}
|
|
353
|
+
owner = objectGetPrototypeOf(owner);
|
|
354
|
+
}
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
218
357
|
function redactValue(value, depth, seen, mode, budget) {
|
|
219
358
|
if (budget.remainingNodes <= 0) {
|
|
220
359
|
budget.exhausted = true;
|
|
@@ -223,10 +362,11 @@ function redactValue(value, depth, seen, mode, budget) {
|
|
|
223
362
|
budget.remainingNodes--;
|
|
224
363
|
if (typeof value === "string")
|
|
225
364
|
return sanitizeUrlCredentials(value);
|
|
226
|
-
if (typeof value === "bigint")
|
|
227
|
-
return mode === "serialization" ? value
|
|
365
|
+
if (typeof value === "bigint") {
|
|
366
|
+
return mode === "serialization" ? apply(bigIntToString, value, []) : value;
|
|
367
|
+
}
|
|
228
368
|
if (typeof value === "number") {
|
|
229
|
-
return mode === "serialization" && !
|
|
369
|
+
return mode === "serialization" && !numberIsFinite(value) ? null : value;
|
|
230
370
|
}
|
|
231
371
|
if (typeof value === "boolean" || value === null)
|
|
232
372
|
return value;
|
|
@@ -237,13 +377,13 @@ function redactValue(value, depth, seen, mode, budget) {
|
|
|
237
377
|
if (arrayClassification === null)
|
|
238
378
|
return REDACTED;
|
|
239
379
|
if (arrayClassification) {
|
|
240
|
-
if (depth >= MAX_DEPTH || seen
|
|
380
|
+
if (depth >= MAX_DEPTH || apply(setHas, seen, [value]))
|
|
241
381
|
return REDACTED;
|
|
242
|
-
seen
|
|
382
|
+
apply(setAdd, seen, [value]);
|
|
243
383
|
try {
|
|
244
384
|
const arrayValue = value;
|
|
245
385
|
const length = arrayValue.length;
|
|
246
|
-
if (!
|
|
386
|
+
if (!numberIsInteger(length) || length < 0 || length > MAX_CONTAINER_ENTRIES) {
|
|
247
387
|
return REDACTED;
|
|
248
388
|
}
|
|
249
389
|
const redacted = mode === "compatible" ? new Array(length) : [];
|
|
@@ -257,7 +397,7 @@ function redactValue(value, depth, seen, mode, budget) {
|
|
|
257
397
|
redacted[index] = item;
|
|
258
398
|
}
|
|
259
399
|
else {
|
|
260
|
-
redacted
|
|
400
|
+
apply(arrayPush, redacted, [item]);
|
|
261
401
|
}
|
|
262
402
|
}
|
|
263
403
|
return redacted;
|
|
@@ -268,7 +408,7 @@ function redactValue(value, depth, seen, mode, budget) {
|
|
|
268
408
|
return REDACTED;
|
|
269
409
|
}
|
|
270
410
|
finally {
|
|
271
|
-
seen
|
|
411
|
+
apply(setDelete, seen, [value]);
|
|
272
412
|
}
|
|
273
413
|
}
|
|
274
414
|
// Objects defining `toJSON` (Date, URL, custom serializers) are serialized
|
|
@@ -279,11 +419,11 @@ function redactValue(value, depth, seen, mode, budget) {
|
|
|
279
419
|
// returns an object, array, or scalar, the serialization API redacts *that*
|
|
280
420
|
// snapshot. The compatibility API keeps scalar serializers such as Date and
|
|
281
421
|
// URL intact, preserving the established generic return contract.
|
|
282
|
-
if (depth >= MAX_DEPTH || seen
|
|
422
|
+
if (depth >= MAX_DEPTH || apply(setHas, seen, [value]))
|
|
283
423
|
return REDACTED;
|
|
284
424
|
let toJSON;
|
|
285
425
|
try {
|
|
286
|
-
toJSON = value
|
|
426
|
+
toJSON = readSerializationHook(value);
|
|
287
427
|
}
|
|
288
428
|
catch {
|
|
289
429
|
// Accessors can throw before a serializer is callable. Never inspect the
|
|
@@ -291,7 +431,7 @@ function redactValue(value, depth, seen, mode, budget) {
|
|
|
291
431
|
return REDACTED;
|
|
292
432
|
}
|
|
293
433
|
if (typeof toJSON === "function") {
|
|
294
|
-
seen
|
|
434
|
+
apply(setAdd, seen, [value]);
|
|
295
435
|
try {
|
|
296
436
|
const serialized = apply(toJSON, value, []);
|
|
297
437
|
if (mode === "serialization") {
|
|
@@ -315,22 +455,22 @@ function redactValue(value, depth, seen, mode, budget) {
|
|
|
315
455
|
return REDACTED;
|
|
316
456
|
}
|
|
317
457
|
finally {
|
|
318
|
-
seen
|
|
458
|
+
apply(setDelete, seen, [value]);
|
|
319
459
|
}
|
|
320
460
|
}
|
|
321
|
-
seen
|
|
461
|
+
apply(setAdd, seen, [value]);
|
|
322
462
|
try {
|
|
323
463
|
const out = {};
|
|
324
464
|
const record = value;
|
|
325
465
|
let propertyCount = 0;
|
|
326
466
|
for (const key in record) {
|
|
327
|
-
if (!
|
|
467
|
+
if (!objectHasOwn(record, key))
|
|
328
468
|
continue;
|
|
329
469
|
propertyCount++;
|
|
330
470
|
if (propertyCount > MAX_CONTAINER_ENTRIES)
|
|
331
471
|
return REDACTED;
|
|
332
472
|
if (isSensitiveKey(key)) {
|
|
333
|
-
|
|
473
|
+
objectDefineProperty(out, key, {
|
|
334
474
|
configurable: true,
|
|
335
475
|
enumerable: true,
|
|
336
476
|
value: REDACTED,
|
|
@@ -346,7 +486,7 @@ function redactValue(value, depth, seen, mode, budget) {
|
|
|
346
486
|
const redactedChild = redactValue(child, depth + 1, seen, mode, budget);
|
|
347
487
|
if (budget.exhausted)
|
|
348
488
|
return REDACTED;
|
|
349
|
-
|
|
489
|
+
objectDefineProperty(out, key, {
|
|
350
490
|
configurable: true,
|
|
351
491
|
enumerable: true,
|
|
352
492
|
value: redactedChild,
|
|
@@ -361,7 +501,7 @@ function redactValue(value, depth, seen, mode, budget) {
|
|
|
361
501
|
return REDACTED;
|
|
362
502
|
}
|
|
363
503
|
finally {
|
|
364
|
-
seen
|
|
504
|
+
apply(setDelete, seen, [value]);
|
|
365
505
|
}
|
|
366
506
|
}
|
|
367
507
|
/**
|
|
@@ -375,7 +515,7 @@ function redactValue(value, depth, seen, mode, budget) {
|
|
|
375
515
|
* functions, symbols, and custom `toJSON` implementations must be normalized.
|
|
376
516
|
*/
|
|
377
517
|
export function redactSensitive(context) {
|
|
378
|
-
return redactValue(context, 0, new
|
|
518
|
+
return redactValue(context, 0, new NativeSet(), "compatible", {
|
|
379
519
|
remainingNodes: MAX_TRAVERSAL_NODES,
|
|
380
520
|
exhausted: false,
|
|
381
521
|
});
|
|
@@ -387,7 +527,7 @@ export function redactSensitive(context) {
|
|
|
387
527
|
* closed. Objects with `toJSON` are snapshotted exactly once before redaction.
|
|
388
528
|
*/
|
|
389
529
|
export function redactForSerialization(context) {
|
|
390
|
-
return redactValue(context, 0, new
|
|
530
|
+
return redactValue(context, 0, new NativeSet(), "serialization", {
|
|
391
531
|
remainingNodes: MAX_TRAVERSAL_NODES,
|
|
392
532
|
exhausted: false,
|
|
393
533
|
});
|
|
@@ -419,7 +559,12 @@ const SENSITIVE_URL_PARAMS = [
|
|
|
419
559
|
"x-goog-credential",
|
|
420
560
|
"x-goog-signature",
|
|
421
561
|
];
|
|
422
|
-
const NORMALIZED_SENSITIVE_URL_PARAMS = new
|
|
562
|
+
const NORMALIZED_SENSITIVE_URL_PARAMS = new NativeSet();
|
|
563
|
+
for (let index = 0; index < SENSITIVE_URL_PARAMS.length; index++) {
|
|
564
|
+
apply(setAdd, NORMALIZED_SENSITIVE_URL_PARAMS, [
|
|
565
|
+
normalizeToAlphanumeric(SENSITIVE_URL_PARAMS[index]),
|
|
566
|
+
]);
|
|
567
|
+
}
|
|
423
568
|
const URL_USERINFO_RE = /(\b[a-z][a-z0-9+.-]*:\/\/|\/\/)([^/?#\s]+)@/gi;
|
|
424
569
|
const HORIZONTAL_WHITESPACE_URL_USERINFO_RE = /(\b[a-z][a-z0-9+.-]*:\/\/|\/\/)([a-z0-9._~!$&'()*+,;=%-]+):([^/?#@\r\n \t]+[ \t][^/?#@\r\n]*)@/gi;
|
|
425
570
|
const MAX_URL_PARAMETER_DECODE_PASSES = 3;
|
|
@@ -435,7 +580,7 @@ function isHorizontalAssignmentBoundary(character) {
|
|
|
435
580
|
function isAsciiLetter(character) {
|
|
436
581
|
if (!character)
|
|
437
582
|
return false;
|
|
438
|
-
const code = character
|
|
583
|
+
const code = stringCodeUnitAt(character, 0);
|
|
439
584
|
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
|
440
585
|
}
|
|
441
586
|
function isAssignmentKeyStartCharacter(character) {
|
|
@@ -444,7 +589,7 @@ function isAssignmentKeyStartCharacter(character) {
|
|
|
444
589
|
function isAssignmentKeyCharacter(character) {
|
|
445
590
|
if (!character)
|
|
446
591
|
return false;
|
|
447
|
-
const code = character
|
|
592
|
+
const code = stringCodeUnitAt(character, 0);
|
|
448
593
|
return (isAssignmentKeyStartCharacter(character) ||
|
|
449
594
|
(code >= 48 && code <= 57) ||
|
|
450
595
|
character === "." ||
|
|
@@ -495,7 +640,7 @@ function assignmentValueEndsAt(input, start) {
|
|
|
495
640
|
function redactAssignmentValue(input, start) {
|
|
496
641
|
let scanStart = start;
|
|
497
642
|
let preserveValueQuote = true;
|
|
498
|
-
if (input
|
|
643
|
+
if (apply(stringStartsWith, input, [REDACTED, start])) {
|
|
499
644
|
const markerEnd = start + REDACTED.length;
|
|
500
645
|
if (assignmentValueEndsAt(input, markerEnd)) {
|
|
501
646
|
return {
|
|
@@ -525,6 +670,7 @@ function redactAssignmentValue(input, start) {
|
|
|
525
670
|
if (character === quote) {
|
|
526
671
|
if (quoteStart === scanStart && expectedClosings.length === 0) {
|
|
527
672
|
wrapperQuoteClosed = true;
|
|
673
|
+
return { end: index + 1, replacement: replacement() };
|
|
528
674
|
}
|
|
529
675
|
quote = "";
|
|
530
676
|
quoteStart = -1;
|
|
@@ -539,16 +685,16 @@ function redactAssignmentValue(input, start) {
|
|
|
539
685
|
continue;
|
|
540
686
|
}
|
|
541
687
|
if (character === "{" || character === "[") {
|
|
542
|
-
expectedClosings
|
|
688
|
+
apply(arrayPush, expectedClosings, [character === "{" ? "}" : "]"]);
|
|
543
689
|
index++;
|
|
544
690
|
continue;
|
|
545
691
|
}
|
|
546
692
|
if (expectedClosings.length > 0 &&
|
|
547
693
|
(character === "}" || character === "]")) {
|
|
548
|
-
if (expectedClosings.
|
|
694
|
+
if (expectedClosings[expectedClosings.length - 1] !== character) {
|
|
549
695
|
return { end: input.length, replacement: replacement() };
|
|
550
696
|
}
|
|
551
|
-
expectedClosings
|
|
697
|
+
apply(arrayPop, expectedClosings, []);
|
|
552
698
|
index++;
|
|
553
699
|
if (expectedClosings.length === 0 &&
|
|
554
700
|
assignmentValueEndsAt(input, index)) {
|
|
@@ -574,7 +720,7 @@ function redactCredentialAssignments(input, prefixPattern, keyGroup, urlParamete
|
|
|
574
720
|
let result = "";
|
|
575
721
|
for (let match = apply(regExpExec, prefixPattern, [input]); match; match = apply(regExpExec, prefixPattern, [input])) {
|
|
576
722
|
const key = match[keyGroup];
|
|
577
|
-
if (!
|
|
723
|
+
if (!isSensitiveAssignmentKey(key))
|
|
578
724
|
continue;
|
|
579
725
|
const valueStart = prefixPattern.lastIndex;
|
|
580
726
|
const boundary = urlParameterBoundaryGroup === undefined
|
|
@@ -582,7 +728,7 @@ function redactCredentialAssignments(input, prefixPattern, keyGroup, urlParamete
|
|
|
582
728
|
: match[urlParameterBoundaryGroup];
|
|
583
729
|
const markerEnd = valueStart + REDACTED.length;
|
|
584
730
|
if ((boundary === "?" || boundary === "&" || boundary === ";") &&
|
|
585
|
-
input
|
|
731
|
+
apply(stringStartsWith, input, [REDACTED, valueStart]) &&
|
|
586
732
|
input[markerEnd] === "#") {
|
|
587
733
|
// The URL-parameter pass already bounded this credential at the URI
|
|
588
734
|
// fragment delimiter. Preserve that delimiter without treating an
|
|
@@ -598,15 +744,56 @@ function redactCredentialAssignments(input, prefixPattern, keyGroup, urlParamete
|
|
|
598
744
|
}
|
|
599
745
|
return cursor === 0 ? input : result + sliceString(input, cursor);
|
|
600
746
|
}
|
|
747
|
+
/**
|
|
748
|
+
* Classify free-text assignment keys without applying the structured-key
|
|
749
|
+
* substring policy to ordinary words. Identifier and camel-case boundaries
|
|
750
|
+
* still recognize `refreshToken`, `client_secret`, and `x-api-key`, while
|
|
751
|
+
* words such as `mapping` and `considered` stay intact.
|
|
752
|
+
*/
|
|
753
|
+
function isSensitiveAssignmentKey(key) {
|
|
754
|
+
// The token-range classifier below is quadratic in the number of identifier
|
|
755
|
+
// tokens. Oversized attacker-controlled keys fail closed before that work.
|
|
756
|
+
if (key.length > MAX_ASSIGNMENT_KEY_LENGTH)
|
|
757
|
+
return true;
|
|
758
|
+
const withAcronymBoundaries = replaceWithCapturedExec(key, ACRONYM_BOUNDARY_PATTERN, (match) => `${match[1]} ${match[2]}`);
|
|
759
|
+
const withBoundaries = replaceWithCapturedExec(withAcronymBoundaries, CAMEL_CASE_BOUNDARY_PATTERN, (match) => `${match[1]} ${match[2]}`);
|
|
760
|
+
const lowercase = apply(stringToLowerCase, withBoundaries, []);
|
|
761
|
+
const tokens = splitIdentifierTokens(lowercase);
|
|
762
|
+
for (let start = 0; start < tokens.length; start++) {
|
|
763
|
+
if (tokens[start].length === 0)
|
|
764
|
+
continue;
|
|
765
|
+
let candidate = "";
|
|
766
|
+
for (let end = start; end < tokens.length; end++) {
|
|
767
|
+
const token = tokens[end];
|
|
768
|
+
if (token.length === 0)
|
|
769
|
+
continue;
|
|
770
|
+
candidate += token;
|
|
771
|
+
for (let index = 0; index < SENSITIVE_KEY_PATTERNS.length; index++) {
|
|
772
|
+
if (candidate === SENSITIVE_KEY_PATTERNS[index])
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return tokens.length === 1 && tokens[0] === "auth";
|
|
778
|
+
}
|
|
601
779
|
function isStandaloneUrlAuthorityBeforeWhitespace(scheme, user, password) {
|
|
602
|
-
|
|
780
|
+
let whitespaceIndex = -1;
|
|
781
|
+
for (let index = 0; index < password.length; index++) {
|
|
782
|
+
const codeUnit = stringCodeUnitAt(password, index);
|
|
783
|
+
if (codeUnit === 0x20 || codeUnit === 0x09) {
|
|
784
|
+
whitespaceIndex = index;
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
603
788
|
if (whitespaceIndex < 0)
|
|
604
789
|
return false;
|
|
605
790
|
const authority = `${user}:${sliceString(password, 0, whitespaceIndex)}`;
|
|
606
791
|
const candidate = scheme === "//" ? `https://${authority}` : `${scheme}${authority}`;
|
|
607
792
|
try {
|
|
608
|
-
const url = new
|
|
609
|
-
|
|
793
|
+
const url = new NativeURL(candidate);
|
|
794
|
+
const username = apply(urlUsernameGetter, url, []);
|
|
795
|
+
const password = apply(urlPasswordGetter, url, []);
|
|
796
|
+
return username.length === 0 && password.length === 0;
|
|
610
797
|
}
|
|
611
798
|
catch {
|
|
612
799
|
return false;
|
|
@@ -617,7 +804,7 @@ function decodeUrlParameterName(value) {
|
|
|
617
804
|
for (let pass = 0; pass < MAX_URL_PARAMETER_DECODE_PASSES; pass++) {
|
|
618
805
|
let next;
|
|
619
806
|
try {
|
|
620
|
-
next =
|
|
807
|
+
next = nativeDecodeURIComponent(decoded);
|
|
621
808
|
}
|
|
622
809
|
catch {
|
|
623
810
|
break;
|
|
@@ -634,8 +821,10 @@ function decodeUrlParameterName(value) {
|
|
|
634
821
|
* {@link redactSensitive}, which is key-based, this scrubs secrets embedded in
|
|
635
822
|
* the *value* itself:
|
|
636
823
|
*
|
|
637
|
-
* - URL userinfo: `
|
|
638
|
-
* - sensitive query params: `?access_token=abc`
|
|
824
|
+
* - URL userinfo: `https://user:password@example.test/path` -> `https://user:[REDACTED]@example.test/path`
|
|
825
|
+
* - sensitive query params: `?access_token=abc` -> `?access_token=[REDACTED]`
|
|
826
|
+
* - credential assignments: `refreshToken=abc` -> `refreshToken=[REDACTED]`
|
|
827
|
+
* - common provider tokens: `Using token sk-...` -> `Using token [REDACTED]`
|
|
639
828
|
*
|
|
640
829
|
* It is intentionally tolerant: it operates on any string (a DSN, a Mongo URI,
|
|
641
830
|
* an axios error message containing a URL) via regex rather than requiring a
|
|
@@ -646,68 +835,63 @@ export function sanitizeUrlCredentials(input) {
|
|
|
646
835
|
if (typeof input !== "string" || input.length === 0)
|
|
647
836
|
return input;
|
|
648
837
|
// 1) userinfo: scheme://user:pass@ → mask the password (and any bare creds).
|
|
649
|
-
let out =
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
]);
|
|
838
|
+
let out = replaceWithCapturedExec(input, URL_USERINFO_RE, (match) => {
|
|
839
|
+
const scheme = match[1];
|
|
840
|
+
const userinfo = match[2];
|
|
841
|
+
const colon = apply(stringIndexOf, userinfo, [":"]);
|
|
842
|
+
if (colon === -1) {
|
|
843
|
+
// `scheme://token@host` — the whole userinfo is credential-like.
|
|
844
|
+
return `${scheme}${REDACTED}@`;
|
|
845
|
+
}
|
|
846
|
+
const user = sliceString(userinfo, 0, colon);
|
|
847
|
+
return `${scheme}${user}:${REDACTED}@`;
|
|
848
|
+
});
|
|
849
|
+
out = replaceWithCapturedExec(out, HORIZONTAL_WHITESPACE_URL_USERINFO_RE, (match) => {
|
|
850
|
+
const scheme = match[1];
|
|
851
|
+
const user = match[2];
|
|
852
|
+
const password = match[3];
|
|
853
|
+
// Do not reinterpret a complete URL followed later by an email address
|
|
854
|
+
// on the same line as malformed userinfo. Raw-horizontal-whitespace
|
|
855
|
+
// recovery is limited to explicit `user:password` shapes whose prefix
|
|
856
|
+
// cannot already be parsed as a standalone authority.
|
|
857
|
+
if (isStandaloneUrlAuthorityBeforeWhitespace(scheme, user, password)) {
|
|
858
|
+
return match[0];
|
|
859
|
+
}
|
|
860
|
+
return `${scheme}${user}:${REDACTED}@`;
|
|
861
|
+
});
|
|
674
862
|
// 2) sensitive query/fragment params: `key=value` → `key=[REDACTED]`.
|
|
675
863
|
// Match `?key=`, `#key=`, `&key=`, and `;key=` separators and stop at the
|
|
676
864
|
// next delimiter. OAuth implicit-flow tokens commonly appear after `#`.
|
|
677
|
-
out =
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
865
|
+
out = replaceWithCapturedExec(out, /([?#&;])([-a-z0-9_.%\[\]]+)=([^&#;\s]*)/gi, (match) => {
|
|
866
|
+
const sep = match[1];
|
|
867
|
+
const key = match[2];
|
|
868
|
+
const decodedKey = decodeUrlParameterName(key);
|
|
869
|
+
const sensitive = apply(setHas, NORMALIZED_SENSITIVE_URL_PARAMS, [
|
|
870
|
+
normalizeToAlphanumeric(decodedKey),
|
|
871
|
+
]) ||
|
|
872
|
+
isSensitiveKey(decodedKey);
|
|
873
|
+
return sensitive ? `${sep}${key}=${REDACTED}` : match[0];
|
|
874
|
+
});
|
|
686
875
|
// 3) Cookie header values.
|
|
687
876
|
// Cookie headers can carry multiple independent credentials separated by
|
|
688
877
|
// semicolons (and Set-Cookie attributes can contain commas). Mask the entire
|
|
689
878
|
// header line before the generic assignment scanner can stop at the first
|
|
690
879
|
// delimiter and expose later values.
|
|
691
|
-
out =
|
|
692
|
-
out,
|
|
693
|
-
(_match, boundary, prefix) => `${boundary}${prefix}${REDACTED}`,
|
|
694
|
-
]);
|
|
880
|
+
out = replaceWithCapturedExec(out, /(^|[^a-z0-9_-])((?:set-cookie|cookie)\s*:\s*)[^\r\n]*/gi, (match) => `${match[1]}${match[2]}${REDACTED}`);
|
|
695
881
|
// 4) Header-shaped authorization values and standalone auth schemes.
|
|
696
882
|
// Authorization schemes are extensible (AWS SigV4, Digest, custom proxy
|
|
697
883
|
// schemes, and others), so mask the complete line instead of trying to
|
|
698
884
|
// enumerate schemes or parse their credential-bearing parameters.
|
|
699
|
-
out =
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
out =
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
// deny-list used for structured context. This keeps JSON snippets, header
|
|
710
|
-
// dumps, and ordinary `key=value` text from drifting to a weaker policy.
|
|
885
|
+
out = replaceWithCapturedExec(out, /\b(authorization\s*[:=]\s*)[^\r\n]*/gi, (match) => `${match[1]}${REDACTED}`);
|
|
886
|
+
out = replaceWithCapturedExec(out, /\b(bearer|basic)(\s+)(?:"[^"\r\n]*"|'[^'\r\n]*'|[a-z0-9._~+/=-]+)/gi, (match) => `${match[1]}${match[2]}${REDACTED}`);
|
|
887
|
+
// 5) Common provider token shapes can appear as bare values without an
|
|
888
|
+
// assignment delimiter, for example `Using token sk-...`.
|
|
889
|
+
out = replaceWithCapturedExec(out, PROVIDER_CREDENTIAL_PATTERN, REDACTED);
|
|
890
|
+
// 6) Credential assignments embedded in free-form messages/errors. Match
|
|
891
|
+
// generic identifier-shaped keys and apply the same credential vocabulary
|
|
892
|
+
// at identifier boundaries. This keeps JSON snippets, header dumps, and
|
|
893
|
+
// ordinary `key=value` text from drifting to a weaker policy without
|
|
894
|
+
// masking benign words that merely contain a short pattern.
|
|
711
895
|
// Handle quoted JSON/object keys first and preserve their quoting so the
|
|
712
896
|
// sanitized text remains intelligible and structurally valid.
|
|
713
897
|
out = redactCredentialAssignments(out, /(["'])([_$a-z][a-z0-9_.$-]*)\1(\s*[:=]\s*)/gi, 2);
|
|
@@ -715,20 +899,20 @@ export function sanitizeUrlCredentials(input) {
|
|
|
715
899
|
return out;
|
|
716
900
|
}
|
|
717
901
|
function firstUrlDelimiterIndex(input) {
|
|
718
|
-
const queryIndex = input
|
|
719
|
-
const hashIndex = input
|
|
902
|
+
const queryIndex = apply(stringIndexOf, input, ["?"]);
|
|
903
|
+
const hashIndex = apply(stringIndexOf, input, ["#"]);
|
|
720
904
|
if (queryIndex === -1)
|
|
721
905
|
return hashIndex;
|
|
722
906
|
if (hashIndex === -1)
|
|
723
907
|
return queryIndex;
|
|
724
|
-
return
|
|
908
|
+
return queryIndex < hashIndex ? queryIndex : hashIndex;
|
|
725
909
|
}
|
|
726
910
|
function sanitizeProtocolRelativeUrlForSpan(input) {
|
|
727
|
-
if (!input
|
|
911
|
+
if (!apply(stringStartsWith, input, ["//"]))
|
|
728
912
|
return null;
|
|
729
913
|
try {
|
|
730
|
-
const url = new
|
|
731
|
-
return `//${url
|
|
914
|
+
const url = new NativeURL(`https:${input}`);
|
|
915
|
+
return `//${apply(urlHostGetter, url, [])}${apply(urlPathnameGetter, url, [])}`;
|
|
732
916
|
}
|
|
733
917
|
catch (_) {
|
|
734
918
|
return null;
|
|
@@ -747,20 +931,24 @@ export function sanitizeUrlForSpan(input) {
|
|
|
747
931
|
if (typeof input !== "string" || input.length === 0)
|
|
748
932
|
return input;
|
|
749
933
|
try {
|
|
750
|
-
const url = new
|
|
751
|
-
|
|
934
|
+
const url = new NativeURL(input);
|
|
935
|
+
const protocol = apply(urlProtocolGetter, url, []);
|
|
936
|
+
const pathname = apply(urlPathnameGetter, url, []);
|
|
937
|
+
const origin = apply(urlOriginGetter, url, []);
|
|
938
|
+
if (protocol === "blob:") {
|
|
752
939
|
try {
|
|
753
|
-
const embeddedUrl = new
|
|
754
|
-
|
|
940
|
+
const embeddedUrl = new NativeURL(pathname);
|
|
941
|
+
const embeddedOrigin = apply(urlOriginGetter, embeddedUrl, []);
|
|
942
|
+
return embeddedOrigin === "null" ? "blob:" : `blob:${embeddedOrigin}`;
|
|
755
943
|
}
|
|
756
944
|
catch (_) {
|
|
757
945
|
return "blob:";
|
|
758
946
|
}
|
|
759
947
|
}
|
|
760
|
-
if (
|
|
761
|
-
return `${
|
|
762
|
-
if (/^[a-z][a-z0-9+.-]*:/i
|
|
763
|
-
return
|
|
948
|
+
if (origin !== "null")
|
|
949
|
+
return `${origin}${pathname}`;
|
|
950
|
+
if (apply(regExpExec, /^[a-z][a-z0-9+.-]*:/i, [input]) !== null)
|
|
951
|
+
return protocol;
|
|
764
952
|
}
|
|
765
953
|
catch (_) {
|
|
766
954
|
// Relative or malformed URL-shaped strings are handled by the fallback.
|