@patchstack/connect 0.3.29 → 0.3.31
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 +42 -23
- package/README.md +40 -11
- package/dist/{chunk-MJOTFUDE.js → chunk-LLKP5EJS.js} +1 -1
- package/dist/chunk-LLKP5EJS.js.map +1 -0
- package/dist/cli.js +1265 -287
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +456 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -10
- package/dist/index.d.ts +76 -10
- package/dist/index.js +449 -104
- package/dist/index.js.map +1 -1
- package/dist/protect/templates/astro-middleware.ts +33 -13
- package/dist/protect/templates/demo-rules.json +2 -3
- package/dist/protect/templates/express-guard.cjs +26 -15
- package/dist/protect/templates/express-guard.js +26 -15
- package/dist/protect/templates/express-guard.ts +33 -16
- package/dist/protect/templates/fastify-plugin.cjs +33 -11
- package/dist/protect/templates/fastify-plugin.js +33 -11
- package/dist/protect/templates/fastify-plugin.ts +40 -12
- package/dist/protect/templates/generic-guard.cjs +28 -12
- package/dist/protect/templates/generic-guard.js +28 -13
- package/dist/protect/templates/generic-guard.ts +41 -20
- package/dist/protect/templates/guard.ts +47 -27
- package/dist/protect/templates/next-middleware.ts +29 -12
- package/dist/protect/templates/nuxt-middleware.ts +29 -12
- package/dist/protect/templates/rules.json +2 -2
- package/dist/protect/templates/sveltekit-hooks.ts +33 -13
- package/dist/protect.cjs +1204 -252
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +39 -11
- package/dist/protect.edge.js +792 -143
- package/dist/protect.edge.js.map +4 -4
- package/dist/protect.js +794 -145
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-VRBE6RH6.js → refresh-manifest-KNZQBC4V.js} +387 -100
- package/dist/refresh-manifest-KNZQBC4V.js.map +1 -0
- package/package.json +5 -2
- package/dist/chunk-MJOTFUDE.js.map +0 -1
- package/dist/refresh-manifest-VRBE6RH6.js.map +0 -1
|
@@ -7,29 +7,49 @@ import fallbackRules from "./patchstack.rules.json";
|
|
|
7
7
|
|
|
8
8
|
const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* One protection policy, memoized on the IN-FLIGHT promise rather than the resolved value.
|
|
12
|
+
*
|
|
13
|
+
* A cold start takes several concurrent requests. Caching only the finished value lets each of them see an
|
|
14
|
+
* empty cache and start its own build — several rule fetches, several refresh loops, and several policies
|
|
15
|
+
* where the app is meant to have one. Holding the promise means the first request starts it and the rest
|
|
16
|
+
* await the same one. A failed build is not cached, so the next request retries rather than inheriting one
|
|
17
|
+
* bad boot for the life of the process.
|
|
18
|
+
*/
|
|
19
|
+
let _protection: Promise<Awaited<ReturnType<typeof createProtection>>> | undefined;
|
|
11
20
|
async function getProtection() {
|
|
12
21
|
if (!_protection) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
_protection = await createProtection(
|
|
18
|
-
siteUuid
|
|
19
|
-
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
20
|
-
: token
|
|
21
|
-
? { ...common, token, cacheDir: ".patchstack" }
|
|
22
|
-
: { ...common, rules: fallbackRules as never },
|
|
23
|
-
);
|
|
22
|
+
_protection = buildProtection().catch((err) => {
|
|
23
|
+
_protection = undefined; // don't cache a failed boot
|
|
24
|
+
throw err;
|
|
25
|
+
});
|
|
24
26
|
}
|
|
27
|
+
|
|
25
28
|
return _protection;
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
async function buildProtection() {
|
|
32
|
+
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
33
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
34
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
35
|
+
const common = { mode, egress: true } as const;
|
|
36
|
+
return createProtection(
|
|
37
|
+
siteUuid
|
|
38
|
+
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
39
|
+
: token
|
|
40
|
+
? { ...common, token, cacheDir: ".patchstack" }
|
|
41
|
+
: { ...common, rules: fallbackRules as never },
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
// #region patchstack-astro (managed by patchstack-connect protect — do not edit)
|
|
29
46
|
export const onRequest: MiddlewareHandler = async (context, next) => {
|
|
30
47
|
const protection = await getProtection();
|
|
31
48
|
const blocked = await protection.fetchGuard()(context.request);
|
|
32
49
|
if (blocked) return blocked; // 403 — blocked before it reaches your route
|
|
33
|
-
|
|
50
|
+
// Response rules can be scoped to a route or a method, and the engine can only apply that scope if it is
|
|
51
|
+
// given the request the response belongs to. Passed through here for that reason: without it a scoped
|
|
52
|
+
// response rule is delivered, counted as protection, and never matches anything.
|
|
53
|
+
return protection.screenResponse(await next(), context.request);
|
|
34
54
|
};
|
|
35
55
|
// #endregion patchstack-astro
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"parameter": [
|
|
55
55
|
"get.file",
|
|
56
56
|
"post.file",
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
"get.path",
|
|
59
59
|
"post.path"
|
|
60
60
|
],
|
|
@@ -172,8 +172,7 @@
|
|
|
172
172
|
{
|
|
173
173
|
"parameter": [
|
|
174
174
|
"get.url",
|
|
175
|
-
"post.url"
|
|
176
|
-
"raw.url"
|
|
175
|
+
"post.url"
|
|
177
176
|
],
|
|
178
177
|
"mutations": [
|
|
179
178
|
"urldecode"
|
|
@@ -7,25 +7,36 @@ let protection;
|
|
|
7
7
|
|
|
8
8
|
async function getProtection() {
|
|
9
9
|
if (!protection) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const common = { mode, egress: true, refreshMs };
|
|
18
|
-
protection = await createProtection(
|
|
19
|
-
siteUuid
|
|
20
|
-
? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
|
|
21
|
-
: token
|
|
22
|
-
? { ...common, token, cacheDir: ".patchstack" }
|
|
23
|
-
: { ...common, rules: fallbackRules },
|
|
24
|
-
);
|
|
10
|
+
// Memoized on the in-flight promise, not the resolved value: a cold start takes several
|
|
11
|
+
// concurrent requests, and caching only the finished value lets each of them build its own
|
|
12
|
+
// policy — several rule fetches and several refresh loops where the app should have one.
|
|
13
|
+
protection = buildProtection().catch((err) => {
|
|
14
|
+
protection = undefined; // don't cache a failed boot
|
|
15
|
+
throw err;
|
|
16
|
+
});
|
|
25
17
|
}
|
|
18
|
+
|
|
26
19
|
return protection;
|
|
27
20
|
}
|
|
28
21
|
|
|
22
|
+
async function buildProtection() {
|
|
23
|
+
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
24
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
25
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
26
|
+
// The sandbox dev server is long-lived and isn't restarted on change, so refresh the live
|
|
27
|
+
// rules periodically — a dependency flagged after boot is then enforced without a restart.
|
|
28
|
+
// Production relies on a redeploy (which re-fetches at boot), so refresh stays off there.
|
|
29
|
+
const refreshMs = process.env.PATCHSTACK_ENVIRONMENT === "sandbox" ? 15000 : 0;
|
|
30
|
+
const common = { mode, egress: true, refreshMs };
|
|
31
|
+
return createProtection(
|
|
32
|
+
siteUuid
|
|
33
|
+
? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
|
|
34
|
+
: token
|
|
35
|
+
? { ...common, token, cacheDir: ".patchstack" }
|
|
36
|
+
: { ...common, rules: fallbackRules },
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
function patchstackMiddleware(req, res, next) {
|
|
30
41
|
getProtection()
|
|
31
42
|
.then((active) => active.express()(req, res, next))
|
|
@@ -8,25 +8,36 @@ let protection;
|
|
|
8
8
|
|
|
9
9
|
async function getProtection() {
|
|
10
10
|
if (!protection) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const common = { mode, egress: true, refreshMs };
|
|
19
|
-
protection = await createProtection(
|
|
20
|
-
siteUuid
|
|
21
|
-
? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
|
|
22
|
-
: token
|
|
23
|
-
? { ...common, token, cacheDir: ".patchstack" }
|
|
24
|
-
: { ...common, rules: fallbackRules },
|
|
25
|
-
);
|
|
11
|
+
// Memoized on the in-flight promise, not the resolved value: a cold start takes several
|
|
12
|
+
// concurrent requests, and caching only the finished value lets each of them build its own
|
|
13
|
+
// policy — several rule fetches and several refresh loops where the app should have one.
|
|
14
|
+
protection = buildProtection().catch((err) => {
|
|
15
|
+
protection = undefined; // don't cache a failed boot
|
|
16
|
+
throw err;
|
|
17
|
+
});
|
|
26
18
|
}
|
|
19
|
+
|
|
27
20
|
return protection;
|
|
28
21
|
}
|
|
29
22
|
|
|
23
|
+
async function buildProtection() {
|
|
24
|
+
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
25
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
26
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
27
|
+
// The sandbox dev server is long-lived and isn't restarted on change, so refresh the live
|
|
28
|
+
// rules periodically — a dependency flagged after boot is then enforced without a restart.
|
|
29
|
+
// Production relies on a redeploy (which re-fetches at boot), so refresh stays off there.
|
|
30
|
+
const refreshMs = process.env.PATCHSTACK_ENVIRONMENT === "sandbox" ? 15000 : 0;
|
|
31
|
+
const common = { mode, egress: true, refreshMs };
|
|
32
|
+
return createProtection(
|
|
33
|
+
siteUuid
|
|
34
|
+
? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
|
|
35
|
+
: token
|
|
36
|
+
? { ...common, token, cacheDir: ".patchstack" }
|
|
37
|
+
: { ...common, rules: fallbackRules },
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
export function patchstackMiddleware(req, res, next) {
|
|
31
42
|
getProtection()
|
|
32
43
|
.then((active) => active.express()(req, res, next))
|
|
@@ -4,29 +4,46 @@ import { createProtection } from "@patchstack/connect/protect";
|
|
|
4
4
|
import fallbackRules from "./rules.json";
|
|
5
5
|
|
|
6
6
|
const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* One protection policy, memoized on the IN-FLIGHT promise rather than the resolved value.
|
|
9
|
+
*
|
|
10
|
+
* A cold start takes several concurrent requests. Caching only the finished value lets each of them see an
|
|
11
|
+
* empty cache and start its own build — several rule fetches, several refresh loops, and several policies
|
|
12
|
+
* where the app is meant to have one. Holding the promise means the first request starts it and the rest
|
|
13
|
+
* await the same one. A failed build is not cached, so the next request retries rather than inheriting one
|
|
14
|
+
* bad boot for the life of the process.
|
|
15
|
+
*/
|
|
16
|
+
let _protection: Promise<Awaited<ReturnType<typeof createProtection>>> | undefined;
|
|
8
17
|
|
|
9
18
|
async function getProtection() {
|
|
10
19
|
if (!_protection) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// rules periodically — a dependency flagged after boot is then enforced without a restart.
|
|
16
|
-
// Production relies on a redeploy (which re-fetches at boot), so refresh stays off there.
|
|
17
|
-
const refreshMs = process.env.PATCHSTACK_ENVIRONMENT === "sandbox" ? 15000 : 0;
|
|
18
|
-
const common = { mode, egress: true, refreshMs } as const;
|
|
19
|
-
_protection = await createProtection(
|
|
20
|
-
siteUuid
|
|
21
|
-
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
22
|
-
: token
|
|
23
|
-
? { ...common, token, cacheDir: ".patchstack" }
|
|
24
|
-
: { ...common, rules: fallbackRules as never },
|
|
25
|
-
);
|
|
20
|
+
_protection = buildProtection().catch((err) => {
|
|
21
|
+
_protection = undefined; // don't cache a failed boot
|
|
22
|
+
throw err;
|
|
23
|
+
});
|
|
26
24
|
}
|
|
25
|
+
|
|
27
26
|
return _protection;
|
|
28
27
|
}
|
|
29
28
|
|
|
29
|
+
async function buildProtection() {
|
|
30
|
+
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
31
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
32
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
33
|
+
// The sandbox dev server is long-lived and isn't restarted on change, so refresh the live
|
|
34
|
+
// rules periodically — a dependency flagged after boot is then enforced without a restart.
|
|
35
|
+
// Production relies on a redeploy (which re-fetches at boot), so refresh stays off there.
|
|
36
|
+
const refreshMs = process.env.PATCHSTACK_ENVIRONMENT === "sandbox" ? 15000 : 0;
|
|
37
|
+
const common = { mode, egress: true, refreshMs } as const;
|
|
38
|
+
return createProtection(
|
|
39
|
+
siteUuid
|
|
40
|
+
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
41
|
+
: token
|
|
42
|
+
? { ...common, token, cacheDir: ".patchstack" }
|
|
43
|
+
: { ...common, rules: fallbackRules as never },
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
export function patchstackMiddleware(req: unknown, res: unknown, next: (err?: unknown) => void) {
|
|
31
48
|
getProtection()
|
|
32
49
|
.then((protection) =>
|
|
@@ -8,21 +8,32 @@ let protection;
|
|
|
8
8
|
|
|
9
9
|
async function getProtection() {
|
|
10
10
|
if (!protection) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
: token
|
|
19
|
-
? { ...common, token, cacheDir: ".patchstack" }
|
|
20
|
-
: { ...common, rules: fallbackRules },
|
|
21
|
-
);
|
|
11
|
+
// Memoized on the in-flight promise, not the resolved value: a cold start takes several
|
|
12
|
+
// concurrent requests, and caching only the finished value lets each of them build its own
|
|
13
|
+
// policy — several rule fetches and several refresh loops where the app should have one.
|
|
14
|
+
protection = buildProtection().catch((err) => {
|
|
15
|
+
protection = undefined; // don't cache a failed boot
|
|
16
|
+
throw err;
|
|
17
|
+
});
|
|
22
18
|
}
|
|
19
|
+
|
|
23
20
|
return protection;
|
|
24
21
|
}
|
|
25
22
|
|
|
23
|
+
async function buildProtection() {
|
|
24
|
+
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
25
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
26
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
27
|
+
const common = { mode, egress: true };
|
|
28
|
+
return createProtection(
|
|
29
|
+
siteUuid
|
|
30
|
+
? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
|
|
31
|
+
: token
|
|
32
|
+
? { ...common, token, cacheDir: ".patchstack" }
|
|
33
|
+
: { ...common, rules: fallbackRules },
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
async function patchstackFastify(fastify) {
|
|
27
38
|
const protection = await getProtection();
|
|
28
39
|
const guard = protection.fetchGuard();
|
|
@@ -43,4 +54,15 @@ async function patchstackFastify(fastify) {
|
|
|
43
54
|
});
|
|
44
55
|
}
|
|
45
56
|
|
|
57
|
+
// Fastify ENCAPSULATES a registered plugin: hooks added inside one apply to that plugin's context and
|
|
58
|
+
// its children, and to nothing else. Registered as an ordinary plugin, this guard would screen nothing
|
|
59
|
+
// on the root instance and nothing in sibling route plugins — which is most of an application, and it
|
|
60
|
+
// would look installed the whole time.
|
|
61
|
+
//
|
|
62
|
+
// This is the marker `fastify-plugin` sets, and the mechanism Fastify documents for opting out: with it,
|
|
63
|
+
// `register` runs the function against the ROOT instance instead of a child context, so the hook applies
|
|
64
|
+
// to every route. Set here rather than pulling in `fastify-plugin` so an installed app gains no
|
|
65
|
+
// dependency it did not already have.
|
|
66
|
+
patchstackFastify[Symbol.for("skip-override")] = true;
|
|
67
|
+
|
|
46
68
|
module.exports = { patchstackFastify };
|
|
@@ -9,21 +9,32 @@ let protection;
|
|
|
9
9
|
|
|
10
10
|
async function getProtection() {
|
|
11
11
|
if (!protection) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
: token
|
|
20
|
-
? { ...common, token, cacheDir: ".patchstack" }
|
|
21
|
-
: { ...common, rules: fallbackRules },
|
|
22
|
-
);
|
|
12
|
+
// Memoized on the in-flight promise, not the resolved value: a cold start takes several
|
|
13
|
+
// concurrent requests, and caching only the finished value lets each of them build its own
|
|
14
|
+
// policy — several rule fetches and several refresh loops where the app should have one.
|
|
15
|
+
protection = buildProtection().catch((err) => {
|
|
16
|
+
protection = undefined; // don't cache a failed boot
|
|
17
|
+
throw err;
|
|
18
|
+
});
|
|
23
19
|
}
|
|
20
|
+
|
|
24
21
|
return protection;
|
|
25
22
|
}
|
|
26
23
|
|
|
24
|
+
async function buildProtection() {
|
|
25
|
+
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
26
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
27
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
28
|
+
const common = { mode, egress: true };
|
|
29
|
+
return createProtection(
|
|
30
|
+
siteUuid
|
|
31
|
+
? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
|
|
32
|
+
: token
|
|
33
|
+
? { ...common, token, cacheDir: ".patchstack" }
|
|
34
|
+
: { ...common, rules: fallbackRules },
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
export async function patchstackFastify(fastify) {
|
|
28
39
|
const protection = await getProtection();
|
|
29
40
|
const guard = protection.fetchGuard();
|
|
@@ -43,3 +54,14 @@ export async function patchstackFastify(fastify) {
|
|
|
43
54
|
}
|
|
44
55
|
});
|
|
45
56
|
}
|
|
57
|
+
|
|
58
|
+
// Fastify ENCAPSULATES a registered plugin: hooks added inside one apply to that plugin's context and
|
|
59
|
+
// its children, and to nothing else. Registered as an ordinary plugin, this guard would screen nothing
|
|
60
|
+
// on the root instance and nothing in sibling route plugins — which is most of an application, and it
|
|
61
|
+
// would look installed the whole time.
|
|
62
|
+
//
|
|
63
|
+
// This is the marker `fastify-plugin` sets, and the mechanism Fastify documents for opting out: with it,
|
|
64
|
+
// `register` runs the function against the ROOT instance instead of a child context, so the hook applies
|
|
65
|
+
// to every route. Set here rather than pulling in `fastify-plugin` so an installed app gains no
|
|
66
|
+
// dependency it did not already have.
|
|
67
|
+
patchstackFastify[Symbol.for("skip-override")] = true;
|
|
@@ -7,24 +7,41 @@ import fallbackRules from "./rules.json";
|
|
|
7
7
|
|
|
8
8
|
const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* One protection policy, memoized on the IN-FLIGHT promise rather than the resolved value.
|
|
12
|
+
*
|
|
13
|
+
* A cold start takes several concurrent requests. Caching only the finished value lets each of them see an
|
|
14
|
+
* empty cache and start its own build — several rule fetches, several refresh loops, and several policies
|
|
15
|
+
* where the app is meant to have one. Holding the promise means the first request starts it and the rest
|
|
16
|
+
* await the same one. A failed build is not cached, so the next request retries rather than inheriting one
|
|
17
|
+
* bad boot for the life of the process.
|
|
18
|
+
*/
|
|
19
|
+
let _protection: Promise<Awaited<ReturnType<typeof createProtection>>> | undefined;
|
|
11
20
|
async function getProtection() {
|
|
12
21
|
if (!_protection) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
_protection = await createProtection(
|
|
18
|
-
siteUuid
|
|
19
|
-
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
20
|
-
: token
|
|
21
|
-
? { ...common, token, cacheDir: ".patchstack" }
|
|
22
|
-
: { ...common, rules: fallbackRules as never },
|
|
23
|
-
);
|
|
22
|
+
_protection = buildProtection().catch((err) => {
|
|
23
|
+
_protection = undefined; // don't cache a failed boot
|
|
24
|
+
throw err;
|
|
25
|
+
});
|
|
24
26
|
}
|
|
27
|
+
|
|
25
28
|
return _protection;
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
async function buildProtection() {
|
|
32
|
+
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
33
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
34
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
35
|
+
const common = { mode, egress: true } as const;
|
|
36
|
+
return createProtection(
|
|
37
|
+
siteUuid
|
|
38
|
+
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
39
|
+
: token
|
|
40
|
+
? { ...common, token, cacheDir: ".patchstack" }
|
|
41
|
+
: { ...common, rules: fallbackRules as never },
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
// #region patchstack-fastify (managed by patchstack-connect protect — do not edit)
|
|
29
46
|
export async function patchstackFastify(fastify: any) {
|
|
30
47
|
const protection = await getProtection();
|
|
@@ -46,3 +63,14 @@ export async function patchstackFastify(fastify: any) {
|
|
|
46
63
|
});
|
|
47
64
|
}
|
|
48
65
|
// #endregion patchstack-fastify
|
|
66
|
+
|
|
67
|
+
// Fastify ENCAPSULATES a registered plugin: hooks added inside one apply to that plugin's context and
|
|
68
|
+
// its children, and to nothing else. Registered as an ordinary plugin, this guard would screen nothing
|
|
69
|
+
// on the root instance and nothing in sibling route plugins — which is most of an application, and it
|
|
70
|
+
// would look installed the whole time.
|
|
71
|
+
//
|
|
72
|
+
// This is the marker `fastify-plugin` sets, and the mechanism Fastify documents for opting out: with it,
|
|
73
|
+
// `register` runs the function against the ROOT instance instead of a child context, so the hook applies
|
|
74
|
+
// to every route. Set here rather than pulling in `fastify-plugin` so an installed app gains no
|
|
75
|
+
// dependency it did not already have.
|
|
76
|
+
(patchstackFastify as any)[Symbol.for("skip-override")] = true;
|
|
@@ -8,30 +8,46 @@ let protection;
|
|
|
8
8
|
|
|
9
9
|
async function getProtection() {
|
|
10
10
|
if (!protection) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
: token
|
|
19
|
-
? { ...common, token, cacheDir: ".patchstack" }
|
|
20
|
-
: { ...common, rules: fallbackRules },
|
|
21
|
-
);
|
|
11
|
+
// Memoized on the in-flight promise, not the resolved value: a cold start takes several
|
|
12
|
+
// concurrent requests, and caching only the finished value lets each of them build its own
|
|
13
|
+
// policy — several rule fetches and several refresh loops where the app should have one.
|
|
14
|
+
protection = buildProtection().catch((err) => {
|
|
15
|
+
protection = undefined; // don't cache a failed boot
|
|
16
|
+
throw err;
|
|
17
|
+
});
|
|
22
18
|
}
|
|
19
|
+
|
|
23
20
|
return protection;
|
|
24
21
|
}
|
|
25
22
|
|
|
23
|
+
async function buildProtection() {
|
|
24
|
+
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
25
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
26
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
27
|
+
const common = { mode, egress: true };
|
|
28
|
+
return createProtection(
|
|
29
|
+
siteUuid
|
|
30
|
+
? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
|
|
31
|
+
: token
|
|
32
|
+
? { ...common, token, cacheDir: ".patchstack" }
|
|
33
|
+
: { ...common, rules: fallbackRules },
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
function protectFetch(handler) {
|
|
27
38
|
return async (request, ...rest) => {
|
|
28
39
|
const active = await getProtection();
|
|
29
40
|
const blocked = await active.fetchGuard()(request);
|
|
30
41
|
if (blocked) return blocked;
|
|
31
|
-
|
|
42
|
+
// Response rules can be scoped to a route or a method, and the engine can only apply that scope if it is
|
|
43
|
+
// given the request the response belongs to. Passed through here for that reason: without it a scoped
|
|
44
|
+
// response rule is delivered, counted as protection, and never matches anything.
|
|
45
|
+
return active.screenResponse(await handler(request, ...rest), request);
|
|
32
46
|
};
|
|
33
47
|
}
|
|
34
48
|
|
|
49
|
+
// Node / Connect: app.use(patchstackMiddleware) — before any body parser. This guard reads the request
|
|
50
|
+
// stream itself and exposes what it read as req.body, so a parser is not also needed.
|
|
35
51
|
function patchstackMiddleware(req, res, next) {
|
|
36
52
|
getProtection()
|
|
37
53
|
.then((active) => active.node()(req, res, next))
|
|
@@ -9,32 +9,47 @@ let protection;
|
|
|
9
9
|
|
|
10
10
|
export async function getProtection() {
|
|
11
11
|
if (!protection) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
: token
|
|
20
|
-
? { ...common, token, cacheDir: ".patchstack" }
|
|
21
|
-
: { ...common, rules: fallbackRules },
|
|
22
|
-
);
|
|
12
|
+
// Memoized on the in-flight promise, not the resolved value: a cold start takes several
|
|
13
|
+
// concurrent requests, and caching only the finished value lets each of them build its own
|
|
14
|
+
// policy — several rule fetches and several refresh loops where the app should have one.
|
|
15
|
+
protection = buildProtection().catch((err) => {
|
|
16
|
+
protection = undefined; // don't cache a failed boot
|
|
17
|
+
throw err;
|
|
18
|
+
});
|
|
23
19
|
}
|
|
20
|
+
|
|
24
21
|
return protection;
|
|
25
22
|
}
|
|
26
23
|
|
|
24
|
+
async function buildProtection() {
|
|
25
|
+
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
26
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
27
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
28
|
+
const common = { mode, egress: true };
|
|
29
|
+
return createProtection(
|
|
30
|
+
siteUuid
|
|
31
|
+
? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
|
|
32
|
+
: token
|
|
33
|
+
? { ...common, token, cacheDir: ".patchstack" }
|
|
34
|
+
: { ...common, rules: fallbackRules },
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
// Web-Fetch: export default { fetch: protectFetch(originalFetch) }
|
|
28
39
|
export function protectFetch(handler) {
|
|
29
40
|
return async (request, ...rest) => {
|
|
30
41
|
const active = await getProtection();
|
|
31
42
|
const blocked = await active.fetchGuard()(request);
|
|
32
43
|
if (blocked) return blocked;
|
|
33
|
-
|
|
44
|
+
// Response rules can be scoped to a route or a method, and the engine can only apply that scope if it is
|
|
45
|
+
// given the request the response belongs to. Passed through here for that reason: without it a scoped
|
|
46
|
+
// response rule is delivered, counted as protection, and never matches anything.
|
|
47
|
+
return active.screenResponse(await handler(request, ...rest), request);
|
|
34
48
|
};
|
|
35
49
|
}
|
|
36
50
|
|
|
37
|
-
// Node /
|
|
51
|
+
// Node / Connect: app.use(patchstackMiddleware) — before any body parser. This guard reads the request
|
|
52
|
+
// stream itself and exposes what it read as req.body, so a parser is not also needed.
|
|
38
53
|
export function patchstackMiddleware(req, res, next) {
|
|
39
54
|
getProtection()
|
|
40
55
|
.then((active) => active.node()(req, res, next))
|