@patchstack/connect 0.3.29 → 0.3.30
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 +37 -21
- package/README.md +24 -10
- package/dist/{chunk-MJOTFUDE.js → chunk-LLKP5EJS.js} +1 -1
- package/dist/chunk-LLKP5EJS.js.map +1 -0
- package/dist/cli.js +1062 -189
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +419 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -9
- package/dist/index.d.ts +28 -9
- package/dist/index.js +412 -97
- 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 +1039 -235
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +39 -11
- package/dist/protect.edge.js +663 -132
- package/dist/protect.edge.js.map +4 -4
- package/dist/protect.js +665 -134
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-VRBE6RH6.js → refresh-manifest-ZSP76JWQ.js} +351 -94
- package/dist/refresh-manifest-ZSP76JWQ.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
|
@@ -10,31 +10,48 @@ import fallbackRules from "./rules.json";
|
|
|
10
10
|
// Baked by `patchstack-connect protect` from .patchstackrc.json when available.
|
|
11
11
|
const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
|
|
12
12
|
|
|
13
|
-
let _protection: Awaited<ReturnType<typeof createProtection
|
|
13
|
+
let _protection: Promise<Awaited<ReturnType<typeof createProtection>>> | undefined;
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
15
|
+
/**
|
|
16
|
+
* One protection policy, memoized on the IN-FLIGHT promise rather than the resolved value.
|
|
17
|
+
*
|
|
18
|
+
* A cold start takes several concurrent requests. Caching only the finished value lets each of them see an
|
|
19
|
+
* empty cache and start its own build — several rule fetches, several refresh loops, and several policies
|
|
20
|
+
* where the app is meant to have one. Holding the promise means the first request starts it and the rest
|
|
21
|
+
* await the same one.
|
|
22
|
+
*
|
|
23
|
+
* A failed build is not cached: the slot is cleared so the next request tries again rather than inheriting
|
|
24
|
+
* one bad boot for the life of the process.
|
|
25
|
+
*/
|
|
17
26
|
export async function getProtection() {
|
|
18
27
|
if (!_protection) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// rules periodically — a dependency flagged after boot is then enforced without a restart.
|
|
24
|
-
// Production relies on a redeploy (which re-fetches at boot), so refresh stays off there.
|
|
25
|
-
const refreshMs = process.env.PATCHSTACK_ENVIRONMENT === "sandbox" ? 15000 : 0;
|
|
26
|
-
const common = { mode, egress: true, refreshMs } as const;
|
|
27
|
-
_protection = await createProtection(
|
|
28
|
-
siteUuid
|
|
29
|
-
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
30
|
-
: token
|
|
31
|
-
? { ...common, token, cacheDir: ".patchstack" }
|
|
32
|
-
: { ...common, rules: fallbackRules as never },
|
|
33
|
-
);
|
|
28
|
+
_protection = buildProtection().catch((err) => {
|
|
29
|
+
_protection = undefined; // don't cache a failed boot
|
|
30
|
+
throw err;
|
|
31
|
+
});
|
|
34
32
|
}
|
|
33
|
+
|
|
35
34
|
return _protection;
|
|
36
35
|
}
|
|
37
36
|
|
|
37
|
+
async function buildProtection() {
|
|
38
|
+
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
39
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
40
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
41
|
+
// The sandbox dev server is long-lived and isn't restarted on change, so refresh the live
|
|
42
|
+
// rules periodically — a dependency flagged after boot is then enforced without a restart.
|
|
43
|
+
// Production relies on a redeploy (which re-fetches at boot), so refresh stays off there.
|
|
44
|
+
const refreshMs = process.env.PATCHSTACK_ENVIRONMENT === "sandbox" ? 15000 : 0;
|
|
45
|
+
const common = { mode, egress: true, refreshMs } as const;
|
|
46
|
+
return createProtection(
|
|
47
|
+
siteUuid
|
|
48
|
+
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
49
|
+
: token
|
|
50
|
+
? { ...common, token, cacheDir: ".patchstack" }
|
|
51
|
+
: { ...common, rules: fallbackRules as never },
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
// --- Web Fetch (Cloudflare Workers, Bun, Deno, Hono, Next edge, TanStack server.ts) ---------
|
|
39
56
|
// Wrap your fetch handler: export default { fetch: protectFetch(originalFetch) }
|
|
40
57
|
export function protectFetch<H extends (request: Request, ...rest: unknown[]) => unknown>(handler: H): H {
|
|
@@ -42,12 +59,16 @@ export function protectFetch<H extends (request: Request, ...rest: unknown[]) =>
|
|
|
42
59
|
const protection = await getProtection();
|
|
43
60
|
const blocked = await protection.fetchGuard()(request);
|
|
44
61
|
if (blocked) return blocked;
|
|
45
|
-
|
|
62
|
+
// Response rules can be scoped to a route or a method, and the engine can only apply that scope if it is
|
|
63
|
+
// given the request the response belongs to. Passed through here for that reason: without it a scoped
|
|
64
|
+
// response rule is delivered, counted as protection, and never matches anything.
|
|
65
|
+
return protection.screenResponse(await handler(request, ...rest) as Response, request);
|
|
46
66
|
}) as H;
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
// --- Node / Express -------------------------------------------------------------------------
|
|
50
|
-
//
|
|
70
|
+
// app.use(patchstackMiddleware) — register it before any body parser and before your routes. This guard
|
|
71
|
+
// reads the request stream itself and exposes what it read as req.body, so a parser is not also needed.
|
|
51
72
|
export function patchstackMiddleware(req: unknown, res: unknown, next: (err?: unknown) => void) {
|
|
52
73
|
getProtection()
|
|
53
74
|
.then((protection) => (protection.node() as (a: unknown, b: unknown, c: (e?: unknown) => void) => void)(req, res, next))
|
|
@@ -26,37 +26,54 @@ const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
|
|
|
26
26
|
export { GUARD_PATH };
|
|
27
27
|
|
|
28
28
|
// One shared protection policy for both guards (rules load once).
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* One protection policy, memoized on the IN-FLIGHT promise rather than the resolved value.
|
|
31
|
+
*
|
|
32
|
+
* A cold start takes several concurrent requests. Caching only the finished value lets each of them see an
|
|
33
|
+
* empty cache and start its own build — several rule fetches, several refresh loops, and several policies
|
|
34
|
+
* where the app is meant to have one. Holding the promise means the first request starts it and the rest
|
|
35
|
+
* await the same one. A failed build is not cached, so the next request retries rather than inheriting one
|
|
36
|
+
* bad boot for the life of the process.
|
|
37
|
+
*/
|
|
38
|
+
let _protection: Promise<Awaited<ReturnType<typeof createProtection>>> | undefined;
|
|
30
39
|
async function getProtection() {
|
|
31
40
|
if (!_protection) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Egress SSRF screening: block the app's outbound calls to internal / metadata addresses,
|
|
37
|
-
// but never its own Supabase project.
|
|
38
|
-
let allowHosts: string[] = [];
|
|
39
|
-
try {
|
|
40
|
-
if (process.env.SUPABASE_URL) allowHosts = [new URL(process.env.SUPABASE_URL).host];
|
|
41
|
-
} catch {
|
|
42
|
-
/* ignore a malformed SUPABASE_URL — just don't add an allow entry */
|
|
43
|
-
}
|
|
44
|
-
// The sandbox dev server is long-lived and isn't restarted on change, so refresh the live
|
|
45
|
-
// rules periodically — a dependency flagged after boot is then enforced without a restart.
|
|
46
|
-
// Production relies on a redeploy (which re-fetches at boot), so refresh stays off there.
|
|
47
|
-
const refreshMs = process.env.PATCHSTACK_ENVIRONMENT === "sandbox" ? 15000 : 0;
|
|
48
|
-
const common = { mode, egress: true, allowHosts, refreshMs };
|
|
49
|
-
_protection = await createProtection(
|
|
50
|
-
siteUuid
|
|
51
|
-
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" } // live per-site rules; bundled = offline fallback
|
|
52
|
-
: token
|
|
53
|
-
? { ...common, token, cacheDir: ".patchstack" } // live per-site WAF rules from the Patchstack API (cached)
|
|
54
|
-
: { ...common, rules: fallbackRules as never }, // demo fallback until a site UUID / token is set
|
|
55
|
-
);
|
|
41
|
+
_protection = buildProtection().catch((err) => {
|
|
42
|
+
_protection = undefined; // don't cache a failed boot
|
|
43
|
+
throw err;
|
|
44
|
+
});
|
|
56
45
|
}
|
|
46
|
+
|
|
57
47
|
return _protection;
|
|
58
48
|
}
|
|
59
49
|
|
|
50
|
+
async function buildProtection() {
|
|
51
|
+
// Always-on: block by default. An explicit PATCHSTACK_MODE=dry-run downgrades to log-only.
|
|
52
|
+
const mode: "block" | "dry-run" = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
53
|
+
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
54
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
55
|
+
// Egress SSRF screening: block the app's outbound calls to internal / metadata addresses,
|
|
56
|
+
// but never its own Supabase project.
|
|
57
|
+
let allowHosts: string[] = [];
|
|
58
|
+
try {
|
|
59
|
+
if (process.env.SUPABASE_URL) allowHosts = [new URL(process.env.SUPABASE_URL).host];
|
|
60
|
+
} catch {
|
|
61
|
+
/* ignore a malformed SUPABASE_URL — just don't add an allow entry */
|
|
62
|
+
}
|
|
63
|
+
// The sandbox dev server is long-lived and isn't restarted on change, so refresh the live
|
|
64
|
+
// rules periodically — a dependency flagged after boot is then enforced without a restart.
|
|
65
|
+
// Production relies on a redeploy (which re-fetches at boot), so refresh stays off there.
|
|
66
|
+
const refreshMs = process.env.PATCHSTACK_ENVIRONMENT === "sandbox" ? 15000 : 0;
|
|
67
|
+
const common = { mode, egress: true, allowHosts, refreshMs };
|
|
68
|
+
return createProtection(
|
|
69
|
+
siteUuid
|
|
70
|
+
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" } // live per-site rules; bundled = offline fallback
|
|
71
|
+
: token
|
|
72
|
+
? { ...common, token, cacheDir: ".patchstack" } // live per-site WAF rules from the Patchstack API (cached)
|
|
73
|
+
: { ...common, rules: fallbackRules as never }, // demo fallback until a site UUID / token is set
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
60
77
|
// Request-middleware path: the browser tunnels its direct Supabase calls here.
|
|
61
78
|
let _handle: ((request: Request) => Promise<Response>) | undefined;
|
|
62
79
|
export async function handleGuardRequest(request: Request): Promise<Response> {
|
|
@@ -84,11 +101,14 @@ export async function inspectServerFn(data: unknown): Promise<{ rule?: string; m
|
|
|
84
101
|
// src/start.ts (the browser→Supabase tunnel screens its own forwarded response). Only acts on a
|
|
85
102
|
// web Response (text/JSON/HTML) — anything else, or any error, passes through untouched (fail-open,
|
|
86
103
|
// never breaks a response).
|
|
87
|
-
|
|
104
|
+
// `request` is optional but should be passed wherever it is available: response rules can be scoped to a
|
|
105
|
+
// route or a method, and the engine can only apply that scope if it is given the request the response
|
|
106
|
+
// belongs to. Without it a scoped response rule is delivered, counted as protection, and matches nothing.
|
|
107
|
+
export async function screenResponse<T>(response: T, request?: Request): Promise<T> {
|
|
88
108
|
try {
|
|
89
109
|
if (!(response instanceof Response)) return response;
|
|
90
110
|
const protection = await getProtection();
|
|
91
|
-
return (protection.screenResponse ? await protection.screenResponse(response) : response) as T;
|
|
111
|
+
return (protection.screenResponse ? await protection.screenResponse(response, request) : response) as T;
|
|
92
112
|
} catch {
|
|
93
113
|
return response; // fail open
|
|
94
114
|
}
|
|
@@ -7,24 +7,41 @@ 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-next (managed by patchstack-connect protect — do not edit)
|
|
29
46
|
export async function middleware(request: Request) {
|
|
30
47
|
const protection = await getProtection();
|
|
@@ -7,24 +7,41 @@ 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-nuxt (managed by patchstack-connect protect — do not edit)
|
|
29
46
|
export default defineEventHandler(async (event) => {
|
|
30
47
|
const protection = await getProtection();
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"title": "Path traversal in a file/path parameter",
|
|
22
22
|
"category": "lfi",
|
|
23
23
|
"rule_v2": [
|
|
24
|
-
{ "parameter": ["get.file", "post.file", "
|
|
24
|
+
{ "parameter": ["get.file", "post.file", "get.path", "post.path"], "mutations": ["urldecode"], "match": { "type": "contains", "value": ".." } }
|
|
25
25
|
]
|
|
26
26
|
},
|
|
27
27
|
{
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"title": "SSRF via a url parameter pointing at an internal/metadata address",
|
|
30
30
|
"category": "ssrf",
|
|
31
31
|
"rule_v2": [
|
|
32
|
-
{ "parameter": ["get.url", "post.url"
|
|
32
|
+
{ "parameter": ["get.url", "post.url"], "mutations": ["urldecode"], "match": { "type": "regex", "value": "/localhost|127\\.0\\.0\\.1|169\\.254\\.169\\.254|::1|metadata\\.google/i" } }
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
{
|
|
@@ -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-sveltekit (managed by patchstack-connect protect — do not edit)
|
|
29
46
|
export const handle: Handle = async ({ event, resolve }) => {
|
|
30
47
|
const protection = await getProtection();
|
|
31
48
|
const blocked = await protection.fetchGuard()(event.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 resolve(event), event.request);
|
|
34
54
|
};
|
|
35
55
|
// #endregion patchstack-sveltekit
|