@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.cjs
CHANGED
|
@@ -397,13 +397,13 @@ function buildEndpointUrl(base, siteUuid) {
|
|
|
397
397
|
function authFailureMessage(status, config) {
|
|
398
398
|
const hasCredential = typeof config.pulseAuth === "string" && config.pulseAuth.length > 0;
|
|
399
399
|
if (status === 401 && !hasCredential) {
|
|
400
|
-
return "Patchstack
|
|
400
|
+
return "Patchstack needs this project's API key, and none is set here. Run npx @patchstack/connect login, or set PATCHSTACK_API_KEY.";
|
|
401
401
|
}
|
|
402
402
|
if (status === 401) {
|
|
403
|
-
return "Patchstack
|
|
403
|
+
return "Patchstack did not accept this project's API key: it may have expired or been revoked, or the project may no longer exist. Run npx @patchstack/connect login to get a new one.";
|
|
404
404
|
}
|
|
405
405
|
if (status === 403) {
|
|
406
|
-
return "This API
|
|
406
|
+
return "This API key belongs to a different project. Check that siteUuid in .patchstackrc.json is the project the key was made for.";
|
|
407
407
|
}
|
|
408
408
|
return null;
|
|
409
409
|
}
|
|
@@ -1145,7 +1145,7 @@ function unreadableWarning(filename, report) {
|
|
|
1145
1145
|
if (report.unreadable === 0) return null;
|
|
1146
1146
|
const shown = report.samples.join(", ");
|
|
1147
1147
|
const one = report.unreadable === 1;
|
|
1148
|
-
return `${filename}: ${report.unreadable} entr${one ? "y
|
|
1148
|
+
return `${filename}: ${report.unreadable} entr${one ? "y" : "ies"} could not be read (${shown}${report.unreadable > report.samples.length ? ", \u2026" : ""}), so ${one ? "that package is" : "those packages are"} not being checked.`;
|
|
1149
1149
|
}
|
|
1150
1150
|
var MAX_SAMPLES;
|
|
1151
1151
|
var init_report = __esm({
|
|
@@ -2083,7 +2083,7 @@ async function scanLockfile(cwd) {
|
|
|
2083
2083
|
warnings.push(staleWarning("node_modules/", missing));
|
|
2084
2084
|
}
|
|
2085
2085
|
warnings.push(
|
|
2086
|
-
walkWasCandidate ? "
|
|
2086
|
+
walkWasCandidate ? "Checked the installed packages in node_modules/, which is what the build loads." : "Checked the installed packages in node_modules/ instead. Delete the out-of-date package list file to stop this warning."
|
|
2087
2087
|
);
|
|
2088
2088
|
return manifestWith(walked, warnings);
|
|
2089
2089
|
}
|
|
@@ -2095,7 +2095,7 @@ async function scanLockfile(cwd) {
|
|
|
2095
2095
|
);
|
|
2096
2096
|
}
|
|
2097
2097
|
warnings.push(
|
|
2098
|
-
`No
|
|
2098
|
+
`No package list matches package.json; checked ${firstParsed.filename}, so some packages may be missing. Reinstall your packages to fix this.`
|
|
2099
2099
|
);
|
|
2100
2100
|
return manifestWith(firstParsed.packages, warnings);
|
|
2101
2101
|
}
|
|
@@ -2109,12 +2109,12 @@ function otherCompleteSources(all, current, declared) {
|
|
|
2109
2109
|
}
|
|
2110
2110
|
function conflictWarning(filename, conflicts) {
|
|
2111
2111
|
const shown = conflicts.slice(0, 3).map(({ other, conflict }) => `${conflict.name} ${conflict.version} vs ${conflict.otherVersion} in ${other}`).join("; ");
|
|
2112
|
-
return `${filename} and another
|
|
2112
|
+
return `${filename} and another package list file give different versions (${shown}${conflicts.length > 3 ? `, +${conflicts.length - 3} more` : ""}). Checked node_modules/ instead. Delete the file your package manager does not use.`;
|
|
2113
2113
|
}
|
|
2114
2114
|
function staleWarning(source, missing) {
|
|
2115
2115
|
const sample = missing.slice(0, 3).join(", ");
|
|
2116
2116
|
const suffix = missing.length > 3 ? `, +${missing.length - 3} more` : "";
|
|
2117
|
-
return `${source}
|
|
2117
|
+
return `${source} is out of date: it is missing ${missing.length} package${missing.length === 1 ? "" : "s"} listed in package.json (${sample}${suffix}). Run ${INSTALL_BY_SOURCE[source] ?? "your install command"} to update it.`;
|
|
2118
2118
|
}
|
|
2119
2119
|
async function presentLockfiles(cwd) {
|
|
2120
2120
|
const probed = await Promise.all(
|
|
@@ -2149,7 +2149,7 @@ async function exists(filePath) {
|
|
|
2149
2149
|
return false;
|
|
2150
2150
|
}
|
|
2151
2151
|
}
|
|
2152
|
-
var import_promises9, import_node_path8, LOCKFILE_CANDIDATES;
|
|
2152
|
+
var import_promises9, import_node_path8, LOCKFILE_CANDIDATES, INSTALL_BY_SOURCE;
|
|
2153
2153
|
var init_parsers = __esm({
|
|
2154
2154
|
"src/parsers/index.ts"() {
|
|
2155
2155
|
"use strict";
|
|
@@ -2173,6 +2173,13 @@ var init_parsers = __esm({
|
|
|
2173
2173
|
{ filename: "pnpm-lock.yaml", strategy: "pnpm-lockfile" },
|
|
2174
2174
|
{ filename: "yarn.lock", strategy: "yarn-lockfile" }
|
|
2175
2175
|
];
|
|
2176
|
+
INSTALL_BY_SOURCE = {
|
|
2177
|
+
"package-lock.json": "npm install",
|
|
2178
|
+
"bun.lock": "bun install",
|
|
2179
|
+
"bun.lockb": "bun install",
|
|
2180
|
+
"pnpm-lock.yaml": "pnpm install",
|
|
2181
|
+
"yarn.lock": "yarn install"
|
|
2182
|
+
};
|
|
2176
2183
|
}
|
|
2177
2184
|
});
|
|
2178
2185
|
|
|
@@ -3527,6 +3534,184 @@ function hasAdjacentUnbounded(body, flags) {
|
|
|
3527
3534
|
}
|
|
3528
3535
|
return false;
|
|
3529
3536
|
}
|
|
3537
|
+
function parseRepetition(body) {
|
|
3538
|
+
let i = 0;
|
|
3539
|
+
const parseAlternation = () => {
|
|
3540
|
+
const branches = [parseSequence()];
|
|
3541
|
+
while (body[i] === "|") {
|
|
3542
|
+
i++;
|
|
3543
|
+
branches.push(parseSequence());
|
|
3544
|
+
}
|
|
3545
|
+
return branches.length === 1 ? branches[0] : { type: "alt", branches };
|
|
3546
|
+
};
|
|
3547
|
+
const parseSequence = () => {
|
|
3548
|
+
const items = [];
|
|
3549
|
+
while (i < body.length && body[i] !== "|" && body[i] !== ")") {
|
|
3550
|
+
const atom = parseAtom();
|
|
3551
|
+
if (atom === null) return null;
|
|
3552
|
+
const quantified = parseQuantifier(atom);
|
|
3553
|
+
if (quantified === null) return null;
|
|
3554
|
+
items.push(quantified);
|
|
3555
|
+
}
|
|
3556
|
+
return { type: "seq", items };
|
|
3557
|
+
};
|
|
3558
|
+
const parseAtom = () => {
|
|
3559
|
+
const ch = body[i];
|
|
3560
|
+
if (ch === "(") {
|
|
3561
|
+
const start2 = i;
|
|
3562
|
+
i++;
|
|
3563
|
+
let kind = "capture";
|
|
3564
|
+
if (body.startsWith("?:", i)) {
|
|
3565
|
+
kind = "group";
|
|
3566
|
+
i += 2;
|
|
3567
|
+
} else if (body.startsWith("?=", i) || body.startsWith("?!", i)) {
|
|
3568
|
+
kind = "look";
|
|
3569
|
+
i += 2;
|
|
3570
|
+
} else if (body.startsWith("?<=", i) || body.startsWith("?<!", i)) {
|
|
3571
|
+
kind = "look";
|
|
3572
|
+
i += 3;
|
|
3573
|
+
} else if (body.startsWith("?<", i)) {
|
|
3574
|
+
const close = body.indexOf(">", i + 2);
|
|
3575
|
+
if (close === -1) return null;
|
|
3576
|
+
i = close + 1;
|
|
3577
|
+
} else if (body[i] === "?") {
|
|
3578
|
+
return null;
|
|
3579
|
+
}
|
|
3580
|
+
const inner = parseAlternation();
|
|
3581
|
+
if (inner === null || body[i] !== ")") return null;
|
|
3582
|
+
i++;
|
|
3583
|
+
return { type: "group", kind, body: inner, source: body.slice(start2, i) };
|
|
3584
|
+
}
|
|
3585
|
+
const start = i;
|
|
3586
|
+
if (ch === "[") {
|
|
3587
|
+
i++;
|
|
3588
|
+
if (body[i] === "^") i++;
|
|
3589
|
+
if (body[i] === "]") i++;
|
|
3590
|
+
while (i < body.length && body[i] !== "]") i += body[i] === "\\" ? 2 : 1;
|
|
3591
|
+
if (body[i] !== "]") return null;
|
|
3592
|
+
i++;
|
|
3593
|
+
return { type: "atom", source: body.slice(start, i) };
|
|
3594
|
+
}
|
|
3595
|
+
if (ch === "\\") {
|
|
3596
|
+
const next = body[i + 1];
|
|
3597
|
+
if (next === void 0) return null;
|
|
3598
|
+
if ((next === "p" || next === "P" || next === "u") && body[i + 2] === "{") {
|
|
3599
|
+
const close = body.indexOf("}", i + 3);
|
|
3600
|
+
if (close === -1) return null;
|
|
3601
|
+
i = close + 1;
|
|
3602
|
+
} else if (next === "k" && body[i + 2] === "<") {
|
|
3603
|
+
const close = body.indexOf(">", i + 3);
|
|
3604
|
+
if (close === -1) return null;
|
|
3605
|
+
i = close + 1;
|
|
3606
|
+
} else {
|
|
3607
|
+
i += 2;
|
|
3608
|
+
}
|
|
3609
|
+
return { type: /[bB]/.test(next) ? "assert" : "atom", source: body.slice(start, i) };
|
|
3610
|
+
}
|
|
3611
|
+
if (ch === "^" || ch === "$") {
|
|
3612
|
+
i++;
|
|
3613
|
+
return { type: "assert" };
|
|
3614
|
+
}
|
|
3615
|
+
if (ch === "*" || ch === "+" || ch === "?") return null;
|
|
3616
|
+
i++;
|
|
3617
|
+
return { type: "atom", source: body.slice(start, i) };
|
|
3618
|
+
};
|
|
3619
|
+
const parseQuantifier = (atom) => {
|
|
3620
|
+
let min;
|
|
3621
|
+
let max;
|
|
3622
|
+
const ch = body[i];
|
|
3623
|
+
if (ch === "*") [min, max, i] = [0, Infinity, i + 1];
|
|
3624
|
+
else if (ch === "+") [min, max, i] = [1, Infinity, i + 1];
|
|
3625
|
+
else if (ch === "?") [min, max, i] = [0, 1, i + 1];
|
|
3626
|
+
else if (ch === "{") {
|
|
3627
|
+
const bounds = /^\{(\d+)(,(\d*))?\}/.exec(body.slice(i));
|
|
3628
|
+
if (!bounds) return atom;
|
|
3629
|
+
min = Number(bounds[1]);
|
|
3630
|
+
max = bounds[2] === void 0 ? min : bounds[3] === "" ? Infinity : Number(bounds[3]);
|
|
3631
|
+
i += bounds[0].length;
|
|
3632
|
+
} else {
|
|
3633
|
+
return atom;
|
|
3634
|
+
}
|
|
3635
|
+
if (body[i] === "?") i++;
|
|
3636
|
+
if (body[i] === "*" || body[i] === "+" || body[i] === "?" || body[i] === "{") return null;
|
|
3637
|
+
return { type: "quant", min, max, body: atom };
|
|
3638
|
+
};
|
|
3639
|
+
const root = parseAlternation();
|
|
3640
|
+
return root !== null && i === body.length ? root : null;
|
|
3641
|
+
}
|
|
3642
|
+
function* nodesOf(node) {
|
|
3643
|
+
if (!node) return;
|
|
3644
|
+
yield node;
|
|
3645
|
+
if (node.type === "alt") for (const branch of node.branches) yield* nodesOf(branch);
|
|
3646
|
+
else if (node.type === "seq") for (const item of node.items) yield* nodesOf(item);
|
|
3647
|
+
else if (node.type === "group" || node.type === "quant") yield* nodesOf(node.body);
|
|
3648
|
+
}
|
|
3649
|
+
var BOUNDED_NESTING_LIMIT = 100;
|
|
3650
|
+
var BOUNDED_ALTERNATION_LIMIT = 16;
|
|
3651
|
+
var OPTIONAL_REPEAT_LIMIT = 8;
|
|
3652
|
+
function hasFencedRepetitions(quant, flags) {
|
|
3653
|
+
let body = quant.body;
|
|
3654
|
+
if (body.type === "group" && body.kind !== "look") body = body.body;
|
|
3655
|
+
if (body.type !== "seq" || body.items.length < 2) return false;
|
|
3656
|
+
const variable = [...nodesOf(body)].filter((n) => n.type === "quant" && (n.max > 1 || n.min < n.max));
|
|
3657
|
+
if (variable.some((n) => n.body.type !== "atom" || n.body.source === void 0)) return false;
|
|
3658
|
+
const atomFlags = flags.replace(/[^i]/g, "");
|
|
3659
|
+
const unicode = /[uv]/.test(flags);
|
|
3660
|
+
let insides;
|
|
3661
|
+
try {
|
|
3662
|
+
insides = variable.map((n) => new RegExp(`^(?:${n.body.source})$`, `${atomFlags}${unicode ? "u" : ""}`));
|
|
3663
|
+
} catch {
|
|
3664
|
+
return false;
|
|
3665
|
+
}
|
|
3666
|
+
const literalOf = (source) => {
|
|
3667
|
+
if (source.length === 1 && !".[]()|^$\\".includes(source)) return source;
|
|
3668
|
+
if (source.length === 2 && source[0] === "\\" && /[^\w\s]/.test(source[1])) return source[1];
|
|
3669
|
+
return null;
|
|
3670
|
+
};
|
|
3671
|
+
const disjoint = (edge) => {
|
|
3672
|
+
if (edge?.type !== "atom" || edge.source === void 0) return false;
|
|
3673
|
+
const literal = literalOf(edge.source);
|
|
3674
|
+
if (literal !== null) return insides.every((re) => !re.test(literal));
|
|
3675
|
+
if (unicode) return false;
|
|
3676
|
+
let fence;
|
|
3677
|
+
try {
|
|
3678
|
+
fence = new RegExp(`^(?:${edge.source})$`, atomFlags);
|
|
3679
|
+
} catch {
|
|
3680
|
+
return false;
|
|
3681
|
+
}
|
|
3682
|
+
for (let unit = 0; unit <= 65535; unit++) {
|
|
3683
|
+
const ch = String.fromCharCode(unit);
|
|
3684
|
+
if (fence.test(ch) && insides.some((re) => re.test(ch))) return false;
|
|
3685
|
+
}
|
|
3686
|
+
return true;
|
|
3687
|
+
};
|
|
3688
|
+
return disjoint(body.items[0]) || disjoint(body.items[body.items.length - 1]);
|
|
3689
|
+
}
|
|
3690
|
+
function hasCatastrophicShape(body, flags) {
|
|
3691
|
+
const root = parseRepetition(body);
|
|
3692
|
+
if (root === null) return null;
|
|
3693
|
+
for (const node of nodesOf(root)) {
|
|
3694
|
+
if (node.type === "group" && node.kind === "look" && hasAdjacentUnbounded(lookaroundBody(node.source), flags)) return true;
|
|
3695
|
+
if (node.type !== "quant" || node.max <= 1) continue;
|
|
3696
|
+
const fenced = hasFencedRepetitions(node, flags);
|
|
3697
|
+
for (const inner of nodesOf(node.body)) {
|
|
3698
|
+
if (inner === node.body && inner.type === "quant") continue;
|
|
3699
|
+
if (inner.type === "quant" && inner.max > 1 && !fenced) {
|
|
3700
|
+
if (node.max === Infinity || inner.max === Infinity || node.max * inner.max > BOUNDED_NESTING_LIMIT) return true;
|
|
3701
|
+
}
|
|
3702
|
+
if (inner.type === "quant" && inner.min === 0 && inner.max <= 1 && !fenced && (node.max === Infinity || node.max > OPTIONAL_REPEAT_LIMIT)) {
|
|
3703
|
+
return true;
|
|
3704
|
+
}
|
|
3705
|
+
if (inner.type === "alt" && inner.branches.length > 1 && (node.max === Infinity || node.max > BOUNDED_ALTERNATION_LIMIT)) {
|
|
3706
|
+
return true;
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
return false;
|
|
3711
|
+
}
|
|
3712
|
+
function lookaroundBody(source) {
|
|
3713
|
+
return source.slice(source.startsWith("(?<") ? 4 : 3, -1);
|
|
3714
|
+
}
|
|
3530
3715
|
function groupEnd(body, start) {
|
|
3531
3716
|
let depth = 0;
|
|
3532
3717
|
let inClass = false;
|
|
@@ -3553,6 +3738,8 @@ function warnRejectedPatternOnce(pattern) {
|
|
|
3553
3738
|
);
|
|
3554
3739
|
}
|
|
3555
3740
|
var MAX_PATTERN_LENGTH = 1e3;
|
|
3741
|
+
var regexVerdicts = /* @__PURE__ */ new Map();
|
|
3742
|
+
var REGEX_VERDICT_LIMIT = 2048;
|
|
3556
3743
|
function safeRegExp(pattern) {
|
|
3557
3744
|
if (!pattern) {
|
|
3558
3745
|
return null;
|
|
@@ -3564,16 +3751,32 @@ function safeRegExp(pattern) {
|
|
|
3564
3751
|
if (!match) {
|
|
3565
3752
|
return null;
|
|
3566
3753
|
}
|
|
3567
|
-
|
|
3568
|
-
|
|
3754
|
+
let safe = regexVerdicts.get(pattern);
|
|
3755
|
+
if (safe === void 0) {
|
|
3756
|
+
safe = isSafeBody(match[1], match[2]);
|
|
3757
|
+
if (regexVerdicts.size >= REGEX_VERDICT_LIMIT) regexVerdicts.clear();
|
|
3758
|
+
regexVerdicts.set(pattern, safe);
|
|
3569
3759
|
}
|
|
3570
|
-
if (
|
|
3760
|
+
if (!safe) return null;
|
|
3571
3761
|
try {
|
|
3572
3762
|
return new RegExp(match[1], match[2]);
|
|
3573
3763
|
} catch {
|
|
3574
3764
|
return null;
|
|
3575
3765
|
}
|
|
3576
3766
|
}
|
|
3767
|
+
function isSafeBody(body, flags) {
|
|
3768
|
+
for (const dangerous of REDOS_PATTERNS) {
|
|
3769
|
+
if (dangerous.test(body)) return false;
|
|
3770
|
+
}
|
|
3771
|
+
if (hasAdjacentUnbounded(body, flags)) return false;
|
|
3772
|
+
if (hasCatastrophicShape(body, flags) === true) return false;
|
|
3773
|
+
try {
|
|
3774
|
+
new RegExp(body, flags);
|
|
3775
|
+
} catch {
|
|
3776
|
+
return false;
|
|
3777
|
+
}
|
|
3778
|
+
return true;
|
|
3779
|
+
}
|
|
3577
3780
|
function ctypeResult(strValue, isClass, matchVal) {
|
|
3578
3781
|
if (strValue === "") {
|
|
3579
3782
|
return false;
|
|
@@ -5297,7 +5500,7 @@ function concatChunks(chunks, total) {
|
|
|
5297
5500
|
}
|
|
5298
5501
|
function parseBody(rawBody, contentType) {
|
|
5299
5502
|
const ct = String(contentType || "").toLowerCase();
|
|
5300
|
-
const
|
|
5503
|
+
const isJson2 = ct.includes("application/json") || /\+json\b/.test(ct);
|
|
5301
5504
|
const isForm = ct.includes("application/x-www-form-urlencoded");
|
|
5302
5505
|
const isMultipart = ct.includes("multipart/form-data");
|
|
5303
5506
|
const isAmbiguous = ct === "" || ct.startsWith("text/plain") || ct.includes("csp-report") || ct.includes("/json");
|
|
@@ -5311,7 +5514,7 @@ function parseBody(rawBody, contentType) {
|
|
|
5311
5514
|
for (const [k, v] of new URLSearchParams(rawBody)) appendOwn(body, k, v);
|
|
5312
5515
|
return { body, files: void 0 };
|
|
5313
5516
|
}
|
|
5314
|
-
if (
|
|
5517
|
+
if (isJson2 || isAmbiguous) {
|
|
5315
5518
|
try {
|
|
5316
5519
|
const parsed = JSON.parse(rawBody);
|
|
5317
5520
|
if (parsed && typeof parsed === "object") return { body: parsed, files: void 0 };
|
|
@@ -5327,22 +5530,68 @@ function parseMultipart(rawBody, boundary) {
|
|
|
5327
5530
|
const sep = /\r?\n\r?\n/.exec(part);
|
|
5328
5531
|
if (!sep) continue;
|
|
5329
5532
|
const rawHeaders = part.slice(0, sep.index);
|
|
5330
|
-
const disposition = /content-disposition:[^\r\n]
|
|
5331
|
-
if (
|
|
5332
|
-
const
|
|
5333
|
-
if (
|
|
5533
|
+
const disposition = /content-disposition:([^\r\n]*)/i.exec(rawHeaders)?.[1];
|
|
5534
|
+
if (disposition === void 0) continue;
|
|
5535
|
+
const { names, filename } = dispositionFields(disposition);
|
|
5536
|
+
if (names.length === 0) continue;
|
|
5334
5537
|
const content = part.slice(sep.index + sep[0].length).replace(/\r?\n$/, "");
|
|
5335
|
-
const
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
appendOwn(files, name, file);
|
|
5340
|
-
} else {
|
|
5341
|
-
appendOwn(body, name, content);
|
|
5538
|
+
const file = filename === void 0 ? void 0 : { filename, type: /content-type:\s*([^\r\n;]+)/i.exec(rawHeaders)?.[1]?.trim() || "", content };
|
|
5539
|
+
for (const name of names) {
|
|
5540
|
+
if (file) appendOwn(files, name, file);
|
|
5541
|
+
else appendOwn(body, name, content);
|
|
5342
5542
|
}
|
|
5343
5543
|
}
|
|
5344
5544
|
return { body, files };
|
|
5345
5545
|
}
|
|
5546
|
+
function dispositionFields(value) {
|
|
5547
|
+
const text = String(value);
|
|
5548
|
+
const names = /* @__PURE__ */ new Set();
|
|
5549
|
+
let filename;
|
|
5550
|
+
let extendedFilename;
|
|
5551
|
+
let i = text.indexOf(";");
|
|
5552
|
+
while (i !== -1 && i < text.length) {
|
|
5553
|
+
i++;
|
|
5554
|
+
while (text[i] === " " || text[i] === " ") i++;
|
|
5555
|
+
let key = "";
|
|
5556
|
+
while (i < text.length && text[i] !== "=" && text[i] !== ";") key += text[i++];
|
|
5557
|
+
key = key.trim().toLowerCase();
|
|
5558
|
+
let val = null;
|
|
5559
|
+
if (text[i] === "=") {
|
|
5560
|
+
i++;
|
|
5561
|
+
while (text[i] === " " || text[i] === " ") i++;
|
|
5562
|
+
if (text[i] === '"') {
|
|
5563
|
+
i++;
|
|
5564
|
+
val = "";
|
|
5565
|
+
while (i < text.length && text[i] !== '"') {
|
|
5566
|
+
if (text[i] === "\\" && i + 1 < text.length) i++;
|
|
5567
|
+
val += text[i++];
|
|
5568
|
+
}
|
|
5569
|
+
i++;
|
|
5570
|
+
while (i < text.length && text[i] !== ";") i++;
|
|
5571
|
+
} else {
|
|
5572
|
+
let bare = "";
|
|
5573
|
+
while (i < text.length && text[i] !== ";") bare += text[i++];
|
|
5574
|
+
val = bare.trim();
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
if (val !== null) {
|
|
5578
|
+
if (key === "name") names.add(val);
|
|
5579
|
+
else if (key === "filename") filename = val;
|
|
5580
|
+
else if (key === "filename*") extendedFilename = decodeExtendedValue(val);
|
|
5581
|
+
}
|
|
5582
|
+
i = i < text.length && text[i] === ";" ? i : -1;
|
|
5583
|
+
}
|
|
5584
|
+
return { names: [...names], filename: extendedFilename ?? filename };
|
|
5585
|
+
}
|
|
5586
|
+
function decodeExtendedValue(value) {
|
|
5587
|
+
const match = /^([^']*)'[^']*'(.*)$/.exec(value);
|
|
5588
|
+
if (!match) return value;
|
|
5589
|
+
try {
|
|
5590
|
+
return decodeURIComponent(match[2]);
|
|
5591
|
+
} catch {
|
|
5592
|
+
return match[2];
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5346
5595
|
function parseCookies(header) {
|
|
5347
5596
|
const cookies = {};
|
|
5348
5597
|
if (!header) {
|
|
@@ -5613,14 +5862,29 @@ async function installEgressGuard({ shouldBlock, onBlock, onSkip, dnsScreen = tr
|
|
|
5613
5862
|
if (globalThis.fetch === guarded) globalThis.fetch = originalFetch;
|
|
5614
5863
|
});
|
|
5615
5864
|
}
|
|
5865
|
+
let syncBuiltins = () => {
|
|
5866
|
+
};
|
|
5867
|
+
try {
|
|
5868
|
+
const { syncBuiltinESMExports } = await import("module");
|
|
5869
|
+
if (typeof syncBuiltinESMExports === "function") syncBuiltins = syncBuiltinESMExports;
|
|
5870
|
+
} catch {
|
|
5871
|
+
}
|
|
5872
|
+
let patchedAny = false;
|
|
5616
5873
|
for (const moduleName of ["node:http", "node:https"]) {
|
|
5617
5874
|
try {
|
|
5618
5875
|
const mod = await import(moduleName);
|
|
5619
5876
|
const restore = patchHttpModule(mod.default ?? mod, block, screen, skip);
|
|
5620
|
-
if (restore)
|
|
5877
|
+
if (restore) {
|
|
5878
|
+
patchedAny = true;
|
|
5879
|
+
restores.push(() => {
|
|
5880
|
+
restore();
|
|
5881
|
+
syncBuiltins();
|
|
5882
|
+
});
|
|
5883
|
+
}
|
|
5621
5884
|
} catch {
|
|
5622
5885
|
}
|
|
5623
5886
|
}
|
|
5887
|
+
if (patchedAny) syncBuiltins();
|
|
5624
5888
|
return () => {
|
|
5625
5889
|
for (const restore of restores) {
|
|
5626
5890
|
try {
|
|
@@ -5634,18 +5898,22 @@ function patchHttpModule(http, block, screen, skip) {
|
|
|
5634
5898
|
if (!http || typeof http.request !== "function" || http.__patchstackGuarded) return null;
|
|
5635
5899
|
const originalRequest = http.request;
|
|
5636
5900
|
const originalGet = http.get;
|
|
5637
|
-
const
|
|
5901
|
+
const OriginalClientRequest = typeof http.ClientRequest === "function" ? http.ClientRequest : null;
|
|
5902
|
+
const guardArgs = (args) => {
|
|
5638
5903
|
const target = extractHttpTarget(args);
|
|
5639
5904
|
if (target && block(target.url, target.host, target.method)) {
|
|
5640
5905
|
throw new Error(`Patchstack blocked an outbound request to a disallowed address: ${target.host ?? target.url}`);
|
|
5641
5906
|
}
|
|
5642
5907
|
if (target && screen && target.host && screen.isIP(target.host) === 0 && !screen.isExempt(target.host)) {
|
|
5643
5908
|
try {
|
|
5644
|
-
|
|
5909
|
+
return withScreeningLookup(args, target, block, screen.lookup, skip);
|
|
5645
5910
|
} catch {
|
|
5646
5911
|
}
|
|
5647
5912
|
}
|
|
5648
|
-
return
|
|
5913
|
+
return args;
|
|
5914
|
+
};
|
|
5915
|
+
const wrap = (original) => function(...args) {
|
|
5916
|
+
return original.apply(this, guardArgs(args));
|
|
5649
5917
|
};
|
|
5650
5918
|
const guardedRequest = wrap(originalRequest);
|
|
5651
5919
|
http.request = guardedRequest;
|
|
@@ -5654,10 +5922,21 @@ function patchHttpModule(http, block, screen, skip) {
|
|
|
5654
5922
|
guardedGet = wrap(originalGet);
|
|
5655
5923
|
http.get = guardedGet;
|
|
5656
5924
|
}
|
|
5925
|
+
let GuardedClientRequest;
|
|
5926
|
+
if (OriginalClientRequest) {
|
|
5927
|
+
GuardedClientRequest = function ClientRequest(...args) {
|
|
5928
|
+
const screened = guardArgs(args);
|
|
5929
|
+
return new.target ? Reflect.construct(OriginalClientRequest, screened, new.target) : Reflect.construct(OriginalClientRequest, screened);
|
|
5930
|
+
};
|
|
5931
|
+
GuardedClientRequest.prototype = OriginalClientRequest.prototype;
|
|
5932
|
+
Object.setPrototypeOf(GuardedClientRequest, OriginalClientRequest);
|
|
5933
|
+
http.ClientRequest = GuardedClientRequest;
|
|
5934
|
+
}
|
|
5657
5935
|
http.__patchstackGuarded = true;
|
|
5658
5936
|
return () => {
|
|
5659
5937
|
if (http.request === guardedRequest) http.request = originalRequest;
|
|
5660
5938
|
if (guardedGet && http.get === guardedGet) http.get = originalGet;
|
|
5939
|
+
if (GuardedClientRequest && http.ClientRequest === GuardedClientRequest) http.ClientRequest = OriginalClientRequest;
|
|
5661
5940
|
delete http.__patchstackGuarded;
|
|
5662
5941
|
};
|
|
5663
5942
|
}
|
|
@@ -5667,19 +5946,27 @@ function extractHttpTarget(args) {
|
|
|
5667
5946
|
if (typeof first === "string" || first instanceof URL) {
|
|
5668
5947
|
const url = new URL(String(first));
|
|
5669
5948
|
const opts = args.find((a) => a && typeof a === "object" && !(a instanceof URL));
|
|
5670
|
-
return { url
|
|
5671
|
-
}
|
|
5672
|
-
if (first && typeof first === "object") {
|
|
5673
|
-
const host = normalizeHost(first.hostname || first.host) || "localhost";
|
|
5674
|
-
const protocol = first.protocol || "http:";
|
|
5675
|
-
const port = first.port ? `:${first.port}` : "";
|
|
5676
|
-
const path9 = first.path || "/";
|
|
5677
|
-
return { url: `${protocol}//${host}${port}${path9}`, host, method: first.method || "GET" };
|
|
5949
|
+
return targetOf({ ...optionsFromUrl(url), ...opts ?? {} });
|
|
5678
5950
|
}
|
|
5951
|
+
if (first && typeof first === "object") return targetOf(first);
|
|
5679
5952
|
} catch {
|
|
5680
5953
|
}
|
|
5681
5954
|
return null;
|
|
5682
5955
|
}
|
|
5956
|
+
function optionsFromUrl(url) {
|
|
5957
|
+
const hostname = url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : url.hostname;
|
|
5958
|
+
const options = { protocol: url.protocol, hostname, path: `${url.pathname || "/"}${url.search}` };
|
|
5959
|
+
if (url.port !== "") options.port = Number(url.port);
|
|
5960
|
+
return options;
|
|
5961
|
+
}
|
|
5962
|
+
function targetOf(options) {
|
|
5963
|
+
const host = normalizeHost(options.hostname || options.host) || "localhost";
|
|
5964
|
+
const protocol = options.protocol || "http:";
|
|
5965
|
+
const port = options.port ? `:${options.port}` : "";
|
|
5966
|
+
const path9 = options.path || "/";
|
|
5967
|
+
const authority = host.includes(":") ? `[${host}]` : host;
|
|
5968
|
+
return { url: `${protocol}//${authority}${port}${path9}`, host, method: options.method || "GET" };
|
|
5969
|
+
}
|
|
5683
5970
|
function normalizeHost(raw) {
|
|
5684
5971
|
const host = String(raw || "").trim();
|
|
5685
5972
|
const bracketed = /^\[([^\]]+)\]/.exec(host);
|
|
@@ -6061,19 +6348,49 @@ async function loadFs() {
|
|
|
6061
6348
|
}
|
|
6062
6349
|
return fsMod;
|
|
6063
6350
|
}
|
|
6351
|
+
async function sourceIdentity(options = {}) {
|
|
6352
|
+
if (typeof options.siteUuid === "string" && options.siteUuid.trim() !== "") {
|
|
6353
|
+
const endpoint = typeof options.pulseRulesUrl === "string" ? options.pulseRulesUrl : "";
|
|
6354
|
+
return `site:${options.siteUuid.trim().toLowerCase()}@${endpoint}`;
|
|
6355
|
+
}
|
|
6356
|
+
if (typeof options.token === "string" && options.token !== "") {
|
|
6357
|
+
const digest = await sha256Hex(options.token);
|
|
6358
|
+
if (digest === null) return null;
|
|
6359
|
+
const endpoint = typeof options.baseUrl === "string" ? options.baseUrl : "";
|
|
6360
|
+
return `token:${digest}@${endpoint}`;
|
|
6361
|
+
}
|
|
6362
|
+
return null;
|
|
6363
|
+
}
|
|
6364
|
+
async function sha256Hex(text) {
|
|
6365
|
+
try {
|
|
6366
|
+
const subtle = globalThis.crypto?.subtle;
|
|
6367
|
+
if (!subtle || typeof TextEncoder === "undefined") return null;
|
|
6368
|
+
const digest = await subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
6369
|
+
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
6370
|
+
} catch {
|
|
6371
|
+
return null;
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6064
6374
|
function makeStore(options = {}) {
|
|
6065
6375
|
let mem = null;
|
|
6066
6376
|
const durable = durableTier(options);
|
|
6377
|
+
let identity;
|
|
6378
|
+
const identityOf = () => identity ??= sourceIdentity(options);
|
|
6067
6379
|
return {
|
|
6068
6380
|
async read() {
|
|
6069
6381
|
if (mem) return mem;
|
|
6382
|
+
const source = await identityOf();
|
|
6383
|
+
if (source === null) return null;
|
|
6070
6384
|
const env = await durable.read();
|
|
6071
|
-
if (env)
|
|
6385
|
+
if (!env || env.source !== source) return null;
|
|
6386
|
+
mem = env;
|
|
6072
6387
|
return env;
|
|
6073
6388
|
},
|
|
6074
6389
|
async write(env) {
|
|
6075
|
-
if (env)
|
|
6076
|
-
await
|
|
6390
|
+
if (!env) return;
|
|
6391
|
+
const stamped = { ...env, source: await identityOf() };
|
|
6392
|
+
mem = stamped;
|
|
6393
|
+
await durable.write(stamped);
|
|
6077
6394
|
}
|
|
6078
6395
|
};
|
|
6079
6396
|
}
|
|
@@ -6195,11 +6512,12 @@ function toEnvelope(value) {
|
|
|
6195
6512
|
buildId,
|
|
6196
6513
|
// A confirmation belongs to the presentation stored beside it. A crossed or partially written
|
|
6197
6514
|
// envelope confirms nothing, even when one of its fields happens to name the current map.
|
|
6198
|
-
matchedBuildId: buildId !== null && matched === buildId ? matched : null
|
|
6515
|
+
matchedBuildId: buildId !== null && matched === buildId ? matched : null,
|
|
6516
|
+
source: typeof value.source === "string" ? value.source : null
|
|
6199
6517
|
};
|
|
6200
6518
|
}
|
|
6201
6519
|
if (Array.isArray(value.firewall) || Array.isArray(value.whitelists)) {
|
|
6202
|
-
return { bundle: value, etag: null, buildId: null, matchedBuildId: null };
|
|
6520
|
+
return { bundle: value, etag: null, buildId: null, matchedBuildId: null, source: null };
|
|
6203
6521
|
}
|
|
6204
6522
|
return null;
|
|
6205
6523
|
}
|
|
@@ -6267,13 +6585,17 @@ function withScopedDryRun(bundle, confirmed, reason, options = {}) {
|
|
|
6267
6585
|
...unusable > 0 ? [`${unusable} rule(s) carry an unusable ${BUILD_SCOPE_PROPERTY}`] : [],
|
|
6268
6586
|
...held > unusable ? [reason] : []
|
|
6269
6587
|
];
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
"onError"
|
|
6274
|
-
);
|
|
6588
|
+
const message = `${held} build-scoped rule(s) are detecting only, not blocking: ${explanations.join("; ")}`;
|
|
6589
|
+
if (typeof options.onError === "function") notify(options.onError, new Error(message), "onError");
|
|
6590
|
+
else warnHeldOnce(message);
|
|
6275
6591
|
return { ...bundle, firewall };
|
|
6276
6592
|
}
|
|
6593
|
+
var warnedHeld = /* @__PURE__ */ new Set();
|
|
6594
|
+
function warnHeldOnce(message) {
|
|
6595
|
+
if (warnedHeld.has(message)) return;
|
|
6596
|
+
warnedHeld.add(message);
|
|
6597
|
+
console.warn(`[patchstack] ${message}. Pass { onError } to handle this yourself.`);
|
|
6598
|
+
}
|
|
6277
6599
|
function confirmedBy(res, presented) {
|
|
6278
6600
|
if (presented === null) return null;
|
|
6279
6601
|
const verdict = res?.build;
|
|
@@ -6448,6 +6770,60 @@ function startRefresh(tick, { refreshMs, onError } = {}) {
|
|
|
6448
6770
|
}
|
|
6449
6771
|
};
|
|
6450
6772
|
}
|
|
6773
|
+
var RECOVERY_DELAYS_MS = [5e3, 15e3, 45e3, 12e4, 3e5, 6e5];
|
|
6774
|
+
function startRecovery(tick, { onError } = {}) {
|
|
6775
|
+
let stopped = false;
|
|
6776
|
+
let attempt = 0;
|
|
6777
|
+
let timer = null;
|
|
6778
|
+
const schedule = () => {
|
|
6779
|
+
if (stopped) return;
|
|
6780
|
+
const delay = RECOVERY_DELAYS_MS[Math.min(attempt, RECOVERY_DELAYS_MS.length - 1)];
|
|
6781
|
+
timer = setTimeout(run, delay * (1 - Math.random() * JITTER_FRACTION3));
|
|
6782
|
+
timer?.unref?.();
|
|
6783
|
+
};
|
|
6784
|
+
const run = async () => {
|
|
6785
|
+
if (stopped) return;
|
|
6786
|
+
attempt++;
|
|
6787
|
+
try {
|
|
6788
|
+
const status = await tick();
|
|
6789
|
+
if (!status || status.ok !== false) {
|
|
6790
|
+
stopped = true;
|
|
6791
|
+
return;
|
|
6792
|
+
}
|
|
6793
|
+
} catch (err) {
|
|
6794
|
+
notify(onError, err, "onError");
|
|
6795
|
+
}
|
|
6796
|
+
schedule();
|
|
6797
|
+
};
|
|
6798
|
+
schedule();
|
|
6799
|
+
return {
|
|
6800
|
+
stop() {
|
|
6801
|
+
stopped = true;
|
|
6802
|
+
if (timer) clearTimeout(timer);
|
|
6803
|
+
}
|
|
6804
|
+
};
|
|
6805
|
+
}
|
|
6806
|
+
function serialise(tick) {
|
|
6807
|
+
let running = null;
|
|
6808
|
+
let next = null;
|
|
6809
|
+
const run = () => {
|
|
6810
|
+
if (!running) {
|
|
6811
|
+
running = Promise.resolve().then(() => tick()).finally(() => {
|
|
6812
|
+
running = null;
|
|
6813
|
+
});
|
|
6814
|
+
return running;
|
|
6815
|
+
}
|
|
6816
|
+
if (!next) {
|
|
6817
|
+
next = running.catch(() => {
|
|
6818
|
+
}).then(() => {
|
|
6819
|
+
next = null;
|
|
6820
|
+
return run();
|
|
6821
|
+
});
|
|
6822
|
+
}
|
|
6823
|
+
return next;
|
|
6824
|
+
};
|
|
6825
|
+
return run;
|
|
6826
|
+
}
|
|
6451
6827
|
function makeRefreshHandler(tick, secret) {
|
|
6452
6828
|
let inflight2 = null;
|
|
6453
6829
|
const runOnce = () => {
|
|
@@ -7603,7 +7979,7 @@ function createSupabaseGuard({
|
|
|
7603
7979
|
statusText: upstream.statusText,
|
|
7604
7980
|
headers: responseHeaders(upstream.headers)
|
|
7605
7981
|
});
|
|
7606
|
-
return protection.screenResponse ? protection.screenResponse(forwarded) : forwarded;
|
|
7982
|
+
return protection.screenResponse ? protection.screenResponse(forwarded, evalReq) : forwarded;
|
|
7607
7983
|
};
|
|
7608
7984
|
}
|
|
7609
7985
|
|
|
@@ -7697,6 +8073,7 @@ async function createProtection(options = {}) {
|
|
|
7697
8073
|
"onDetect"
|
|
7698
8074
|
);
|
|
7699
8075
|
};
|
|
8076
|
+
const nodeHttp = await import("http").then((m) => m.default ?? m, () => null);
|
|
7700
8077
|
const store = makeStore(options);
|
|
7701
8078
|
const bootTimeoutMs = Number(options.bootTimeoutMs) > 0 ? Number(options.bootTimeoutMs) : 5e3;
|
|
7702
8079
|
const pulseAuth = await resolvePulseAuth(options);
|
|
@@ -7722,6 +8099,21 @@ async function createProtection(options = {}) {
|
|
|
7722
8099
|
pulseAuth,
|
|
7723
8100
|
detectionState: preFetchState
|
|
7724
8101
|
});
|
|
8102
|
+
let ruleSource = { ...bundle.source ?? { ok: true, origin: "empty" } };
|
|
8103
|
+
const warnedSources = /* @__PURE__ */ new Set();
|
|
8104
|
+
const hasLiveSource = Boolean(options.siteUuid || options.token);
|
|
8105
|
+
const noteRuleSource = (source) => {
|
|
8106
|
+
ruleSource = { ...source ?? { ok: true, origin: ruleSource.origin } };
|
|
8107
|
+
if (!hasLiveSource || typeof onError === "function" || ruleSource.ok !== false) return;
|
|
8108
|
+
const cause = `${ruleSource.origin}:${ruleSource.reason ?? ""}`;
|
|
8109
|
+
if (warnedSources.has(cause)) return;
|
|
8110
|
+
warnedSources.add(cause);
|
|
8111
|
+
const running = ruleSource.origin === "cache" ? "the last rules it received" : ruleSource.origin === "bundled" ? "its bundled fallback rules" : "no rules at all";
|
|
8112
|
+
console.warn(
|
|
8113
|
+
`[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.`
|
|
8114
|
+
);
|
|
8115
|
+
};
|
|
8116
|
+
noteRuleSource(bundle.source);
|
|
7725
8117
|
let detectionReporting = "not-enrolled";
|
|
7726
8118
|
let currentOrigin;
|
|
7727
8119
|
const applyReportingState = async (origin) => {
|
|
@@ -7900,7 +8292,9 @@ async function createProtection(options = {}) {
|
|
|
7900
8292
|
const spanRedactors = redactors.filter((r) => !r.jsonPath);
|
|
7901
8293
|
if (pathRedactors.length) body = applyPathRedactors(body, pathRedactors, mask, screenCap, transform);
|
|
7902
8294
|
if (!spanRedactors.length) continue;
|
|
8295
|
+
const beforeSpan = body;
|
|
7903
8296
|
body = applyRedactors(body, spanRedactors, mask, transform);
|
|
8297
|
+
if (body !== beforeSpan && !preservesJsonStructure(beforeSpan, body)) return { verdict: "block" };
|
|
7904
8298
|
if (transform) continue;
|
|
7905
8299
|
for (const name of Object.keys(headers)) {
|
|
7906
8300
|
const value = headers[name];
|
|
@@ -7915,6 +8309,7 @@ async function createProtection(options = {}) {
|
|
|
7915
8309
|
}
|
|
7916
8310
|
}
|
|
7917
8311
|
}
|
|
8312
|
+
if (body !== text && isJson(text) && !isJson(body)) return { verdict: "block" };
|
|
7918
8313
|
for (const rule of headerMutations) applyHeaderMutation(headers, rule);
|
|
7919
8314
|
return { verdict: "redact", body, headers };
|
|
7920
8315
|
};
|
|
@@ -8017,7 +8412,9 @@ async function createProtection(options = {}) {
|
|
|
8017
8412
|
const screenResp = async (response, reqCtx) => {
|
|
8018
8413
|
const read = await readTextResponse(response, screenCap);
|
|
8019
8414
|
if (read.skip) {
|
|
8020
|
-
if (read.skip !== "not-a-response")
|
|
8415
|
+
if (read.skip !== "not-a-response") {
|
|
8416
|
+
recordSkip("response", read.skip, { status: response?.status, ...read.encoding ? { encoding: read.encoding } : {} });
|
|
8417
|
+
}
|
|
8021
8418
|
if (read.skip === "not-a-response") return response;
|
|
8022
8419
|
return hardenHeadersOnly(response, reqCtx);
|
|
8023
8420
|
}
|
|
@@ -8098,6 +8495,11 @@ async function createProtection(options = {}) {
|
|
|
8098
8495
|
const wrapNodeResponse = (res, reqCtx) => {
|
|
8099
8496
|
const origWrite = res.write.bind(res);
|
|
8100
8497
|
const origEnd = res.end.bind(res);
|
|
8498
|
+
const setHeaderNow = typeof res.setHeader === "function" ? res.setHeader.bind(res) : null;
|
|
8499
|
+
const removeHeaderNow = typeof res.removeHeader === "function" ? res.removeHeader.bind(res) : null;
|
|
8500
|
+
const sentAccessor = accessorOf(res, "headersSent");
|
|
8501
|
+
let sentValue = sentAccessor ? void 0 : res.headersSent;
|
|
8502
|
+
const reallySent = () => Boolean(sentAccessor ? sentAccessor.call(res) : sentValue);
|
|
8101
8503
|
const chunks = [];
|
|
8102
8504
|
let size = 0;
|
|
8103
8505
|
let overflow = false;
|
|
@@ -8108,7 +8510,7 @@ async function createProtection(options = {}) {
|
|
|
8108
8510
|
if (hardened) return null;
|
|
8109
8511
|
hardened = true;
|
|
8110
8512
|
try {
|
|
8111
|
-
if (
|
|
8513
|
+
if (reallySent() || !setHeaderNow) return null;
|
|
8112
8514
|
const set2 = typeof res.getHeaders === "function" ? res.getHeaders() : {};
|
|
8113
8515
|
const headers = effective && effective.headers ? { ...set2, ...effective.headers } : set2;
|
|
8114
8516
|
const status = effective && effective.status !== void 0 ? effective.status : res.statusCode;
|
|
@@ -8120,8 +8522,8 @@ async function createProtection(options = {}) {
|
|
|
8120
8522
|
if (!headerValueChanged(headers[name], value)) continue;
|
|
8121
8523
|
changed.set(name.toLowerCase(), value);
|
|
8122
8524
|
try {
|
|
8123
|
-
if (value === null || value === void 0)
|
|
8124
|
-
else
|
|
8525
|
+
if (value === null || value === void 0) removeHeaderNow?.(name);
|
|
8526
|
+
else setHeaderNow(name, value);
|
|
8125
8527
|
} catch {
|
|
8126
8528
|
}
|
|
8127
8529
|
}
|
|
@@ -8132,26 +8534,139 @@ async function createProtection(options = {}) {
|
|
|
8132
8534
|
}
|
|
8133
8535
|
};
|
|
8134
8536
|
const stillToAnswer = (rule) => !hardensWithoutBody(rule);
|
|
8135
|
-
|
|
8136
|
-
|
|
8537
|
+
let pendingHead = null;
|
|
8538
|
+
let sending = false;
|
|
8539
|
+
const origWriteHead = typeof res.writeHead === "function" ? res.writeHead.bind(res) : null;
|
|
8540
|
+
if (origWriteHead) {
|
|
8137
8541
|
res.writeHead = function(...args) {
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
const
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8542
|
+
if (sending || reallySent()) return origWriteHead(...args);
|
|
8543
|
+
if (pendingHead) throw headersSentError("write");
|
|
8544
|
+
const judged = judgeWriteHead(args);
|
|
8545
|
+
if (judged) {
|
|
8546
|
+
res.statusCode = judged.statusCode;
|
|
8547
|
+
res.statusMessage = judged.statusMessage;
|
|
8548
|
+
mirrorHeaders(judged.headers);
|
|
8549
|
+
pendingHead = args;
|
|
8550
|
+
} else {
|
|
8551
|
+
pendingHead = args;
|
|
8552
|
+
if (typeof args[0] === "number") res.statusCode = args[0];
|
|
8553
|
+
if (typeof args[1] === "string") res.statusMessage = args[1];
|
|
8554
|
+
}
|
|
8555
|
+
return res;
|
|
8148
8556
|
};
|
|
8149
8557
|
}
|
|
8558
|
+
function judgeWriteHead(args) {
|
|
8559
|
+
const ServerResponse = nodeHttp?.ServerResponse;
|
|
8560
|
+
if (typeof ServerResponse !== "function") return null;
|
|
8561
|
+
const detached = new ServerResponse({ method: res.req?.method ?? "GET", httpVersionMajor: 1, httpVersionMinor: 1, headers: {} });
|
|
8562
|
+
if (res.statusMessage !== void 0) detached.statusMessage = res.statusMessage;
|
|
8563
|
+
for (const [name, value] of headerEntries(res)) detached.setHeader(name, value);
|
|
8564
|
+
detached.writeHead(...args);
|
|
8565
|
+
return { statusCode: detached.statusCode, statusMessage: detached.statusMessage, headers: headerEntries(detached) };
|
|
8566
|
+
}
|
|
8567
|
+
function headerEntries(target) {
|
|
8568
|
+
const names = typeof target.getRawHeaderNames === "function" ? target.getRawHeaderNames() : typeof target.getHeaderNames === "function" ? target.getHeaderNames() : [];
|
|
8569
|
+
return names.map((name) => [name, target.getHeader(name)]);
|
|
8570
|
+
}
|
|
8571
|
+
function mirrorHeaders(entries) {
|
|
8572
|
+
if (!setHeaderNow) return;
|
|
8573
|
+
for (const [name, value] of entries) {
|
|
8574
|
+
const current = res.getHeader?.(name);
|
|
8575
|
+
if (current === void 0 || headerValueChanged(current, value)) setHeaderNow(name, value);
|
|
8576
|
+
}
|
|
8577
|
+
}
|
|
8578
|
+
Object.defineProperty(res, "headersSent", {
|
|
8579
|
+
configurable: true,
|
|
8580
|
+
enumerable: true,
|
|
8581
|
+
get: () => pendingHead !== null || reallySent(),
|
|
8582
|
+
// A response-like object that tracks the flag itself keeps doing so.
|
|
8583
|
+
set: (value) => {
|
|
8584
|
+
if (!sentAccessor) sentValue = value;
|
|
8585
|
+
}
|
|
8586
|
+
});
|
|
8587
|
+
for (const [method, verb] of [["setHeader", "set"], ["removeHeader", "remove"], ["appendHeader", "append"]]) {
|
|
8588
|
+
const original = typeof res[method] === "function" ? res[method].bind(res) : null;
|
|
8589
|
+
if (!original) continue;
|
|
8590
|
+
res[method] = function(...args) {
|
|
8591
|
+
if (pendingHead) throw headersSentError(verb);
|
|
8592
|
+
return original(...args);
|
|
8593
|
+
};
|
|
8594
|
+
}
|
|
8595
|
+
if (typeof res.flushHeaders === "function") {
|
|
8596
|
+
const origFlushHeaders = res.flushHeaders.bind(res);
|
|
8597
|
+
res.flushHeaders = function() {
|
|
8598
|
+
sendHead();
|
|
8599
|
+
return origFlushHeaders();
|
|
8600
|
+
};
|
|
8601
|
+
}
|
|
8602
|
+
let hardenDone = false;
|
|
8603
|
+
let hardenedValues = null;
|
|
8604
|
+
const hardenOnce = () => {
|
|
8605
|
+
if (hardenDone) return hardenedValues;
|
|
8606
|
+
hardenDone = true;
|
|
8607
|
+
if (!pendingHead) {
|
|
8608
|
+
hardenedValues = hardenBeforeFlush();
|
|
8609
|
+
return hardenedValues;
|
|
8610
|
+
}
|
|
8611
|
+
const at = writeHeadHeaderIndex(pendingHead);
|
|
8612
|
+
hardenedValues = hardenBeforeFlush({
|
|
8613
|
+
status: typeof pendingHead[0] === "number" ? pendingHead[0] : void 0,
|
|
8614
|
+
headers: at === -1 ? null : writeHeadHeaderObject(pendingHead[at])
|
|
8615
|
+
});
|
|
8616
|
+
return hardenedValues;
|
|
8617
|
+
};
|
|
8618
|
+
const effectiveHead = () => {
|
|
8619
|
+
const set2 = typeof res.getHeaders === "function" ? { ...res.getHeaders() } : {};
|
|
8620
|
+
if (typeof res.getHeaders !== "function" && typeof res.getHeader === "function") {
|
|
8621
|
+
for (const name of ["content-type", "content-length"]) {
|
|
8622
|
+
const value = res.getHeader(name);
|
|
8623
|
+
if (value !== void 0) set2[name] = value;
|
|
8624
|
+
}
|
|
8625
|
+
}
|
|
8626
|
+
if (!pendingHead) return { status: res.statusCode, headers: set2 };
|
|
8627
|
+
const at = writeHeadHeaderIndex(pendingHead);
|
|
8628
|
+
return {
|
|
8629
|
+
status: typeof pendingHead[0] === "number" ? pendingHead[0] : res.statusCode,
|
|
8630
|
+
headers: at === -1 ? set2 : { ...set2, ...writeHeadHeaderObject(pendingHead[at]) }
|
|
8631
|
+
};
|
|
8632
|
+
};
|
|
8633
|
+
const sendHead = (changes) => {
|
|
8634
|
+
if (sending) return;
|
|
8635
|
+
const hardened2 = hardenOnce();
|
|
8636
|
+
sending = true;
|
|
8637
|
+
if (!pendingHead) {
|
|
8638
|
+
if (changes) applyToResponse(changes);
|
|
8639
|
+
return;
|
|
8640
|
+
}
|
|
8641
|
+
const args = [...pendingHead];
|
|
8642
|
+
pendingHead = null;
|
|
8643
|
+
const merged = new Map(hardened2 ?? []);
|
|
8644
|
+
for (const [name, value] of changes?.headers ?? []) merged.set(name, value);
|
|
8645
|
+
if (changes?.status !== void 0) {
|
|
8646
|
+
args[0] = changes.status;
|
|
8647
|
+
if (typeof args[1] === "string") args.splice(1, 1);
|
|
8648
|
+
}
|
|
8649
|
+
const headerAt = writeHeadHeaderIndex(args);
|
|
8650
|
+
if (headerAt !== -1 && merged.size) args[headerAt] = rewriteWriteHeadHeaders(args[headerAt], merged);
|
|
8651
|
+
applyToResponse({ headers: merged });
|
|
8652
|
+
origWriteHead(...args);
|
|
8653
|
+
};
|
|
8654
|
+
const applyToResponse = (changes) => {
|
|
8655
|
+
if (changes.status !== void 0) res.statusCode = changes.status;
|
|
8656
|
+
for (const [name, value] of changes.headers ?? []) {
|
|
8657
|
+
try {
|
|
8658
|
+
if (value === null || value === void 0) removeHeaderNow?.(name);
|
|
8659
|
+
else setHeaderNow?.(name, value);
|
|
8660
|
+
} catch {
|
|
8661
|
+
}
|
|
8662
|
+
}
|
|
8663
|
+
};
|
|
8150
8664
|
const collect = (chunk, enc) => {
|
|
8151
8665
|
if (chunk == null) return;
|
|
8152
8666
|
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, typeof enc === "string" ? enc : "utf8");
|
|
8153
8667
|
size += buf.length;
|
|
8154
8668
|
if (size > MAX) {
|
|
8669
|
+
sendHead();
|
|
8155
8670
|
for (const c of chunks) origWrite(c);
|
|
8156
8671
|
chunks.length = 0;
|
|
8157
8672
|
origWrite(buf);
|
|
@@ -8162,7 +8677,6 @@ async function createProtection(options = {}) {
|
|
|
8162
8677
|
chunks.push(buf);
|
|
8163
8678
|
};
|
|
8164
8679
|
res.write = function(chunk, enc, cb) {
|
|
8165
|
-
hardenBeforeFlush();
|
|
8166
8680
|
if (overflow) return origWrite(chunk, enc, cb);
|
|
8167
8681
|
collect(chunk, enc);
|
|
8168
8682
|
if (typeof enc === "function") enc();
|
|
@@ -8170,7 +8684,6 @@ async function createProtection(options = {}) {
|
|
|
8170
8684
|
return true;
|
|
8171
8685
|
};
|
|
8172
8686
|
res.end = function(chunk, enc, cb) {
|
|
8173
|
-
hardenBeforeFlush();
|
|
8174
8687
|
if (typeof chunk === "function") {
|
|
8175
8688
|
cb = chunk;
|
|
8176
8689
|
chunk = void 0;
|
|
@@ -8185,68 +8698,77 @@ async function createProtection(options = {}) {
|
|
|
8185
8698
|
}
|
|
8186
8699
|
collect(chunk, enc);
|
|
8187
8700
|
if (overflow) return origEnd(cb);
|
|
8701
|
+
hardenOnce();
|
|
8702
|
+
const passThrough = () => {
|
|
8703
|
+
sendHead();
|
|
8704
|
+
for (const c of chunks) origWrite(c);
|
|
8705
|
+
return origEnd(cb);
|
|
8706
|
+
};
|
|
8188
8707
|
const buffer = Buffer.concat(chunks);
|
|
8189
|
-
|
|
8708
|
+
const head = effectiveHead();
|
|
8709
|
+
let ct = head.headers["content-type"];
|
|
8190
8710
|
if (Array.isArray(ct)) ct = ct[0];
|
|
8191
8711
|
const kind = screenableContentType(ct);
|
|
8192
8712
|
if (kind === "skip" || kind === "sniff" && looksBinary(buffer)) {
|
|
8193
8713
|
recordSkip("response", kind === "skip" ? baseContentType(ct) === "text/event-stream" ? "live-stream" : "non-text-content-type" : "binary-body");
|
|
8194
|
-
|
|
8195
|
-
|
|
8714
|
+
return passThrough();
|
|
8715
|
+
}
|
|
8716
|
+
const codings = declaredCodings(head.headers["content-encoding"]);
|
|
8717
|
+
if (codings.length > 0 && stillEncoded(buffer)) {
|
|
8718
|
+
recordSkip("response", "encoded-body", { encoding: codings.join(", ") });
|
|
8719
|
+
return passThrough();
|
|
8196
8720
|
}
|
|
8197
8721
|
const text = buffer.toString("utf8");
|
|
8198
8722
|
let r;
|
|
8199
8723
|
try {
|
|
8200
|
-
r = screenText(
|
|
8201
|
-
text,
|
|
8202
|
-
{ status: res.statusCode, headers: res.getHeaders ? res.getHeaders() : {} },
|
|
8203
|
-
reqCtx,
|
|
8204
|
-
answeredWithoutBody ? stillToAnswer : void 0
|
|
8205
|
-
);
|
|
8724
|
+
r = screenText(text, head, reqCtx, answeredWithoutBody ? stillToAnswer : void 0);
|
|
8206
8725
|
} catch (err) {
|
|
8207
8726
|
notify(onError, err, "onError");
|
|
8208
|
-
|
|
8209
|
-
|
|
8727
|
+
return passThrough();
|
|
8728
|
+
}
|
|
8729
|
+
if (r.verdict !== "block" && r.verdict !== "redact") return passThrough();
|
|
8730
|
+
if (reallySent() && res.chunkedEncoding !== true) {
|
|
8731
|
+
notify(onError, new Error("Patchstack: response withheld \u2014 its headers were sent before the body could be screened"), "onError");
|
|
8732
|
+
res.destroy?.();
|
|
8733
|
+
return res;
|
|
8210
8734
|
}
|
|
8211
8735
|
if (r.verdict === "block") {
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
res.
|
|
8215
|
-
|
|
8736
|
+
const body = JSON.stringify({ error: "Response withheld by Patchstack (sensitive data detected)" });
|
|
8737
|
+
if (reallySent()) {
|
|
8738
|
+
res.destroy?.();
|
|
8739
|
+
return res;
|
|
8216
8740
|
}
|
|
8217
|
-
|
|
8741
|
+
sendHead({
|
|
8742
|
+
status: 500,
|
|
8743
|
+
headers: /* @__PURE__ */ new Map([
|
|
8744
|
+
["content-type", "application/json"],
|
|
8745
|
+
["content-length", String(Buffer.byteLength(body))],
|
|
8746
|
+
["transfer-encoding", null]
|
|
8747
|
+
])
|
|
8748
|
+
});
|
|
8749
|
+
return origEnd(body, cb);
|
|
8218
8750
|
}
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8751
|
+
const changed = /* @__PURE__ */ new Map();
|
|
8752
|
+
if (r.headers) {
|
|
8753
|
+
for (const [name, value] of Object.entries(r.headers)) {
|
|
8754
|
+
if (name.toLowerCase() === "content-length") continue;
|
|
8755
|
+
if (!headerValueChanged(head.headers[name], value)) continue;
|
|
8756
|
+
changed.set(name.toLowerCase(), value);
|
|
8223
8757
|
}
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
}
|
|
8233
|
-
} else if (Array.isArray(value)) {
|
|
8234
|
-
try {
|
|
8235
|
-
res.setHeader(name, value);
|
|
8236
|
-
} catch {
|
|
8237
|
-
}
|
|
8238
|
-
} else if (typeof value === "string" && current[name] !== value) {
|
|
8239
|
-
try {
|
|
8240
|
-
res.setHeader(name, value);
|
|
8241
|
-
} catch {
|
|
8242
|
-
}
|
|
8243
|
-
}
|
|
8244
|
-
}
|
|
8758
|
+
}
|
|
8759
|
+
if (!reallySent()) {
|
|
8760
|
+
const declared = head.headers["content-length"];
|
|
8761
|
+
if (declared === void 0) {
|
|
8762
|
+
changed.set("content-length", null);
|
|
8763
|
+
} else {
|
|
8764
|
+
changed.set("content-length", String(Buffer.byteLength(r.body)));
|
|
8765
|
+
changed.set("transfer-encoding", null);
|
|
8245
8766
|
}
|
|
8246
|
-
|
|
8767
|
+
sendHead({ headers: changed });
|
|
8768
|
+
} else if (changed.size) {
|
|
8769
|
+
recordSkip("response", "headers-sent", { headers: [...changed.keys()] });
|
|
8247
8770
|
}
|
|
8248
|
-
|
|
8249
|
-
return origEnd(cb);
|
|
8771
|
+
return origEnd(r.body, cb);
|
|
8250
8772
|
};
|
|
8251
8773
|
};
|
|
8252
8774
|
const allow = new Set(allowHosts.map((h) => String(h).toLowerCase()));
|
|
@@ -8459,6 +8981,7 @@ async function createProtection(options = {}) {
|
|
|
8459
8981
|
notify(onError, err, "onError");
|
|
8460
8982
|
}
|
|
8461
8983
|
}
|
|
8984
|
+
let recovery = null;
|
|
8462
8985
|
const runRefreshTick = async () => {
|
|
8463
8986
|
if (reporter) {
|
|
8464
8987
|
try {
|
|
@@ -8475,23 +8998,33 @@ async function createProtection(options = {}) {
|
|
|
8475
8998
|
});
|
|
8476
8999
|
mode = resolveMode(options, next);
|
|
8477
9000
|
applyBundle(next);
|
|
9001
|
+
noteRuleSource(next.source);
|
|
9002
|
+
if (next.source?.ok !== false) recovery?.stop();
|
|
8478
9003
|
await applyAndAcknowledge(next.source?.origin, declaredState);
|
|
8479
9004
|
if (detections) detections.setRulesEtag((await store.read())?.etag ?? null);
|
|
8480
9005
|
return next.source ?? { ok: true };
|
|
8481
9006
|
};
|
|
9007
|
+
const refreshTick = serialise(runRefreshTick);
|
|
8482
9008
|
if (live) {
|
|
8483
|
-
protection.refresh = () =>
|
|
8484
|
-
protection.refreshHandler = () => makeRefreshHandler(
|
|
9009
|
+
protection.refresh = () => refreshTick();
|
|
9010
|
+
protection.refreshHandler = () => makeRefreshHandler(refreshTick, refreshSecret);
|
|
8485
9011
|
}
|
|
8486
|
-
const loop = options.refreshMs > 0 && live ? startRefresh(
|
|
9012
|
+
const loop = options.refreshMs > 0 && live ? startRefresh(refreshTick, { refreshMs: options.refreshMs, onError }) : null;
|
|
9013
|
+
const canAsk = Boolean(options.token || pulseAuth);
|
|
9014
|
+
recovery = live && canAsk && !loop && ruleSource.ok === false ? startRecovery(refreshTick, { onError }) : null;
|
|
8487
9015
|
protection.stop = () => {
|
|
8488
9016
|
loop?.stop();
|
|
9017
|
+
recovery?.stop();
|
|
8489
9018
|
const outstanding = [firewallLog?.stop(), detections?.stop()].filter(
|
|
8490
9019
|
(wait) => wait && typeof wait.then === "function"
|
|
8491
9020
|
);
|
|
8492
9021
|
return Promise.all(outstanding).then(() => void 0);
|
|
8493
9022
|
};
|
|
8494
9023
|
protection.stopRefresh = protection.stop;
|
|
9024
|
+
Object.defineProperty(protection, "ruleSource", {
|
|
9025
|
+
get: () => ({ ...ruleSource }),
|
|
9026
|
+
enumerable: true
|
|
9027
|
+
});
|
|
8495
9028
|
Object.defineProperty(protection, "detectionReporting", {
|
|
8496
9029
|
get: () => detectionReporting,
|
|
8497
9030
|
enumerable: true
|
|
@@ -8502,6 +9035,16 @@ async function createProtection(options = {}) {
|
|
|
8502
9035
|
});
|
|
8503
9036
|
return protection;
|
|
8504
9037
|
}
|
|
9038
|
+
function accessorOf(obj, name) {
|
|
9039
|
+
for (let o = obj; o; o = Object.getPrototypeOf(o)) {
|
|
9040
|
+
const d = Object.getOwnPropertyDescriptor(o, name);
|
|
9041
|
+
if (d) return typeof d.get === "function" ? d.get : null;
|
|
9042
|
+
}
|
|
9043
|
+
return null;
|
|
9044
|
+
}
|
|
9045
|
+
function headersSentError(verb) {
|
|
9046
|
+
return Object.assign(new Error(`Cannot ${verb} headers after they are sent to the client`), { code: "ERR_HTTP_HEADERS_SENT" });
|
|
9047
|
+
}
|
|
8505
9048
|
function resolveMode(options, bundle) {
|
|
8506
9049
|
const env = typeof process !== "undefined" ? process.env?.PATCHSTACK_MODE : void 0;
|
|
8507
9050
|
if (env === "block" || env === "dry-run") return env;
|
|
@@ -8574,11 +9117,26 @@ function looksBinary(bytes) {
|
|
|
8574
9117
|
}
|
|
8575
9118
|
return n > 0 && ctrl / n > 0.1;
|
|
8576
9119
|
}
|
|
9120
|
+
function declaredCodings(value) {
|
|
9121
|
+
const raw = Array.isArray(value) ? value.join(",") : String(value ?? "");
|
|
9122
|
+
return raw.toLowerCase().split(",").map((c) => c.trim()).filter((c) => c !== "" && c !== "identity");
|
|
9123
|
+
}
|
|
9124
|
+
function stillEncoded(bytes) {
|
|
9125
|
+
if (looksBinary(bytes)) return true;
|
|
9126
|
+
try {
|
|
9127
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
9128
|
+
return false;
|
|
9129
|
+
} catch {
|
|
9130
|
+
return true;
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
8577
9133
|
async function readTextResponse(response, cap = DEFAULT_SCREEN_CAP) {
|
|
8578
9134
|
if (!response || typeof response.clone !== "function") return { skip: "not-a-response" };
|
|
8579
9135
|
const ct = response.headers?.get?.("content-type") || "";
|
|
8580
9136
|
const kind = screenableContentType(ct);
|
|
8581
9137
|
if (kind === "skip") return { skip: baseContentType(ct) === "text/event-stream" ? "live-stream" : "non-text-content-type" };
|
|
9138
|
+
const codings = declaredCodings(response.headers?.get?.("content-encoding"));
|
|
9139
|
+
const encodedSkip = () => ({ skip: "encoded-body", encoding: codings.join(", ") });
|
|
8582
9140
|
const sniff = kind === "sniff";
|
|
8583
9141
|
const len = Number(response.headers?.get?.("content-length") || 0);
|
|
8584
9142
|
if (len && len > cap) return { skip: "body-cap" };
|
|
@@ -8622,13 +9180,21 @@ async function readTextResponse(response, cap = DEFAULT_SCREEN_CAP) {
|
|
|
8622
9180
|
} finally {
|
|
8623
9181
|
reader.releaseLock();
|
|
8624
9182
|
}
|
|
9183
|
+
const bytes = concatBytes(chunks, size);
|
|
9184
|
+
if (codings.length > 0 && stillEncoded(bytes)) return encodedSkip();
|
|
8625
9185
|
try {
|
|
8626
|
-
return { text: new TextDecoder().decode(
|
|
9186
|
+
return { text: new TextDecoder().decode(bytes) };
|
|
8627
9187
|
} catch {
|
|
8628
9188
|
return { skip: "decode-failed" };
|
|
8629
9189
|
}
|
|
8630
9190
|
}
|
|
8631
9191
|
try {
|
|
9192
|
+
if (codings.length > 0) {
|
|
9193
|
+
const bytes = new Uint8Array(await clone.arrayBuffer());
|
|
9194
|
+
if (bytes.byteLength > cap) return { skip: "body-cap" };
|
|
9195
|
+
if (stillEncoded(bytes)) return encodedSkip();
|
|
9196
|
+
return { text: new TextDecoder().decode(bytes) };
|
|
9197
|
+
}
|
|
8632
9198
|
const text = await clone.text();
|
|
8633
9199
|
if (text.length > cap) return { skip: "body-cap" };
|
|
8634
9200
|
if (sniff && looksBinary(new TextEncoder().encode(text.slice(0, 512)))) return { skip: "binary-body" };
|
|
@@ -8702,6 +9268,39 @@ function extractRedactors(rule) {
|
|
|
8702
9268
|
function htmlEscape(str) {
|
|
8703
9269
|
return String(str).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
8704
9270
|
}
|
|
9271
|
+
function isJson(text) {
|
|
9272
|
+
try {
|
|
9273
|
+
JSON.parse(text);
|
|
9274
|
+
return true;
|
|
9275
|
+
} catch {
|
|
9276
|
+
return false;
|
|
9277
|
+
}
|
|
9278
|
+
}
|
|
9279
|
+
function preservesJsonStructure(before, after) {
|
|
9280
|
+
if (!isJson(before)) return true;
|
|
9281
|
+
if (!isJson(after)) return false;
|
|
9282
|
+
const expected = jsonStructure(before);
|
|
9283
|
+
const actual = jsonStructure(after);
|
|
9284
|
+
for (; ; ) {
|
|
9285
|
+
const left = expected.next();
|
|
9286
|
+
const right = actual.next();
|
|
9287
|
+
if (left.done || right.done) return left.done === right.done;
|
|
9288
|
+
if (left.value !== right.value) return false;
|
|
9289
|
+
}
|
|
9290
|
+
}
|
|
9291
|
+
function* jsonStructure(text) {
|
|
9292
|
+
const tokens = /"(?:[^"\\]|\\[\s\S])*"|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|[^\s]/g;
|
|
9293
|
+
for (const match of text.matchAll(tokens)) {
|
|
9294
|
+
const token = match[0];
|
|
9295
|
+
if (token[0] !== '"') {
|
|
9296
|
+
yield "token:" + token;
|
|
9297
|
+
continue;
|
|
9298
|
+
}
|
|
9299
|
+
let next = match.index + token.length;
|
|
9300
|
+
while (text[next] === " " || text[next] === " " || text[next] === "\r" || text[next] === "\n") next++;
|
|
9301
|
+
yield text[next] === ":" ? "key:" + JSON.parse(token) : "string";
|
|
9302
|
+
}
|
|
9303
|
+
}
|
|
8705
9304
|
function applyRedactors(body, redactors, mask, transform) {
|
|
8706
9305
|
let out = body;
|
|
8707
9306
|
for (const r of redactors) {
|
|
@@ -8744,10 +9343,12 @@ function applyPathRedactors(text, pathRedactors, mask, cap, transform) {
|
|
|
8744
9343
|
if (!pathRedactors.length || typeof text !== "string" || text.length > cap) return text;
|
|
8745
9344
|
const head = text.trimStart()[0];
|
|
8746
9345
|
if (head !== "{" && head !== "[") return text;
|
|
8747
|
-
|
|
9346
|
+
let preserved;
|
|
8748
9347
|
let obj;
|
|
8749
9348
|
try {
|
|
8750
|
-
|
|
9349
|
+
JSON.parse(text);
|
|
9350
|
+
preserved = preserveBigInts(text, mask);
|
|
9351
|
+
obj = JSON.parse(preserved.text);
|
|
8751
9352
|
} catch {
|
|
8752
9353
|
return text;
|
|
8753
9354
|
}
|
|
@@ -8756,65 +9357,44 @@ function applyPathRedactors(text, pathRedactors, mask, cap, transform) {
|
|
|
8756
9357
|
const pred = conditionPredicate(r.condition);
|
|
8757
9358
|
walkLeaves(obj, r.jsonPath, (loc) => {
|
|
8758
9359
|
try {
|
|
8759
|
-
|
|
8760
|
-
|
|
9360
|
+
const number = preserved.numbers.get(loc.value);
|
|
9361
|
+
if (pred(number === void 0 ? loc.value : Number(number))) {
|
|
9362
|
+
setOwn(loc.parent, loc.key, transform ? transform(number ?? String(loc.value)) : mask);
|
|
8761
9363
|
changed = true;
|
|
8762
9364
|
}
|
|
8763
9365
|
} catch {
|
|
8764
9366
|
}
|
|
8765
9367
|
});
|
|
8766
9368
|
}
|
|
8767
|
-
return changed ? restoreBigInts(JSON.stringify(obj)) : text;
|
|
8768
|
-
}
|
|
8769
|
-
var BIGINT_OPEN = "__PSBIGINT_9c2f__";
|
|
8770
|
-
var BIGINT_CLOSE = "__DNEGIB__";
|
|
8771
|
-
function preserveBigInts(text) {
|
|
8772
|
-
let out = "";
|
|
8773
|
-
let inStr = false;
|
|
8774
|
-
for (let i = 0; i < text.length; ) {
|
|
8775
|
-
const ch = text[i];
|
|
8776
|
-
if (inStr) {
|
|
8777
|
-
out += ch;
|
|
8778
|
-
if (ch === "\\") {
|
|
8779
|
-
out += text[i + 1] ?? "";
|
|
8780
|
-
i += 2;
|
|
8781
|
-
continue;
|
|
8782
|
-
}
|
|
8783
|
-
if (ch === '"') inStr = false;
|
|
8784
|
-
i++;
|
|
8785
|
-
continue;
|
|
8786
|
-
}
|
|
8787
|
-
if (ch === '"') {
|
|
8788
|
-
inStr = true;
|
|
8789
|
-
out += ch;
|
|
8790
|
-
i++;
|
|
8791
|
-
continue;
|
|
8792
|
-
}
|
|
8793
|
-
if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
8794
|
-
let j = ch === "-" ? i + 1 : i;
|
|
8795
|
-
let digits = 0;
|
|
8796
|
-
while (j < text.length && text[j] >= "0" && text[j] <= "9") {
|
|
8797
|
-
digits++;
|
|
8798
|
-
j++;
|
|
8799
|
-
}
|
|
8800
|
-
const next = text[j];
|
|
8801
|
-
const isIntToken = digits > 0 && next !== "." && next !== "e" && next !== "E";
|
|
8802
|
-
if (isIntToken && digits >= 16) {
|
|
8803
|
-
out += `"${BIGINT_OPEN}${text.slice(i, j)}${BIGINT_CLOSE}"`;
|
|
8804
|
-
} else {
|
|
8805
|
-
out += text.slice(i, j || i + 1);
|
|
8806
|
-
}
|
|
8807
|
-
i = j > i ? j : i + 1;
|
|
8808
|
-
continue;
|
|
8809
|
-
}
|
|
8810
|
-
out += ch;
|
|
8811
|
-
i++;
|
|
8812
|
-
}
|
|
8813
|
-
return out;
|
|
9369
|
+
return changed ? restoreBigInts(JSON.stringify(obj), preserved.numbers) : text;
|
|
8814
9370
|
}
|
|
8815
|
-
function
|
|
8816
|
-
|
|
8817
|
-
|
|
9371
|
+
function preserveBigInts(text, mask) {
|
|
9372
|
+
const occupied = /* @__PURE__ */ new Set([mask]);
|
|
9373
|
+
const integers = [];
|
|
9374
|
+
const tokens = /"(?:[^"\\]|\\[\s\S])*"|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/g;
|
|
9375
|
+
for (const match of text.matchAll(tokens)) {
|
|
9376
|
+
const token = match[0];
|
|
9377
|
+
if (token[0] === '"') occupied.add(JSON.parse(token));
|
|
9378
|
+
else if (/^-?\d{16,}$/.test(token)) integers.push({ start: match.index, token });
|
|
9379
|
+
}
|
|
9380
|
+
const numbers = /* @__PURE__ */ new Map();
|
|
9381
|
+
const chunks = [];
|
|
9382
|
+
let offset = 0;
|
|
9383
|
+
let index = 0;
|
|
9384
|
+
for (const { start, token } of integers) {
|
|
9385
|
+
let marker;
|
|
9386
|
+
do {
|
|
9387
|
+
marker = "__PSNUMBER_" + index++ + "__";
|
|
9388
|
+
} while (occupied.has(marker));
|
|
9389
|
+
numbers.set(marker, token);
|
|
9390
|
+
chunks.push(text.slice(offset, start), JSON.stringify(marker));
|
|
9391
|
+
offset = start + token.length;
|
|
9392
|
+
}
|
|
9393
|
+
chunks.push(text.slice(offset));
|
|
9394
|
+
return { text: chunks.join(""), numbers };
|
|
9395
|
+
}
|
|
9396
|
+
function restoreBigInts(text, numbers) {
|
|
9397
|
+
return text.replace(/"(__PSNUMBER_\d+__)"/g, (token, marker) => numbers.get(marker) ?? token);
|
|
8818
9398
|
}
|
|
8819
9399
|
function isHeaderMutation(action) {
|
|
8820
9400
|
return action === "set-header" || action === "remove-header" || action === "harden-cookie";
|