@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.edge.js
CHANGED
|
@@ -2913,7 +2913,7 @@ async function installEgressGuard({ shouldBlock, onBlock, onSkip, dnsScreen = tr
|
|
|
2913
2913
|
for (const moduleName of ["node:http", "node:https"]) {
|
|
2914
2914
|
try {
|
|
2915
2915
|
const mod = await import(moduleName);
|
|
2916
|
-
const restore = patchHttpModule(mod.default ?? mod, block, screen);
|
|
2916
|
+
const restore = patchHttpModule(mod.default ?? mod, block, screen, skip);
|
|
2917
2917
|
if (restore) restores.push(restore);
|
|
2918
2918
|
} catch {
|
|
2919
2919
|
}
|
|
@@ -2927,7 +2927,7 @@ async function installEgressGuard({ shouldBlock, onBlock, onSkip, dnsScreen = tr
|
|
|
2927
2927
|
}
|
|
2928
2928
|
};
|
|
2929
2929
|
}
|
|
2930
|
-
function patchHttpModule(http, block, screen) {
|
|
2930
|
+
function patchHttpModule(http, block, screen, skip) {
|
|
2931
2931
|
if (!http || typeof http.request !== "function" || http.__patchstackGuarded) return null;
|
|
2932
2932
|
const originalRequest = http.request;
|
|
2933
2933
|
const originalGet = http.get;
|
|
@@ -2938,7 +2938,7 @@ function patchHttpModule(http, block, screen) {
|
|
|
2938
2938
|
}
|
|
2939
2939
|
if (target && screen && target.host && screen.isIP(target.host) === 0 && !screen.isExempt(target.host)) {
|
|
2940
2940
|
try {
|
|
2941
|
-
args = withScreeningLookup(args, target, block, screen.lookup);
|
|
2941
|
+
args = withScreeningLookup(args, target, block, screen.lookup, skip);
|
|
2942
2942
|
} catch {
|
|
2943
2943
|
}
|
|
2944
2944
|
}
|
|
@@ -2991,7 +2991,9 @@ function screenResolved(addresses, target, block) {
|
|
|
2991
2991
|
}
|
|
2992
2992
|
return null;
|
|
2993
2993
|
}
|
|
2994
|
-
function withScreeningLookup(args, target, block, lookup) {
|
|
2994
|
+
function withScreeningLookup(args, target, block, lookup, skip) {
|
|
2995
|
+
const own = callerLookup(args);
|
|
2996
|
+
const resolve = typeof own === "function" ? own : lookup;
|
|
2995
2997
|
const screeningLookup = (hostname, options, callback) => {
|
|
2996
2998
|
let opts = options;
|
|
2997
2999
|
let cb = callback;
|
|
@@ -3000,29 +3002,43 @@ function withScreeningLookup(args, target, block, lookup) {
|
|
|
3000
3002
|
opts = {};
|
|
3001
3003
|
}
|
|
3002
3004
|
if (!opts || typeof opts !== "object") opts = {};
|
|
3005
|
+
const handOver = (list) => {
|
|
3006
|
+
if (opts.all) return cb(null, list);
|
|
3007
|
+
const first = list[0];
|
|
3008
|
+
if (!first) return cb(new Error(`Patchstack: could not resolve ${hostname}`));
|
|
3009
|
+
return cb(null, first.address, first.family);
|
|
3010
|
+
};
|
|
3011
|
+
let answered = false;
|
|
3003
3012
|
try {
|
|
3004
|
-
|
|
3013
|
+
resolve(hostname, { ...opts, all: true }, (err, addresses) => {
|
|
3014
|
+
answered = true;
|
|
3005
3015
|
if (err) return cb(err);
|
|
3006
3016
|
const list = Array.isArray(addresses) ? addresses : [];
|
|
3007
|
-
|
|
3017
|
+
let blocked;
|
|
3018
|
+
try {
|
|
3019
|
+
blocked = screenResolved(list, target, block);
|
|
3020
|
+
} catch {
|
|
3021
|
+
skip("screen-failed", { host: hostname });
|
|
3022
|
+
return handOver(list);
|
|
3023
|
+
}
|
|
3008
3024
|
if (blocked) {
|
|
3009
3025
|
return cb(new Error(`Patchstack blocked an outbound request to a disallowed address: ${target.host} resolved to ${blocked}`));
|
|
3010
3026
|
}
|
|
3011
|
-
|
|
3012
|
-
const first = list[0];
|
|
3013
|
-
if (!first) return cb(new Error(`Patchstack: could not resolve ${hostname}`));
|
|
3014
|
-
return cb(null, first.address, first.family);
|
|
3027
|
+
return handOver(list);
|
|
3015
3028
|
});
|
|
3016
|
-
} catch {
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
} catch {
|
|
3020
|
-
cb(new Error(`Patchstack: lookup failed for ${hostname}`));
|
|
3021
|
-
}
|
|
3029
|
+
} catch (err) {
|
|
3030
|
+
if (answered) throw err;
|
|
3031
|
+
return cb(err);
|
|
3022
3032
|
}
|
|
3023
3033
|
};
|
|
3024
3034
|
return injectLookupOption(args, screeningLookup);
|
|
3025
3035
|
}
|
|
3036
|
+
function callerLookup(args) {
|
|
3037
|
+
for (const arg of args) {
|
|
3038
|
+
if (arg && typeof arg === "object" && !(arg instanceof URL) && typeof arg.lookup === "function") return arg.lookup;
|
|
3039
|
+
}
|
|
3040
|
+
return void 0;
|
|
3041
|
+
}
|
|
3026
3042
|
function injectLookupOption(args, lookup) {
|
|
3027
3043
|
const first = args[0];
|
|
3028
3044
|
if (first && typeof first === "object" && !(first instanceof URL)) {
|
|
@@ -4602,8 +4618,17 @@ function createServerFnGuard({ protection }) {
|
|
|
4602
4618
|
return { rule: body.rule, message: body.message || "Blocked by Patchstack" };
|
|
4603
4619
|
};
|
|
4604
4620
|
}
|
|
4621
|
+
function listOption(value, fallback, name, onError) {
|
|
4622
|
+
if (value === void 0) return fallback;
|
|
4623
|
+
if (Array.isArray(value)) return value;
|
|
4624
|
+
notify(onError, new Error(`patchstack: \`${name}\` has to be an array; the value configured is ignored`), "onError");
|
|
4625
|
+
return fallback;
|
|
4626
|
+
}
|
|
4605
4627
|
async function createProtection(options = {}) {
|
|
4606
4628
|
const onError = options.onError;
|
|
4629
|
+
const allowHosts = listOption(options.allowHosts, [], "allowHosts", onError);
|
|
4630
|
+
const configuredResponseRules = listOption(options.responseRules, DEFAULT_RESPONSE_RULES, "responseRules", onError);
|
|
4631
|
+
const configuredEgressRules = listOption(options.egressRules, DEFAULT_EGRESS_RULES, "egressRules", onError);
|
|
4607
4632
|
const userOnDetect = options.onDetect ?? defaultOnDetect;
|
|
4608
4633
|
const apiKey = await resolveApiKey(options);
|
|
4609
4634
|
const firewallLog = apiKey && telemetryEnabled() && options.reportFirewallLog !== false ? createFirewallLogReporter({
|
|
@@ -4703,9 +4728,9 @@ async function createProtection(options = {}) {
|
|
|
4703
4728
|
const applyBundle = (delivered) => {
|
|
4704
4729
|
const incoming = delivered.firewall ?? [];
|
|
4705
4730
|
requestRules = byPhase(incoming, "request");
|
|
4706
|
-
responseRules = [...
|
|
4731
|
+
responseRules = [...configuredResponseRules, ...byPhase(incoming, "response")];
|
|
4707
4732
|
screenCap = responseScreenCap(responseRules);
|
|
4708
|
-
egressRules = [...
|
|
4733
|
+
egressRules = [...configuredEgressRules, ...byPhase(incoming, "egress")];
|
|
4709
4734
|
engine = new RuleEngine({
|
|
4710
4735
|
firewall: requestRules,
|
|
4711
4736
|
whitelists: delivered.whitelists,
|
|
@@ -5174,7 +5199,7 @@ async function createProtection(options = {}) {
|
|
|
5174
5199
|
return origEnd(cb);
|
|
5175
5200
|
};
|
|
5176
5201
|
};
|
|
5177
|
-
const allow = new Set(
|
|
5202
|
+
const allow = new Set(allowHosts.map((h) => String(h).toLowerCase()));
|
|
5178
5203
|
const egressShouldBlock = (url, host, method) => {
|
|
5179
5204
|
if (host && allow.has(host.toLowerCase())) return false;
|
|
5180
5205
|
let egressPath = null;
|
|
@@ -5226,10 +5251,17 @@ async function createProtection(options = {}) {
|
|
|
5226
5251
|
return { request: requestRules, response: responseRules, egress: egressRules };
|
|
5227
5252
|
},
|
|
5228
5253
|
/**
|
|
5229
|
-
* Enforcement coverage: how often the guard
|
|
5230
|
-
* `<phase>:<reason>` (e.g. `response:body-cap`, `request:body-cap`,
|
|
5231
|
-
* `egress:resolver-failed`
|
|
5232
|
-
*
|
|
5254
|
+
* Enforcement coverage: how often the guard failed open rather than inspecting, in the cases it can
|
|
5255
|
+
* observe itself — keyed `<phase>:<reason>` (e.g. `response:body-cap`, `request:body-cap`,
|
|
5256
|
+
* `response:live-stream`, `egress:resolver-failed` for a fetch call whose address could not be
|
|
5257
|
+
* resolved). "Always-on" is not "always inspected", so surface this (or pass `onSkip`) rather than
|
|
5258
|
+
* assuming coverage.
|
|
5259
|
+
*
|
|
5260
|
+
* Not a total, in two ways. An outbound call can miss the address check without being counted:
|
|
5261
|
+
* whether it resolved through the screening resolver depends on the agent and the socket pool,
|
|
5262
|
+
* which this guard cannot observe. And a resolver failure on the node path appears nowhere here
|
|
5263
|
+
* because it is not a bypass — that resolver is the connection's, so the call fails instead.
|
|
5264
|
+
* Zero skips means nothing the guard can see was bypassed, not that nothing was.
|
|
5233
5265
|
*/
|
|
5234
5266
|
coverage() {
|
|
5235
5267
|
return { skipped: { ...skipCounts } };
|
|
@@ -5362,7 +5394,7 @@ async function createProtection(options = {}) {
|
|
|
5362
5394
|
// same skip accounting as the request/response phases.
|
|
5363
5395
|
onSkip: ({ reason, detail }) => recordSkip("egress", reason, detail),
|
|
5364
5396
|
dnsScreen: options.screenDns !== false,
|
|
5365
|
-
allowHosts
|
|
5397
|
+
allowHosts
|
|
5366
5398
|
});
|
|
5367
5399
|
}
|
|
5368
5400
|
const cwd = options.cwd ?? (typeof process !== "undefined" ? process.cwd() : void 0);
|