@patchstack/connect 0.5.26 → 0.5.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 +34 -19
- package/README.md +73 -17
- package/dist/{chunk-YG5C7HYB.js → chunk-52IV7LN7.js} +1 -1
- package/dist/{chunk-YG5C7HYB.js.map → chunk-52IV7LN7.js.map} +1 -1
- package/dist/cli.js +2755 -371
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +33 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +33 -12
- package/dist/index.js.map +1 -1
- package/dist/protect/templates/astro-middleware.ts +5 -4
- package/dist/protect/templates/express-guard.cjs +5 -8
- package/dist/protect/templates/express-guard.js +5 -8
- package/dist/protect/templates/express-guard.ts +5 -8
- package/dist/protect/templates/fastify-plugin.cjs +52 -6
- package/dist/protect/templates/fastify-plugin.js +52 -6
- package/dist/protect/templates/fastify-plugin.ts +53 -7
- package/dist/protect/templates/fetch-guard.ts +52 -0
- package/dist/protect/templates/generic-guard.cjs +6 -5
- package/dist/protect/templates/generic-guard.js +6 -5
- package/dist/protect/templates/generic-guard.ts +5 -8
- package/dist/protect/templates/guard.ts +4 -7
- package/dist/protect/templates/next-guard.ts +5 -4
- package/dist/protect/templates/next-middleware.ts +5 -4
- package/dist/protect/templates/nuxt-middleware.ts +5 -4
- package/dist/protect/templates/sveltekit-hooks.ts +5 -4
- package/dist/protect.cjs +33 -12
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.cts +1 -1
- package/dist/protect.d.ts +1 -1
- package/dist/protect.edge.js +2 -2
- package/dist/protect.edge.js.map +1 -1
- package/dist/protect.js +4 -4
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-OMRDCNFD.js → refresh-manifest-EBORKM75.js} +33 -12
- package/dist/refresh-manifest-EBORKM75.js.map +1 -0
- package/package.json +5 -2
- package/dist/refresh-manifest-OMRDCNFD.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Patchstack runtime guard for Fastify — plugin. Managed by `patchstack-connect protect`.
|
|
2
2
|
// Register it once (`app.register(patchstackFastify)`); it adds a preHandler hook that runs the
|
|
3
|
-
// request-phase WAF (+ egress SSRF)
|
|
3
|
+
// request-phase WAF (+ egress SSRF) and filters buffered onSend output. Fastify's request/reply aren't Web-Fetch
|
|
4
4
|
// shaped, so we reconstruct a Request from the parsed fastify request and run the fetch guard.
|
|
5
5
|
import { createProtection, sentinelAnswer, VERIFY_HEADER } from "@patchstack/connect/protect";
|
|
6
6
|
import fallbackRules from "./rules.json";
|
|
@@ -29,10 +29,11 @@ async function getProtection() {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
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
|
|
32
|
+
const mode = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_MODE) === "dry-run" ? "dry-run" : "block";
|
|
33
|
+
const token = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_WAF_TOKEN);
|
|
34
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_SITE_UUID) : PS_SITE_UUID;
|
|
35
|
+
const refreshMs = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_ENVIRONMENT) === "sandbox" ? 15000 : 300000;
|
|
36
|
+
const common = { mode, egress: true, refreshMs } as const;
|
|
36
37
|
return createProtection(
|
|
37
38
|
siteUuid
|
|
38
39
|
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
@@ -92,8 +93,23 @@ export async function patchstackFastify(fastify: any) {
|
|
|
92
93
|
const host = request.headers?.host ?? "localhost";
|
|
93
94
|
const url = `http://${host}${request.url ?? "/"}`;
|
|
94
95
|
const hasBody = method !== "GET" && method !== "HEAD" && request.body != null;
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
let blocked;
|
|
97
|
+
try {
|
|
98
|
+
const headers = new Headers();
|
|
99
|
+
for (const [name, value] of Object.entries(request.headers)) {
|
|
100
|
+
if (value !== undefined) headers.set(name, Array.isArray(value) ? value.join(", ") : String(value));
|
|
101
|
+
}
|
|
102
|
+
const parsed = hasBody && typeof request.body === "object" && !Buffer.isBuffer(request.body);
|
|
103
|
+
const body = hasBody ? (parsed ? JSON.stringify(request.body) : request.body) : undefined;
|
|
104
|
+
// Fastify already parsed form fields. Screen that object as JSON, not JSON mislabeled as a form.
|
|
105
|
+
// Only the screening copy changes; the route keeps its original body, headers and raw stream.
|
|
106
|
+
if (parsed) headers.set("content-type", "application/json");
|
|
107
|
+
headers.delete("content-length");
|
|
108
|
+
blocked = await guard(new Request(url, { method, headers, body }));
|
|
109
|
+
} catch (err) {
|
|
110
|
+
psStepAside(err);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
97
113
|
if (blocked) {
|
|
98
114
|
const contentType = blocked.headers.get("content-type");
|
|
99
115
|
reply.code(blocked.status);
|
|
@@ -102,6 +118,36 @@ export async function patchstackFastify(fastify: any) {
|
|
|
102
118
|
return reply; // stop the request here
|
|
103
119
|
}
|
|
104
120
|
});
|
|
121
|
+
|
|
122
|
+
fastify.addHook("onSend", async (request: any, reply: any, payload: any) => {
|
|
123
|
+
// Do not consume streams, hijacked responses, or bodyless status codes.
|
|
124
|
+
if ((typeof payload !== "string" && !Buffer.isBuffer(payload))
|
|
125
|
+
|| request.method === "HEAD" || reply.statusCode < 200 || [204, 205, 304].includes(reply.statusCode)) return payload;
|
|
126
|
+
const protection = await getProtection().catch(psStepAside);
|
|
127
|
+
if (!protection) return payload;
|
|
128
|
+
try {
|
|
129
|
+
const headers = new Headers();
|
|
130
|
+
for (const [name, value] of Object.entries(reply.getHeaders())) {
|
|
131
|
+
for (const item of Array.isArray(value) ? value : [value]) {
|
|
132
|
+
if (item !== undefined) headers.append(name, String(item));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const original = new Response(typeof payload === "string" ? payload : new Uint8Array(payload).buffer, { status: reply.statusCode, headers });
|
|
136
|
+
const context = new Request(`http://${request.headers?.host ?? "localhost"}${request.url ?? "/"}`, { method: request.method, headers: request.headers });
|
|
137
|
+
const screened = await protection.screenResponse(original, context);
|
|
138
|
+
if (screened === original) return payload;
|
|
139
|
+
const body = Buffer.from(await screened.arrayBuffer());
|
|
140
|
+
reply.code(screened.status);
|
|
141
|
+
for (const name of Object.keys(reply.getHeaders())) reply.removeHeader(name);
|
|
142
|
+
screened.headers.forEach((value, name) => { if (name !== "set-cookie") reply.header(name, value); });
|
|
143
|
+
const cookies = screened.headers.getSetCookie();
|
|
144
|
+
if (cookies.length) reply.header("set-cookie", cookies);
|
|
145
|
+
return body;
|
|
146
|
+
} catch (err) {
|
|
147
|
+
psStepAside(err);
|
|
148
|
+
return payload;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
105
151
|
}
|
|
106
152
|
// #endregion patchstack-fastify
|
|
107
153
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Server-only Fetch guard for bundled applications. Never import this module into client code.
|
|
2
|
+
import { createProtection, sentinelAnswer, VERIFY_HEADER } from "@patchstack/connect/protect";
|
|
3
|
+
import fallbackRules from "./rules.json";
|
|
4
|
+
|
|
5
|
+
const PS_SITE_UUID = "__PATCHSTACK_SITE_UUID__";
|
|
6
|
+
type Protection = Awaited<ReturnType<typeof createProtection>>;
|
|
7
|
+
const policies = new WeakMap<object, Promise<Protection>>();
|
|
8
|
+
const emptyEnvironment = {};
|
|
9
|
+
let warned = false;
|
|
10
|
+
|
|
11
|
+
function environment(value: unknown): Record<string, unknown> {
|
|
12
|
+
if (value && typeof value === "object" && ["PATCHSTACK_API_KEY", "PATCHSTACK_SITE_UUID", "PATCHSTACK_PULSE_AUTH"].some(key => key in value)) {
|
|
13
|
+
return value as Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
return typeof process === "undefined" ? emptyEnvironment : process.env;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function getProtection(bindings: unknown) {
|
|
19
|
+
const env = environment(bindings);
|
|
20
|
+
let pending = policies.get(env);
|
|
21
|
+
if (!pending) {
|
|
22
|
+
const value = (key: string) => typeof env[key] === "string" ? env[key] as string : undefined;
|
|
23
|
+
pending = createProtection({
|
|
24
|
+
siteUuid: PS_SITE_UUID.startsWith("__") ? value("PATCHSTACK_SITE_UUID") : PS_SITE_UUID,
|
|
25
|
+
apiKey: value("PATCHSTACK_API_KEY"),
|
|
26
|
+
pulseAuth: value("PATCHSTACK_PULSE_AUTH") ?? value("PATCHSTACK_API_KEY"),
|
|
27
|
+
mode: value("PATCHSTACK_MODE") === "dry-run" ? "dry-run" : "block",
|
|
28
|
+
rules: fallbackRules as never,
|
|
29
|
+
cacheDir: ".patchstack",
|
|
30
|
+
refreshMs: value("PATCHSTACK_ENVIRONMENT") === "sandbox" ? 15000 : 300000,
|
|
31
|
+
egress: true,
|
|
32
|
+
}).catch(error => { policies.delete(env); throw error; });
|
|
33
|
+
policies.set(env, pending);
|
|
34
|
+
}
|
|
35
|
+
return pending.catch(error => {
|
|
36
|
+
if (!warned) {
|
|
37
|
+
warned = true;
|
|
38
|
+
console.warn("[patchstack] protection is unavailable; traffic may pass through unscreened until a later attempt succeeds.", error instanceof Error ? error.message : String(error));
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function protectFetch<Args extends unknown[]>(handler: (request: Request, ...args: Args) => Response | Promise<Response>) {
|
|
45
|
+
return async function(this: unknown, request: Request, ...args: Args): Promise<Response> {
|
|
46
|
+
const answered = await sentinelAnswer(request.headers.get(VERIFY_HEADER));
|
|
47
|
+
if (answered) return new Response(answered, { headers: { "content-type": "text/plain" } });
|
|
48
|
+
const protection = await getProtection(args[0]);
|
|
49
|
+
// Only initialization fails open here. Application failures propagate without repeating the handler.
|
|
50
|
+
return protection ? protection.fetch(handler).call(this, request, ...args) : handler.call(this, request, ...args);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -31,10 +31,11 @@ async function getProtection() {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async function buildProtection() {
|
|
34
|
-
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
35
|
-
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
36
|
-
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
37
|
-
const
|
|
34
|
+
const mode = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_MODE) === "dry-run" ? "dry-run" : "block";
|
|
35
|
+
const token = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_WAF_TOKEN);
|
|
36
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_SITE_UUID) : PS_SITE_UUID;
|
|
37
|
+
const refreshMs = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_ENVIRONMENT) === "sandbox" ? 15000 : 300000;
|
|
38
|
+
const common = { mode, egress: true, refreshMs };
|
|
38
39
|
return createProtection(
|
|
39
40
|
siteUuid
|
|
40
41
|
? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
|
|
@@ -105,7 +106,7 @@ function patchstackMiddleware(req, res, next) {
|
|
|
105
106
|
// there is nothing to answer and the request is screened as normal.
|
|
106
107
|
const screen = () => {
|
|
107
108
|
getProtection().then(
|
|
108
|
-
(active) => active.node()(req, res, carryOn),
|
|
109
|
+
(active) => active.node({ screenResponses: true })(req, res, carryOn),
|
|
109
110
|
(err) => {
|
|
110
111
|
psStepAside(err);
|
|
111
112
|
carryOn();
|
|
@@ -32,10 +32,11 @@ export async function getProtection() {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
async function buildProtection() {
|
|
35
|
-
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
36
|
-
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
37
|
-
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
38
|
-
const
|
|
35
|
+
const mode = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_MODE) === "dry-run" ? "dry-run" : "block";
|
|
36
|
+
const token = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_WAF_TOKEN);
|
|
37
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_SITE_UUID) : PS_SITE_UUID;
|
|
38
|
+
const refreshMs = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_ENVIRONMENT) === "sandbox" ? 15000 : 300000;
|
|
39
|
+
const common = { mode, egress: true, refreshMs };
|
|
39
40
|
return createProtection(
|
|
40
41
|
siteUuid
|
|
41
42
|
? { ...common, siteUuid, rules: fallbackRules, cacheDir: ".patchstack" }
|
|
@@ -107,7 +108,7 @@ export function patchstackMiddleware(req, res, next) {
|
|
|
107
108
|
// there is nothing to answer and the request is screened as normal.
|
|
108
109
|
const screen = () => {
|
|
109
110
|
getProtection().then(
|
|
110
|
-
(active) => active.node()(req, res, carryOn),
|
|
111
|
+
(active) => active.node({ screenResponses: true })(req, res, carryOn),
|
|
111
112
|
(err) => {
|
|
112
113
|
psStepAside(err);
|
|
113
114
|
carryOn();
|
|
@@ -35,13 +35,10 @@ export async function getProtection() {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
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
|
-
|
|
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;
|
|
38
|
+
const mode = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_MODE) === "dry-run" ? "dry-run" : "block";
|
|
39
|
+
const token = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_WAF_TOKEN);
|
|
40
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_SITE_UUID) : PS_SITE_UUID;
|
|
41
|
+
const refreshMs = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_ENVIRONMENT) === "sandbox" ? 15000 : 300000;
|
|
45
42
|
const common = { mode, egress: true, refreshMs } as const;
|
|
46
43
|
return createProtection(
|
|
47
44
|
siteUuid
|
|
@@ -116,7 +113,7 @@ export function patchstackMiddleware(req: unknown, res: unknown, next: (err?: un
|
|
|
116
113
|
// there is nothing to answer and the request is screened as normal.
|
|
117
114
|
const screen = () => {
|
|
118
115
|
getProtection().then(
|
|
119
|
-
(protection) => (protection.node() as (a: unknown, b: unknown, c: (e?: unknown) => void) => void)(req, res, carryOn),
|
|
116
|
+
(protection) => (protection.node({ screenResponses: true }) as (a: unknown, b: unknown, c: (e?: unknown) => void) => void)(req, res, carryOn),
|
|
120
117
|
(err) => {
|
|
121
118
|
psStepAside(err);
|
|
122
119
|
carryOn();
|
|
@@ -49,9 +49,9 @@ async function getProtection() {
|
|
|
49
49
|
|
|
50
50
|
async function buildProtection() {
|
|
51
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;
|
|
52
|
+
const mode: "block" | "dry-run" = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_MODE) === "dry-run" ? "dry-run" : "block";
|
|
53
|
+
const token = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_WAF_TOKEN);
|
|
54
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_SITE_UUID) : PS_SITE_UUID;
|
|
55
55
|
// Egress SSRF screening: block the app's outbound calls to internal / metadata addresses,
|
|
56
56
|
// but never its own Supabase project.
|
|
57
57
|
let allowHosts: string[] = [];
|
|
@@ -60,10 +60,7 @@ async function buildProtection() {
|
|
|
60
60
|
} catch {
|
|
61
61
|
/* ignore a malformed SUPABASE_URL — just don't add an allow entry */
|
|
62
62
|
}
|
|
63
|
-
|
|
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;
|
|
63
|
+
const refreshMs = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_ENVIRONMENT) === "sandbox" ? 15000 : 300000;
|
|
67
64
|
const common = { mode, egress: true, allowHosts, refreshMs };
|
|
68
65
|
return createProtection(
|
|
69
66
|
siteUuid
|
|
@@ -18,10 +18,11 @@ async function getProtection() {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
async function buildProtection() {
|
|
21
|
-
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
|
|
22
|
-
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
|
|
23
|
-
const token = process.env.PATCHSTACK_WAF_TOKEN;
|
|
24
|
-
const
|
|
21
|
+
const mode = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_MODE) === "dry-run" ? "dry-run" : "block";
|
|
22
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_SITE_UUID) : PS_SITE_UUID;
|
|
23
|
+
const token = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_WAF_TOKEN);
|
|
24
|
+
const refreshMs = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_ENVIRONMENT) === "sandbox" ? 15000 : 300000;
|
|
25
|
+
const common = { mode, egress: true, refreshMs } as const;
|
|
25
26
|
return createProtection(
|
|
26
27
|
siteUuid
|
|
27
28
|
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
@@ -29,10 +29,11 @@ async function getProtection() {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
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
|
|
32
|
+
const mode = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_MODE) === "dry-run" ? "dry-run" : "block";
|
|
33
|
+
const token = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_WAF_TOKEN);
|
|
34
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_SITE_UUID) : PS_SITE_UUID;
|
|
35
|
+
const refreshMs = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_ENVIRONMENT) === "sandbox" ? 15000 : 300000;
|
|
36
|
+
const common = { mode, egress: true, refreshMs } as const;
|
|
36
37
|
return createProtection(
|
|
37
38
|
siteUuid
|
|
38
39
|
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
@@ -29,10 +29,11 @@ async function getProtection() {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
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
|
|
32
|
+
const mode = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_MODE) === "dry-run" ? "dry-run" : "block";
|
|
33
|
+
const token = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_WAF_TOKEN);
|
|
34
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_SITE_UUID) : PS_SITE_UUID;
|
|
35
|
+
const refreshMs = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_ENVIRONMENT) === "sandbox" ? 15000 : 300000;
|
|
36
|
+
const common = { mode, egress: true, refreshMs } as const;
|
|
36
37
|
return createProtection(
|
|
37
38
|
siteUuid
|
|
38
39
|
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
|
@@ -29,10 +29,11 @@ async function getProtection() {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
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
|
|
32
|
+
const mode = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_MODE) === "dry-run" ? "dry-run" : "block";
|
|
33
|
+
const token = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_WAF_TOKEN);
|
|
34
|
+
const siteUuid = PS_SITE_UUID.startsWith("__") ? (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_SITE_UUID) : PS_SITE_UUID;
|
|
35
|
+
const refreshMs = (typeof process === "undefined" ? undefined : process.env.PATCHSTACK_ENVIRONMENT) === "sandbox" ? 15000 : 300000;
|
|
36
|
+
const common = { mode, egress: true, refreshMs } as const;
|
|
36
37
|
return createProtection(
|
|
37
38
|
siteUuid
|
|
38
39
|
? { ...common, siteUuid, rules: fallbackRules as never, cacheDir: ".patchstack" }
|
package/dist/protect.cjs
CHANGED
|
@@ -367,8 +367,8 @@ var init_hosting = __esm({
|
|
|
367
367
|
"src/hosting.ts"() {
|
|
368
368
|
"use strict";
|
|
369
369
|
RULES = [
|
|
370
|
-
{ platform: "netlify", any: ["NETLIFY", "NETLIFY_BUILD_BASE", "DEPLOY_PRIME_URL"] },
|
|
371
|
-
{ platform: "vercel", any: ["VERCEL", "VERCEL_ENV", "VERCEL_URL"] },
|
|
370
|
+
{ platform: "netlify", any: ["NETLIFY", "NETLIFY_PREVIEW_SERVER", "NETLIFY_DEV", "NETLIFY_BUILD_BASE", "DEPLOY_PRIME_URL"] },
|
|
371
|
+
{ platform: "vercel", any: ["VERCEL", "VERCEL_TARGET_ENV", "VERCEL_ENV", "VERCEL_URL"] },
|
|
372
372
|
{ platform: "cloudflare", any: ["CF_PAGES", "CF_PAGES_URL", "WORKERS_CI", "WORKERS_CI_BRANCH", "CLOUDFLARE_ACCOUNT_ID"] },
|
|
373
373
|
{ platform: "aws", any: ["AWS_APP_ID", "AWS_BRANCH", "AWS_LAMBDA_FUNCTION_NAME", "AWS_EXECUTION_ENV"] },
|
|
374
374
|
{ platform: "render", any: ["RENDER", "RENDER_SERVICE_ID", "RENDER_EXTERNAL_URL"] },
|
|
@@ -614,7 +614,7 @@ var init_environment = __esm({
|
|
|
614
614
|
"use strict";
|
|
615
615
|
import_node_fs = require("fs");
|
|
616
616
|
import_node_path = __toESM(require("path"), 1);
|
|
617
|
-
set = (value) => value !== void 0 && value !== "";
|
|
617
|
+
set = (value) => value !== void 0 && value.trim() !== "";
|
|
618
618
|
PRODUCTION_BRANCHES = /* @__PURE__ */ new Set([
|
|
619
619
|
"main",
|
|
620
620
|
"master",
|
|
@@ -627,14 +627,31 @@ var init_environment = __esm({
|
|
|
627
627
|
{
|
|
628
628
|
platform: "vercel",
|
|
629
629
|
read: (env) => {
|
|
630
|
-
|
|
631
|
-
|
|
630
|
+
const variable = set(env.VERCEL_TARGET_ENV) ? "VERCEL_TARGET_ENV" : "VERCEL_ENV";
|
|
631
|
+
const tier = env[variable];
|
|
632
|
+
if (set(tier)) {
|
|
633
|
+
return {
|
|
634
|
+
environment: tier === "production" ? "production" : "sandbox",
|
|
635
|
+
evidence: `${variable}=${tier}`
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
if (env.VERCEL !== "1") return null;
|
|
639
|
+
return { environment: "local", evidence: "VERCEL=1 without a deployment tier; set PATCHSTACK_ENVIRONMENT" };
|
|
632
640
|
}
|
|
633
641
|
},
|
|
634
642
|
{
|
|
635
643
|
platform: "netlify",
|
|
636
644
|
read: (env) => {
|
|
637
|
-
if (env.
|
|
645
|
+
if (env.NETLIFY_PREVIEW_SERVER === "true") {
|
|
646
|
+
return { environment: "sandbox", evidence: "NETLIFY_PREVIEW_SERVER=true" };
|
|
647
|
+
}
|
|
648
|
+
if (env.NETLIFY_DEV === "true") {
|
|
649
|
+
return { environment: "local", evidence: "NETLIFY_DEV=true (the local development server)" };
|
|
650
|
+
}
|
|
651
|
+
if (env.NETLIFY !== "true") return null;
|
|
652
|
+
if (!set(env.CONTEXT)) {
|
|
653
|
+
return { environment: "local", evidence: "NETLIFY=true without CONTEXT; set PATCHSTACK_ENVIRONMENT" };
|
|
654
|
+
}
|
|
638
655
|
return env.CONTEXT === "production" ? { environment: "production", evidence: "CONTEXT=production" } : { environment: "sandbox", evidence: `CONTEXT=${env.CONTEXT}` };
|
|
639
656
|
}
|
|
640
657
|
},
|
|
@@ -959,10 +976,13 @@ async function resolveConfig(options) {
|
|
|
959
976
|
"CONFIG_INVALID"
|
|
960
977
|
);
|
|
961
978
|
}
|
|
962
|
-
const inferred =
|
|
963
|
-
const
|
|
964
|
-
const
|
|
965
|
-
const
|
|
979
|
+
const inferred = fromEnv.environment === void 0 ? inferEnvironment(process.env, detectHostedBuilder(options.cwd)) : null;
|
|
980
|
+
const fileLabelOverruled = fromEnv.environment === void 0 && environmentRaw !== void 0 && environmentRaw !== "production" && inferred.environment === "production";
|
|
981
|
+
const labelled = fileLabelOverruled ? void 0 : environmentRaw;
|
|
982
|
+
const environment = labelled ?? inferred.environment;
|
|
983
|
+
const environmentEvidence = labelled === void 0 ? inferred.evidence : [];
|
|
984
|
+
const environmentSource = labelled !== void 0 ? "override" : inferred.source;
|
|
985
|
+
const ignoredFileEnvironment = fileLabelOverruled ? environmentRaw : null;
|
|
966
986
|
if (siteUuid !== null && siteUuid.length > 0 && !isCanonicalUuid(siteUuid)) {
|
|
967
987
|
throw new PatchstackError(
|
|
968
988
|
`Site UUID "${siteUuid}" does not look like a valid UUID.`,
|
|
@@ -991,6 +1011,7 @@ async function resolveConfig(options) {
|
|
|
991
1011
|
environment,
|
|
992
1012
|
environmentEvidence,
|
|
993
1013
|
environmentSource,
|
|
1014
|
+
ignoredFileEnvironment,
|
|
994
1015
|
widget: fromFile.widget !== false,
|
|
995
1016
|
claimToken
|
|
996
1017
|
};
|
|
@@ -9343,10 +9364,10 @@ async function createProtection(options = {}) {
|
|
|
9343
9364
|
},
|
|
9344
9365
|
// Wrap a fetch handler: screens the request, then the response (redact/block).
|
|
9345
9366
|
fetch(handler) {
|
|
9346
|
-
return async (request, ...rest)
|
|
9367
|
+
return async function(request, ...rest) {
|
|
9347
9368
|
const { blocked, client } = await screenFetchRequest(request, rest);
|
|
9348
9369
|
if (blocked) return blocked;
|
|
9349
|
-
const response = await handler(request, ...rest);
|
|
9370
|
+
const response = await handler.call(this, request, ...rest);
|
|
9350
9371
|
return screenResp(response, reqContextFromFetch(request, client));
|
|
9351
9372
|
};
|
|
9352
9373
|
},
|