@patchstack/connect 0.5.11 → 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 +49 -20
- package/README.md +19 -19
- package/dist/cli.js +760 -644
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +20 -12
- 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 +20 -12
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +751 -170
- 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-PPV22Q2E.js → refresh-manifest-DZQOEO3Q.js} +19 -11
- package/dist/refresh-manifest-DZQOEO3Q.js.map +1 -0
- package/package.json +1 -1
- package/dist/refresh-manifest-PPV22Q2E.js.map +0 -1
package/dist/protect.js
CHANGED
|
@@ -1185,6 +1185,184 @@ function hasAdjacentUnbounded(body, flags) {
|
|
|
1185
1185
|
}
|
|
1186
1186
|
return false;
|
|
1187
1187
|
}
|
|
1188
|
+
function parseRepetition(body) {
|
|
1189
|
+
let i = 0;
|
|
1190
|
+
const parseAlternation = () => {
|
|
1191
|
+
const branches = [parseSequence()];
|
|
1192
|
+
while (body[i] === "|") {
|
|
1193
|
+
i++;
|
|
1194
|
+
branches.push(parseSequence());
|
|
1195
|
+
}
|
|
1196
|
+
return branches.length === 1 ? branches[0] : { type: "alt", branches };
|
|
1197
|
+
};
|
|
1198
|
+
const parseSequence = () => {
|
|
1199
|
+
const items = [];
|
|
1200
|
+
while (i < body.length && body[i] !== "|" && body[i] !== ")") {
|
|
1201
|
+
const atom = parseAtom();
|
|
1202
|
+
if (atom === null) return null;
|
|
1203
|
+
const quantified = parseQuantifier(atom);
|
|
1204
|
+
if (quantified === null) return null;
|
|
1205
|
+
items.push(quantified);
|
|
1206
|
+
}
|
|
1207
|
+
return { type: "seq", items };
|
|
1208
|
+
};
|
|
1209
|
+
const parseAtom = () => {
|
|
1210
|
+
const ch = body[i];
|
|
1211
|
+
if (ch === "(") {
|
|
1212
|
+
const start2 = i;
|
|
1213
|
+
i++;
|
|
1214
|
+
let kind = "capture";
|
|
1215
|
+
if (body.startsWith("?:", i)) {
|
|
1216
|
+
kind = "group";
|
|
1217
|
+
i += 2;
|
|
1218
|
+
} else if (body.startsWith("?=", i) || body.startsWith("?!", i)) {
|
|
1219
|
+
kind = "look";
|
|
1220
|
+
i += 2;
|
|
1221
|
+
} else if (body.startsWith("?<=", i) || body.startsWith("?<!", i)) {
|
|
1222
|
+
kind = "look";
|
|
1223
|
+
i += 3;
|
|
1224
|
+
} else if (body.startsWith("?<", i)) {
|
|
1225
|
+
const close = body.indexOf(">", i + 2);
|
|
1226
|
+
if (close === -1) return null;
|
|
1227
|
+
i = close + 1;
|
|
1228
|
+
} else if (body[i] === "?") {
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
const inner = parseAlternation();
|
|
1232
|
+
if (inner === null || body[i] !== ")") return null;
|
|
1233
|
+
i++;
|
|
1234
|
+
return { type: "group", kind, body: inner, source: body.slice(start2, i) };
|
|
1235
|
+
}
|
|
1236
|
+
const start = i;
|
|
1237
|
+
if (ch === "[") {
|
|
1238
|
+
i++;
|
|
1239
|
+
if (body[i] === "^") i++;
|
|
1240
|
+
if (body[i] === "]") i++;
|
|
1241
|
+
while (i < body.length && body[i] !== "]") i += body[i] === "\\" ? 2 : 1;
|
|
1242
|
+
if (body[i] !== "]") return null;
|
|
1243
|
+
i++;
|
|
1244
|
+
return { type: "atom", source: body.slice(start, i) };
|
|
1245
|
+
}
|
|
1246
|
+
if (ch === "\\") {
|
|
1247
|
+
const next = body[i + 1];
|
|
1248
|
+
if (next === void 0) return null;
|
|
1249
|
+
if ((next === "p" || next === "P" || next === "u") && body[i + 2] === "{") {
|
|
1250
|
+
const close = body.indexOf("}", i + 3);
|
|
1251
|
+
if (close === -1) return null;
|
|
1252
|
+
i = close + 1;
|
|
1253
|
+
} else if (next === "k" && body[i + 2] === "<") {
|
|
1254
|
+
const close = body.indexOf(">", i + 3);
|
|
1255
|
+
if (close === -1) return null;
|
|
1256
|
+
i = close + 1;
|
|
1257
|
+
} else {
|
|
1258
|
+
i += 2;
|
|
1259
|
+
}
|
|
1260
|
+
return { type: /[bB]/.test(next) ? "assert" : "atom", source: body.slice(start, i) };
|
|
1261
|
+
}
|
|
1262
|
+
if (ch === "^" || ch === "$") {
|
|
1263
|
+
i++;
|
|
1264
|
+
return { type: "assert" };
|
|
1265
|
+
}
|
|
1266
|
+
if (ch === "*" || ch === "+" || ch === "?") return null;
|
|
1267
|
+
i++;
|
|
1268
|
+
return { type: "atom", source: body.slice(start, i) };
|
|
1269
|
+
};
|
|
1270
|
+
const parseQuantifier = (atom) => {
|
|
1271
|
+
let min;
|
|
1272
|
+
let max;
|
|
1273
|
+
const ch = body[i];
|
|
1274
|
+
if (ch === "*") [min, max, i] = [0, Infinity, i + 1];
|
|
1275
|
+
else if (ch === "+") [min, max, i] = [1, Infinity, i + 1];
|
|
1276
|
+
else if (ch === "?") [min, max, i] = [0, 1, i + 1];
|
|
1277
|
+
else if (ch === "{") {
|
|
1278
|
+
const bounds = /^\{(\d+)(,(\d*))?\}/.exec(body.slice(i));
|
|
1279
|
+
if (!bounds) return atom;
|
|
1280
|
+
min = Number(bounds[1]);
|
|
1281
|
+
max = bounds[2] === void 0 ? min : bounds[3] === "" ? Infinity : Number(bounds[3]);
|
|
1282
|
+
i += bounds[0].length;
|
|
1283
|
+
} else {
|
|
1284
|
+
return atom;
|
|
1285
|
+
}
|
|
1286
|
+
if (body[i] === "?") i++;
|
|
1287
|
+
if (body[i] === "*" || body[i] === "+" || body[i] === "?" || body[i] === "{") return null;
|
|
1288
|
+
return { type: "quant", min, max, body: atom };
|
|
1289
|
+
};
|
|
1290
|
+
const root = parseAlternation();
|
|
1291
|
+
return root !== null && i === body.length ? root : null;
|
|
1292
|
+
}
|
|
1293
|
+
function* nodesOf(node) {
|
|
1294
|
+
if (!node) return;
|
|
1295
|
+
yield node;
|
|
1296
|
+
if (node.type === "alt") for (const branch of node.branches) yield* nodesOf(branch);
|
|
1297
|
+
else if (node.type === "seq") for (const item of node.items) yield* nodesOf(item);
|
|
1298
|
+
else if (node.type === "group" || node.type === "quant") yield* nodesOf(node.body);
|
|
1299
|
+
}
|
|
1300
|
+
var BOUNDED_NESTING_LIMIT = 100;
|
|
1301
|
+
var BOUNDED_ALTERNATION_LIMIT = 16;
|
|
1302
|
+
var OPTIONAL_REPEAT_LIMIT = 8;
|
|
1303
|
+
function hasFencedRepetitions(quant, flags) {
|
|
1304
|
+
let body = quant.body;
|
|
1305
|
+
if (body.type === "group" && body.kind !== "look") body = body.body;
|
|
1306
|
+
if (body.type !== "seq" || body.items.length < 2) return false;
|
|
1307
|
+
const variable = [...nodesOf(body)].filter((n) => n.type === "quant" && (n.max > 1 || n.min < n.max));
|
|
1308
|
+
if (variable.some((n) => n.body.type !== "atom" || n.body.source === void 0)) return false;
|
|
1309
|
+
const atomFlags = flags.replace(/[^i]/g, "");
|
|
1310
|
+
const unicode = /[uv]/.test(flags);
|
|
1311
|
+
let insides;
|
|
1312
|
+
try {
|
|
1313
|
+
insides = variable.map((n) => new RegExp(`^(?:${n.body.source})$`, `${atomFlags}${unicode ? "u" : ""}`));
|
|
1314
|
+
} catch {
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
const literalOf = (source) => {
|
|
1318
|
+
if (source.length === 1 && !".[]()|^$\\".includes(source)) return source;
|
|
1319
|
+
if (source.length === 2 && source[0] === "\\" && /[^\w\s]/.test(source[1])) return source[1];
|
|
1320
|
+
return null;
|
|
1321
|
+
};
|
|
1322
|
+
const disjoint = (edge) => {
|
|
1323
|
+
if (edge?.type !== "atom" || edge.source === void 0) return false;
|
|
1324
|
+
const literal = literalOf(edge.source);
|
|
1325
|
+
if (literal !== null) return insides.every((re) => !re.test(literal));
|
|
1326
|
+
if (unicode) return false;
|
|
1327
|
+
let fence;
|
|
1328
|
+
try {
|
|
1329
|
+
fence = new RegExp(`^(?:${edge.source})$`, atomFlags);
|
|
1330
|
+
} catch {
|
|
1331
|
+
return false;
|
|
1332
|
+
}
|
|
1333
|
+
for (let unit = 0; unit <= 65535; unit++) {
|
|
1334
|
+
const ch = String.fromCharCode(unit);
|
|
1335
|
+
if (fence.test(ch) && insides.some((re) => re.test(ch))) return false;
|
|
1336
|
+
}
|
|
1337
|
+
return true;
|
|
1338
|
+
};
|
|
1339
|
+
return disjoint(body.items[0]) || disjoint(body.items[body.items.length - 1]);
|
|
1340
|
+
}
|
|
1341
|
+
function hasCatastrophicShape(body, flags) {
|
|
1342
|
+
const root = parseRepetition(body);
|
|
1343
|
+
if (root === null) return null;
|
|
1344
|
+
for (const node of nodesOf(root)) {
|
|
1345
|
+
if (node.type === "group" && node.kind === "look" && hasAdjacentUnbounded(lookaroundBody(node.source), flags)) return true;
|
|
1346
|
+
if (node.type !== "quant" || node.max <= 1) continue;
|
|
1347
|
+
const fenced = hasFencedRepetitions(node, flags);
|
|
1348
|
+
for (const inner of nodesOf(node.body)) {
|
|
1349
|
+
if (inner === node.body && inner.type === "quant") continue;
|
|
1350
|
+
if (inner.type === "quant" && inner.max > 1 && !fenced) {
|
|
1351
|
+
if (node.max === Infinity || inner.max === Infinity || node.max * inner.max > BOUNDED_NESTING_LIMIT) return true;
|
|
1352
|
+
}
|
|
1353
|
+
if (inner.type === "quant" && inner.min === 0 && inner.max <= 1 && !fenced && (node.max === Infinity || node.max > OPTIONAL_REPEAT_LIMIT)) {
|
|
1354
|
+
return true;
|
|
1355
|
+
}
|
|
1356
|
+
if (inner.type === "alt" && inner.branches.length > 1 && (node.max === Infinity || node.max > BOUNDED_ALTERNATION_LIMIT)) {
|
|
1357
|
+
return true;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
return false;
|
|
1362
|
+
}
|
|
1363
|
+
function lookaroundBody(source) {
|
|
1364
|
+
return source.slice(source.startsWith("(?<") ? 4 : 3, -1);
|
|
1365
|
+
}
|
|
1188
1366
|
function groupEnd(body, start) {
|
|
1189
1367
|
let depth = 0;
|
|
1190
1368
|
let inClass = false;
|
|
@@ -1211,6 +1389,8 @@ function warnRejectedPatternOnce(pattern) {
|
|
|
1211
1389
|
);
|
|
1212
1390
|
}
|
|
1213
1391
|
var MAX_PATTERN_LENGTH = 1e3;
|
|
1392
|
+
var regexVerdicts = /* @__PURE__ */ new Map();
|
|
1393
|
+
var REGEX_VERDICT_LIMIT = 2048;
|
|
1214
1394
|
function safeRegExp(pattern) {
|
|
1215
1395
|
if (!pattern) {
|
|
1216
1396
|
return null;
|
|
@@ -1222,16 +1402,32 @@ function safeRegExp(pattern) {
|
|
|
1222
1402
|
if (!match) {
|
|
1223
1403
|
return null;
|
|
1224
1404
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1405
|
+
let safe = regexVerdicts.get(pattern);
|
|
1406
|
+
if (safe === void 0) {
|
|
1407
|
+
safe = isSafeBody(match[1], match[2]);
|
|
1408
|
+
if (regexVerdicts.size >= REGEX_VERDICT_LIMIT) regexVerdicts.clear();
|
|
1409
|
+
regexVerdicts.set(pattern, safe);
|
|
1227
1410
|
}
|
|
1228
|
-
if (
|
|
1411
|
+
if (!safe) return null;
|
|
1229
1412
|
try {
|
|
1230
1413
|
return new RegExp(match[1], match[2]);
|
|
1231
1414
|
} catch {
|
|
1232
1415
|
return null;
|
|
1233
1416
|
}
|
|
1234
1417
|
}
|
|
1418
|
+
function isSafeBody(body, flags) {
|
|
1419
|
+
for (const dangerous of REDOS_PATTERNS) {
|
|
1420
|
+
if (dangerous.test(body)) return false;
|
|
1421
|
+
}
|
|
1422
|
+
if (hasAdjacentUnbounded(body, flags)) return false;
|
|
1423
|
+
if (hasCatastrophicShape(body, flags) === true) return false;
|
|
1424
|
+
try {
|
|
1425
|
+
new RegExp(body, flags);
|
|
1426
|
+
} catch {
|
|
1427
|
+
return false;
|
|
1428
|
+
}
|
|
1429
|
+
return true;
|
|
1430
|
+
}
|
|
1235
1431
|
function ctypeResult(strValue, isClass, matchVal) {
|
|
1236
1432
|
if (strValue === "") {
|
|
1237
1433
|
return false;
|
|
@@ -2952,7 +3148,7 @@ function concatChunks(chunks, total) {
|
|
|
2952
3148
|
}
|
|
2953
3149
|
function parseBody(rawBody, contentType) {
|
|
2954
3150
|
const ct = String(contentType || "").toLowerCase();
|
|
2955
|
-
const
|
|
3151
|
+
const isJson2 = ct.includes("application/json") || /\+json\b/.test(ct);
|
|
2956
3152
|
const isForm = ct.includes("application/x-www-form-urlencoded");
|
|
2957
3153
|
const isMultipart = ct.includes("multipart/form-data");
|
|
2958
3154
|
const isAmbiguous = ct === "" || ct.startsWith("text/plain") || ct.includes("csp-report") || ct.includes("/json");
|
|
@@ -2966,7 +3162,7 @@ function parseBody(rawBody, contentType) {
|
|
|
2966
3162
|
for (const [k, v] of new URLSearchParams(rawBody)) appendOwn(body, k, v);
|
|
2967
3163
|
return { body, files: void 0 };
|
|
2968
3164
|
}
|
|
2969
|
-
if (
|
|
3165
|
+
if (isJson2 || isAmbiguous) {
|
|
2970
3166
|
try {
|
|
2971
3167
|
const parsed = JSON.parse(rawBody);
|
|
2972
3168
|
if (parsed && typeof parsed === "object") return { body: parsed, files: void 0 };
|
|
@@ -2982,22 +3178,68 @@ function parseMultipart(rawBody, boundary) {
|
|
|
2982
3178
|
const sep = /\r?\n\r?\n/.exec(part);
|
|
2983
3179
|
if (!sep) continue;
|
|
2984
3180
|
const rawHeaders = part.slice(0, sep.index);
|
|
2985
|
-
const disposition = /content-disposition:[^\r\n]
|
|
2986
|
-
if (
|
|
2987
|
-
const
|
|
2988
|
-
if (
|
|
3181
|
+
const disposition = /content-disposition:([^\r\n]*)/i.exec(rawHeaders)?.[1];
|
|
3182
|
+
if (disposition === void 0) continue;
|
|
3183
|
+
const { names, filename } = dispositionFields(disposition);
|
|
3184
|
+
if (names.length === 0) continue;
|
|
2989
3185
|
const content = part.slice(sep.index + sep[0].length).replace(/\r?\n$/, "");
|
|
2990
|
-
const
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
appendOwn(files, name, file);
|
|
2995
|
-
} else {
|
|
2996
|
-
appendOwn(body, name, content);
|
|
3186
|
+
const file = filename === void 0 ? void 0 : { filename, type: /content-type:\s*([^\r\n;]+)/i.exec(rawHeaders)?.[1]?.trim() || "", content };
|
|
3187
|
+
for (const name of names) {
|
|
3188
|
+
if (file) appendOwn(files, name, file);
|
|
3189
|
+
else appendOwn(body, name, content);
|
|
2997
3190
|
}
|
|
2998
3191
|
}
|
|
2999
3192
|
return { body, files };
|
|
3000
3193
|
}
|
|
3194
|
+
function dispositionFields(value) {
|
|
3195
|
+
const text = String(value);
|
|
3196
|
+
const names = /* @__PURE__ */ new Set();
|
|
3197
|
+
let filename;
|
|
3198
|
+
let extendedFilename;
|
|
3199
|
+
let i = text.indexOf(";");
|
|
3200
|
+
while (i !== -1 && i < text.length) {
|
|
3201
|
+
i++;
|
|
3202
|
+
while (text[i] === " " || text[i] === " ") i++;
|
|
3203
|
+
let key = "";
|
|
3204
|
+
while (i < text.length && text[i] !== "=" && text[i] !== ";") key += text[i++];
|
|
3205
|
+
key = key.trim().toLowerCase();
|
|
3206
|
+
let val = null;
|
|
3207
|
+
if (text[i] === "=") {
|
|
3208
|
+
i++;
|
|
3209
|
+
while (text[i] === " " || text[i] === " ") i++;
|
|
3210
|
+
if (text[i] === '"') {
|
|
3211
|
+
i++;
|
|
3212
|
+
val = "";
|
|
3213
|
+
while (i < text.length && text[i] !== '"') {
|
|
3214
|
+
if (text[i] === "\\" && i + 1 < text.length) i++;
|
|
3215
|
+
val += text[i++];
|
|
3216
|
+
}
|
|
3217
|
+
i++;
|
|
3218
|
+
while (i < text.length && text[i] !== ";") i++;
|
|
3219
|
+
} else {
|
|
3220
|
+
let bare = "";
|
|
3221
|
+
while (i < text.length && text[i] !== ";") bare += text[i++];
|
|
3222
|
+
val = bare.trim();
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
if (val !== null) {
|
|
3226
|
+
if (key === "name") names.add(val);
|
|
3227
|
+
else if (key === "filename") filename = val;
|
|
3228
|
+
else if (key === "filename*") extendedFilename = decodeExtendedValue(val);
|
|
3229
|
+
}
|
|
3230
|
+
i = i < text.length && text[i] === ";" ? i : -1;
|
|
3231
|
+
}
|
|
3232
|
+
return { names: [...names], filename: extendedFilename ?? filename };
|
|
3233
|
+
}
|
|
3234
|
+
function decodeExtendedValue(value) {
|
|
3235
|
+
const match = /^([^']*)'[^']*'(.*)$/.exec(value);
|
|
3236
|
+
if (!match) return value;
|
|
3237
|
+
try {
|
|
3238
|
+
return decodeURIComponent(match[2]);
|
|
3239
|
+
} catch {
|
|
3240
|
+
return match[2];
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3001
3243
|
function parseCookies(header) {
|
|
3002
3244
|
const cookies = {};
|
|
3003
3245
|
if (!header) {
|
|
@@ -3268,14 +3510,29 @@ async function installEgressGuard({ shouldBlock, onBlock, onSkip, dnsScreen = tr
|
|
|
3268
3510
|
if (globalThis.fetch === guarded) globalThis.fetch = originalFetch;
|
|
3269
3511
|
});
|
|
3270
3512
|
}
|
|
3513
|
+
let syncBuiltins = () => {
|
|
3514
|
+
};
|
|
3515
|
+
try {
|
|
3516
|
+
const { syncBuiltinESMExports } = await import("module");
|
|
3517
|
+
if (typeof syncBuiltinESMExports === "function") syncBuiltins = syncBuiltinESMExports;
|
|
3518
|
+
} catch {
|
|
3519
|
+
}
|
|
3520
|
+
let patchedAny = false;
|
|
3271
3521
|
for (const moduleName of ["node:http", "node:https"]) {
|
|
3272
3522
|
try {
|
|
3273
3523
|
const mod = await import(moduleName);
|
|
3274
3524
|
const restore = patchHttpModule(mod.default ?? mod, block, screen, skip);
|
|
3275
|
-
if (restore)
|
|
3525
|
+
if (restore) {
|
|
3526
|
+
patchedAny = true;
|
|
3527
|
+
restores.push(() => {
|
|
3528
|
+
restore();
|
|
3529
|
+
syncBuiltins();
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3276
3532
|
} catch {
|
|
3277
3533
|
}
|
|
3278
3534
|
}
|
|
3535
|
+
if (patchedAny) syncBuiltins();
|
|
3279
3536
|
return () => {
|
|
3280
3537
|
for (const restore of restores) {
|
|
3281
3538
|
try {
|
|
@@ -3289,18 +3546,22 @@ function patchHttpModule(http, block, screen, skip) {
|
|
|
3289
3546
|
if (!http || typeof http.request !== "function" || http.__patchstackGuarded) return null;
|
|
3290
3547
|
const originalRequest = http.request;
|
|
3291
3548
|
const originalGet = http.get;
|
|
3292
|
-
const
|
|
3549
|
+
const OriginalClientRequest = typeof http.ClientRequest === "function" ? http.ClientRequest : null;
|
|
3550
|
+
const guardArgs = (args) => {
|
|
3293
3551
|
const target = extractHttpTarget(args);
|
|
3294
3552
|
if (target && block(target.url, target.host, target.method)) {
|
|
3295
3553
|
throw new Error(`Patchstack blocked an outbound request to a disallowed address: ${target.host ?? target.url}`);
|
|
3296
3554
|
}
|
|
3297
3555
|
if (target && screen && target.host && screen.isIP(target.host) === 0 && !screen.isExempt(target.host)) {
|
|
3298
3556
|
try {
|
|
3299
|
-
|
|
3557
|
+
return withScreeningLookup(args, target, block, screen.lookup, skip);
|
|
3300
3558
|
} catch {
|
|
3301
3559
|
}
|
|
3302
3560
|
}
|
|
3303
|
-
return
|
|
3561
|
+
return args;
|
|
3562
|
+
};
|
|
3563
|
+
const wrap = (original) => function(...args) {
|
|
3564
|
+
return original.apply(this, guardArgs(args));
|
|
3304
3565
|
};
|
|
3305
3566
|
const guardedRequest = wrap(originalRequest);
|
|
3306
3567
|
http.request = guardedRequest;
|
|
@@ -3309,10 +3570,21 @@ function patchHttpModule(http, block, screen, skip) {
|
|
|
3309
3570
|
guardedGet = wrap(originalGet);
|
|
3310
3571
|
http.get = guardedGet;
|
|
3311
3572
|
}
|
|
3573
|
+
let GuardedClientRequest;
|
|
3574
|
+
if (OriginalClientRequest) {
|
|
3575
|
+
GuardedClientRequest = function ClientRequest(...args) {
|
|
3576
|
+
const screened = guardArgs(args);
|
|
3577
|
+
return new.target ? Reflect.construct(OriginalClientRequest, screened, new.target) : Reflect.construct(OriginalClientRequest, screened);
|
|
3578
|
+
};
|
|
3579
|
+
GuardedClientRequest.prototype = OriginalClientRequest.prototype;
|
|
3580
|
+
Object.setPrototypeOf(GuardedClientRequest, OriginalClientRequest);
|
|
3581
|
+
http.ClientRequest = GuardedClientRequest;
|
|
3582
|
+
}
|
|
3312
3583
|
http.__patchstackGuarded = true;
|
|
3313
3584
|
return () => {
|
|
3314
3585
|
if (http.request === guardedRequest) http.request = originalRequest;
|
|
3315
3586
|
if (guardedGet && http.get === guardedGet) http.get = originalGet;
|
|
3587
|
+
if (GuardedClientRequest && http.ClientRequest === GuardedClientRequest) http.ClientRequest = OriginalClientRequest;
|
|
3316
3588
|
delete http.__patchstackGuarded;
|
|
3317
3589
|
};
|
|
3318
3590
|
}
|
|
@@ -3322,19 +3594,27 @@ function extractHttpTarget(args) {
|
|
|
3322
3594
|
if (typeof first === "string" || first instanceof URL) {
|
|
3323
3595
|
const url = new URL(String(first));
|
|
3324
3596
|
const opts = args.find((a) => a && typeof a === "object" && !(a instanceof URL));
|
|
3325
|
-
return { url
|
|
3326
|
-
}
|
|
3327
|
-
if (first && typeof first === "object") {
|
|
3328
|
-
const host = normalizeHost(first.hostname || first.host) || "localhost";
|
|
3329
|
-
const protocol = first.protocol || "http:";
|
|
3330
|
-
const port = first.port ? `:${first.port}` : "";
|
|
3331
|
-
const path = first.path || "/";
|
|
3332
|
-
return { url: `${protocol}//${host}${port}${path}`, host, method: first.method || "GET" };
|
|
3597
|
+
return targetOf({ ...optionsFromUrl(url), ...opts ?? {} });
|
|
3333
3598
|
}
|
|
3599
|
+
if (first && typeof first === "object") return targetOf(first);
|
|
3334
3600
|
} catch {
|
|
3335
3601
|
}
|
|
3336
3602
|
return null;
|
|
3337
3603
|
}
|
|
3604
|
+
function optionsFromUrl(url) {
|
|
3605
|
+
const hostname = url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : url.hostname;
|
|
3606
|
+
const options = { protocol: url.protocol, hostname, path: `${url.pathname || "/"}${url.search}` };
|
|
3607
|
+
if (url.port !== "") options.port = Number(url.port);
|
|
3608
|
+
return options;
|
|
3609
|
+
}
|
|
3610
|
+
function targetOf(options) {
|
|
3611
|
+
const host = normalizeHost(options.hostname || options.host) || "localhost";
|
|
3612
|
+
const protocol = options.protocol || "http:";
|
|
3613
|
+
const port = options.port ? `:${options.port}` : "";
|
|
3614
|
+
const path = options.path || "/";
|
|
3615
|
+
const authority = host.includes(":") ? `[${host}]` : host;
|
|
3616
|
+
return { url: `${protocol}//${authority}${port}${path}`, host, method: options.method || "GET" };
|
|
3617
|
+
}
|
|
3338
3618
|
function normalizeHost(raw) {
|
|
3339
3619
|
const host = String(raw || "").trim();
|
|
3340
3620
|
const bracketed = /^\[([^\]]+)\]/.exec(host);
|
|
@@ -3715,19 +3995,49 @@ async function loadFs() {
|
|
|
3715
3995
|
}
|
|
3716
3996
|
return fsMod;
|
|
3717
3997
|
}
|
|
3998
|
+
async function sourceIdentity(options = {}) {
|
|
3999
|
+
if (typeof options.siteUuid === "string" && options.siteUuid.trim() !== "") {
|
|
4000
|
+
const endpoint = typeof options.pulseRulesUrl === "string" ? options.pulseRulesUrl : "";
|
|
4001
|
+
return `site:${options.siteUuid.trim().toLowerCase()}@${endpoint}`;
|
|
4002
|
+
}
|
|
4003
|
+
if (typeof options.token === "string" && options.token !== "") {
|
|
4004
|
+
const digest = await sha256Hex(options.token);
|
|
4005
|
+
if (digest === null) return null;
|
|
4006
|
+
const endpoint = typeof options.baseUrl === "string" ? options.baseUrl : "";
|
|
4007
|
+
return `token:${digest}@${endpoint}`;
|
|
4008
|
+
}
|
|
4009
|
+
return null;
|
|
4010
|
+
}
|
|
4011
|
+
async function sha256Hex(text) {
|
|
4012
|
+
try {
|
|
4013
|
+
const subtle = globalThis.crypto?.subtle;
|
|
4014
|
+
if (!subtle || typeof TextEncoder === "undefined") return null;
|
|
4015
|
+
const digest = await subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
4016
|
+
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4017
|
+
} catch {
|
|
4018
|
+
return null;
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
3718
4021
|
function makeStore(options = {}) {
|
|
3719
4022
|
let mem = null;
|
|
3720
4023
|
const durable = durableTier(options);
|
|
4024
|
+
let identity;
|
|
4025
|
+
const identityOf = () => identity ??= sourceIdentity(options);
|
|
3721
4026
|
return {
|
|
3722
4027
|
async read() {
|
|
3723
4028
|
if (mem) return mem;
|
|
4029
|
+
const source = await identityOf();
|
|
4030
|
+
if (source === null) return null;
|
|
3724
4031
|
const env = await durable.read();
|
|
3725
|
-
if (env)
|
|
4032
|
+
if (!env || env.source !== source) return null;
|
|
4033
|
+
mem = env;
|
|
3726
4034
|
return env;
|
|
3727
4035
|
},
|
|
3728
4036
|
async write(env) {
|
|
3729
|
-
if (env)
|
|
3730
|
-
await
|
|
4037
|
+
if (!env) return;
|
|
4038
|
+
const stamped = { ...env, source: await identityOf() };
|
|
4039
|
+
mem = stamped;
|
|
4040
|
+
await durable.write(stamped);
|
|
3731
4041
|
}
|
|
3732
4042
|
};
|
|
3733
4043
|
}
|
|
@@ -3849,11 +4159,12 @@ function toEnvelope(value) {
|
|
|
3849
4159
|
buildId,
|
|
3850
4160
|
// A confirmation belongs to the presentation stored beside it. A crossed or partially written
|
|
3851
4161
|
// envelope confirms nothing, even when one of its fields happens to name the current map.
|
|
3852
|
-
matchedBuildId: buildId !== null && matched === buildId ? matched : null
|
|
4162
|
+
matchedBuildId: buildId !== null && matched === buildId ? matched : null,
|
|
4163
|
+
source: typeof value.source === "string" ? value.source : null
|
|
3853
4164
|
};
|
|
3854
4165
|
}
|
|
3855
4166
|
if (Array.isArray(value.firewall) || Array.isArray(value.whitelists)) {
|
|
3856
|
-
return { bundle: value, etag: null, buildId: null, matchedBuildId: null };
|
|
4167
|
+
return { bundle: value, etag: null, buildId: null, matchedBuildId: null, source: null };
|
|
3857
4168
|
}
|
|
3858
4169
|
return null;
|
|
3859
4170
|
}
|
|
@@ -3920,13 +4231,17 @@ function withScopedDryRun(bundle, confirmed, reason, options = {}) {
|
|
|
3920
4231
|
...unusable > 0 ? [`${unusable} rule(s) carry an unusable ${BUILD_SCOPE_PROPERTY}`] : [],
|
|
3921
4232
|
...held > unusable ? [reason] : []
|
|
3922
4233
|
];
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
"onError"
|
|
3927
|
-
);
|
|
4234
|
+
const message = `${held} build-scoped rule(s) are detecting only, not blocking: ${explanations.join("; ")}`;
|
|
4235
|
+
if (typeof options.onError === "function") notify(options.onError, new Error(message), "onError");
|
|
4236
|
+
else warnHeldOnce(message);
|
|
3928
4237
|
return { ...bundle, firewall };
|
|
3929
4238
|
}
|
|
4239
|
+
var warnedHeld = /* @__PURE__ */ new Set();
|
|
4240
|
+
function warnHeldOnce(message) {
|
|
4241
|
+
if (warnedHeld.has(message)) return;
|
|
4242
|
+
warnedHeld.add(message);
|
|
4243
|
+
console.warn(`[patchstack] ${message}. Pass { onError } to handle this yourself.`);
|
|
4244
|
+
}
|
|
3930
4245
|
function confirmedBy(res, presented) {
|
|
3931
4246
|
if (presented === null) return null;
|
|
3932
4247
|
const verdict = res?.build;
|
|
@@ -4101,6 +4416,60 @@ function startRefresh(tick, { refreshMs, onError } = {}) {
|
|
|
4101
4416
|
}
|
|
4102
4417
|
};
|
|
4103
4418
|
}
|
|
4419
|
+
var RECOVERY_DELAYS_MS = [5e3, 15e3, 45e3, 12e4, 3e5, 6e5];
|
|
4420
|
+
function startRecovery(tick, { onError } = {}) {
|
|
4421
|
+
let stopped = false;
|
|
4422
|
+
let attempt = 0;
|
|
4423
|
+
let timer = null;
|
|
4424
|
+
const schedule = () => {
|
|
4425
|
+
if (stopped) return;
|
|
4426
|
+
const delay = RECOVERY_DELAYS_MS[Math.min(attempt, RECOVERY_DELAYS_MS.length - 1)];
|
|
4427
|
+
timer = setTimeout(run, delay * (1 - Math.random() * JITTER_FRACTION3));
|
|
4428
|
+
timer?.unref?.();
|
|
4429
|
+
};
|
|
4430
|
+
const run = async () => {
|
|
4431
|
+
if (stopped) return;
|
|
4432
|
+
attempt++;
|
|
4433
|
+
try {
|
|
4434
|
+
const status = await tick();
|
|
4435
|
+
if (!status || status.ok !== false) {
|
|
4436
|
+
stopped = true;
|
|
4437
|
+
return;
|
|
4438
|
+
}
|
|
4439
|
+
} catch (err) {
|
|
4440
|
+
notify(onError, err, "onError");
|
|
4441
|
+
}
|
|
4442
|
+
schedule();
|
|
4443
|
+
};
|
|
4444
|
+
schedule();
|
|
4445
|
+
return {
|
|
4446
|
+
stop() {
|
|
4447
|
+
stopped = true;
|
|
4448
|
+
if (timer) clearTimeout(timer);
|
|
4449
|
+
}
|
|
4450
|
+
};
|
|
4451
|
+
}
|
|
4452
|
+
function serialise(tick) {
|
|
4453
|
+
let running = null;
|
|
4454
|
+
let next = null;
|
|
4455
|
+
const run = () => {
|
|
4456
|
+
if (!running) {
|
|
4457
|
+
running = Promise.resolve().then(() => tick()).finally(() => {
|
|
4458
|
+
running = null;
|
|
4459
|
+
});
|
|
4460
|
+
return running;
|
|
4461
|
+
}
|
|
4462
|
+
if (!next) {
|
|
4463
|
+
next = running.catch(() => {
|
|
4464
|
+
}).then(() => {
|
|
4465
|
+
next = null;
|
|
4466
|
+
return run();
|
|
4467
|
+
});
|
|
4468
|
+
}
|
|
4469
|
+
return next;
|
|
4470
|
+
};
|
|
4471
|
+
return run;
|
|
4472
|
+
}
|
|
4104
4473
|
function makeRefreshHandler(tick, secret) {
|
|
4105
4474
|
let inflight = null;
|
|
4106
4475
|
const runOnce = () => {
|
|
@@ -5252,7 +5621,7 @@ function createSupabaseGuard({
|
|
|
5252
5621
|
statusText: upstream.statusText,
|
|
5253
5622
|
headers: responseHeaders(upstream.headers)
|
|
5254
5623
|
});
|
|
5255
|
-
return protection.screenResponse ? protection.screenResponse(forwarded) : forwarded;
|
|
5624
|
+
return protection.screenResponse ? protection.screenResponse(forwarded, evalReq) : forwarded;
|
|
5256
5625
|
};
|
|
5257
5626
|
}
|
|
5258
5627
|
|
|
@@ -5346,6 +5715,7 @@ async function createProtection(options = {}) {
|
|
|
5346
5715
|
"onDetect"
|
|
5347
5716
|
);
|
|
5348
5717
|
};
|
|
5718
|
+
const nodeHttp = await import("http").then((m) => m.default ?? m, () => null);
|
|
5349
5719
|
const store = makeStore(options);
|
|
5350
5720
|
const bootTimeoutMs = Number(options.bootTimeoutMs) > 0 ? Number(options.bootTimeoutMs) : 5e3;
|
|
5351
5721
|
const pulseAuth = await resolvePulseAuth(options);
|
|
@@ -5371,6 +5741,21 @@ async function createProtection(options = {}) {
|
|
|
5371
5741
|
pulseAuth,
|
|
5372
5742
|
detectionState: preFetchState
|
|
5373
5743
|
});
|
|
5744
|
+
let ruleSource = { ...bundle.source ?? { ok: true, origin: "empty" } };
|
|
5745
|
+
const warnedSources = /* @__PURE__ */ new Set();
|
|
5746
|
+
const hasLiveSource = Boolean(options.siteUuid || options.token);
|
|
5747
|
+
const noteRuleSource = (source) => {
|
|
5748
|
+
ruleSource = { ...source ?? { ok: true, origin: ruleSource.origin } };
|
|
5749
|
+
if (!hasLiveSource || typeof onError === "function" || ruleSource.ok !== false) return;
|
|
5750
|
+
const cause = `${ruleSource.origin}:${ruleSource.reason ?? ""}`;
|
|
5751
|
+
if (warnedSources.has(cause)) return;
|
|
5752
|
+
warnedSources.add(cause);
|
|
5753
|
+
const running = ruleSource.origin === "cache" ? "the last rules it received" : ruleSource.origin === "bundled" ? "its bundled fallback rules" : "no rules at all";
|
|
5754
|
+
console.warn(
|
|
5755
|
+
`[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.`
|
|
5756
|
+
);
|
|
5757
|
+
};
|
|
5758
|
+
noteRuleSource(bundle.source);
|
|
5374
5759
|
let detectionReporting = "not-enrolled";
|
|
5375
5760
|
let currentOrigin;
|
|
5376
5761
|
const applyReportingState = async (origin) => {
|
|
@@ -5549,7 +5934,9 @@ async function createProtection(options = {}) {
|
|
|
5549
5934
|
const spanRedactors = redactors.filter((r) => !r.jsonPath);
|
|
5550
5935
|
if (pathRedactors.length) body = applyPathRedactors(body, pathRedactors, mask, screenCap, transform);
|
|
5551
5936
|
if (!spanRedactors.length) continue;
|
|
5937
|
+
const beforeSpan = body;
|
|
5552
5938
|
body = applyRedactors(body, spanRedactors, mask, transform);
|
|
5939
|
+
if (body !== beforeSpan && !preservesJsonStructure(beforeSpan, body)) return { verdict: "block" };
|
|
5553
5940
|
if (transform) continue;
|
|
5554
5941
|
for (const name of Object.keys(headers)) {
|
|
5555
5942
|
const value = headers[name];
|
|
@@ -5564,6 +5951,7 @@ async function createProtection(options = {}) {
|
|
|
5564
5951
|
}
|
|
5565
5952
|
}
|
|
5566
5953
|
}
|
|
5954
|
+
if (body !== text && isJson(text) && !isJson(body)) return { verdict: "block" };
|
|
5567
5955
|
for (const rule of headerMutations) applyHeaderMutation(headers, rule);
|
|
5568
5956
|
return { verdict: "redact", body, headers };
|
|
5569
5957
|
};
|
|
@@ -5666,7 +6054,9 @@ async function createProtection(options = {}) {
|
|
|
5666
6054
|
const screenResp = async (response, reqCtx) => {
|
|
5667
6055
|
const read = await readTextResponse(response, screenCap);
|
|
5668
6056
|
if (read.skip) {
|
|
5669
|
-
if (read.skip !== "not-a-response")
|
|
6057
|
+
if (read.skip !== "not-a-response") {
|
|
6058
|
+
recordSkip("response", read.skip, { status: response?.status, ...read.encoding ? { encoding: read.encoding } : {} });
|
|
6059
|
+
}
|
|
5670
6060
|
if (read.skip === "not-a-response") return response;
|
|
5671
6061
|
return hardenHeadersOnly(response, reqCtx);
|
|
5672
6062
|
}
|
|
@@ -5747,6 +6137,11 @@ async function createProtection(options = {}) {
|
|
|
5747
6137
|
const wrapNodeResponse = (res, reqCtx) => {
|
|
5748
6138
|
const origWrite = res.write.bind(res);
|
|
5749
6139
|
const origEnd = res.end.bind(res);
|
|
6140
|
+
const setHeaderNow = typeof res.setHeader === "function" ? res.setHeader.bind(res) : null;
|
|
6141
|
+
const removeHeaderNow = typeof res.removeHeader === "function" ? res.removeHeader.bind(res) : null;
|
|
6142
|
+
const sentAccessor = accessorOf(res, "headersSent");
|
|
6143
|
+
let sentValue = sentAccessor ? void 0 : res.headersSent;
|
|
6144
|
+
const reallySent = () => Boolean(sentAccessor ? sentAccessor.call(res) : sentValue);
|
|
5750
6145
|
const chunks = [];
|
|
5751
6146
|
let size = 0;
|
|
5752
6147
|
let overflow = false;
|
|
@@ -5757,7 +6152,7 @@ async function createProtection(options = {}) {
|
|
|
5757
6152
|
if (hardened) return null;
|
|
5758
6153
|
hardened = true;
|
|
5759
6154
|
try {
|
|
5760
|
-
if (
|
|
6155
|
+
if (reallySent() || !setHeaderNow) return null;
|
|
5761
6156
|
const set = typeof res.getHeaders === "function" ? res.getHeaders() : {};
|
|
5762
6157
|
const headers = effective && effective.headers ? { ...set, ...effective.headers } : set;
|
|
5763
6158
|
const status = effective && effective.status !== void 0 ? effective.status : res.statusCode;
|
|
@@ -5769,8 +6164,8 @@ async function createProtection(options = {}) {
|
|
|
5769
6164
|
if (!headerValueChanged(headers[name], value)) continue;
|
|
5770
6165
|
changed.set(name.toLowerCase(), value);
|
|
5771
6166
|
try {
|
|
5772
|
-
if (value === null || value === void 0)
|
|
5773
|
-
else
|
|
6167
|
+
if (value === null || value === void 0) removeHeaderNow?.(name);
|
|
6168
|
+
else setHeaderNow(name, value);
|
|
5774
6169
|
} catch {
|
|
5775
6170
|
}
|
|
5776
6171
|
}
|
|
@@ -5781,26 +6176,139 @@ async function createProtection(options = {}) {
|
|
|
5781
6176
|
}
|
|
5782
6177
|
};
|
|
5783
6178
|
const stillToAnswer = (rule) => !hardensWithoutBody(rule);
|
|
5784
|
-
|
|
5785
|
-
|
|
6179
|
+
let pendingHead = null;
|
|
6180
|
+
let sending = false;
|
|
6181
|
+
const origWriteHead = typeof res.writeHead === "function" ? res.writeHead.bind(res) : null;
|
|
6182
|
+
if (origWriteHead) {
|
|
5786
6183
|
res.writeHead = function(...args) {
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
const
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
6184
|
+
if (sending || reallySent()) return origWriteHead(...args);
|
|
6185
|
+
if (pendingHead) throw headersSentError("write");
|
|
6186
|
+
const judged = judgeWriteHead(args);
|
|
6187
|
+
if (judged) {
|
|
6188
|
+
res.statusCode = judged.statusCode;
|
|
6189
|
+
res.statusMessage = judged.statusMessage;
|
|
6190
|
+
mirrorHeaders(judged.headers);
|
|
6191
|
+
pendingHead = args;
|
|
6192
|
+
} else {
|
|
6193
|
+
pendingHead = args;
|
|
6194
|
+
if (typeof args[0] === "number") res.statusCode = args[0];
|
|
6195
|
+
if (typeof args[1] === "string") res.statusMessage = args[1];
|
|
6196
|
+
}
|
|
6197
|
+
return res;
|
|
5797
6198
|
};
|
|
5798
6199
|
}
|
|
6200
|
+
function judgeWriteHead(args) {
|
|
6201
|
+
const ServerResponse = nodeHttp?.ServerResponse;
|
|
6202
|
+
if (typeof ServerResponse !== "function") return null;
|
|
6203
|
+
const detached = new ServerResponse({ method: res.req?.method ?? "GET", httpVersionMajor: 1, httpVersionMinor: 1, headers: {} });
|
|
6204
|
+
if (res.statusMessage !== void 0) detached.statusMessage = res.statusMessage;
|
|
6205
|
+
for (const [name, value] of headerEntries(res)) detached.setHeader(name, value);
|
|
6206
|
+
detached.writeHead(...args);
|
|
6207
|
+
return { statusCode: detached.statusCode, statusMessage: detached.statusMessage, headers: headerEntries(detached) };
|
|
6208
|
+
}
|
|
6209
|
+
function headerEntries(target) {
|
|
6210
|
+
const names = typeof target.getRawHeaderNames === "function" ? target.getRawHeaderNames() : typeof target.getHeaderNames === "function" ? target.getHeaderNames() : [];
|
|
6211
|
+
return names.map((name) => [name, target.getHeader(name)]);
|
|
6212
|
+
}
|
|
6213
|
+
function mirrorHeaders(entries) {
|
|
6214
|
+
if (!setHeaderNow) return;
|
|
6215
|
+
for (const [name, value] of entries) {
|
|
6216
|
+
const current = res.getHeader?.(name);
|
|
6217
|
+
if (current === void 0 || headerValueChanged(current, value)) setHeaderNow(name, value);
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
Object.defineProperty(res, "headersSent", {
|
|
6221
|
+
configurable: true,
|
|
6222
|
+
enumerable: true,
|
|
6223
|
+
get: () => pendingHead !== null || reallySent(),
|
|
6224
|
+
// A response-like object that tracks the flag itself keeps doing so.
|
|
6225
|
+
set: (value) => {
|
|
6226
|
+
if (!sentAccessor) sentValue = value;
|
|
6227
|
+
}
|
|
6228
|
+
});
|
|
6229
|
+
for (const [method, verb] of [["setHeader", "set"], ["removeHeader", "remove"], ["appendHeader", "append"]]) {
|
|
6230
|
+
const original = typeof res[method] === "function" ? res[method].bind(res) : null;
|
|
6231
|
+
if (!original) continue;
|
|
6232
|
+
res[method] = function(...args) {
|
|
6233
|
+
if (pendingHead) throw headersSentError(verb);
|
|
6234
|
+
return original(...args);
|
|
6235
|
+
};
|
|
6236
|
+
}
|
|
6237
|
+
if (typeof res.flushHeaders === "function") {
|
|
6238
|
+
const origFlushHeaders = res.flushHeaders.bind(res);
|
|
6239
|
+
res.flushHeaders = function() {
|
|
6240
|
+
sendHead();
|
|
6241
|
+
return origFlushHeaders();
|
|
6242
|
+
};
|
|
6243
|
+
}
|
|
6244
|
+
let hardenDone = false;
|
|
6245
|
+
let hardenedValues = null;
|
|
6246
|
+
const hardenOnce = () => {
|
|
6247
|
+
if (hardenDone) return hardenedValues;
|
|
6248
|
+
hardenDone = true;
|
|
6249
|
+
if (!pendingHead) {
|
|
6250
|
+
hardenedValues = hardenBeforeFlush();
|
|
6251
|
+
return hardenedValues;
|
|
6252
|
+
}
|
|
6253
|
+
const at = writeHeadHeaderIndex(pendingHead);
|
|
6254
|
+
hardenedValues = hardenBeforeFlush({
|
|
6255
|
+
status: typeof pendingHead[0] === "number" ? pendingHead[0] : void 0,
|
|
6256
|
+
headers: at === -1 ? null : writeHeadHeaderObject(pendingHead[at])
|
|
6257
|
+
});
|
|
6258
|
+
return hardenedValues;
|
|
6259
|
+
};
|
|
6260
|
+
const effectiveHead = () => {
|
|
6261
|
+
const set = typeof res.getHeaders === "function" ? { ...res.getHeaders() } : {};
|
|
6262
|
+
if (typeof res.getHeaders !== "function" && typeof res.getHeader === "function") {
|
|
6263
|
+
for (const name of ["content-type", "content-length"]) {
|
|
6264
|
+
const value = res.getHeader(name);
|
|
6265
|
+
if (value !== void 0) set[name] = value;
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
if (!pendingHead) return { status: res.statusCode, headers: set };
|
|
6269
|
+
const at = writeHeadHeaderIndex(pendingHead);
|
|
6270
|
+
return {
|
|
6271
|
+
status: typeof pendingHead[0] === "number" ? pendingHead[0] : res.statusCode,
|
|
6272
|
+
headers: at === -1 ? set : { ...set, ...writeHeadHeaderObject(pendingHead[at]) }
|
|
6273
|
+
};
|
|
6274
|
+
};
|
|
6275
|
+
const sendHead = (changes) => {
|
|
6276
|
+
if (sending) return;
|
|
6277
|
+
const hardened2 = hardenOnce();
|
|
6278
|
+
sending = true;
|
|
6279
|
+
if (!pendingHead) {
|
|
6280
|
+
if (changes) applyToResponse(changes);
|
|
6281
|
+
return;
|
|
6282
|
+
}
|
|
6283
|
+
const args = [...pendingHead];
|
|
6284
|
+
pendingHead = null;
|
|
6285
|
+
const merged = new Map(hardened2 ?? []);
|
|
6286
|
+
for (const [name, value] of changes?.headers ?? []) merged.set(name, value);
|
|
6287
|
+
if (changes?.status !== void 0) {
|
|
6288
|
+
args[0] = changes.status;
|
|
6289
|
+
if (typeof args[1] === "string") args.splice(1, 1);
|
|
6290
|
+
}
|
|
6291
|
+
const headerAt = writeHeadHeaderIndex(args);
|
|
6292
|
+
if (headerAt !== -1 && merged.size) args[headerAt] = rewriteWriteHeadHeaders(args[headerAt], merged);
|
|
6293
|
+
applyToResponse({ headers: merged });
|
|
6294
|
+
origWriteHead(...args);
|
|
6295
|
+
};
|
|
6296
|
+
const applyToResponse = (changes) => {
|
|
6297
|
+
if (changes.status !== void 0) res.statusCode = changes.status;
|
|
6298
|
+
for (const [name, value] of changes.headers ?? []) {
|
|
6299
|
+
try {
|
|
6300
|
+
if (value === null || value === void 0) removeHeaderNow?.(name);
|
|
6301
|
+
else setHeaderNow?.(name, value);
|
|
6302
|
+
} catch {
|
|
6303
|
+
}
|
|
6304
|
+
}
|
|
6305
|
+
};
|
|
5799
6306
|
const collect = (chunk, enc) => {
|
|
5800
6307
|
if (chunk == null) return;
|
|
5801
6308
|
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, typeof enc === "string" ? enc : "utf8");
|
|
5802
6309
|
size += buf.length;
|
|
5803
6310
|
if (size > MAX) {
|
|
6311
|
+
sendHead();
|
|
5804
6312
|
for (const c of chunks) origWrite(c);
|
|
5805
6313
|
chunks.length = 0;
|
|
5806
6314
|
origWrite(buf);
|
|
@@ -5811,7 +6319,6 @@ async function createProtection(options = {}) {
|
|
|
5811
6319
|
chunks.push(buf);
|
|
5812
6320
|
};
|
|
5813
6321
|
res.write = function(chunk, enc, cb) {
|
|
5814
|
-
hardenBeforeFlush();
|
|
5815
6322
|
if (overflow) return origWrite(chunk, enc, cb);
|
|
5816
6323
|
collect(chunk, enc);
|
|
5817
6324
|
if (typeof enc === "function") enc();
|
|
@@ -5819,7 +6326,6 @@ async function createProtection(options = {}) {
|
|
|
5819
6326
|
return true;
|
|
5820
6327
|
};
|
|
5821
6328
|
res.end = function(chunk, enc, cb) {
|
|
5822
|
-
hardenBeforeFlush();
|
|
5823
6329
|
if (typeof chunk === "function") {
|
|
5824
6330
|
cb = chunk;
|
|
5825
6331
|
chunk = void 0;
|
|
@@ -5834,68 +6340,77 @@ async function createProtection(options = {}) {
|
|
|
5834
6340
|
}
|
|
5835
6341
|
collect(chunk, enc);
|
|
5836
6342
|
if (overflow) return origEnd(cb);
|
|
6343
|
+
hardenOnce();
|
|
6344
|
+
const passThrough = () => {
|
|
6345
|
+
sendHead();
|
|
6346
|
+
for (const c of chunks) origWrite(c);
|
|
6347
|
+
return origEnd(cb);
|
|
6348
|
+
};
|
|
5837
6349
|
const buffer = Buffer.concat(chunks);
|
|
5838
|
-
|
|
6350
|
+
const head = effectiveHead();
|
|
6351
|
+
let ct = head.headers["content-type"];
|
|
5839
6352
|
if (Array.isArray(ct)) ct = ct[0];
|
|
5840
6353
|
const kind = screenableContentType(ct);
|
|
5841
6354
|
if (kind === "skip" || kind === "sniff" && looksBinary(buffer)) {
|
|
5842
6355
|
recordSkip("response", kind === "skip" ? baseContentType(ct) === "text/event-stream" ? "live-stream" : "non-text-content-type" : "binary-body");
|
|
5843
|
-
|
|
5844
|
-
|
|
6356
|
+
return passThrough();
|
|
6357
|
+
}
|
|
6358
|
+
const codings = declaredCodings(head.headers["content-encoding"]);
|
|
6359
|
+
if (codings.length > 0 && stillEncoded(buffer)) {
|
|
6360
|
+
recordSkip("response", "encoded-body", { encoding: codings.join(", ") });
|
|
6361
|
+
return passThrough();
|
|
5845
6362
|
}
|
|
5846
6363
|
const text = buffer.toString("utf8");
|
|
5847
6364
|
let r;
|
|
5848
6365
|
try {
|
|
5849
|
-
r = screenText(
|
|
5850
|
-
text,
|
|
5851
|
-
{ status: res.statusCode, headers: res.getHeaders ? res.getHeaders() : {} },
|
|
5852
|
-
reqCtx,
|
|
5853
|
-
answeredWithoutBody ? stillToAnswer : void 0
|
|
5854
|
-
);
|
|
6366
|
+
r = screenText(text, head, reqCtx, answeredWithoutBody ? stillToAnswer : void 0);
|
|
5855
6367
|
} catch (err) {
|
|
5856
6368
|
notify(onError, err, "onError");
|
|
5857
|
-
|
|
5858
|
-
|
|
6369
|
+
return passThrough();
|
|
6370
|
+
}
|
|
6371
|
+
if (r.verdict !== "block" && r.verdict !== "redact") return passThrough();
|
|
6372
|
+
if (reallySent() && res.chunkedEncoding !== true) {
|
|
6373
|
+
notify(onError, new Error("Patchstack: response withheld \u2014 its headers were sent before the body could be screened"), "onError");
|
|
6374
|
+
res.destroy?.();
|
|
6375
|
+
return res;
|
|
5859
6376
|
}
|
|
5860
6377
|
if (r.verdict === "block") {
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
res.
|
|
5864
|
-
|
|
6378
|
+
const body = JSON.stringify({ error: "Response withheld by Patchstack (sensitive data detected)" });
|
|
6379
|
+
if (reallySent()) {
|
|
6380
|
+
res.destroy?.();
|
|
6381
|
+
return res;
|
|
5865
6382
|
}
|
|
5866
|
-
|
|
6383
|
+
sendHead({
|
|
6384
|
+
status: 500,
|
|
6385
|
+
headers: /* @__PURE__ */ new Map([
|
|
6386
|
+
["content-type", "application/json"],
|
|
6387
|
+
["content-length", String(Buffer.byteLength(body))],
|
|
6388
|
+
["transfer-encoding", null]
|
|
6389
|
+
])
|
|
6390
|
+
});
|
|
6391
|
+
return origEnd(body, cb);
|
|
5867
6392
|
}
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
6393
|
+
const changed = /* @__PURE__ */ new Map();
|
|
6394
|
+
if (r.headers) {
|
|
6395
|
+
for (const [name, value] of Object.entries(r.headers)) {
|
|
6396
|
+
if (name.toLowerCase() === "content-length") continue;
|
|
6397
|
+
if (!headerValueChanged(head.headers[name], value)) continue;
|
|
6398
|
+
changed.set(name.toLowerCase(), value);
|
|
5872
6399
|
}
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
}
|
|
5882
|
-
} else if (Array.isArray(value)) {
|
|
5883
|
-
try {
|
|
5884
|
-
res.setHeader(name, value);
|
|
5885
|
-
} catch {
|
|
5886
|
-
}
|
|
5887
|
-
} else if (typeof value === "string" && current[name] !== value) {
|
|
5888
|
-
try {
|
|
5889
|
-
res.setHeader(name, value);
|
|
5890
|
-
} catch {
|
|
5891
|
-
}
|
|
5892
|
-
}
|
|
5893
|
-
}
|
|
6400
|
+
}
|
|
6401
|
+
if (!reallySent()) {
|
|
6402
|
+
const declared = head.headers["content-length"];
|
|
6403
|
+
if (declared === void 0) {
|
|
6404
|
+
changed.set("content-length", null);
|
|
6405
|
+
} else {
|
|
6406
|
+
changed.set("content-length", String(Buffer.byteLength(r.body)));
|
|
6407
|
+
changed.set("transfer-encoding", null);
|
|
5894
6408
|
}
|
|
5895
|
-
|
|
6409
|
+
sendHead({ headers: changed });
|
|
6410
|
+
} else if (changed.size) {
|
|
6411
|
+
recordSkip("response", "headers-sent", { headers: [...changed.keys()] });
|
|
5896
6412
|
}
|
|
5897
|
-
|
|
5898
|
-
return origEnd(cb);
|
|
6413
|
+
return origEnd(r.body, cb);
|
|
5899
6414
|
};
|
|
5900
6415
|
};
|
|
5901
6416
|
const allow = new Set(allowHosts.map((h) => String(h).toLowerCase()));
|
|
@@ -6103,11 +6618,12 @@ async function createProtection(options = {}) {
|
|
|
6103
6618
|
let reporter = null;
|
|
6104
6619
|
if (refreshable && options.siteUuid && options.reportManifest !== false && cwd) {
|
|
6105
6620
|
try {
|
|
6106
|
-
({ reportManifest: reporter } = await import("./refresh-manifest-
|
|
6621
|
+
({ reportManifest: reporter } = await import("./refresh-manifest-DZQOEO3Q.js"));
|
|
6107
6622
|
} catch (err) {
|
|
6108
6623
|
notify(onError, err, "onError");
|
|
6109
6624
|
}
|
|
6110
6625
|
}
|
|
6626
|
+
let recovery = null;
|
|
6111
6627
|
const runRefreshTick = async () => {
|
|
6112
6628
|
if (reporter) {
|
|
6113
6629
|
try {
|
|
@@ -6124,23 +6640,33 @@ async function createProtection(options = {}) {
|
|
|
6124
6640
|
});
|
|
6125
6641
|
mode = resolveMode(options, next);
|
|
6126
6642
|
applyBundle(next);
|
|
6643
|
+
noteRuleSource(next.source);
|
|
6644
|
+
if (next.source?.ok !== false) recovery?.stop();
|
|
6127
6645
|
await applyAndAcknowledge(next.source?.origin, declaredState);
|
|
6128
6646
|
if (detections) detections.setRulesEtag((await store.read())?.etag ?? null);
|
|
6129
6647
|
return next.source ?? { ok: true };
|
|
6130
6648
|
};
|
|
6649
|
+
const refreshTick = serialise(runRefreshTick);
|
|
6131
6650
|
if (live) {
|
|
6132
|
-
protection.refresh = () =>
|
|
6133
|
-
protection.refreshHandler = () => makeRefreshHandler(
|
|
6651
|
+
protection.refresh = () => refreshTick();
|
|
6652
|
+
protection.refreshHandler = () => makeRefreshHandler(refreshTick, refreshSecret);
|
|
6134
6653
|
}
|
|
6135
|
-
const loop = options.refreshMs > 0 && live ? startRefresh(
|
|
6654
|
+
const loop = options.refreshMs > 0 && live ? startRefresh(refreshTick, { refreshMs: options.refreshMs, onError }) : null;
|
|
6655
|
+
const canAsk = Boolean(options.token || pulseAuth);
|
|
6656
|
+
recovery = live && canAsk && !loop && ruleSource.ok === false ? startRecovery(refreshTick, { onError }) : null;
|
|
6136
6657
|
protection.stop = () => {
|
|
6137
6658
|
loop?.stop();
|
|
6659
|
+
recovery?.stop();
|
|
6138
6660
|
const outstanding = [firewallLog?.stop(), detections?.stop()].filter(
|
|
6139
6661
|
(wait) => wait && typeof wait.then === "function"
|
|
6140
6662
|
);
|
|
6141
6663
|
return Promise.all(outstanding).then(() => void 0);
|
|
6142
6664
|
};
|
|
6143
6665
|
protection.stopRefresh = protection.stop;
|
|
6666
|
+
Object.defineProperty(protection, "ruleSource", {
|
|
6667
|
+
get: () => ({ ...ruleSource }),
|
|
6668
|
+
enumerable: true
|
|
6669
|
+
});
|
|
6144
6670
|
Object.defineProperty(protection, "detectionReporting", {
|
|
6145
6671
|
get: () => detectionReporting,
|
|
6146
6672
|
enumerable: true
|
|
@@ -6151,6 +6677,16 @@ async function createProtection(options = {}) {
|
|
|
6151
6677
|
});
|
|
6152
6678
|
return protection;
|
|
6153
6679
|
}
|
|
6680
|
+
function accessorOf(obj, name) {
|
|
6681
|
+
for (let o = obj; o; o = Object.getPrototypeOf(o)) {
|
|
6682
|
+
const d = Object.getOwnPropertyDescriptor(o, name);
|
|
6683
|
+
if (d) return typeof d.get === "function" ? d.get : null;
|
|
6684
|
+
}
|
|
6685
|
+
return null;
|
|
6686
|
+
}
|
|
6687
|
+
function headersSentError(verb) {
|
|
6688
|
+
return Object.assign(new Error(`Cannot ${verb} headers after they are sent to the client`), { code: "ERR_HTTP_HEADERS_SENT" });
|
|
6689
|
+
}
|
|
6154
6690
|
function resolveMode(options, bundle) {
|
|
6155
6691
|
const env = typeof process !== "undefined" ? process.env?.PATCHSTACK_MODE : void 0;
|
|
6156
6692
|
if (env === "block" || env === "dry-run") return env;
|
|
@@ -6223,11 +6759,26 @@ function looksBinary(bytes) {
|
|
|
6223
6759
|
}
|
|
6224
6760
|
return n > 0 && ctrl / n > 0.1;
|
|
6225
6761
|
}
|
|
6762
|
+
function declaredCodings(value) {
|
|
6763
|
+
const raw = Array.isArray(value) ? value.join(",") : String(value ?? "");
|
|
6764
|
+
return raw.toLowerCase().split(",").map((c) => c.trim()).filter((c) => c !== "" && c !== "identity");
|
|
6765
|
+
}
|
|
6766
|
+
function stillEncoded(bytes) {
|
|
6767
|
+
if (looksBinary(bytes)) return true;
|
|
6768
|
+
try {
|
|
6769
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
6770
|
+
return false;
|
|
6771
|
+
} catch {
|
|
6772
|
+
return true;
|
|
6773
|
+
}
|
|
6774
|
+
}
|
|
6226
6775
|
async function readTextResponse(response, cap = DEFAULT_SCREEN_CAP) {
|
|
6227
6776
|
if (!response || typeof response.clone !== "function") return { skip: "not-a-response" };
|
|
6228
6777
|
const ct = response.headers?.get?.("content-type") || "";
|
|
6229
6778
|
const kind = screenableContentType(ct);
|
|
6230
6779
|
if (kind === "skip") return { skip: baseContentType(ct) === "text/event-stream" ? "live-stream" : "non-text-content-type" };
|
|
6780
|
+
const codings = declaredCodings(response.headers?.get?.("content-encoding"));
|
|
6781
|
+
const encodedSkip = () => ({ skip: "encoded-body", encoding: codings.join(", ") });
|
|
6231
6782
|
const sniff = kind === "sniff";
|
|
6232
6783
|
const len = Number(response.headers?.get?.("content-length") || 0);
|
|
6233
6784
|
if (len && len > cap) return { skip: "body-cap" };
|
|
@@ -6271,13 +6822,21 @@ async function readTextResponse(response, cap = DEFAULT_SCREEN_CAP) {
|
|
|
6271
6822
|
} finally {
|
|
6272
6823
|
reader.releaseLock();
|
|
6273
6824
|
}
|
|
6825
|
+
const bytes = concatBytes(chunks, size);
|
|
6826
|
+
if (codings.length > 0 && stillEncoded(bytes)) return encodedSkip();
|
|
6274
6827
|
try {
|
|
6275
|
-
return { text: new TextDecoder().decode(
|
|
6828
|
+
return { text: new TextDecoder().decode(bytes) };
|
|
6276
6829
|
} catch {
|
|
6277
6830
|
return { skip: "decode-failed" };
|
|
6278
6831
|
}
|
|
6279
6832
|
}
|
|
6280
6833
|
try {
|
|
6834
|
+
if (codings.length > 0) {
|
|
6835
|
+
const bytes = new Uint8Array(await clone.arrayBuffer());
|
|
6836
|
+
if (bytes.byteLength > cap) return { skip: "body-cap" };
|
|
6837
|
+
if (stillEncoded(bytes)) return encodedSkip();
|
|
6838
|
+
return { text: new TextDecoder().decode(bytes) };
|
|
6839
|
+
}
|
|
6281
6840
|
const text = await clone.text();
|
|
6282
6841
|
if (text.length > cap) return { skip: "body-cap" };
|
|
6283
6842
|
if (sniff && looksBinary(new TextEncoder().encode(text.slice(0, 512)))) return { skip: "binary-body" };
|
|
@@ -6351,6 +6910,39 @@ function extractRedactors(rule) {
|
|
|
6351
6910
|
function htmlEscape(str) {
|
|
6352
6911
|
return String(str).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
6353
6912
|
}
|
|
6913
|
+
function isJson(text) {
|
|
6914
|
+
try {
|
|
6915
|
+
JSON.parse(text);
|
|
6916
|
+
return true;
|
|
6917
|
+
} catch {
|
|
6918
|
+
return false;
|
|
6919
|
+
}
|
|
6920
|
+
}
|
|
6921
|
+
function preservesJsonStructure(before, after) {
|
|
6922
|
+
if (!isJson(before)) return true;
|
|
6923
|
+
if (!isJson(after)) return false;
|
|
6924
|
+
const expected = jsonStructure(before);
|
|
6925
|
+
const actual = jsonStructure(after);
|
|
6926
|
+
for (; ; ) {
|
|
6927
|
+
const left = expected.next();
|
|
6928
|
+
const right = actual.next();
|
|
6929
|
+
if (left.done || right.done) return left.done === right.done;
|
|
6930
|
+
if (left.value !== right.value) return false;
|
|
6931
|
+
}
|
|
6932
|
+
}
|
|
6933
|
+
function* jsonStructure(text) {
|
|
6934
|
+
const tokens = /"(?:[^"\\]|\\[\s\S])*"|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|[^\s]/g;
|
|
6935
|
+
for (const match of text.matchAll(tokens)) {
|
|
6936
|
+
const token = match[0];
|
|
6937
|
+
if (token[0] !== '"') {
|
|
6938
|
+
yield "token:" + token;
|
|
6939
|
+
continue;
|
|
6940
|
+
}
|
|
6941
|
+
let next = match.index + token.length;
|
|
6942
|
+
while (text[next] === " " || text[next] === " " || text[next] === "\r" || text[next] === "\n") next++;
|
|
6943
|
+
yield text[next] === ":" ? "key:" + JSON.parse(token) : "string";
|
|
6944
|
+
}
|
|
6945
|
+
}
|
|
6354
6946
|
function applyRedactors(body, redactors, mask, transform) {
|
|
6355
6947
|
let out = body;
|
|
6356
6948
|
for (const r of redactors) {
|
|
@@ -6393,10 +6985,12 @@ function applyPathRedactors(text, pathRedactors, mask, cap, transform) {
|
|
|
6393
6985
|
if (!pathRedactors.length || typeof text !== "string" || text.length > cap) return text;
|
|
6394
6986
|
const head = text.trimStart()[0];
|
|
6395
6987
|
if (head !== "{" && head !== "[") return text;
|
|
6396
|
-
|
|
6988
|
+
let preserved;
|
|
6397
6989
|
let obj;
|
|
6398
6990
|
try {
|
|
6399
|
-
|
|
6991
|
+
JSON.parse(text);
|
|
6992
|
+
preserved = preserveBigInts(text, mask);
|
|
6993
|
+
obj = JSON.parse(preserved.text);
|
|
6400
6994
|
} catch {
|
|
6401
6995
|
return text;
|
|
6402
6996
|
}
|
|
@@ -6405,65 +6999,44 @@ function applyPathRedactors(text, pathRedactors, mask, cap, transform) {
|
|
|
6405
6999
|
const pred = conditionPredicate(r.condition);
|
|
6406
7000
|
walkLeaves(obj, r.jsonPath, (loc) => {
|
|
6407
7001
|
try {
|
|
6408
|
-
|
|
6409
|
-
|
|
7002
|
+
const number = preserved.numbers.get(loc.value);
|
|
7003
|
+
if (pred(number === void 0 ? loc.value : Number(number))) {
|
|
7004
|
+
setOwn(loc.parent, loc.key, transform ? transform(number ?? String(loc.value)) : mask);
|
|
6410
7005
|
changed = true;
|
|
6411
7006
|
}
|
|
6412
7007
|
} catch {
|
|
6413
7008
|
}
|
|
6414
7009
|
});
|
|
6415
7010
|
}
|
|
6416
|
-
return changed ? restoreBigInts(JSON.stringify(obj)) : text;
|
|
6417
|
-
}
|
|
6418
|
-
var BIGINT_OPEN = "__PSBIGINT_9c2f__";
|
|
6419
|
-
var BIGINT_CLOSE = "__DNEGIB__";
|
|
6420
|
-
function preserveBigInts(text) {
|
|
6421
|
-
let out = "";
|
|
6422
|
-
let inStr = false;
|
|
6423
|
-
for (let i = 0; i < text.length; ) {
|
|
6424
|
-
const ch = text[i];
|
|
6425
|
-
if (inStr) {
|
|
6426
|
-
out += ch;
|
|
6427
|
-
if (ch === "\\") {
|
|
6428
|
-
out += text[i + 1] ?? "";
|
|
6429
|
-
i += 2;
|
|
6430
|
-
continue;
|
|
6431
|
-
}
|
|
6432
|
-
if (ch === '"') inStr = false;
|
|
6433
|
-
i++;
|
|
6434
|
-
continue;
|
|
6435
|
-
}
|
|
6436
|
-
if (ch === '"') {
|
|
6437
|
-
inStr = true;
|
|
6438
|
-
out += ch;
|
|
6439
|
-
i++;
|
|
6440
|
-
continue;
|
|
6441
|
-
}
|
|
6442
|
-
if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
6443
|
-
let j = ch === "-" ? i + 1 : i;
|
|
6444
|
-
let digits = 0;
|
|
6445
|
-
while (j < text.length && text[j] >= "0" && text[j] <= "9") {
|
|
6446
|
-
digits++;
|
|
6447
|
-
j++;
|
|
6448
|
-
}
|
|
6449
|
-
const next = text[j];
|
|
6450
|
-
const isIntToken = digits > 0 && next !== "." && next !== "e" && next !== "E";
|
|
6451
|
-
if (isIntToken && digits >= 16) {
|
|
6452
|
-
out += `"${BIGINT_OPEN}${text.slice(i, j)}${BIGINT_CLOSE}"`;
|
|
6453
|
-
} else {
|
|
6454
|
-
out += text.slice(i, j || i + 1);
|
|
6455
|
-
}
|
|
6456
|
-
i = j > i ? j : i + 1;
|
|
6457
|
-
continue;
|
|
6458
|
-
}
|
|
6459
|
-
out += ch;
|
|
6460
|
-
i++;
|
|
6461
|
-
}
|
|
6462
|
-
return out;
|
|
7011
|
+
return changed ? restoreBigInts(JSON.stringify(obj), preserved.numbers) : text;
|
|
6463
7012
|
}
|
|
6464
|
-
function
|
|
6465
|
-
|
|
6466
|
-
|
|
7013
|
+
function preserveBigInts(text, mask) {
|
|
7014
|
+
const occupied = /* @__PURE__ */ new Set([mask]);
|
|
7015
|
+
const integers = [];
|
|
7016
|
+
const tokens = /"(?:[^"\\]|\\[\s\S])*"|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/g;
|
|
7017
|
+
for (const match of text.matchAll(tokens)) {
|
|
7018
|
+
const token = match[0];
|
|
7019
|
+
if (token[0] === '"') occupied.add(JSON.parse(token));
|
|
7020
|
+
else if (/^-?\d{16,}$/.test(token)) integers.push({ start: match.index, token });
|
|
7021
|
+
}
|
|
7022
|
+
const numbers = /* @__PURE__ */ new Map();
|
|
7023
|
+
const chunks = [];
|
|
7024
|
+
let offset = 0;
|
|
7025
|
+
let index = 0;
|
|
7026
|
+
for (const { start, token } of integers) {
|
|
7027
|
+
let marker;
|
|
7028
|
+
do {
|
|
7029
|
+
marker = "__PSNUMBER_" + index++ + "__";
|
|
7030
|
+
} while (occupied.has(marker));
|
|
7031
|
+
numbers.set(marker, token);
|
|
7032
|
+
chunks.push(text.slice(offset, start), JSON.stringify(marker));
|
|
7033
|
+
offset = start + token.length;
|
|
7034
|
+
}
|
|
7035
|
+
chunks.push(text.slice(offset));
|
|
7036
|
+
return { text: chunks.join(""), numbers };
|
|
7037
|
+
}
|
|
7038
|
+
function restoreBigInts(text, numbers) {
|
|
7039
|
+
return text.replace(/"(__PSNUMBER_\d+__)"/g, (token, marker) => numbers.get(marker) ?? token);
|
|
6467
7040
|
}
|
|
6468
7041
|
function isHeaderMutation(action) {
|
|
6469
7042
|
return action === "set-header" || action === "remove-header" || action === "harden-cookie";
|