@patchstack/connect 0.5.12 → 0.5.15
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/AGENT-INSTALL.md +24 -22
- package/README.md +19 -19
- package/dist/cli.js +637 -484
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +16 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +16 -9
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +748 -168
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.cts +25 -1
- package/dist/protect.d.ts +25 -1
- package/dist/protect.edge.js +732 -159
- package/dist/protect.edge.js.map +2 -2
- package/dist/protect.js +733 -160
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-47HJRCDX.js → refresh-manifest-DZQOEO3Q.js} +17 -10
- package/dist/refresh-manifest-DZQOEO3Q.js.map +1 -0
- package/package.json +1 -1
- package/dist/refresh-manifest-47HJRCDX.js.map +0 -1
package/dist/protect.edge.js
CHANGED
|
@@ -1235,6 +1235,184 @@ function hasAdjacentUnbounded(body, flags) {
|
|
|
1235
1235
|
}
|
|
1236
1236
|
return false;
|
|
1237
1237
|
}
|
|
1238
|
+
function parseRepetition(body) {
|
|
1239
|
+
let i = 0;
|
|
1240
|
+
const parseAlternation = () => {
|
|
1241
|
+
const branches = [parseSequence()];
|
|
1242
|
+
while (body[i] === "|") {
|
|
1243
|
+
i++;
|
|
1244
|
+
branches.push(parseSequence());
|
|
1245
|
+
}
|
|
1246
|
+
return branches.length === 1 ? branches[0] : { type: "alt", branches };
|
|
1247
|
+
};
|
|
1248
|
+
const parseSequence = () => {
|
|
1249
|
+
const items = [];
|
|
1250
|
+
while (i < body.length && body[i] !== "|" && body[i] !== ")") {
|
|
1251
|
+
const atom = parseAtom();
|
|
1252
|
+
if (atom === null) return null;
|
|
1253
|
+
const quantified = parseQuantifier(atom);
|
|
1254
|
+
if (quantified === null) return null;
|
|
1255
|
+
items.push(quantified);
|
|
1256
|
+
}
|
|
1257
|
+
return { type: "seq", items };
|
|
1258
|
+
};
|
|
1259
|
+
const parseAtom = () => {
|
|
1260
|
+
const ch = body[i];
|
|
1261
|
+
if (ch === "(") {
|
|
1262
|
+
const start2 = i;
|
|
1263
|
+
i++;
|
|
1264
|
+
let kind = "capture";
|
|
1265
|
+
if (body.startsWith("?:", i)) {
|
|
1266
|
+
kind = "group";
|
|
1267
|
+
i += 2;
|
|
1268
|
+
} else if (body.startsWith("?=", i) || body.startsWith("?!", i)) {
|
|
1269
|
+
kind = "look";
|
|
1270
|
+
i += 2;
|
|
1271
|
+
} else if (body.startsWith("?<=", i) || body.startsWith("?<!", i)) {
|
|
1272
|
+
kind = "look";
|
|
1273
|
+
i += 3;
|
|
1274
|
+
} else if (body.startsWith("?<", i)) {
|
|
1275
|
+
const close = body.indexOf(">", i + 2);
|
|
1276
|
+
if (close === -1) return null;
|
|
1277
|
+
i = close + 1;
|
|
1278
|
+
} else if (body[i] === "?") {
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
const inner = parseAlternation();
|
|
1282
|
+
if (inner === null || body[i] !== ")") return null;
|
|
1283
|
+
i++;
|
|
1284
|
+
return { type: "group", kind, body: inner, source: body.slice(start2, i) };
|
|
1285
|
+
}
|
|
1286
|
+
const start = i;
|
|
1287
|
+
if (ch === "[") {
|
|
1288
|
+
i++;
|
|
1289
|
+
if (body[i] === "^") i++;
|
|
1290
|
+
if (body[i] === "]") i++;
|
|
1291
|
+
while (i < body.length && body[i] !== "]") i += body[i] === "\\" ? 2 : 1;
|
|
1292
|
+
if (body[i] !== "]") return null;
|
|
1293
|
+
i++;
|
|
1294
|
+
return { type: "atom", source: body.slice(start, i) };
|
|
1295
|
+
}
|
|
1296
|
+
if (ch === "\\") {
|
|
1297
|
+
const next = body[i + 1];
|
|
1298
|
+
if (next === void 0) return null;
|
|
1299
|
+
if ((next === "p" || next === "P" || next === "u") && body[i + 2] === "{") {
|
|
1300
|
+
const close = body.indexOf("}", i + 3);
|
|
1301
|
+
if (close === -1) return null;
|
|
1302
|
+
i = close + 1;
|
|
1303
|
+
} else if (next === "k" && body[i + 2] === "<") {
|
|
1304
|
+
const close = body.indexOf(">", i + 3);
|
|
1305
|
+
if (close === -1) return null;
|
|
1306
|
+
i = close + 1;
|
|
1307
|
+
} else {
|
|
1308
|
+
i += 2;
|
|
1309
|
+
}
|
|
1310
|
+
return { type: /[bB]/.test(next) ? "assert" : "atom", source: body.slice(start, i) };
|
|
1311
|
+
}
|
|
1312
|
+
if (ch === "^" || ch === "$") {
|
|
1313
|
+
i++;
|
|
1314
|
+
return { type: "assert" };
|
|
1315
|
+
}
|
|
1316
|
+
if (ch === "*" || ch === "+" || ch === "?") return null;
|
|
1317
|
+
i++;
|
|
1318
|
+
return { type: "atom", source: body.slice(start, i) };
|
|
1319
|
+
};
|
|
1320
|
+
const parseQuantifier = (atom) => {
|
|
1321
|
+
let min;
|
|
1322
|
+
let max;
|
|
1323
|
+
const ch = body[i];
|
|
1324
|
+
if (ch === "*") [min, max, i] = [0, Infinity, i + 1];
|
|
1325
|
+
else if (ch === "+") [min, max, i] = [1, Infinity, i + 1];
|
|
1326
|
+
else if (ch === "?") [min, max, i] = [0, 1, i + 1];
|
|
1327
|
+
else if (ch === "{") {
|
|
1328
|
+
const bounds = /^\{(\d+)(,(\d*))?\}/.exec(body.slice(i));
|
|
1329
|
+
if (!bounds) return atom;
|
|
1330
|
+
min = Number(bounds[1]);
|
|
1331
|
+
max = bounds[2] === void 0 ? min : bounds[3] === "" ? Infinity : Number(bounds[3]);
|
|
1332
|
+
i += bounds[0].length;
|
|
1333
|
+
} else {
|
|
1334
|
+
return atom;
|
|
1335
|
+
}
|
|
1336
|
+
if (body[i] === "?") i++;
|
|
1337
|
+
if (body[i] === "*" || body[i] === "+" || body[i] === "?" || body[i] === "{") return null;
|
|
1338
|
+
return { type: "quant", min, max, body: atom };
|
|
1339
|
+
};
|
|
1340
|
+
const root = parseAlternation();
|
|
1341
|
+
return root !== null && i === body.length ? root : null;
|
|
1342
|
+
}
|
|
1343
|
+
function* nodesOf(node) {
|
|
1344
|
+
if (!node) return;
|
|
1345
|
+
yield node;
|
|
1346
|
+
if (node.type === "alt") for (const branch of node.branches) yield* nodesOf(branch);
|
|
1347
|
+
else if (node.type === "seq") for (const item of node.items) yield* nodesOf(item);
|
|
1348
|
+
else if (node.type === "group" || node.type === "quant") yield* nodesOf(node.body);
|
|
1349
|
+
}
|
|
1350
|
+
var BOUNDED_NESTING_LIMIT = 100;
|
|
1351
|
+
var BOUNDED_ALTERNATION_LIMIT = 16;
|
|
1352
|
+
var OPTIONAL_REPEAT_LIMIT = 8;
|
|
1353
|
+
function hasFencedRepetitions(quant, flags) {
|
|
1354
|
+
let body = quant.body;
|
|
1355
|
+
if (body.type === "group" && body.kind !== "look") body = body.body;
|
|
1356
|
+
if (body.type !== "seq" || body.items.length < 2) return false;
|
|
1357
|
+
const variable = [...nodesOf(body)].filter((n) => n.type === "quant" && (n.max > 1 || n.min < n.max));
|
|
1358
|
+
if (variable.some((n) => n.body.type !== "atom" || n.body.source === void 0)) return false;
|
|
1359
|
+
const atomFlags = flags.replace(/[^i]/g, "");
|
|
1360
|
+
const unicode = /[uv]/.test(flags);
|
|
1361
|
+
let insides;
|
|
1362
|
+
try {
|
|
1363
|
+
insides = variable.map((n) => new RegExp(`^(?:${n.body.source})$`, `${atomFlags}${unicode ? "u" : ""}`));
|
|
1364
|
+
} catch {
|
|
1365
|
+
return false;
|
|
1366
|
+
}
|
|
1367
|
+
const literalOf = (source) => {
|
|
1368
|
+
if (source.length === 1 && !".[]()|^$\\".includes(source)) return source;
|
|
1369
|
+
if (source.length === 2 && source[0] === "\\" && /[^\w\s]/.test(source[1])) return source[1];
|
|
1370
|
+
return null;
|
|
1371
|
+
};
|
|
1372
|
+
const disjoint = (edge) => {
|
|
1373
|
+
if (edge?.type !== "atom" || edge.source === void 0) return false;
|
|
1374
|
+
const literal = literalOf(edge.source);
|
|
1375
|
+
if (literal !== null) return insides.every((re) => !re.test(literal));
|
|
1376
|
+
if (unicode) return false;
|
|
1377
|
+
let fence;
|
|
1378
|
+
try {
|
|
1379
|
+
fence = new RegExp(`^(?:${edge.source})$`, atomFlags);
|
|
1380
|
+
} catch {
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
for (let unit = 0; unit <= 65535; unit++) {
|
|
1384
|
+
const ch = String.fromCharCode(unit);
|
|
1385
|
+
if (fence.test(ch) && insides.some((re) => re.test(ch))) return false;
|
|
1386
|
+
}
|
|
1387
|
+
return true;
|
|
1388
|
+
};
|
|
1389
|
+
return disjoint(body.items[0]) || disjoint(body.items[body.items.length - 1]);
|
|
1390
|
+
}
|
|
1391
|
+
function hasCatastrophicShape(body, flags) {
|
|
1392
|
+
const root = parseRepetition(body);
|
|
1393
|
+
if (root === null) return null;
|
|
1394
|
+
for (const node of nodesOf(root)) {
|
|
1395
|
+
if (node.type === "group" && node.kind === "look" && hasAdjacentUnbounded(lookaroundBody(node.source), flags)) return true;
|
|
1396
|
+
if (node.type !== "quant" || node.max <= 1) continue;
|
|
1397
|
+
const fenced = hasFencedRepetitions(node, flags);
|
|
1398
|
+
for (const inner of nodesOf(node.body)) {
|
|
1399
|
+
if (inner === node.body && inner.type === "quant") continue;
|
|
1400
|
+
if (inner.type === "quant" && inner.max > 1 && !fenced) {
|
|
1401
|
+
if (node.max === Infinity || inner.max === Infinity || node.max * inner.max > BOUNDED_NESTING_LIMIT) return true;
|
|
1402
|
+
}
|
|
1403
|
+
if (inner.type === "quant" && inner.min === 0 && inner.max <= 1 && !fenced && (node.max === Infinity || node.max > OPTIONAL_REPEAT_LIMIT)) {
|
|
1404
|
+
return true;
|
|
1405
|
+
}
|
|
1406
|
+
if (inner.type === "alt" && inner.branches.length > 1 && (node.max === Infinity || node.max > BOUNDED_ALTERNATION_LIMIT)) {
|
|
1407
|
+
return true;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
return false;
|
|
1412
|
+
}
|
|
1413
|
+
function lookaroundBody(source) {
|
|
1414
|
+
return source.slice(source.startsWith("(?<") ? 4 : 3, -1);
|
|
1415
|
+
}
|
|
1238
1416
|
function groupEnd(body, start) {
|
|
1239
1417
|
let depth = 0;
|
|
1240
1418
|
let inClass = false;
|
|
@@ -1261,6 +1439,8 @@ function warnRejectedPatternOnce(pattern) {
|
|
|
1261
1439
|
);
|
|
1262
1440
|
}
|
|
1263
1441
|
var MAX_PATTERN_LENGTH = 1e3;
|
|
1442
|
+
var regexVerdicts = /* @__PURE__ */ new Map();
|
|
1443
|
+
var REGEX_VERDICT_LIMIT = 2048;
|
|
1264
1444
|
function safeRegExp(pattern) {
|
|
1265
1445
|
if (!pattern) {
|
|
1266
1446
|
return null;
|
|
@@ -1272,16 +1452,32 @@ function safeRegExp(pattern) {
|
|
|
1272
1452
|
if (!match) {
|
|
1273
1453
|
return null;
|
|
1274
1454
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1455
|
+
let safe = regexVerdicts.get(pattern);
|
|
1456
|
+
if (safe === void 0) {
|
|
1457
|
+
safe = isSafeBody(match[1], match[2]);
|
|
1458
|
+
if (regexVerdicts.size >= REGEX_VERDICT_LIMIT) regexVerdicts.clear();
|
|
1459
|
+
regexVerdicts.set(pattern, safe);
|
|
1277
1460
|
}
|
|
1278
|
-
if (
|
|
1461
|
+
if (!safe) return null;
|
|
1279
1462
|
try {
|
|
1280
1463
|
return new RegExp(match[1], match[2]);
|
|
1281
1464
|
} catch {
|
|
1282
1465
|
return null;
|
|
1283
1466
|
}
|
|
1284
1467
|
}
|
|
1468
|
+
function isSafeBody(body, flags) {
|
|
1469
|
+
for (const dangerous of REDOS_PATTERNS) {
|
|
1470
|
+
if (dangerous.test(body)) return false;
|
|
1471
|
+
}
|
|
1472
|
+
if (hasAdjacentUnbounded(body, flags)) return false;
|
|
1473
|
+
if (hasCatastrophicShape(body, flags) === true) return false;
|
|
1474
|
+
try {
|
|
1475
|
+
new RegExp(body, flags);
|
|
1476
|
+
} catch {
|
|
1477
|
+
return false;
|
|
1478
|
+
}
|
|
1479
|
+
return true;
|
|
1480
|
+
}
|
|
1285
1481
|
function ctypeResult(strValue, isClass, matchVal) {
|
|
1286
1482
|
if (strValue === "") {
|
|
1287
1483
|
return false;
|
|
@@ -3238,7 +3434,7 @@ function concatChunks(chunks, total) {
|
|
|
3238
3434
|
}
|
|
3239
3435
|
function parseBody(rawBody, contentType) {
|
|
3240
3436
|
const ct = String(contentType || "").toLowerCase();
|
|
3241
|
-
const
|
|
3437
|
+
const isJson2 = ct.includes("application/json") || /\+json\b/.test(ct);
|
|
3242
3438
|
const isForm = ct.includes("application/x-www-form-urlencoded");
|
|
3243
3439
|
const isMultipart = ct.includes("multipart/form-data");
|
|
3244
3440
|
const isAmbiguous = ct === "" || ct.startsWith("text/plain") || ct.includes("csp-report") || ct.includes("/json");
|
|
@@ -3252,7 +3448,7 @@ function parseBody(rawBody, contentType) {
|
|
|
3252
3448
|
for (const [k, v] of new URLSearchParams(rawBody)) appendOwn(body, k, v);
|
|
3253
3449
|
return { body, files: void 0 };
|
|
3254
3450
|
}
|
|
3255
|
-
if (
|
|
3451
|
+
if (isJson2 || isAmbiguous) {
|
|
3256
3452
|
try {
|
|
3257
3453
|
const parsed = JSON.parse(rawBody);
|
|
3258
3454
|
if (parsed && typeof parsed === "object") return { body: parsed, files: void 0 };
|
|
@@ -3268,22 +3464,68 @@ function parseMultipart(rawBody, boundary) {
|
|
|
3268
3464
|
const sep = /\r?\n\r?\n/.exec(part);
|
|
3269
3465
|
if (!sep) continue;
|
|
3270
3466
|
const rawHeaders = part.slice(0, sep.index);
|
|
3271
|
-
const disposition = /content-disposition:[^\r\n]
|
|
3272
|
-
if (
|
|
3273
|
-
const
|
|
3274
|
-
if (
|
|
3467
|
+
const disposition = /content-disposition:([^\r\n]*)/i.exec(rawHeaders)?.[1];
|
|
3468
|
+
if (disposition === void 0) continue;
|
|
3469
|
+
const { names, filename } = dispositionFields(disposition);
|
|
3470
|
+
if (names.length === 0) continue;
|
|
3275
3471
|
const content = part.slice(sep.index + sep[0].length).replace(/\r?\n$/, "");
|
|
3276
|
-
const
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
appendOwn(files, name, file);
|
|
3281
|
-
} else {
|
|
3282
|
-
appendOwn(body, name, content);
|
|
3472
|
+
const file = filename === void 0 ? void 0 : { filename, type: /content-type:\s*([^\r\n;]+)/i.exec(rawHeaders)?.[1]?.trim() || "", content };
|
|
3473
|
+
for (const name of names) {
|
|
3474
|
+
if (file) appendOwn(files, name, file);
|
|
3475
|
+
else appendOwn(body, name, content);
|
|
3283
3476
|
}
|
|
3284
3477
|
}
|
|
3285
3478
|
return { body, files };
|
|
3286
3479
|
}
|
|
3480
|
+
function dispositionFields(value) {
|
|
3481
|
+
const text = String(value);
|
|
3482
|
+
const names = /* @__PURE__ */ new Set();
|
|
3483
|
+
let filename;
|
|
3484
|
+
let extendedFilename;
|
|
3485
|
+
let i = text.indexOf(";");
|
|
3486
|
+
while (i !== -1 && i < text.length) {
|
|
3487
|
+
i++;
|
|
3488
|
+
while (text[i] === " " || text[i] === " ") i++;
|
|
3489
|
+
let key = "";
|
|
3490
|
+
while (i < text.length && text[i] !== "=" && text[i] !== ";") key += text[i++];
|
|
3491
|
+
key = key.trim().toLowerCase();
|
|
3492
|
+
let val = null;
|
|
3493
|
+
if (text[i] === "=") {
|
|
3494
|
+
i++;
|
|
3495
|
+
while (text[i] === " " || text[i] === " ") i++;
|
|
3496
|
+
if (text[i] === '"') {
|
|
3497
|
+
i++;
|
|
3498
|
+
val = "";
|
|
3499
|
+
while (i < text.length && text[i] !== '"') {
|
|
3500
|
+
if (text[i] === "\\" && i + 1 < text.length) i++;
|
|
3501
|
+
val += text[i++];
|
|
3502
|
+
}
|
|
3503
|
+
i++;
|
|
3504
|
+
while (i < text.length && text[i] !== ";") i++;
|
|
3505
|
+
} else {
|
|
3506
|
+
let bare = "";
|
|
3507
|
+
while (i < text.length && text[i] !== ";") bare += text[i++];
|
|
3508
|
+
val = bare.trim();
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
if (val !== null) {
|
|
3512
|
+
if (key === "name") names.add(val);
|
|
3513
|
+
else if (key === "filename") filename = val;
|
|
3514
|
+
else if (key === "filename*") extendedFilename = decodeExtendedValue(val);
|
|
3515
|
+
}
|
|
3516
|
+
i = i < text.length && text[i] === ";" ? i : -1;
|
|
3517
|
+
}
|
|
3518
|
+
return { names: [...names], filename: extendedFilename ?? filename };
|
|
3519
|
+
}
|
|
3520
|
+
function decodeExtendedValue(value) {
|
|
3521
|
+
const match = /^([^']*)'[^']*'(.*)$/.exec(value);
|
|
3522
|
+
if (!match) return value;
|
|
3523
|
+
try {
|
|
3524
|
+
return decodeURIComponent(match[2]);
|
|
3525
|
+
} catch {
|
|
3526
|
+
return match[2];
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3287
3529
|
function parseCookies(header) {
|
|
3288
3530
|
const cookies = {};
|
|
3289
3531
|
if (!header) {
|
|
@@ -3554,14 +3796,29 @@ async function installEgressGuard({ shouldBlock, onBlock, onSkip, dnsScreen = tr
|
|
|
3554
3796
|
if (globalThis.fetch === guarded) globalThis.fetch = originalFetch;
|
|
3555
3797
|
});
|
|
3556
3798
|
}
|
|
3799
|
+
let syncBuiltins = () => {
|
|
3800
|
+
};
|
|
3801
|
+
try {
|
|
3802
|
+
const { syncBuiltinESMExports } = await Promise.resolve().then(() => __toESM(require_ps_edge_stub(), 1));
|
|
3803
|
+
if (typeof syncBuiltinESMExports === "function") syncBuiltins = syncBuiltinESMExports;
|
|
3804
|
+
} catch {
|
|
3805
|
+
}
|
|
3806
|
+
let patchedAny = false;
|
|
3557
3807
|
for (const moduleName of ["node:http", "node:https"]) {
|
|
3558
3808
|
try {
|
|
3559
3809
|
const mod = await import(moduleName);
|
|
3560
3810
|
const restore = patchHttpModule(mod.default ?? mod, block, screen, skip);
|
|
3561
|
-
if (restore)
|
|
3811
|
+
if (restore) {
|
|
3812
|
+
patchedAny = true;
|
|
3813
|
+
restores.push(() => {
|
|
3814
|
+
restore();
|
|
3815
|
+
syncBuiltins();
|
|
3816
|
+
});
|
|
3817
|
+
}
|
|
3562
3818
|
} catch {
|
|
3563
3819
|
}
|
|
3564
3820
|
}
|
|
3821
|
+
if (patchedAny) syncBuiltins();
|
|
3565
3822
|
return () => {
|
|
3566
3823
|
for (const restore of restores) {
|
|
3567
3824
|
try {
|
|
@@ -3575,18 +3832,22 @@ function patchHttpModule(http, block, screen, skip) {
|
|
|
3575
3832
|
if (!http || typeof http.request !== "function" || http.__patchstackGuarded) return null;
|
|
3576
3833
|
const originalRequest = http.request;
|
|
3577
3834
|
const originalGet = http.get;
|
|
3578
|
-
const
|
|
3835
|
+
const OriginalClientRequest = typeof http.ClientRequest === "function" ? http.ClientRequest : null;
|
|
3836
|
+
const guardArgs = (args) => {
|
|
3579
3837
|
const target = extractHttpTarget(args);
|
|
3580
3838
|
if (target && block(target.url, target.host, target.method)) {
|
|
3581
3839
|
throw new Error(`Patchstack blocked an outbound request to a disallowed address: ${target.host ?? target.url}`);
|
|
3582
3840
|
}
|
|
3583
3841
|
if (target && screen && target.host && screen.isIP(target.host) === 0 && !screen.isExempt(target.host)) {
|
|
3584
3842
|
try {
|
|
3585
|
-
|
|
3843
|
+
return withScreeningLookup(args, target, block, screen.lookup, skip);
|
|
3586
3844
|
} catch {
|
|
3587
3845
|
}
|
|
3588
3846
|
}
|
|
3589
|
-
return
|
|
3847
|
+
return args;
|
|
3848
|
+
};
|
|
3849
|
+
const wrap = (original) => function(...args) {
|
|
3850
|
+
return original.apply(this, guardArgs(args));
|
|
3590
3851
|
};
|
|
3591
3852
|
const guardedRequest = wrap(originalRequest);
|
|
3592
3853
|
http.request = guardedRequest;
|
|
@@ -3595,10 +3856,21 @@ function patchHttpModule(http, block, screen, skip) {
|
|
|
3595
3856
|
guardedGet = wrap(originalGet);
|
|
3596
3857
|
http.get = guardedGet;
|
|
3597
3858
|
}
|
|
3859
|
+
let GuardedClientRequest;
|
|
3860
|
+
if (OriginalClientRequest) {
|
|
3861
|
+
GuardedClientRequest = function ClientRequest(...args) {
|
|
3862
|
+
const screened = guardArgs(args);
|
|
3863
|
+
return new.target ? Reflect.construct(OriginalClientRequest, screened, new.target) : Reflect.construct(OriginalClientRequest, screened);
|
|
3864
|
+
};
|
|
3865
|
+
GuardedClientRequest.prototype = OriginalClientRequest.prototype;
|
|
3866
|
+
Object.setPrototypeOf(GuardedClientRequest, OriginalClientRequest);
|
|
3867
|
+
http.ClientRequest = GuardedClientRequest;
|
|
3868
|
+
}
|
|
3598
3869
|
http.__patchstackGuarded = true;
|
|
3599
3870
|
return () => {
|
|
3600
3871
|
if (http.request === guardedRequest) http.request = originalRequest;
|
|
3601
3872
|
if (guardedGet && http.get === guardedGet) http.get = originalGet;
|
|
3873
|
+
if (GuardedClientRequest && http.ClientRequest === GuardedClientRequest) http.ClientRequest = OriginalClientRequest;
|
|
3602
3874
|
delete http.__patchstackGuarded;
|
|
3603
3875
|
};
|
|
3604
3876
|
}
|
|
@@ -3608,19 +3880,27 @@ function extractHttpTarget(args) {
|
|
|
3608
3880
|
if (typeof first === "string" || first instanceof URL) {
|
|
3609
3881
|
const url = new URL(String(first));
|
|
3610
3882
|
const opts = args.find((a) => a && typeof a === "object" && !(a instanceof URL));
|
|
3611
|
-
return { url
|
|
3612
|
-
}
|
|
3613
|
-
if (first && typeof first === "object") {
|
|
3614
|
-
const host = normalizeHost(first.hostname || first.host) || "localhost";
|
|
3615
|
-
const protocol = first.protocol || "http:";
|
|
3616
|
-
const port = first.port ? `:${first.port}` : "";
|
|
3617
|
-
const path = first.path || "/";
|
|
3618
|
-
return { url: `${protocol}//${host}${port}${path}`, host, method: first.method || "GET" };
|
|
3883
|
+
return targetOf({ ...optionsFromUrl(url), ...opts ?? {} });
|
|
3619
3884
|
}
|
|
3885
|
+
if (first && typeof first === "object") return targetOf(first);
|
|
3620
3886
|
} catch {
|
|
3621
3887
|
}
|
|
3622
3888
|
return null;
|
|
3623
3889
|
}
|
|
3890
|
+
function optionsFromUrl(url) {
|
|
3891
|
+
const hostname = url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : url.hostname;
|
|
3892
|
+
const options = { protocol: url.protocol, hostname, path: `${url.pathname || "/"}${url.search}` };
|
|
3893
|
+
if (url.port !== "") options.port = Number(url.port);
|
|
3894
|
+
return options;
|
|
3895
|
+
}
|
|
3896
|
+
function targetOf(options) {
|
|
3897
|
+
const host = normalizeHost(options.hostname || options.host) || "localhost";
|
|
3898
|
+
const protocol = options.protocol || "http:";
|
|
3899
|
+
const port = options.port ? `:${options.port}` : "";
|
|
3900
|
+
const path = options.path || "/";
|
|
3901
|
+
const authority = host.includes(":") ? `[${host}]` : host;
|
|
3902
|
+
return { url: `${protocol}//${authority}${port}${path}`, host, method: options.method || "GET" };
|
|
3903
|
+
}
|
|
3624
3904
|
function normalizeHost(raw) {
|
|
3625
3905
|
const host = String(raw || "").trim();
|
|
3626
3906
|
const bracketed = /^\[([^\]]+)\]/.exec(host);
|
|
@@ -4001,19 +4281,49 @@ async function loadFs() {
|
|
|
4001
4281
|
}
|
|
4002
4282
|
return fsMod;
|
|
4003
4283
|
}
|
|
4284
|
+
async function sourceIdentity(options = {}) {
|
|
4285
|
+
if (typeof options.siteUuid === "string" && options.siteUuid.trim() !== "") {
|
|
4286
|
+
const endpoint = typeof options.pulseRulesUrl === "string" ? options.pulseRulesUrl : "";
|
|
4287
|
+
return `site:${options.siteUuid.trim().toLowerCase()}@${endpoint}`;
|
|
4288
|
+
}
|
|
4289
|
+
if (typeof options.token === "string" && options.token !== "") {
|
|
4290
|
+
const digest = await sha256Hex(options.token);
|
|
4291
|
+
if (digest === null) return null;
|
|
4292
|
+
const endpoint = typeof options.baseUrl === "string" ? options.baseUrl : "";
|
|
4293
|
+
return `token:${digest}@${endpoint}`;
|
|
4294
|
+
}
|
|
4295
|
+
return null;
|
|
4296
|
+
}
|
|
4297
|
+
async function sha256Hex(text) {
|
|
4298
|
+
try {
|
|
4299
|
+
const subtle = globalThis.crypto?.subtle;
|
|
4300
|
+
if (!subtle || typeof TextEncoder === "undefined") return null;
|
|
4301
|
+
const digest = await subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
4302
|
+
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4303
|
+
} catch {
|
|
4304
|
+
return null;
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4004
4307
|
function makeStore(options = {}) {
|
|
4005
4308
|
let mem = null;
|
|
4006
4309
|
const durable = durableTier(options);
|
|
4310
|
+
let identity;
|
|
4311
|
+
const identityOf = () => identity ??= sourceIdentity(options);
|
|
4007
4312
|
return {
|
|
4008
4313
|
async read() {
|
|
4009
4314
|
if (mem) return mem;
|
|
4315
|
+
const source = await identityOf();
|
|
4316
|
+
if (source === null) return null;
|
|
4010
4317
|
const env = await durable.read();
|
|
4011
|
-
if (env)
|
|
4318
|
+
if (!env || env.source !== source) return null;
|
|
4319
|
+
mem = env;
|
|
4012
4320
|
return env;
|
|
4013
4321
|
},
|
|
4014
4322
|
async write(env) {
|
|
4015
|
-
if (env)
|
|
4016
|
-
await
|
|
4323
|
+
if (!env) return;
|
|
4324
|
+
const stamped = { ...env, source: await identityOf() };
|
|
4325
|
+
mem = stamped;
|
|
4326
|
+
await durable.write(stamped);
|
|
4017
4327
|
}
|
|
4018
4328
|
};
|
|
4019
4329
|
}
|
|
@@ -4135,11 +4445,12 @@ function toEnvelope(value) {
|
|
|
4135
4445
|
buildId,
|
|
4136
4446
|
// A confirmation belongs to the presentation stored beside it. A crossed or partially written
|
|
4137
4447
|
// envelope confirms nothing, even when one of its fields happens to name the current map.
|
|
4138
|
-
matchedBuildId: buildId !== null && matched === buildId ? matched : null
|
|
4448
|
+
matchedBuildId: buildId !== null && matched === buildId ? matched : null,
|
|
4449
|
+
source: typeof value.source === "string" ? value.source : null
|
|
4139
4450
|
};
|
|
4140
4451
|
}
|
|
4141
4452
|
if (Array.isArray(value.firewall) || Array.isArray(value.whitelists)) {
|
|
4142
|
-
return { bundle: value, etag: null, buildId: null, matchedBuildId: null };
|
|
4453
|
+
return { bundle: value, etag: null, buildId: null, matchedBuildId: null, source: null };
|
|
4143
4454
|
}
|
|
4144
4455
|
return null;
|
|
4145
4456
|
}
|
|
@@ -4206,13 +4517,17 @@ function withScopedDryRun(bundle, confirmed, reason, options = {}) {
|
|
|
4206
4517
|
...unusable > 0 ? [`${unusable} rule(s) carry an unusable ${BUILD_SCOPE_PROPERTY}`] : [],
|
|
4207
4518
|
...held > unusable ? [reason] : []
|
|
4208
4519
|
];
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
"onError"
|
|
4213
|
-
);
|
|
4520
|
+
const message = `${held} build-scoped rule(s) are detecting only, not blocking: ${explanations.join("; ")}`;
|
|
4521
|
+
if (typeof options.onError === "function") notify(options.onError, new Error(message), "onError");
|
|
4522
|
+
else warnHeldOnce(message);
|
|
4214
4523
|
return { ...bundle, firewall };
|
|
4215
4524
|
}
|
|
4525
|
+
var warnedHeld = /* @__PURE__ */ new Set();
|
|
4526
|
+
function warnHeldOnce(message) {
|
|
4527
|
+
if (warnedHeld.has(message)) return;
|
|
4528
|
+
warnedHeld.add(message);
|
|
4529
|
+
console.warn(`[patchstack] ${message}. Pass { onError } to handle this yourself.`);
|
|
4530
|
+
}
|
|
4216
4531
|
function confirmedBy(res, presented) {
|
|
4217
4532
|
if (presented === null) return null;
|
|
4218
4533
|
const verdict = res?.build;
|
|
@@ -4387,6 +4702,60 @@ function startRefresh(tick, { refreshMs, onError } = {}) {
|
|
|
4387
4702
|
}
|
|
4388
4703
|
};
|
|
4389
4704
|
}
|
|
4705
|
+
var RECOVERY_DELAYS_MS = [5e3, 15e3, 45e3, 12e4, 3e5, 6e5];
|
|
4706
|
+
function startRecovery(tick, { onError } = {}) {
|
|
4707
|
+
let stopped = false;
|
|
4708
|
+
let attempt = 0;
|
|
4709
|
+
let timer = null;
|
|
4710
|
+
const schedule = () => {
|
|
4711
|
+
if (stopped) return;
|
|
4712
|
+
const delay = RECOVERY_DELAYS_MS[Math.min(attempt, RECOVERY_DELAYS_MS.length - 1)];
|
|
4713
|
+
timer = setTimeout(run, delay * (1 - Math.random() * JITTER_FRACTION3));
|
|
4714
|
+
timer?.unref?.();
|
|
4715
|
+
};
|
|
4716
|
+
const run = async () => {
|
|
4717
|
+
if (stopped) return;
|
|
4718
|
+
attempt++;
|
|
4719
|
+
try {
|
|
4720
|
+
const status = await tick();
|
|
4721
|
+
if (!status || status.ok !== false) {
|
|
4722
|
+
stopped = true;
|
|
4723
|
+
return;
|
|
4724
|
+
}
|
|
4725
|
+
} catch (err) {
|
|
4726
|
+
notify(onError, err, "onError");
|
|
4727
|
+
}
|
|
4728
|
+
schedule();
|
|
4729
|
+
};
|
|
4730
|
+
schedule();
|
|
4731
|
+
return {
|
|
4732
|
+
stop() {
|
|
4733
|
+
stopped = true;
|
|
4734
|
+
if (timer) clearTimeout(timer);
|
|
4735
|
+
}
|
|
4736
|
+
};
|
|
4737
|
+
}
|
|
4738
|
+
function serialise(tick) {
|
|
4739
|
+
let running = null;
|
|
4740
|
+
let next = null;
|
|
4741
|
+
const run = () => {
|
|
4742
|
+
if (!running) {
|
|
4743
|
+
running = Promise.resolve().then(() => tick()).finally(() => {
|
|
4744
|
+
running = null;
|
|
4745
|
+
});
|
|
4746
|
+
return running;
|
|
4747
|
+
}
|
|
4748
|
+
if (!next) {
|
|
4749
|
+
next = running.catch(() => {
|
|
4750
|
+
}).then(() => {
|
|
4751
|
+
next = null;
|
|
4752
|
+
return run();
|
|
4753
|
+
});
|
|
4754
|
+
}
|
|
4755
|
+
return next;
|
|
4756
|
+
};
|
|
4757
|
+
return run;
|
|
4758
|
+
}
|
|
4390
4759
|
function makeRefreshHandler(tick, secret) {
|
|
4391
4760
|
let inflight2 = null;
|
|
4392
4761
|
const runOnce = () => {
|
|
@@ -5538,7 +5907,7 @@ function createSupabaseGuard({
|
|
|
5538
5907
|
statusText: upstream.statusText,
|
|
5539
5908
|
headers: responseHeaders(upstream.headers)
|
|
5540
5909
|
});
|
|
5541
|
-
return protection.screenResponse ? protection.screenResponse(forwarded) : forwarded;
|
|
5910
|
+
return protection.screenResponse ? protection.screenResponse(forwarded, evalReq) : forwarded;
|
|
5542
5911
|
};
|
|
5543
5912
|
}
|
|
5544
5913
|
|
|
@@ -5632,6 +6001,7 @@ async function createProtection(options = {}) {
|
|
|
5632
6001
|
"onDetect"
|
|
5633
6002
|
);
|
|
5634
6003
|
};
|
|
6004
|
+
const nodeHttp = await Promise.resolve().then(() => __toESM(require_ps_edge_stub(), 1)).then((m) => m.default ?? m, () => null);
|
|
5635
6005
|
const store = makeStore(options);
|
|
5636
6006
|
const bootTimeoutMs = Number(options.bootTimeoutMs) > 0 ? Number(options.bootTimeoutMs) : 5e3;
|
|
5637
6007
|
const pulseAuth = await resolvePulseAuth(options);
|
|
@@ -5657,6 +6027,21 @@ async function createProtection(options = {}) {
|
|
|
5657
6027
|
pulseAuth,
|
|
5658
6028
|
detectionState: preFetchState
|
|
5659
6029
|
});
|
|
6030
|
+
let ruleSource = { ...bundle.source ?? { ok: true, origin: "empty" } };
|
|
6031
|
+
const warnedSources = /* @__PURE__ */ new Set();
|
|
6032
|
+
const hasLiveSource = Boolean(options.siteUuid || options.token);
|
|
6033
|
+
const noteRuleSource = (source) => {
|
|
6034
|
+
ruleSource = { ...source ?? { ok: true, origin: ruleSource.origin } };
|
|
6035
|
+
if (!hasLiveSource || typeof onError === "function" || ruleSource.ok !== false) return;
|
|
6036
|
+
const cause = `${ruleSource.origin}:${ruleSource.reason ?? ""}`;
|
|
6037
|
+
if (warnedSources.has(cause)) return;
|
|
6038
|
+
warnedSources.add(cause);
|
|
6039
|
+
const running = ruleSource.origin === "cache" ? "the last rules it received" : ruleSource.origin === "bundled" ? "its bundled fallback rules" : "no rules at all";
|
|
6040
|
+
console.warn(
|
|
6041
|
+
`[patchstack] the guard's rules are not current (${ruleSource.reason ?? "the rules source did not answer"}); it is running on ${running}. Pass { onError } to handle this yourself.`
|
|
6042
|
+
);
|
|
6043
|
+
};
|
|
6044
|
+
noteRuleSource(bundle.source);
|
|
5660
6045
|
let detectionReporting = "not-enrolled";
|
|
5661
6046
|
let currentOrigin;
|
|
5662
6047
|
const applyReportingState = async (origin) => {
|
|
@@ -5835,7 +6220,9 @@ async function createProtection(options = {}) {
|
|
|
5835
6220
|
const spanRedactors = redactors.filter((r) => !r.jsonPath);
|
|
5836
6221
|
if (pathRedactors.length) body = applyPathRedactors(body, pathRedactors, mask, screenCap, transform);
|
|
5837
6222
|
if (!spanRedactors.length) continue;
|
|
6223
|
+
const beforeSpan = body;
|
|
5838
6224
|
body = applyRedactors(body, spanRedactors, mask, transform);
|
|
6225
|
+
if (body !== beforeSpan && !preservesJsonStructure(beforeSpan, body)) return { verdict: "block" };
|
|
5839
6226
|
if (transform) continue;
|
|
5840
6227
|
for (const name of Object.keys(headers)) {
|
|
5841
6228
|
const value = headers[name];
|
|
@@ -5850,6 +6237,7 @@ async function createProtection(options = {}) {
|
|
|
5850
6237
|
}
|
|
5851
6238
|
}
|
|
5852
6239
|
}
|
|
6240
|
+
if (body !== text && isJson(text) && !isJson(body)) return { verdict: "block" };
|
|
5853
6241
|
for (const rule of headerMutations) applyHeaderMutation(headers, rule);
|
|
5854
6242
|
return { verdict: "redact", body, headers };
|
|
5855
6243
|
};
|
|
@@ -5952,7 +6340,9 @@ async function createProtection(options = {}) {
|
|
|
5952
6340
|
const screenResp = async (response, reqCtx) => {
|
|
5953
6341
|
const read = await readTextResponse(response, screenCap);
|
|
5954
6342
|
if (read.skip) {
|
|
5955
|
-
if (read.skip !== "not-a-response")
|
|
6343
|
+
if (read.skip !== "not-a-response") {
|
|
6344
|
+
recordSkip("response", read.skip, { status: response?.status, ...read.encoding ? { encoding: read.encoding } : {} });
|
|
6345
|
+
}
|
|
5956
6346
|
if (read.skip === "not-a-response") return response;
|
|
5957
6347
|
return hardenHeadersOnly(response, reqCtx);
|
|
5958
6348
|
}
|
|
@@ -6033,6 +6423,11 @@ async function createProtection(options = {}) {
|
|
|
6033
6423
|
const wrapNodeResponse = (res, reqCtx) => {
|
|
6034
6424
|
const origWrite = res.write.bind(res);
|
|
6035
6425
|
const origEnd = res.end.bind(res);
|
|
6426
|
+
const setHeaderNow = typeof res.setHeader === "function" ? res.setHeader.bind(res) : null;
|
|
6427
|
+
const removeHeaderNow = typeof res.removeHeader === "function" ? res.removeHeader.bind(res) : null;
|
|
6428
|
+
const sentAccessor = accessorOf(res, "headersSent");
|
|
6429
|
+
let sentValue = sentAccessor ? void 0 : res.headersSent;
|
|
6430
|
+
const reallySent = () => Boolean(sentAccessor ? sentAccessor.call(res) : sentValue);
|
|
6036
6431
|
const chunks = [];
|
|
6037
6432
|
let size = 0;
|
|
6038
6433
|
let overflow = false;
|
|
@@ -6043,7 +6438,7 @@ async function createProtection(options = {}) {
|
|
|
6043
6438
|
if (hardened) return null;
|
|
6044
6439
|
hardened = true;
|
|
6045
6440
|
try {
|
|
6046
|
-
if (
|
|
6441
|
+
if (reallySent() || !setHeaderNow) return null;
|
|
6047
6442
|
const set = typeof res.getHeaders === "function" ? res.getHeaders() : {};
|
|
6048
6443
|
const headers = effective && effective.headers ? { ...set, ...effective.headers } : set;
|
|
6049
6444
|
const status = effective && effective.status !== void 0 ? effective.status : res.statusCode;
|
|
@@ -6055,8 +6450,8 @@ async function createProtection(options = {}) {
|
|
|
6055
6450
|
if (!headerValueChanged(headers[name], value)) continue;
|
|
6056
6451
|
changed.set(name.toLowerCase(), value);
|
|
6057
6452
|
try {
|
|
6058
|
-
if (value === null || value === void 0)
|
|
6059
|
-
else
|
|
6453
|
+
if (value === null || value === void 0) removeHeaderNow?.(name);
|
|
6454
|
+
else setHeaderNow(name, value);
|
|
6060
6455
|
} catch {
|
|
6061
6456
|
}
|
|
6062
6457
|
}
|
|
@@ -6067,26 +6462,139 @@ async function createProtection(options = {}) {
|
|
|
6067
6462
|
}
|
|
6068
6463
|
};
|
|
6069
6464
|
const stillToAnswer = (rule) => !hardensWithoutBody(rule);
|
|
6070
|
-
|
|
6071
|
-
|
|
6465
|
+
let pendingHead = null;
|
|
6466
|
+
let sending = false;
|
|
6467
|
+
const origWriteHead = typeof res.writeHead === "function" ? res.writeHead.bind(res) : null;
|
|
6468
|
+
if (origWriteHead) {
|
|
6072
6469
|
res.writeHead = function(...args) {
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
const
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6470
|
+
if (sending || reallySent()) return origWriteHead(...args);
|
|
6471
|
+
if (pendingHead) throw headersSentError("write");
|
|
6472
|
+
const judged = judgeWriteHead(args);
|
|
6473
|
+
if (judged) {
|
|
6474
|
+
res.statusCode = judged.statusCode;
|
|
6475
|
+
res.statusMessage = judged.statusMessage;
|
|
6476
|
+
mirrorHeaders(judged.headers);
|
|
6477
|
+
pendingHead = args;
|
|
6478
|
+
} else {
|
|
6479
|
+
pendingHead = args;
|
|
6480
|
+
if (typeof args[0] === "number") res.statusCode = args[0];
|
|
6481
|
+
if (typeof args[1] === "string") res.statusMessage = args[1];
|
|
6482
|
+
}
|
|
6483
|
+
return res;
|
|
6083
6484
|
};
|
|
6084
6485
|
}
|
|
6486
|
+
function judgeWriteHead(args) {
|
|
6487
|
+
const ServerResponse = nodeHttp?.ServerResponse;
|
|
6488
|
+
if (typeof ServerResponse !== "function") return null;
|
|
6489
|
+
const detached = new ServerResponse({ method: res.req?.method ?? "GET", httpVersionMajor: 1, httpVersionMinor: 1, headers: {} });
|
|
6490
|
+
if (res.statusMessage !== void 0) detached.statusMessage = res.statusMessage;
|
|
6491
|
+
for (const [name, value] of headerEntries(res)) detached.setHeader(name, value);
|
|
6492
|
+
detached.writeHead(...args);
|
|
6493
|
+
return { statusCode: detached.statusCode, statusMessage: detached.statusMessage, headers: headerEntries(detached) };
|
|
6494
|
+
}
|
|
6495
|
+
function headerEntries(target) {
|
|
6496
|
+
const names = typeof target.getRawHeaderNames === "function" ? target.getRawHeaderNames() : typeof target.getHeaderNames === "function" ? target.getHeaderNames() : [];
|
|
6497
|
+
return names.map((name) => [name, target.getHeader(name)]);
|
|
6498
|
+
}
|
|
6499
|
+
function mirrorHeaders(entries) {
|
|
6500
|
+
if (!setHeaderNow) return;
|
|
6501
|
+
for (const [name, value] of entries) {
|
|
6502
|
+
const current = res.getHeader?.(name);
|
|
6503
|
+
if (current === void 0 || headerValueChanged(current, value)) setHeaderNow(name, value);
|
|
6504
|
+
}
|
|
6505
|
+
}
|
|
6506
|
+
Object.defineProperty(res, "headersSent", {
|
|
6507
|
+
configurable: true,
|
|
6508
|
+
enumerable: true,
|
|
6509
|
+
get: () => pendingHead !== null || reallySent(),
|
|
6510
|
+
// A response-like object that tracks the flag itself keeps doing so.
|
|
6511
|
+
set: (value) => {
|
|
6512
|
+
if (!sentAccessor) sentValue = value;
|
|
6513
|
+
}
|
|
6514
|
+
});
|
|
6515
|
+
for (const [method, verb] of [["setHeader", "set"], ["removeHeader", "remove"], ["appendHeader", "append"]]) {
|
|
6516
|
+
const original = typeof res[method] === "function" ? res[method].bind(res) : null;
|
|
6517
|
+
if (!original) continue;
|
|
6518
|
+
res[method] = function(...args) {
|
|
6519
|
+
if (pendingHead) throw headersSentError(verb);
|
|
6520
|
+
return original(...args);
|
|
6521
|
+
};
|
|
6522
|
+
}
|
|
6523
|
+
if (typeof res.flushHeaders === "function") {
|
|
6524
|
+
const origFlushHeaders = res.flushHeaders.bind(res);
|
|
6525
|
+
res.flushHeaders = function() {
|
|
6526
|
+
sendHead();
|
|
6527
|
+
return origFlushHeaders();
|
|
6528
|
+
};
|
|
6529
|
+
}
|
|
6530
|
+
let hardenDone = false;
|
|
6531
|
+
let hardenedValues = null;
|
|
6532
|
+
const hardenOnce = () => {
|
|
6533
|
+
if (hardenDone) return hardenedValues;
|
|
6534
|
+
hardenDone = true;
|
|
6535
|
+
if (!pendingHead) {
|
|
6536
|
+
hardenedValues = hardenBeforeFlush();
|
|
6537
|
+
return hardenedValues;
|
|
6538
|
+
}
|
|
6539
|
+
const at = writeHeadHeaderIndex(pendingHead);
|
|
6540
|
+
hardenedValues = hardenBeforeFlush({
|
|
6541
|
+
status: typeof pendingHead[0] === "number" ? pendingHead[0] : void 0,
|
|
6542
|
+
headers: at === -1 ? null : writeHeadHeaderObject(pendingHead[at])
|
|
6543
|
+
});
|
|
6544
|
+
return hardenedValues;
|
|
6545
|
+
};
|
|
6546
|
+
const effectiveHead = () => {
|
|
6547
|
+
const set = typeof res.getHeaders === "function" ? { ...res.getHeaders() } : {};
|
|
6548
|
+
if (typeof res.getHeaders !== "function" && typeof res.getHeader === "function") {
|
|
6549
|
+
for (const name of ["content-type", "content-length"]) {
|
|
6550
|
+
const value = res.getHeader(name);
|
|
6551
|
+
if (value !== void 0) set[name] = value;
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
if (!pendingHead) return { status: res.statusCode, headers: set };
|
|
6555
|
+
const at = writeHeadHeaderIndex(pendingHead);
|
|
6556
|
+
return {
|
|
6557
|
+
status: typeof pendingHead[0] === "number" ? pendingHead[0] : res.statusCode,
|
|
6558
|
+
headers: at === -1 ? set : { ...set, ...writeHeadHeaderObject(pendingHead[at]) }
|
|
6559
|
+
};
|
|
6560
|
+
};
|
|
6561
|
+
const sendHead = (changes) => {
|
|
6562
|
+
if (sending) return;
|
|
6563
|
+
const hardened2 = hardenOnce();
|
|
6564
|
+
sending = true;
|
|
6565
|
+
if (!pendingHead) {
|
|
6566
|
+
if (changes) applyToResponse(changes);
|
|
6567
|
+
return;
|
|
6568
|
+
}
|
|
6569
|
+
const args = [...pendingHead];
|
|
6570
|
+
pendingHead = null;
|
|
6571
|
+
const merged = new Map(hardened2 ?? []);
|
|
6572
|
+
for (const [name, value] of changes?.headers ?? []) merged.set(name, value);
|
|
6573
|
+
if (changes?.status !== void 0) {
|
|
6574
|
+
args[0] = changes.status;
|
|
6575
|
+
if (typeof args[1] === "string") args.splice(1, 1);
|
|
6576
|
+
}
|
|
6577
|
+
const headerAt = writeHeadHeaderIndex(args);
|
|
6578
|
+
if (headerAt !== -1 && merged.size) args[headerAt] = rewriteWriteHeadHeaders(args[headerAt], merged);
|
|
6579
|
+
applyToResponse({ headers: merged });
|
|
6580
|
+
origWriteHead(...args);
|
|
6581
|
+
};
|
|
6582
|
+
const applyToResponse = (changes) => {
|
|
6583
|
+
if (changes.status !== void 0) res.statusCode = changes.status;
|
|
6584
|
+
for (const [name, value] of changes.headers ?? []) {
|
|
6585
|
+
try {
|
|
6586
|
+
if (value === null || value === void 0) removeHeaderNow?.(name);
|
|
6587
|
+
else setHeaderNow?.(name, value);
|
|
6588
|
+
} catch {
|
|
6589
|
+
}
|
|
6590
|
+
}
|
|
6591
|
+
};
|
|
6085
6592
|
const collect = (chunk, enc) => {
|
|
6086
6593
|
if (chunk == null) return;
|
|
6087
6594
|
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, typeof enc === "string" ? enc : "utf8");
|
|
6088
6595
|
size += buf.length;
|
|
6089
6596
|
if (size > MAX) {
|
|
6597
|
+
sendHead();
|
|
6090
6598
|
for (const c of chunks) origWrite(c);
|
|
6091
6599
|
chunks.length = 0;
|
|
6092
6600
|
origWrite(buf);
|
|
@@ -6097,7 +6605,6 @@ async function createProtection(options = {}) {
|
|
|
6097
6605
|
chunks.push(buf);
|
|
6098
6606
|
};
|
|
6099
6607
|
res.write = function(chunk, enc, cb) {
|
|
6100
|
-
hardenBeforeFlush();
|
|
6101
6608
|
if (overflow) return origWrite(chunk, enc, cb);
|
|
6102
6609
|
collect(chunk, enc);
|
|
6103
6610
|
if (typeof enc === "function") enc();
|
|
@@ -6105,7 +6612,6 @@ async function createProtection(options = {}) {
|
|
|
6105
6612
|
return true;
|
|
6106
6613
|
};
|
|
6107
6614
|
res.end = function(chunk, enc, cb) {
|
|
6108
|
-
hardenBeforeFlush();
|
|
6109
6615
|
if (typeof chunk === "function") {
|
|
6110
6616
|
cb = chunk;
|
|
6111
6617
|
chunk = void 0;
|
|
@@ -6120,68 +6626,77 @@ async function createProtection(options = {}) {
|
|
|
6120
6626
|
}
|
|
6121
6627
|
collect(chunk, enc);
|
|
6122
6628
|
if (overflow) return origEnd(cb);
|
|
6629
|
+
hardenOnce();
|
|
6630
|
+
const passThrough = () => {
|
|
6631
|
+
sendHead();
|
|
6632
|
+
for (const c of chunks) origWrite(c);
|
|
6633
|
+
return origEnd(cb);
|
|
6634
|
+
};
|
|
6123
6635
|
const buffer = Buffer.concat(chunks);
|
|
6124
|
-
|
|
6636
|
+
const head = effectiveHead();
|
|
6637
|
+
let ct = head.headers["content-type"];
|
|
6125
6638
|
if (Array.isArray(ct)) ct = ct[0];
|
|
6126
6639
|
const kind = screenableContentType(ct);
|
|
6127
6640
|
if (kind === "skip" || kind === "sniff" && looksBinary(buffer)) {
|
|
6128
6641
|
recordSkip("response", kind === "skip" ? baseContentType(ct) === "text/event-stream" ? "live-stream" : "non-text-content-type" : "binary-body");
|
|
6129
|
-
|
|
6130
|
-
|
|
6642
|
+
return passThrough();
|
|
6643
|
+
}
|
|
6644
|
+
const codings = declaredCodings(head.headers["content-encoding"]);
|
|
6645
|
+
if (codings.length > 0 && stillEncoded(buffer)) {
|
|
6646
|
+
recordSkip("response", "encoded-body", { encoding: codings.join(", ") });
|
|
6647
|
+
return passThrough();
|
|
6131
6648
|
}
|
|
6132
6649
|
const text = buffer.toString("utf8");
|
|
6133
6650
|
let r;
|
|
6134
6651
|
try {
|
|
6135
|
-
r = screenText(
|
|
6136
|
-
text,
|
|
6137
|
-
{ status: res.statusCode, headers: res.getHeaders ? res.getHeaders() : {} },
|
|
6138
|
-
reqCtx,
|
|
6139
|
-
answeredWithoutBody ? stillToAnswer : void 0
|
|
6140
|
-
);
|
|
6652
|
+
r = screenText(text, head, reqCtx, answeredWithoutBody ? stillToAnswer : void 0);
|
|
6141
6653
|
} catch (err) {
|
|
6142
6654
|
notify(onError, err, "onError");
|
|
6143
|
-
|
|
6144
|
-
|
|
6655
|
+
return passThrough();
|
|
6656
|
+
}
|
|
6657
|
+
if (r.verdict !== "block" && r.verdict !== "redact") return passThrough();
|
|
6658
|
+
if (reallySent() && res.chunkedEncoding !== true) {
|
|
6659
|
+
notify(onError, new Error("Patchstack: response withheld \u2014 its headers were sent before the body could be screened"), "onError");
|
|
6660
|
+
res.destroy?.();
|
|
6661
|
+
return res;
|
|
6145
6662
|
}
|
|
6146
6663
|
if (r.verdict === "block") {
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
res.
|
|
6150
|
-
|
|
6664
|
+
const body = JSON.stringify({ error: "Response withheld by Patchstack (sensitive data detected)" });
|
|
6665
|
+
if (reallySent()) {
|
|
6666
|
+
res.destroy?.();
|
|
6667
|
+
return res;
|
|
6151
6668
|
}
|
|
6152
|
-
|
|
6669
|
+
sendHead({
|
|
6670
|
+
status: 500,
|
|
6671
|
+
headers: /* @__PURE__ */ new Map([
|
|
6672
|
+
["content-type", "application/json"],
|
|
6673
|
+
["content-length", String(Buffer.byteLength(body))],
|
|
6674
|
+
["transfer-encoding", null]
|
|
6675
|
+
])
|
|
6676
|
+
});
|
|
6677
|
+
return origEnd(body, cb);
|
|
6153
6678
|
}
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6679
|
+
const changed = /* @__PURE__ */ new Map();
|
|
6680
|
+
if (r.headers) {
|
|
6681
|
+
for (const [name, value] of Object.entries(r.headers)) {
|
|
6682
|
+
if (name.toLowerCase() === "content-length") continue;
|
|
6683
|
+
if (!headerValueChanged(head.headers[name], value)) continue;
|
|
6684
|
+
changed.set(name.toLowerCase(), value);
|
|
6158
6685
|
}
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
}
|
|
6168
|
-
} else if (Array.isArray(value)) {
|
|
6169
|
-
try {
|
|
6170
|
-
res.setHeader(name, value);
|
|
6171
|
-
} catch {
|
|
6172
|
-
}
|
|
6173
|
-
} else if (typeof value === "string" && current[name] !== value) {
|
|
6174
|
-
try {
|
|
6175
|
-
res.setHeader(name, value);
|
|
6176
|
-
} catch {
|
|
6177
|
-
}
|
|
6178
|
-
}
|
|
6179
|
-
}
|
|
6686
|
+
}
|
|
6687
|
+
if (!reallySent()) {
|
|
6688
|
+
const declared = head.headers["content-length"];
|
|
6689
|
+
if (declared === void 0) {
|
|
6690
|
+
changed.set("content-length", null);
|
|
6691
|
+
} else {
|
|
6692
|
+
changed.set("content-length", String(Buffer.byteLength(r.body)));
|
|
6693
|
+
changed.set("transfer-encoding", null);
|
|
6180
6694
|
}
|
|
6181
|
-
|
|
6695
|
+
sendHead({ headers: changed });
|
|
6696
|
+
} else if (changed.size) {
|
|
6697
|
+
recordSkip("response", "headers-sent", { headers: [...changed.keys()] });
|
|
6182
6698
|
}
|
|
6183
|
-
|
|
6184
|
-
return origEnd(cb);
|
|
6699
|
+
return origEnd(r.body, cb);
|
|
6185
6700
|
};
|
|
6186
6701
|
};
|
|
6187
6702
|
const allow = new Set(allowHosts.map((h) => String(h).toLowerCase()));
|
|
@@ -6394,6 +6909,7 @@ async function createProtection(options = {}) {
|
|
|
6394
6909
|
notify(onError, err, "onError");
|
|
6395
6910
|
}
|
|
6396
6911
|
}
|
|
6912
|
+
let recovery = null;
|
|
6397
6913
|
const runRefreshTick = async () => {
|
|
6398
6914
|
if (reporter) {
|
|
6399
6915
|
try {
|
|
@@ -6410,23 +6926,33 @@ async function createProtection(options = {}) {
|
|
|
6410
6926
|
});
|
|
6411
6927
|
mode = resolveMode(options, next);
|
|
6412
6928
|
applyBundle(next);
|
|
6929
|
+
noteRuleSource(next.source);
|
|
6930
|
+
if (next.source?.ok !== false) recovery?.stop();
|
|
6413
6931
|
await applyAndAcknowledge(next.source?.origin, declaredState);
|
|
6414
6932
|
if (detections) detections.setRulesEtag((await store.read())?.etag ?? null);
|
|
6415
6933
|
return next.source ?? { ok: true };
|
|
6416
6934
|
};
|
|
6935
|
+
const refreshTick = serialise(runRefreshTick);
|
|
6417
6936
|
if (live) {
|
|
6418
|
-
protection.refresh = () =>
|
|
6419
|
-
protection.refreshHandler = () => makeRefreshHandler(
|
|
6937
|
+
protection.refresh = () => refreshTick();
|
|
6938
|
+
protection.refreshHandler = () => makeRefreshHandler(refreshTick, refreshSecret);
|
|
6420
6939
|
}
|
|
6421
|
-
const loop = options.refreshMs > 0 && live ? startRefresh(
|
|
6940
|
+
const loop = options.refreshMs > 0 && live ? startRefresh(refreshTick, { refreshMs: options.refreshMs, onError }) : null;
|
|
6941
|
+
const canAsk = Boolean(options.token || pulseAuth);
|
|
6942
|
+
recovery = live && canAsk && !loop && ruleSource.ok === false ? startRecovery(refreshTick, { onError }) : null;
|
|
6422
6943
|
protection.stop = () => {
|
|
6423
6944
|
loop?.stop();
|
|
6945
|
+
recovery?.stop();
|
|
6424
6946
|
const outstanding = [firewallLog?.stop(), detections?.stop()].filter(
|
|
6425
6947
|
(wait) => wait && typeof wait.then === "function"
|
|
6426
6948
|
);
|
|
6427
6949
|
return Promise.all(outstanding).then(() => void 0);
|
|
6428
6950
|
};
|
|
6429
6951
|
protection.stopRefresh = protection.stop;
|
|
6952
|
+
Object.defineProperty(protection, "ruleSource", {
|
|
6953
|
+
get: () => ({ ...ruleSource }),
|
|
6954
|
+
enumerable: true
|
|
6955
|
+
});
|
|
6430
6956
|
Object.defineProperty(protection, "detectionReporting", {
|
|
6431
6957
|
get: () => detectionReporting,
|
|
6432
6958
|
enumerable: true
|
|
@@ -6437,6 +6963,16 @@ async function createProtection(options = {}) {
|
|
|
6437
6963
|
});
|
|
6438
6964
|
return protection;
|
|
6439
6965
|
}
|
|
6966
|
+
function accessorOf(obj, name) {
|
|
6967
|
+
for (let o = obj; o; o = Object.getPrototypeOf(o)) {
|
|
6968
|
+
const d = Object.getOwnPropertyDescriptor(o, name);
|
|
6969
|
+
if (d) return typeof d.get === "function" ? d.get : null;
|
|
6970
|
+
}
|
|
6971
|
+
return null;
|
|
6972
|
+
}
|
|
6973
|
+
function headersSentError(verb) {
|
|
6974
|
+
return Object.assign(new Error(`Cannot ${verb} headers after they are sent to the client`), { code: "ERR_HTTP_HEADERS_SENT" });
|
|
6975
|
+
}
|
|
6440
6976
|
function resolveMode(options, bundle) {
|
|
6441
6977
|
const env = typeof process !== "undefined" ? process.env?.PATCHSTACK_MODE : void 0;
|
|
6442
6978
|
if (env === "block" || env === "dry-run") return env;
|
|
@@ -6509,11 +7045,26 @@ function looksBinary(bytes) {
|
|
|
6509
7045
|
}
|
|
6510
7046
|
return n > 0 && ctrl / n > 0.1;
|
|
6511
7047
|
}
|
|
7048
|
+
function declaredCodings(value) {
|
|
7049
|
+
const raw = Array.isArray(value) ? value.join(",") : String(value ?? "");
|
|
7050
|
+
return raw.toLowerCase().split(",").map((c) => c.trim()).filter((c) => c !== "" && c !== "identity");
|
|
7051
|
+
}
|
|
7052
|
+
function stillEncoded(bytes) {
|
|
7053
|
+
if (looksBinary(bytes)) return true;
|
|
7054
|
+
try {
|
|
7055
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
7056
|
+
return false;
|
|
7057
|
+
} catch {
|
|
7058
|
+
return true;
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
6512
7061
|
async function readTextResponse(response, cap = DEFAULT_SCREEN_CAP) {
|
|
6513
7062
|
if (!response || typeof response.clone !== "function") return { skip: "not-a-response" };
|
|
6514
7063
|
const ct = response.headers?.get?.("content-type") || "";
|
|
6515
7064
|
const kind = screenableContentType(ct);
|
|
6516
7065
|
if (kind === "skip") return { skip: baseContentType(ct) === "text/event-stream" ? "live-stream" : "non-text-content-type" };
|
|
7066
|
+
const codings = declaredCodings(response.headers?.get?.("content-encoding"));
|
|
7067
|
+
const encodedSkip = () => ({ skip: "encoded-body", encoding: codings.join(", ") });
|
|
6517
7068
|
const sniff = kind === "sniff";
|
|
6518
7069
|
const len = Number(response.headers?.get?.("content-length") || 0);
|
|
6519
7070
|
if (len && len > cap) return { skip: "body-cap" };
|
|
@@ -6557,13 +7108,21 @@ async function readTextResponse(response, cap = DEFAULT_SCREEN_CAP) {
|
|
|
6557
7108
|
} finally {
|
|
6558
7109
|
reader.releaseLock();
|
|
6559
7110
|
}
|
|
7111
|
+
const bytes = concatBytes(chunks, size);
|
|
7112
|
+
if (codings.length > 0 && stillEncoded(bytes)) return encodedSkip();
|
|
6560
7113
|
try {
|
|
6561
|
-
return { text: new TextDecoder().decode(
|
|
7114
|
+
return { text: new TextDecoder().decode(bytes) };
|
|
6562
7115
|
} catch {
|
|
6563
7116
|
return { skip: "decode-failed" };
|
|
6564
7117
|
}
|
|
6565
7118
|
}
|
|
6566
7119
|
try {
|
|
7120
|
+
if (codings.length > 0) {
|
|
7121
|
+
const bytes = new Uint8Array(await clone.arrayBuffer());
|
|
7122
|
+
if (bytes.byteLength > cap) return { skip: "body-cap" };
|
|
7123
|
+
if (stillEncoded(bytes)) return encodedSkip();
|
|
7124
|
+
return { text: new TextDecoder().decode(bytes) };
|
|
7125
|
+
}
|
|
6567
7126
|
const text = await clone.text();
|
|
6568
7127
|
if (text.length > cap) return { skip: "body-cap" };
|
|
6569
7128
|
if (sniff && looksBinary(new TextEncoder().encode(text.slice(0, 512)))) return { skip: "binary-body" };
|
|
@@ -6637,6 +7196,39 @@ function extractRedactors(rule) {
|
|
|
6637
7196
|
function htmlEscape(str) {
|
|
6638
7197
|
return String(str).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
6639
7198
|
}
|
|
7199
|
+
function isJson(text) {
|
|
7200
|
+
try {
|
|
7201
|
+
JSON.parse(text);
|
|
7202
|
+
return true;
|
|
7203
|
+
} catch {
|
|
7204
|
+
return false;
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
7207
|
+
function preservesJsonStructure(before, after) {
|
|
7208
|
+
if (!isJson(before)) return true;
|
|
7209
|
+
if (!isJson(after)) return false;
|
|
7210
|
+
const expected = jsonStructure(before);
|
|
7211
|
+
const actual = jsonStructure(after);
|
|
7212
|
+
for (; ; ) {
|
|
7213
|
+
const left = expected.next();
|
|
7214
|
+
const right = actual.next();
|
|
7215
|
+
if (left.done || right.done) return left.done === right.done;
|
|
7216
|
+
if (left.value !== right.value) return false;
|
|
7217
|
+
}
|
|
7218
|
+
}
|
|
7219
|
+
function* jsonStructure(text) {
|
|
7220
|
+
const tokens = /"(?:[^"\\]|\\[\s\S])*"|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|[^\s]/g;
|
|
7221
|
+
for (const match of text.matchAll(tokens)) {
|
|
7222
|
+
const token = match[0];
|
|
7223
|
+
if (token[0] !== '"') {
|
|
7224
|
+
yield "token:" + token;
|
|
7225
|
+
continue;
|
|
7226
|
+
}
|
|
7227
|
+
let next = match.index + token.length;
|
|
7228
|
+
while (text[next] === " " || text[next] === " " || text[next] === "\r" || text[next] === "\n") next++;
|
|
7229
|
+
yield text[next] === ":" ? "key:" + JSON.parse(token) : "string";
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
6640
7232
|
function applyRedactors(body, redactors, mask, transform) {
|
|
6641
7233
|
let out = body;
|
|
6642
7234
|
for (const r of redactors) {
|
|
@@ -6679,10 +7271,12 @@ function applyPathRedactors(text, pathRedactors, mask, cap, transform) {
|
|
|
6679
7271
|
if (!pathRedactors.length || typeof text !== "string" || text.length > cap) return text;
|
|
6680
7272
|
const head = text.trimStart()[0];
|
|
6681
7273
|
if (head !== "{" && head !== "[") return text;
|
|
6682
|
-
|
|
7274
|
+
let preserved;
|
|
6683
7275
|
let obj;
|
|
6684
7276
|
try {
|
|
6685
|
-
|
|
7277
|
+
JSON.parse(text);
|
|
7278
|
+
preserved = preserveBigInts(text, mask);
|
|
7279
|
+
obj = JSON.parse(preserved.text);
|
|
6686
7280
|
} catch {
|
|
6687
7281
|
return text;
|
|
6688
7282
|
}
|
|
@@ -6691,65 +7285,44 @@ function applyPathRedactors(text, pathRedactors, mask, cap, transform) {
|
|
|
6691
7285
|
const pred = conditionPredicate(r.condition);
|
|
6692
7286
|
walkLeaves(obj, r.jsonPath, (loc) => {
|
|
6693
7287
|
try {
|
|
6694
|
-
|
|
6695
|
-
|
|
7288
|
+
const number = preserved.numbers.get(loc.value);
|
|
7289
|
+
if (pred(number === void 0 ? loc.value : Number(number))) {
|
|
7290
|
+
setOwn(loc.parent, loc.key, transform ? transform(number ?? String(loc.value)) : mask);
|
|
6696
7291
|
changed = true;
|
|
6697
7292
|
}
|
|
6698
7293
|
} catch {
|
|
6699
7294
|
}
|
|
6700
7295
|
});
|
|
6701
7296
|
}
|
|
6702
|
-
return changed ? restoreBigInts(JSON.stringify(obj)) : text;
|
|
6703
|
-
}
|
|
6704
|
-
var BIGINT_OPEN = "__PSBIGINT_9c2f__";
|
|
6705
|
-
var BIGINT_CLOSE = "__DNEGIB__";
|
|
6706
|
-
function preserveBigInts(text) {
|
|
6707
|
-
let out = "";
|
|
6708
|
-
let inStr = false;
|
|
6709
|
-
for (let i = 0; i < text.length; ) {
|
|
6710
|
-
const ch = text[i];
|
|
6711
|
-
if (inStr) {
|
|
6712
|
-
out += ch;
|
|
6713
|
-
if (ch === "\\") {
|
|
6714
|
-
out += text[i + 1] ?? "";
|
|
6715
|
-
i += 2;
|
|
6716
|
-
continue;
|
|
6717
|
-
}
|
|
6718
|
-
if (ch === '"') inStr = false;
|
|
6719
|
-
i++;
|
|
6720
|
-
continue;
|
|
6721
|
-
}
|
|
6722
|
-
if (ch === '"') {
|
|
6723
|
-
inStr = true;
|
|
6724
|
-
out += ch;
|
|
6725
|
-
i++;
|
|
6726
|
-
continue;
|
|
6727
|
-
}
|
|
6728
|
-
if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
6729
|
-
let j = ch === "-" ? i + 1 : i;
|
|
6730
|
-
let digits = 0;
|
|
6731
|
-
while (j < text.length && text[j] >= "0" && text[j] <= "9") {
|
|
6732
|
-
digits++;
|
|
6733
|
-
j++;
|
|
6734
|
-
}
|
|
6735
|
-
const next = text[j];
|
|
6736
|
-
const isIntToken = digits > 0 && next !== "." && next !== "e" && next !== "E";
|
|
6737
|
-
if (isIntToken && digits >= 16) {
|
|
6738
|
-
out += `"${BIGINT_OPEN}${text.slice(i, j)}${BIGINT_CLOSE}"`;
|
|
6739
|
-
} else {
|
|
6740
|
-
out += text.slice(i, j || i + 1);
|
|
6741
|
-
}
|
|
6742
|
-
i = j > i ? j : i + 1;
|
|
6743
|
-
continue;
|
|
6744
|
-
}
|
|
6745
|
-
out += ch;
|
|
6746
|
-
i++;
|
|
6747
|
-
}
|
|
6748
|
-
return out;
|
|
7297
|
+
return changed ? restoreBigInts(JSON.stringify(obj), preserved.numbers) : text;
|
|
6749
7298
|
}
|
|
6750
|
-
function
|
|
6751
|
-
|
|
6752
|
-
|
|
7299
|
+
function preserveBigInts(text, mask) {
|
|
7300
|
+
const occupied = /* @__PURE__ */ new Set([mask]);
|
|
7301
|
+
const integers = [];
|
|
7302
|
+
const tokens = /"(?:[^"\\]|\\[\s\S])*"|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/g;
|
|
7303
|
+
for (const match of text.matchAll(tokens)) {
|
|
7304
|
+
const token = match[0];
|
|
7305
|
+
if (token[0] === '"') occupied.add(JSON.parse(token));
|
|
7306
|
+
else if (/^-?\d{16,}$/.test(token)) integers.push({ start: match.index, token });
|
|
7307
|
+
}
|
|
7308
|
+
const numbers = /* @__PURE__ */ new Map();
|
|
7309
|
+
const chunks = [];
|
|
7310
|
+
let offset = 0;
|
|
7311
|
+
let index = 0;
|
|
7312
|
+
for (const { start, token } of integers) {
|
|
7313
|
+
let marker;
|
|
7314
|
+
do {
|
|
7315
|
+
marker = "__PSNUMBER_" + index++ + "__";
|
|
7316
|
+
} while (occupied.has(marker));
|
|
7317
|
+
numbers.set(marker, token);
|
|
7318
|
+
chunks.push(text.slice(offset, start), JSON.stringify(marker));
|
|
7319
|
+
offset = start + token.length;
|
|
7320
|
+
}
|
|
7321
|
+
chunks.push(text.slice(offset));
|
|
7322
|
+
return { text: chunks.join(""), numbers };
|
|
7323
|
+
}
|
|
7324
|
+
function restoreBigInts(text, numbers) {
|
|
7325
|
+
return text.replace(/"(__PSNUMBER_\d+__)"/g, (token, marker) => numbers.get(marker) ?? token);
|
|
6753
7326
|
}
|
|
6754
7327
|
function isHeaderMutation(action) {
|
|
6755
7328
|
return action === "set-header" || action === "remove-header" || action === "harden-cookie";
|