@patchstack/connect 0.3.19 → 0.3.20

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.
@@ -10,7 +10,11 @@ async function getProtection() {
10
10
  const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
11
11
  const token = process.env.PATCHSTACK_WAF_TOKEN;
12
12
  const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
13
- const common = { mode, egress: true };
13
+ // The sandbox dev server is long-lived and isn't restarted on change, so refresh the live
14
+ // rules periodically — a dependency flagged after boot is then enforced without a restart.
15
+ // Production relies on a redeploy (which re-fetches at boot), so refresh stays off there.
16
+ const refreshMs = process.env.PATCHSTACK_ENVIRONMENT === "sandbox" ? 15000 : 0;
17
+ const common = { mode, egress: true, refreshMs };
14
18
  protection = await createProtection(
15
19
  siteUuid
16
20
  ? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
@@ -11,7 +11,11 @@ async function getProtection() {
11
11
  const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
12
12
  const token = process.env.PATCHSTACK_WAF_TOKEN;
13
13
  const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
14
- const common = { mode, egress: true };
14
+ // The sandbox dev server is long-lived and isn't restarted on change, so refresh the live
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 };
15
19
  protection = await createProtection(
16
20
  siteUuid
17
21
  ? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
@@ -11,7 +11,11 @@ async function getProtection() {
11
11
  const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
12
12
  const token = process.env.PATCHSTACK_WAF_TOKEN;
13
13
  const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
14
- const common = { mode, egress: true } as const;
14
+ // The sandbox dev server is long-lived and isn't restarted on change, so refresh the live
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;
15
19
  _protection = await createProtection(
16
20
  siteUuid
17
21
  ? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
@@ -0,0 +1,46 @@
1
+ // Patchstack runtime guard for CommonJS Fastify apps. Managed by `patchstack-connect protect`.
2
+ // Register once: app.register(patchstackFastify)
3
+ const { createProtection } = require("@patchstack/connect/protect");
4
+ const fallbackRules = require("./rules.json");
5
+
6
+ const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
7
+ let protection;
8
+
9
+ async function getProtection() {
10
+ if (!protection) {
11
+ const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
12
+ const token = process.env.PATCHSTACK_WAF_TOKEN;
13
+ const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
14
+ const common = { mode, egress: true };
15
+ protection = await createProtection(
16
+ siteUuid
17
+ ? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
18
+ : token
19
+ ? { ...common, token, cacheDir: ".patchstack" }
20
+ : { ...common, rules: fallbackRules },
21
+ );
22
+ }
23
+ return protection;
24
+ }
25
+
26
+ async function patchstackFastify(fastify) {
27
+ const protection = await getProtection();
28
+ const guard = protection.fetchGuard();
29
+ fastify.addHook("preHandler", async (request, reply) => {
30
+ const method = (request.method ?? "GET").toUpperCase();
31
+ const host = request.headers?.host ?? "localhost";
32
+ const url = `http://${host}${request.url ?? "/"}`;
33
+ const hasBody = method !== "GET" && method !== "HEAD" && request.body != null;
34
+ const body = hasBody ? (typeof request.body === "string" ? request.body : JSON.stringify(request.body)) : undefined;
35
+ const blocked = await guard(new Request(url, { method, headers: request.headers, body }));
36
+ if (blocked) {
37
+ const contentType = blocked.headers.get("content-type");
38
+ reply.code(blocked.status);
39
+ if (contentType) reply.header("content-type", contentType);
40
+ reply.send(await blocked.text());
41
+ return reply;
42
+ }
43
+ });
44
+ }
45
+
46
+ module.exports = { patchstackFastify };
@@ -0,0 +1,45 @@
1
+ // Patchstack runtime guard for ESM Fastify apps. Managed by `patchstack-connect protect`.
2
+ // Register once: app.register(patchstackFastify)
3
+ import { readFileSync } from "node:fs";
4
+ import { createProtection } from "@patchstack/connect/protect";
5
+
6
+ const fallbackRules = JSON.parse(readFileSync(new URL("./rules.json", import.meta.url), "utf8"));
7
+ const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
8
+ let protection;
9
+
10
+ async function getProtection() {
11
+ if (!protection) {
12
+ const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
13
+ const token = process.env.PATCHSTACK_WAF_TOKEN;
14
+ const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
15
+ const common = { mode, egress: true };
16
+ protection = await createProtection(
17
+ siteUuid
18
+ ? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
19
+ : token
20
+ ? { ...common, token, cacheDir: ".patchstack" }
21
+ : { ...common, rules: fallbackRules },
22
+ );
23
+ }
24
+ return protection;
25
+ }
26
+
27
+ export async function patchstackFastify(fastify) {
28
+ const protection = await getProtection();
29
+ const guard = protection.fetchGuard();
30
+ fastify.addHook("preHandler", async (request, reply) => {
31
+ const method = (request.method ?? "GET").toUpperCase();
32
+ const host = request.headers?.host ?? "localhost";
33
+ const url = `http://${host}${request.url ?? "/"}`;
34
+ const hasBody = method !== "GET" && method !== "HEAD" && request.body != null;
35
+ const body = hasBody ? (typeof request.body === "string" ? request.body : JSON.stringify(request.body)) : undefined;
36
+ const blocked = await guard(new Request(url, { method, headers: request.headers, body }));
37
+ if (blocked) {
38
+ const contentType = blocked.headers.get("content-type");
39
+ reply.code(blocked.status);
40
+ if (contentType) reply.header("content-type", contentType);
41
+ reply.send(await blocked.text());
42
+ return reply;
43
+ }
44
+ });
45
+ }
@@ -0,0 +1,41 @@
1
+ // Patchstack runtime protection — GENERIC guard (CommonJS). Managed by `patchstack-connect protect`.
2
+ // Wire whichever helper fits your server into your request path, then run `protect --check`.
3
+ const { createProtection } = require("@patchstack/connect/protect");
4
+ const fallbackRules = require("./rules.json");
5
+
6
+ const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
7
+ let protection;
8
+
9
+ async function getProtection() {
10
+ if (!protection) {
11
+ const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
12
+ const token = process.env.PATCHSTACK_WAF_TOKEN;
13
+ const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
14
+ const common = { mode, egress: true };
15
+ protection = await createProtection(
16
+ siteUuid
17
+ ? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
18
+ : token
19
+ ? { ...common, token, cacheDir: ".patchstack" }
20
+ : { ...common, rules: fallbackRules },
21
+ );
22
+ }
23
+ return protection;
24
+ }
25
+
26
+ function protectFetch(handler) {
27
+ return async (request, ...rest) => {
28
+ const active = await getProtection();
29
+ const blocked = await active.fetchGuard()(request);
30
+ if (blocked) return blocked;
31
+ return active.screenResponse(await handler(request, ...rest));
32
+ };
33
+ }
34
+
35
+ function patchstackMiddleware(req, res, next) {
36
+ getProtection()
37
+ .then((active) => active.node()(req, res, next))
38
+ .catch(() => next()); // fail open
39
+ }
40
+
41
+ module.exports = { getProtection, protectFetch, patchstackMiddleware };
@@ -0,0 +1,42 @@
1
+ // Patchstack runtime protection — GENERIC guard (ESM). Managed by `patchstack-connect protect`.
2
+ // Wire whichever helper fits your server into your request path, then run `protect --check`.
3
+ import { readFileSync } from "node:fs";
4
+ import { createProtection } from "@patchstack/connect/protect";
5
+
6
+ const fallbackRules = JSON.parse(readFileSync(new URL("./rules.json", import.meta.url), "utf8"));
7
+ const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
8
+ let protection;
9
+
10
+ export async function getProtection() {
11
+ if (!protection) {
12
+ const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
13
+ const token = process.env.PATCHSTACK_WAF_TOKEN;
14
+ const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
15
+ const common = { mode, egress: true };
16
+ protection = await createProtection(
17
+ siteUuid
18
+ ? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
19
+ : token
20
+ ? { ...common, token, cacheDir: ".patchstack" }
21
+ : { ...common, rules: fallbackRules },
22
+ );
23
+ }
24
+ return protection;
25
+ }
26
+
27
+ // Web-Fetch: export default { fetch: protectFetch(originalFetch) }
28
+ export function protectFetch(handler) {
29
+ return async (request, ...rest) => {
30
+ const active = await getProtection();
31
+ const blocked = await active.fetchGuard()(request);
32
+ if (blocked) return blocked;
33
+ return active.screenResponse(await handler(request, ...rest));
34
+ };
35
+ }
36
+
37
+ // Node / Express: app.use(patchstackMiddleware)
38
+ export function patchstackMiddleware(req, res, next) {
39
+ getProtection()
40
+ .then((active) => active.node()(req, res, next))
41
+ .catch(() => next()); // fail open
42
+ }
@@ -19,7 +19,11 @@ export async function getProtection() {
19
19
  const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
20
20
  const token = process.env.PATCHSTACK_WAF_TOKEN;
21
21
  const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
22
- const common = { mode, egress: true } as const;
22
+ // The sandbox dev server is long-lived and isn't restarted on change, so refresh the live
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;
23
27
  _protection = await createProtection(
24
28
  siteUuid
25
29
  ? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
@@ -41,7 +41,11 @@ async function getProtection() {
41
41
  } catch {
42
42
  /* ignore a malformed SUPABASE_URL — just don't add an allow entry */
43
43
  }
44
- const common = { mode, egress: true, allowHosts };
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 };
45
49
  _protection = await createProtection(
46
50
  siteUuid
47
51
  ? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" } // live per-site rules; bundled = offline fallback
@@ -0,0 +1,43 @@
1
+ // Patchstack runtime guard for Nuxt / Nitro — server middleware. Managed by `patchstack-connect protect`.
2
+ // Runs the request-phase WAF (+ egress SSRF) on every request. Nuxt runs each file in
3
+ // `server/middleware/` as independent middleware, so this file coexists with your own.
4
+ import { defineEventHandler, getRequestURL, readRawBody, setResponseStatus, setResponseHeader } from "h3";
5
+ import { createProtection } from "@patchstack/connect/protect";
6
+ import fallbackRules from "./patchstack.rules.json";
7
+
8
+ const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
9
+
10
+ let _protection: Awaited<ReturnType<typeof createProtection>> | undefined;
11
+ async function getProtection() {
12
+ if (!_protection) {
13
+ const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
14
+ const token = process.env.PATCHSTACK_WAF_TOKEN;
15
+ const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
16
+ const common = { mode, egress: true } as const;
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
+ );
24
+ }
25
+ return _protection;
26
+ }
27
+
28
+ // #region patchstack-nuxt (managed by patchstack-connect protect — do not edit)
29
+ export default defineEventHandler(async (event) => {
30
+ const protection = await getProtection();
31
+ const method = event.method ?? "GET";
32
+ // readRawBody caches on the event, so your route handlers can still read the body.
33
+ const body = method !== "GET" && method !== "HEAD" ? await readRawBody(event) : undefined;
34
+ const blocked = await protection
35
+ .fetchGuard()(new Request(getRequestURL(event), { method, headers: event.headers, body: body ?? undefined }));
36
+ if (blocked) {
37
+ setResponseStatus(event, blocked.status);
38
+ const contentType = blocked.headers.get("content-type");
39
+ if (contentType) setResponseHeader(event, "content-type", contentType);
40
+ return blocked.text(); // 403 — blocked before it reaches your route
41
+ }
42
+ });
43
+ // #endregion patchstack-nuxt