@patchstack/connect 0.4.2 → 0.5.1
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 -4
- package/README.md +27 -3
- package/dist/cli.js +319 -61
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +230 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +227 -17
- package/dist/index.js.map +1 -1
- package/dist/protect/templates/astro-middleware.ts +22 -1
- package/dist/protect/templates/express-guard.cjs +54 -4
- package/dist/protect/templates/express-guard.js +54 -4
- package/dist/protect/templates/express-guard.ts +43 -5
- package/dist/protect/templates/fastify-plugin.cjs +41 -3
- package/dist/protect/templates/fastify-plugin.js +41 -3
- package/dist/protect/templates/fastify-plugin.ts +30 -2
- package/dist/protect/templates/generic-guard.cjs +58 -5
- package/dist/protect/templates/generic-guard.js +58 -5
- package/dist/protect/templates/generic-guard.ts +47 -4
- package/dist/protect/templates/guard.ts +42 -8
- package/dist/protect/templates/next-middleware.ts +22 -1
- package/dist/protect/templates/nuxt-middleware.ts +22 -1
- package/dist/protect/templates/sveltekit-hooks.ts +22 -1
- package/dist/protect.cjs +351 -97
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.edge.js +56 -24
- package/dist/protect.edge.js.map +1 -1
- package/dist/protect.js +57 -25
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-JNCTIAM5.js → refresh-manifest-2MMPQN2A.js} +243 -35
- package/dist/refresh-manifest-2MMPQN2A.js.map +1 -0
- package/package.json +3 -3
- package/dist/refresh-manifest-JNCTIAM5.js.map +0 -1
package/dist/protect.js
CHANGED
|
@@ -2799,7 +2799,7 @@ async function installEgressGuard({ shouldBlock, onBlock, onSkip, dnsScreen = tr
|
|
|
2799
2799
|
for (const moduleName of ["node:http", "node:https"]) {
|
|
2800
2800
|
try {
|
|
2801
2801
|
const mod = await import(moduleName);
|
|
2802
|
-
const restore = patchHttpModule(mod.default ?? mod, block, screen);
|
|
2802
|
+
const restore = patchHttpModule(mod.default ?? mod, block, screen, skip);
|
|
2803
2803
|
if (restore) restores.push(restore);
|
|
2804
2804
|
} catch {
|
|
2805
2805
|
}
|
|
@@ -2813,7 +2813,7 @@ async function installEgressGuard({ shouldBlock, onBlock, onSkip, dnsScreen = tr
|
|
|
2813
2813
|
}
|
|
2814
2814
|
};
|
|
2815
2815
|
}
|
|
2816
|
-
function patchHttpModule(http, block, screen) {
|
|
2816
|
+
function patchHttpModule(http, block, screen, skip) {
|
|
2817
2817
|
if (!http || typeof http.request !== "function" || http.__patchstackGuarded) return null;
|
|
2818
2818
|
const originalRequest = http.request;
|
|
2819
2819
|
const originalGet = http.get;
|
|
@@ -2824,7 +2824,7 @@ function patchHttpModule(http, block, screen) {
|
|
|
2824
2824
|
}
|
|
2825
2825
|
if (target && screen && target.host && screen.isIP(target.host) === 0 && !screen.isExempt(target.host)) {
|
|
2826
2826
|
try {
|
|
2827
|
-
args = withScreeningLookup(args, target, block, screen.lookup);
|
|
2827
|
+
args = withScreeningLookup(args, target, block, screen.lookup, skip);
|
|
2828
2828
|
} catch {
|
|
2829
2829
|
}
|
|
2830
2830
|
}
|
|
@@ -2877,7 +2877,9 @@ function screenResolved(addresses, target, block) {
|
|
|
2877
2877
|
}
|
|
2878
2878
|
return null;
|
|
2879
2879
|
}
|
|
2880
|
-
function withScreeningLookup(args, target, block, lookup) {
|
|
2880
|
+
function withScreeningLookup(args, target, block, lookup, skip) {
|
|
2881
|
+
const own = callerLookup(args);
|
|
2882
|
+
const resolve = typeof own === "function" ? own : lookup;
|
|
2881
2883
|
const screeningLookup = (hostname, options, callback) => {
|
|
2882
2884
|
let opts = options;
|
|
2883
2885
|
let cb = callback;
|
|
@@ -2886,29 +2888,43 @@ function withScreeningLookup(args, target, block, lookup) {
|
|
|
2886
2888
|
opts = {};
|
|
2887
2889
|
}
|
|
2888
2890
|
if (!opts || typeof opts !== "object") opts = {};
|
|
2891
|
+
const handOver = (list) => {
|
|
2892
|
+
if (opts.all) return cb(null, list);
|
|
2893
|
+
const first = list[0];
|
|
2894
|
+
if (!first) return cb(new Error(`Patchstack: could not resolve ${hostname}`));
|
|
2895
|
+
return cb(null, first.address, first.family);
|
|
2896
|
+
};
|
|
2897
|
+
let answered = false;
|
|
2889
2898
|
try {
|
|
2890
|
-
|
|
2899
|
+
resolve(hostname, { ...opts, all: true }, (err, addresses) => {
|
|
2900
|
+
answered = true;
|
|
2891
2901
|
if (err) return cb(err);
|
|
2892
2902
|
const list = Array.isArray(addresses) ? addresses : [];
|
|
2893
|
-
|
|
2903
|
+
let blocked;
|
|
2904
|
+
try {
|
|
2905
|
+
blocked = screenResolved(list, target, block);
|
|
2906
|
+
} catch {
|
|
2907
|
+
skip("screen-failed", { host: hostname });
|
|
2908
|
+
return handOver(list);
|
|
2909
|
+
}
|
|
2894
2910
|
if (blocked) {
|
|
2895
2911
|
return cb(new Error(`Patchstack blocked an outbound request to a disallowed address: ${target.host} resolved to ${blocked}`));
|
|
2896
2912
|
}
|
|
2897
|
-
|
|
2898
|
-
const first = list[0];
|
|
2899
|
-
if (!first) return cb(new Error(`Patchstack: could not resolve ${hostname}`));
|
|
2900
|
-
return cb(null, first.address, first.family);
|
|
2913
|
+
return handOver(list);
|
|
2901
2914
|
});
|
|
2902
|
-
} catch {
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
} catch {
|
|
2906
|
-
cb(new Error(`Patchstack: lookup failed for ${hostname}`));
|
|
2907
|
-
}
|
|
2915
|
+
} catch (err) {
|
|
2916
|
+
if (answered) throw err;
|
|
2917
|
+
return cb(err);
|
|
2908
2918
|
}
|
|
2909
2919
|
};
|
|
2910
2920
|
return injectLookupOption(args, screeningLookup);
|
|
2911
2921
|
}
|
|
2922
|
+
function callerLookup(args) {
|
|
2923
|
+
for (const arg of args) {
|
|
2924
|
+
if (arg && typeof arg === "object" && !(arg instanceof URL) && typeof arg.lookup === "function") return arg.lookup;
|
|
2925
|
+
}
|
|
2926
|
+
return void 0;
|
|
2927
|
+
}
|
|
2912
2928
|
function injectLookupOption(args, lookup) {
|
|
2913
2929
|
const first = args[0];
|
|
2914
2930
|
if (first && typeof first === "object" && !(first instanceof URL)) {
|
|
@@ -4488,8 +4504,17 @@ function createServerFnGuard({ protection }) {
|
|
|
4488
4504
|
return { rule: body.rule, message: body.message || "Blocked by Patchstack" };
|
|
4489
4505
|
};
|
|
4490
4506
|
}
|
|
4507
|
+
function listOption(value, fallback, name, onError) {
|
|
4508
|
+
if (value === void 0) return fallback;
|
|
4509
|
+
if (Array.isArray(value)) return value;
|
|
4510
|
+
notify(onError, new Error(`patchstack: \`${name}\` has to be an array; the value configured is ignored`), "onError");
|
|
4511
|
+
return fallback;
|
|
4512
|
+
}
|
|
4491
4513
|
async function createProtection(options = {}) {
|
|
4492
4514
|
const onError = options.onError;
|
|
4515
|
+
const allowHosts = listOption(options.allowHosts, [], "allowHosts", onError);
|
|
4516
|
+
const configuredResponseRules = listOption(options.responseRules, DEFAULT_RESPONSE_RULES, "responseRules", onError);
|
|
4517
|
+
const configuredEgressRules = listOption(options.egressRules, DEFAULT_EGRESS_RULES, "egressRules", onError);
|
|
4493
4518
|
const userOnDetect = options.onDetect ?? defaultOnDetect;
|
|
4494
4519
|
const apiKey = await resolveApiKey(options);
|
|
4495
4520
|
const firewallLog = apiKey && telemetryEnabled() && options.reportFirewallLog !== false ? createFirewallLogReporter({
|
|
@@ -4589,9 +4614,9 @@ async function createProtection(options = {}) {
|
|
|
4589
4614
|
const applyBundle = (delivered) => {
|
|
4590
4615
|
const incoming = delivered.firewall ?? [];
|
|
4591
4616
|
requestRules = byPhase(incoming, "request");
|
|
4592
|
-
responseRules = [...
|
|
4617
|
+
responseRules = [...configuredResponseRules, ...byPhase(incoming, "response")];
|
|
4593
4618
|
screenCap = responseScreenCap(responseRules);
|
|
4594
|
-
egressRules = [...
|
|
4619
|
+
egressRules = [...configuredEgressRules, ...byPhase(incoming, "egress")];
|
|
4595
4620
|
engine = new RuleEngine({
|
|
4596
4621
|
firewall: requestRules,
|
|
4597
4622
|
whitelists: delivered.whitelists,
|
|
@@ -5060,7 +5085,7 @@ async function createProtection(options = {}) {
|
|
|
5060
5085
|
return origEnd(cb);
|
|
5061
5086
|
};
|
|
5062
5087
|
};
|
|
5063
|
-
const allow = new Set(
|
|
5088
|
+
const allow = new Set(allowHosts.map((h) => String(h).toLowerCase()));
|
|
5064
5089
|
const egressShouldBlock = (url, host, method) => {
|
|
5065
5090
|
if (host && allow.has(host.toLowerCase())) return false;
|
|
5066
5091
|
let egressPath = null;
|
|
@@ -5112,10 +5137,17 @@ async function createProtection(options = {}) {
|
|
|
5112
5137
|
return { request: requestRules, response: responseRules, egress: egressRules };
|
|
5113
5138
|
},
|
|
5114
5139
|
/**
|
|
5115
|
-
* Enforcement coverage: how often the guard
|
|
5116
|
-
* `<phase>:<reason>` (e.g. `response:body-cap`, `request:body-cap`,
|
|
5117
|
-
* `egress:resolver-failed`
|
|
5118
|
-
*
|
|
5140
|
+
* Enforcement coverage: how often the guard failed open rather than inspecting, in the cases it can
|
|
5141
|
+
* observe itself — keyed `<phase>:<reason>` (e.g. `response:body-cap`, `request:body-cap`,
|
|
5142
|
+
* `response:live-stream`, `egress:resolver-failed` for a fetch call whose address could not be
|
|
5143
|
+
* resolved). "Always-on" is not "always inspected", so surface this (or pass `onSkip`) rather than
|
|
5144
|
+
* assuming coverage.
|
|
5145
|
+
*
|
|
5146
|
+
* Not a total, in two ways. An outbound call can miss the address check without being counted:
|
|
5147
|
+
* whether it resolved through the screening resolver depends on the agent and the socket pool,
|
|
5148
|
+
* which this guard cannot observe. And a resolver failure on the node path appears nowhere here
|
|
5149
|
+
* because it is not a bypass — that resolver is the connection's, so the call fails instead.
|
|
5150
|
+
* Zero skips means nothing the guard can see was bypassed, not that nothing was.
|
|
5119
5151
|
*/
|
|
5120
5152
|
coverage() {
|
|
5121
5153
|
return { skipped: { ...skipCounts } };
|
|
@@ -5248,7 +5280,7 @@ async function createProtection(options = {}) {
|
|
|
5248
5280
|
// same skip accounting as the request/response phases.
|
|
5249
5281
|
onSkip: ({ reason, detail }) => recordSkip("egress", reason, detail),
|
|
5250
5282
|
dnsScreen: options.screenDns !== false,
|
|
5251
|
-
allowHosts
|
|
5283
|
+
allowHosts
|
|
5252
5284
|
});
|
|
5253
5285
|
}
|
|
5254
5286
|
const cwd = options.cwd ?? (typeof process !== "undefined" ? process.cwd() : void 0);
|
|
@@ -5258,7 +5290,7 @@ async function createProtection(options = {}) {
|
|
|
5258
5290
|
let reporter = null;
|
|
5259
5291
|
if (refreshable && options.siteUuid && options.reportManifest !== false && cwd) {
|
|
5260
5292
|
try {
|
|
5261
|
-
({ reportManifest: reporter } = await import("./refresh-manifest-
|
|
5293
|
+
({ reportManifest: reporter } = await import("./refresh-manifest-2MMPQN2A.js"));
|
|
5262
5294
|
} catch (err) {
|
|
5263
5295
|
notify(onError, err, "onError");
|
|
5264
5296
|
}
|