@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.
- package/AGENT-INSTALL.md +33 -18
- package/README.md +28 -11
- package/dist/cli.js +1204 -1071
- package/dist/cli.js.map +1 -1
- package/dist/protect/templates/express-guard.cjs +5 -1
- package/dist/protect/templates/express-guard.js +5 -1
- package/dist/protect/templates/express-guard.ts +5 -1
- package/dist/protect/templates/fastify-plugin.cjs +46 -0
- package/dist/protect/templates/fastify-plugin.js +45 -0
- package/dist/protect/templates/generic-guard.cjs +41 -0
- package/dist/protect/templates/generic-guard.js +42 -0
- package/dist/protect/templates/generic-guard.ts +5 -1
- package/dist/protect/templates/guard.ts +5 -1
- package/dist/protect/templates/nuxt-middleware.ts +43 -0
- package/dist/protect.cjs +1692 -92
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +24 -0
- package/dist/protect.js +501 -82
- package/dist/protect.js.map +1 -1
- package/dist/refresh-manifest-XBOGN446.js +1090 -0
- package/dist/refresh-manifest-XBOGN446.js.map +1 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|