@patchstack/connect 0.4.2 → 0.5.0
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 +21 -4
- package/README.md +10 -2
- package/dist/cli.js +277 -56
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +221 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +218 -15
- package/dist/index.js.map +1 -1
- package/dist/protect/templates/astro-middleware.ts +22 -1
- package/dist/protect/templates/express-guard.cjs +54 -4
- package/dist/protect/templates/express-guard.js +54 -4
- package/dist/protect/templates/express-guard.ts +43 -5
- package/dist/protect/templates/fastify-plugin.cjs +41 -3
- package/dist/protect/templates/fastify-plugin.js +41 -3
- package/dist/protect/templates/fastify-plugin.ts +30 -2
- package/dist/protect/templates/generic-guard.cjs +58 -5
- package/dist/protect/templates/generic-guard.js +58 -5
- package/dist/protect/templates/generic-guard.ts +47 -4
- package/dist/protect/templates/guard.ts +42 -8
- package/dist/protect/templates/next-middleware.ts +22 -1
- package/dist/protect/templates/nuxt-middleware.ts +22 -1
- package/dist/protect/templates/sveltekit-hooks.ts +22 -1
- package/dist/protect.cjs +341 -94
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.edge.js +56 -24
- package/dist/protect.edge.js.map +1 -1
- package/dist/protect.js +57 -25
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-JNCTIAM5.js → refresh-manifest-LDS35UKV.js} +234 -33
- package/dist/refresh-manifest-LDS35UKV.js.map +1 -0
- package/package.json +3 -3
- package/dist/refresh-manifest-JNCTIAM5.js.map +0 -1
package/dist/protect.cjs
CHANGED
|
@@ -38,8 +38,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
38
38
|
// src/pulse-token.ts
|
|
39
39
|
function buildTokenUrl(manifestEndpoint) {
|
|
40
40
|
const url = new URL(manifestEndpoint);
|
|
41
|
-
const
|
|
42
|
-
url.pathname =
|
|
41
|
+
const path8 = url.pathname.replace(/\/$/, "");
|
|
42
|
+
url.pathname = path8.endsWith("/manifest") ? `${path8.slice(0, -"/manifest".length)}/token` : "/monitor/pulse/token";
|
|
43
43
|
url.search = "";
|
|
44
44
|
url.hash = "";
|
|
45
45
|
return url.toString();
|
|
@@ -159,6 +159,14 @@ function authFailureMessage(status, config) {
|
|
|
159
159
|
}
|
|
160
160
|
return null;
|
|
161
161
|
}
|
|
162
|
+
function buildManifestBody(config, payload) {
|
|
163
|
+
return {
|
|
164
|
+
...payload,
|
|
165
|
+
environment: config.environment,
|
|
166
|
+
...typeof config.siteUrl === "string" && config.siteUrl !== "" ? { url: config.siteUrl } : {},
|
|
167
|
+
...typeof config.siteName === "string" && config.siteName !== "" ? { name: config.siteName } : {}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
162
170
|
async function postManifest(config, payload) {
|
|
163
171
|
const url = buildEndpointUrl(config.endpoint, config.siteUuid);
|
|
164
172
|
const timeoutMs = config.timeoutMs;
|
|
@@ -171,7 +179,7 @@ async function postManifest(config, payload) {
|
|
|
171
179
|
Accept: "application/json",
|
|
172
180
|
"User-Agent": "@patchstack/connect"
|
|
173
181
|
},
|
|
174
|
-
body: JSON.stringify(
|
|
182
|
+
body: JSON.stringify(buildManifestBody(config, payload)),
|
|
175
183
|
signal: AbortSignal.timeout(timeoutMs)
|
|
176
184
|
});
|
|
177
185
|
} catch (cause) {
|
|
@@ -239,7 +247,207 @@ var init_client = __esm({
|
|
|
239
247
|
}
|
|
240
248
|
});
|
|
241
249
|
|
|
250
|
+
// src/site-url.ts
|
|
251
|
+
function isUnpublishableHost(hostname) {
|
|
252
|
+
const host = hostname.toLowerCase().replace(/\.+$/, "");
|
|
253
|
+
if (host === "") return true;
|
|
254
|
+
if (host.startsWith("[")) return true;
|
|
255
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
|
|
256
|
+
if (!host.includes(".")) return true;
|
|
257
|
+
return RESERVED_SUFFIXES.some((suffix) => host.endsWith(suffix));
|
|
258
|
+
}
|
|
259
|
+
function normaliseSiteUrl(value) {
|
|
260
|
+
const trimmed = (value ?? "").trim();
|
|
261
|
+
if (trimmed === "") return null;
|
|
262
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
263
|
+
let parsed;
|
|
264
|
+
try {
|
|
265
|
+
parsed = new URL(withScheme);
|
|
266
|
+
} catch {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
270
|
+
if (parsed.hostname === "" || isUnpublishableHost(parsed.hostname)) return null;
|
|
271
|
+
const origin = `${parsed.protocol}//${parsed.host}`;
|
|
272
|
+
return origin.length <= 191 ? origin : null;
|
|
273
|
+
}
|
|
274
|
+
function detectSiteUrl(env = process.env) {
|
|
275
|
+
for (const source of PRODUCTION_URL_SOURCES) {
|
|
276
|
+
const url = normaliseSiteUrl(source.read(env));
|
|
277
|
+
if (url !== null) {
|
|
278
|
+
return { url, platform: source.platform };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
var RESERVED_SUFFIXES, PRODUCTION_URL_SOURCES;
|
|
284
|
+
var init_site_url = __esm({
|
|
285
|
+
"src/site-url.ts"() {
|
|
286
|
+
"use strict";
|
|
287
|
+
RESERVED_SUFFIXES = [
|
|
288
|
+
".local",
|
|
289
|
+
".localhost",
|
|
290
|
+
".localdomain",
|
|
291
|
+
".internal",
|
|
292
|
+
".intranet",
|
|
293
|
+
".invalid",
|
|
294
|
+
".test",
|
|
295
|
+
".example",
|
|
296
|
+
".home.arpa",
|
|
297
|
+
".in-addr.arpa",
|
|
298
|
+
".ip6.arpa"
|
|
299
|
+
];
|
|
300
|
+
PRODUCTION_URL_SOURCES = [
|
|
301
|
+
{
|
|
302
|
+
// VERCEL_URL is per-deployment and changes every build, so it is deliberately not read here.
|
|
303
|
+
platform: "vercel",
|
|
304
|
+
read: (env) => env.VERCEL_ENV === "production" ? env.VERCEL_PROJECT_PRODUCTION_URL : void 0
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
// `URL` is generic enough to belong to anything, so it counts only alongside Netlify's own markers.
|
|
308
|
+
platform: "netlify",
|
|
309
|
+
read: (env) => env.NETLIFY === "true" && env.CONTEXT === "production" ? env.URL : void 0
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
platform: "render",
|
|
313
|
+
read: (env) => env.RENDER === "true" && env.IS_PULL_REQUEST !== "true" ? env.RENDER_EXTERNAL_URL : void 0
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
platform: "railway",
|
|
317
|
+
read: (env) => env.RAILWAY_ENVIRONMENT_NAME === "production" ? env.RAILWAY_PUBLIC_DOMAIN : void 0
|
|
318
|
+
}
|
|
319
|
+
];
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// src/site-name.ts
|
|
324
|
+
function normaliseSiteName(value) {
|
|
325
|
+
const collapsed = (value ?? "").replace(/\s+/g, " ").trim();
|
|
326
|
+
if (collapsed === "") return null;
|
|
327
|
+
return collapsed.slice(0, NAME_MAX_LENGTH);
|
|
328
|
+
}
|
|
329
|
+
function decodeEntities(text) {
|
|
330
|
+
return text.replace(/&([a-z]+);/gi, (whole, name) => NAMED_ENTITIES2[name.toLowerCase()] ?? whole);
|
|
331
|
+
}
|
|
332
|
+
function nameFromHtmlTitle(html) {
|
|
333
|
+
const match = /<title\b[^>]*>([\s\S]*?)<\/title>/i.exec(html);
|
|
334
|
+
if (match === null) return null;
|
|
335
|
+
const name = normaliseSiteName(decodeEntities(match[1] ?? ""));
|
|
336
|
+
if (name === null || TEMPLATE_TITLES.has(name.toLowerCase())) return null;
|
|
337
|
+
return name;
|
|
338
|
+
}
|
|
339
|
+
function nameFromPackageName(raw) {
|
|
340
|
+
if (typeof raw !== "string") return null;
|
|
341
|
+
const unscoped = raw.trim().replace(/^@[^/]+\//, "");
|
|
342
|
+
if (unscoped === "" || TEMPLATE_PACKAGE_NAMES.has(unscoped.toLowerCase())) return null;
|
|
343
|
+
const words = unscoped.split(/[-_.\s]+/).filter((word) => word !== "").map((word) => word === word.toUpperCase() ? word : word.charAt(0).toUpperCase() + word.slice(1));
|
|
344
|
+
return normaliseSiteName(words.join(" "));
|
|
345
|
+
}
|
|
346
|
+
async function readIfPresent(file) {
|
|
347
|
+
try {
|
|
348
|
+
return await (0, import_promises.readFile)(file, "utf8");
|
|
349
|
+
} catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function detectSiteName(cwd) {
|
|
354
|
+
for (const shell of ["index.html", import_node_path.default.join("public", "index.html")]) {
|
|
355
|
+
const html = await readIfPresent(import_node_path.default.join(cwd, shell));
|
|
356
|
+
if (html === null) continue;
|
|
357
|
+
const name = nameFromHtmlTitle(html);
|
|
358
|
+
if (name !== null) return { name, source: "index.html" };
|
|
359
|
+
}
|
|
360
|
+
const manifest = await readIfPresent(import_node_path.default.join(cwd, "package.json"));
|
|
361
|
+
if (manifest !== null) {
|
|
362
|
+
try {
|
|
363
|
+
const parsed = JSON.parse(manifest);
|
|
364
|
+
const name = nameFromPackageName(
|
|
365
|
+
typeof parsed === "object" && parsed !== null ? parsed.name : void 0
|
|
366
|
+
);
|
|
367
|
+
if (name !== null) return { name, source: "package.json" };
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
var import_promises, import_node_path, NAME_MAX_LENGTH, TEMPLATE_PACKAGE_NAMES, TEMPLATE_TITLES, NAMED_ENTITIES2;
|
|
374
|
+
var init_site_name = __esm({
|
|
375
|
+
"src/site-name.ts"() {
|
|
376
|
+
"use strict";
|
|
377
|
+
import_promises = require("fs/promises");
|
|
378
|
+
import_node_path = __toESM(require("path"), 1);
|
|
379
|
+
NAME_MAX_LENGTH = 191;
|
|
380
|
+
TEMPLATE_PACKAGE_NAMES = /* @__PURE__ */ new Set([
|
|
381
|
+
// Lovable
|
|
382
|
+
"vite_react_shadcn_ts",
|
|
383
|
+
// Bolt
|
|
384
|
+
"vite-react-typescript-starter",
|
|
385
|
+
// Scaffolders and the names people accept from them
|
|
386
|
+
"vite-project",
|
|
387
|
+
"my-app",
|
|
388
|
+
"my-project",
|
|
389
|
+
"my-v0-project",
|
|
390
|
+
"app",
|
|
391
|
+
"frontend",
|
|
392
|
+
"web",
|
|
393
|
+
"client",
|
|
394
|
+
"project",
|
|
395
|
+
"template",
|
|
396
|
+
"example",
|
|
397
|
+
"react-app",
|
|
398
|
+
"next-app",
|
|
399
|
+
"nextjs",
|
|
400
|
+
"starter",
|
|
401
|
+
"test"
|
|
402
|
+
]);
|
|
403
|
+
TEMPLATE_TITLES = /* @__PURE__ */ new Set([
|
|
404
|
+
"vite + react",
|
|
405
|
+
"vite + react + ts",
|
|
406
|
+
"vite + vue",
|
|
407
|
+
"vite + vue + ts",
|
|
408
|
+
"vite app",
|
|
409
|
+
"react app",
|
|
410
|
+
"create next app",
|
|
411
|
+
"document",
|
|
412
|
+
"untitled",
|
|
413
|
+
"index",
|
|
414
|
+
"home",
|
|
415
|
+
"app"
|
|
416
|
+
]);
|
|
417
|
+
NAMED_ENTITIES2 = {
|
|
418
|
+
amp: "&",
|
|
419
|
+
lt: "<",
|
|
420
|
+
gt: ">",
|
|
421
|
+
quot: '"',
|
|
422
|
+
apos: "'",
|
|
423
|
+
nbsp: " "
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
242
428
|
// src/config.ts
|
|
429
|
+
async function resolveSiteIdentity(cwd, fromEnv, fromFile) {
|
|
430
|
+
const configuredUrl = stated(fromEnv.url) ?? stated(fromFile.url);
|
|
431
|
+
const configuredName = stated(fromEnv.name) ?? stated(fromFile.name);
|
|
432
|
+
let url;
|
|
433
|
+
if (configuredUrl === null) {
|
|
434
|
+
url = detectSiteUrl(process.env)?.url ?? null;
|
|
435
|
+
} else {
|
|
436
|
+
url = normaliseSiteUrl(configuredUrl);
|
|
437
|
+
if (url === null) {
|
|
438
|
+
throw new PatchstackError(
|
|
439
|
+
`Site URL "${configuredUrl}" is not an address the public can reach this site at. Set "url" in .patchstackrc.json (or PATCHSTACK_SITE_URL) to the http(s) address visitors use \u2014 a hostname, not an IP address \u2014 or remove it to let the build environment answer.`,
|
|
440
|
+
"CONFIG_INVALID"
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const name = configuredName === null ? (await detectSiteName(cwd))?.name ?? null : normaliseSiteName(configuredName);
|
|
445
|
+
return { url, name };
|
|
446
|
+
}
|
|
447
|
+
function stated(value) {
|
|
448
|
+
const trimmed = (value ?? "").trim();
|
|
449
|
+
return trimmed === "" ? null : trimmed;
|
|
450
|
+
}
|
|
243
451
|
async function resolveConfig(options) {
|
|
244
452
|
const fromFile = await readConfigFile(options.cwd);
|
|
245
453
|
const fromSecretFile = await readSecretFile(options.cwd);
|
|
@@ -269,10 +477,13 @@ async function resolveConfig(options) {
|
|
|
269
477
|
}
|
|
270
478
|
const apiKeyRaw = fromEnv.apiKey ?? fromSecretFile.apiKey ?? fromFile.apiKey ?? null;
|
|
271
479
|
const pulseAuthRaw = fromEnv.pulseAuth ?? fromSecretFile.pulseAuth ?? fromFile.pulseAuth ?? apiKeyRaw;
|
|
480
|
+
const identity = options.detectSiteIdentity === true ? await resolveSiteIdentity(options.cwd, fromEnv, fromFile) : { url: null, name: null };
|
|
272
481
|
return {
|
|
273
482
|
siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,
|
|
274
483
|
apiKey: apiKeyRaw === null || apiKeyRaw.length === 0 ? null : apiKeyRaw,
|
|
275
484
|
pulseAuth: pulseAuthRaw === null || pulseAuthRaw.length === 0 ? null : pulseAuthRaw,
|
|
485
|
+
siteUrl: identity.url,
|
|
486
|
+
siteName: identity.name,
|
|
276
487
|
endpoint,
|
|
277
488
|
timeoutMs,
|
|
278
489
|
environment,
|
|
@@ -287,10 +498,10 @@ async function readConfigFile(cwd) {
|
|
|
287
498
|
return readJsonFile(cwd, CONFIG_FILENAME);
|
|
288
499
|
}
|
|
289
500
|
async function readJsonFile(cwd, filename) {
|
|
290
|
-
const target =
|
|
501
|
+
const target = import_node_path2.default.join(cwd, filename);
|
|
291
502
|
let raw;
|
|
292
503
|
try {
|
|
293
|
-
raw = await (0,
|
|
504
|
+
raw = await (0, import_promises2.readFile)(target, "utf8");
|
|
294
505
|
} catch (err) {
|
|
295
506
|
if (err.code === "ENOENT") {
|
|
296
507
|
return {};
|
|
@@ -327,6 +538,8 @@ function readEnv() {
|
|
|
327
538
|
const environmentRaw = process.env.PATCHSTACK_ENVIRONMENT;
|
|
328
539
|
return {
|
|
329
540
|
siteUuid: process.env.PATCHSTACK_SITE_UUID ?? void 0,
|
|
541
|
+
url: process.env.PATCHSTACK_SITE_URL ?? void 0,
|
|
542
|
+
name: process.env.PATCHSTACK_SITE_NAME ?? void 0,
|
|
330
543
|
apiKey: process.env.PATCHSTACK_API_KEY ?? void 0,
|
|
331
544
|
pulseAuth: process.env.PATCHSTACK_PULSE_AUTH ?? void 0,
|
|
332
545
|
endpoint: process.env.PATCHSTACK_ENDPOINT ?? void 0,
|
|
@@ -340,14 +553,16 @@ function isUuid(value) {
|
|
|
340
553
|
function isEnvironment(value) {
|
|
341
554
|
return value === "production" || value === "sandbox";
|
|
342
555
|
}
|
|
343
|
-
var
|
|
556
|
+
var import_promises2, import_node_path2, CONFIG_FILENAME, SECRET_FILENAME, DEFAULT_ENVIRONMENT;
|
|
344
557
|
var init_config = __esm({
|
|
345
558
|
"src/config.ts"() {
|
|
346
559
|
"use strict";
|
|
347
|
-
|
|
348
|
-
|
|
560
|
+
import_promises2 = require("fs/promises");
|
|
561
|
+
import_node_path2 = __toESM(require("path"), 1);
|
|
349
562
|
init_types();
|
|
350
563
|
init_client();
|
|
564
|
+
init_site_url();
|
|
565
|
+
init_site_name();
|
|
351
566
|
CONFIG_FILENAME = ".patchstackrc.json";
|
|
352
567
|
SECRET_FILENAME = ".patchstackrc.local.json";
|
|
353
568
|
DEFAULT_ENVIRONMENT = "production";
|
|
@@ -358,7 +573,7 @@ var init_config = __esm({
|
|
|
358
573
|
async function readDeclaredDependencyNames(cwd) {
|
|
359
574
|
let raw;
|
|
360
575
|
try {
|
|
361
|
-
raw = await (0,
|
|
576
|
+
raw = await (0, import_promises3.readFile)(import_node_path3.default.join(cwd, "package.json"), "utf8");
|
|
362
577
|
} catch {
|
|
363
578
|
return [];
|
|
364
579
|
}
|
|
@@ -407,12 +622,12 @@ function disagreements(packages, other) {
|
|
|
407
622
|
}
|
|
408
623
|
return conflicts;
|
|
409
624
|
}
|
|
410
|
-
var
|
|
625
|
+
var import_promises3, import_node_path3;
|
|
411
626
|
var init_consistency = __esm({
|
|
412
627
|
"src/parsers/consistency.ts"() {
|
|
413
628
|
"use strict";
|
|
414
|
-
|
|
415
|
-
|
|
629
|
+
import_promises3 = require("fs/promises");
|
|
630
|
+
import_node_path3 = __toESM(require("path"), 1);
|
|
416
631
|
}
|
|
417
632
|
});
|
|
418
633
|
|
|
@@ -443,7 +658,7 @@ var init_report = __esm({
|
|
|
443
658
|
async function parseBunLockfile(lockfilePath, report) {
|
|
444
659
|
let raw;
|
|
445
660
|
try {
|
|
446
|
-
raw = await (0,
|
|
661
|
+
raw = await (0, import_promises4.readFile)(lockfilePath, "utf8");
|
|
447
662
|
} catch (cause) {
|
|
448
663
|
throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause);
|
|
449
664
|
}
|
|
@@ -557,11 +772,11 @@ function stripTrailingCommas(source) {
|
|
|
557
772
|
}
|
|
558
773
|
return out.join("");
|
|
559
774
|
}
|
|
560
|
-
var
|
|
775
|
+
var import_promises4, NON_REGISTRY_VERSION;
|
|
561
776
|
var init_bun = __esm({
|
|
562
777
|
"src/parsers/bun.ts"() {
|
|
563
778
|
"use strict";
|
|
564
|
-
|
|
779
|
+
import_promises4 = require("fs/promises");
|
|
565
780
|
init_types();
|
|
566
781
|
init_report();
|
|
567
782
|
NON_REGISTRY_VERSION = /^[a-z][a-z0-9+.-]*:|\//i;
|
|
@@ -572,7 +787,7 @@ var init_bun = __esm({
|
|
|
572
787
|
async function parseNpmLockfile(lockfilePath) {
|
|
573
788
|
let raw;
|
|
574
789
|
try {
|
|
575
|
-
raw = await (0,
|
|
790
|
+
raw = await (0, import_promises5.readFile)(lockfilePath, "utf8");
|
|
576
791
|
} catch (cause) {
|
|
577
792
|
throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause);
|
|
578
793
|
}
|
|
@@ -630,7 +845,7 @@ function extractFromV1(deps, acc = [], depth = 0) {
|
|
|
630
845
|
return acc;
|
|
631
846
|
}
|
|
632
847
|
function extractNameFromPath(pkgPath) {
|
|
633
|
-
const segments = pkgPath.split("node_modules" +
|
|
848
|
+
const segments = pkgPath.split("node_modules" + import_node_path4.default.sep === pkgPath ? import_node_path4.default.sep : "/");
|
|
634
849
|
const parts = pkgPath.split("/");
|
|
635
850
|
const nmIndex = parts.lastIndexOf("node_modules");
|
|
636
851
|
if (nmIndex === -1 || nmIndex >= parts.length - 1) {
|
|
@@ -651,21 +866,21 @@ function isDirectV2(pkgPath) {
|
|
|
651
866
|
const nmCount = parts.filter((p) => p === "node_modules").length;
|
|
652
867
|
return nmCount === 1;
|
|
653
868
|
}
|
|
654
|
-
var
|
|
869
|
+
var import_promises5, import_node_path4;
|
|
655
870
|
var init_npm = __esm({
|
|
656
871
|
"src/parsers/npm.ts"() {
|
|
657
872
|
"use strict";
|
|
658
|
-
|
|
659
|
-
|
|
873
|
+
import_promises5 = require("fs/promises");
|
|
874
|
+
import_node_path4 = __toESM(require("path"), 1);
|
|
660
875
|
init_types();
|
|
661
876
|
}
|
|
662
877
|
});
|
|
663
878
|
|
|
664
879
|
// src/parsers/node_modules.ts
|
|
665
880
|
async function walkNodeModules(cwd) {
|
|
666
|
-
const root =
|
|
881
|
+
const root = import_node_path5.default.join(cwd, "node_modules");
|
|
667
882
|
try {
|
|
668
|
-
const info = await (0,
|
|
883
|
+
const info = await (0, import_promises6.stat)(root);
|
|
669
884
|
if (!info.isDirectory()) {
|
|
670
885
|
throw new PatchstackError(
|
|
671
886
|
`${root} exists but is not a directory.`,
|
|
@@ -684,7 +899,7 @@ async function walkNodeModules(cwd) {
|
|
|
684
899
|
}
|
|
685
900
|
const seen = /* @__PURE__ */ new Map();
|
|
686
901
|
try {
|
|
687
|
-
seen.set(await (0,
|
|
902
|
+
seen.set(await (0, import_promises6.realpath)(cwd), -1);
|
|
688
903
|
} catch {
|
|
689
904
|
}
|
|
690
905
|
return dedupeByShallowest(await walk(root, 0, seen, cwd));
|
|
@@ -700,26 +915,26 @@ function dedupeByShallowest(found) {
|
|
|
700
915
|
async function walk(dir, depth, seen, root) {
|
|
701
916
|
let names;
|
|
702
917
|
try {
|
|
703
|
-
names = await (0,
|
|
918
|
+
names = await (0, import_promises6.readdir)(dir);
|
|
704
919
|
} catch {
|
|
705
920
|
return [];
|
|
706
921
|
}
|
|
707
922
|
const perEntry = await mapLimit(names, async (name) => {
|
|
708
923
|
if (name.startsWith(".") && !PACKAGE_STORE_DIRS.has(name)) return [];
|
|
709
|
-
const fullPath =
|
|
924
|
+
const fullPath = import_node_path5.default.join(dir, name);
|
|
710
925
|
const resolved = await claim(fullPath, seen, depth);
|
|
711
926
|
if (resolved === null) return [];
|
|
712
927
|
if (PACKAGE_STORE_DIRS.has(name)) return walk(fullPath, depth, seen, root);
|
|
713
928
|
if (name.startsWith("@")) {
|
|
714
929
|
let subNames;
|
|
715
930
|
try {
|
|
716
|
-
subNames = await (0,
|
|
931
|
+
subNames = await (0, import_promises6.readdir)(fullPath);
|
|
717
932
|
} catch {
|
|
718
933
|
return [];
|
|
719
934
|
}
|
|
720
935
|
const scoped = await mapLimit(subNames, async (sub) => {
|
|
721
936
|
if (sub.startsWith(".")) return [];
|
|
722
|
-
const scopedDir =
|
|
937
|
+
const scopedDir = import_node_path5.default.join(fullPath, sub);
|
|
723
938
|
const scopedReal = await claim(scopedDir, seen, depth);
|
|
724
939
|
if (scopedReal === null) return [];
|
|
725
940
|
return packageAndNested(scopedDir, scopedReal, depth, seen, root);
|
|
@@ -746,13 +961,13 @@ async function mapLimit(items, fn) {
|
|
|
746
961
|
await Promise.all(workers);
|
|
747
962
|
return results;
|
|
748
963
|
}
|
|
749
|
-
function installLocation(root, pkgDir, impl =
|
|
964
|
+
function installLocation(root, pkgDir, impl = import_node_path5.default) {
|
|
750
965
|
return impl.relative(root, pkgDir).split(impl.sep).join("/");
|
|
751
966
|
}
|
|
752
967
|
async function readPackage(pkgDir, depth, root) {
|
|
753
968
|
let raw;
|
|
754
969
|
try {
|
|
755
|
-
raw = await (0,
|
|
970
|
+
raw = await (0, import_promises6.readFile)(import_node_path5.default.join(pkgDir, "package.json"), "utf8");
|
|
756
971
|
} catch {
|
|
757
972
|
return null;
|
|
758
973
|
}
|
|
@@ -771,7 +986,7 @@ async function readPackage(pkgDir, depth, root) {
|
|
|
771
986
|
return { name: parsed.name, version: parsed.version, path: installLocation(root, pkgDir), direct: depth === 0 };
|
|
772
987
|
}
|
|
773
988
|
async function walkNested(pkgDir, depth, seen, root) {
|
|
774
|
-
const nested =
|
|
989
|
+
const nested = import_node_path5.default.join(pkgDir, "node_modules");
|
|
775
990
|
if (await claim(nested, seen, depth + 1) === null) {
|
|
776
991
|
return [];
|
|
777
992
|
}
|
|
@@ -780,10 +995,10 @@ async function walkNested(pkgDir, depth, seen, root) {
|
|
|
780
995
|
async function claim(dir, seen, depth) {
|
|
781
996
|
let resolved;
|
|
782
997
|
try {
|
|
783
|
-
const info = await (0,
|
|
998
|
+
const info = await (0, import_promises6.lstat)(dir);
|
|
784
999
|
if (!info.isDirectory() && !info.isSymbolicLink()) return null;
|
|
785
|
-
resolved = await (0,
|
|
786
|
-
const target = await (0,
|
|
1000
|
+
resolved = await (0, import_promises6.realpath)(dir);
|
|
1001
|
+
const target = await (0, import_promises6.stat)(resolved);
|
|
787
1002
|
if (!target.isDirectory()) return null;
|
|
788
1003
|
} catch {
|
|
789
1004
|
return null;
|
|
@@ -793,12 +1008,12 @@ async function claim(dir, seen, depth) {
|
|
|
793
1008
|
seen.set(resolved, depth);
|
|
794
1009
|
return resolved;
|
|
795
1010
|
}
|
|
796
|
-
var
|
|
1011
|
+
var import_promises6, import_node_path5, PACKAGE_STORE_DIRS, CONCURRENCY;
|
|
797
1012
|
var init_node_modules = __esm({
|
|
798
1013
|
"src/parsers/node_modules.ts"() {
|
|
799
1014
|
"use strict";
|
|
800
|
-
|
|
801
|
-
|
|
1015
|
+
import_promises6 = require("fs/promises");
|
|
1016
|
+
import_node_path5 = __toESM(require("path"), 1);
|
|
802
1017
|
init_types();
|
|
803
1018
|
PACKAGE_STORE_DIRS = /* @__PURE__ */ new Set([".bun", ".pnpm", ".store"]);
|
|
804
1019
|
CONCURRENCY = 32;
|
|
@@ -809,7 +1024,7 @@ var init_node_modules = __esm({
|
|
|
809
1024
|
async function parsePnpmLockfile(lockfilePath, report) {
|
|
810
1025
|
let raw;
|
|
811
1026
|
try {
|
|
812
|
-
raw = await (0,
|
|
1027
|
+
raw = await (0, import_promises7.readFile)(lockfilePath, "utf8");
|
|
813
1028
|
} catch (cause) {
|
|
814
1029
|
throw new PatchstackError(
|
|
815
1030
|
`Could not read lockfile at ${lockfilePath}`,
|
|
@@ -1051,11 +1266,11 @@ function extractLeafName(trimmed) {
|
|
|
1051
1266
|
}
|
|
1052
1267
|
return name.length > 0 ? name : null;
|
|
1053
1268
|
}
|
|
1054
|
-
var
|
|
1269
|
+
var import_promises7, DEP_SECTIONS;
|
|
1055
1270
|
var init_pnpm = __esm({
|
|
1056
1271
|
"src/parsers/pnpm.ts"() {
|
|
1057
1272
|
"use strict";
|
|
1058
|
-
|
|
1273
|
+
import_promises7 = require("fs/promises");
|
|
1059
1274
|
init_types();
|
|
1060
1275
|
init_report();
|
|
1061
1276
|
DEP_SECTIONS = /* @__PURE__ */ new Set([
|
|
@@ -1070,7 +1285,7 @@ var init_pnpm = __esm({
|
|
|
1070
1285
|
async function parseYarnLockfile(lockfilePath, report) {
|
|
1071
1286
|
let raw;
|
|
1072
1287
|
try {
|
|
1073
|
-
raw = await (0,
|
|
1288
|
+
raw = await (0, import_promises8.readFile)(lockfilePath, "utf8");
|
|
1074
1289
|
} catch (cause) {
|
|
1075
1290
|
throw new PatchstackError(
|
|
1076
1291
|
`Could not read lockfile at ${lockfilePath}`,
|
|
@@ -1085,7 +1300,7 @@ async function parseYarnLockfile(lockfilePath, report) {
|
|
|
1085
1300
|
"LOCKFILE_PARSE_ERROR"
|
|
1086
1301
|
);
|
|
1087
1302
|
}
|
|
1088
|
-
const directNames = await readDirectDepNames(
|
|
1303
|
+
const directNames = await readDirectDepNames(import_node_path6.default.dirname(lockfilePath));
|
|
1089
1304
|
const entries = [];
|
|
1090
1305
|
const seen = /* @__PURE__ */ new Set();
|
|
1091
1306
|
for (const block of blocks) {
|
|
@@ -1250,7 +1465,7 @@ async function readDirectDepNames(cwd) {
|
|
|
1250
1465
|
const names = /* @__PURE__ */ new Set();
|
|
1251
1466
|
let raw;
|
|
1252
1467
|
try {
|
|
1253
|
-
raw = await (0,
|
|
1468
|
+
raw = await (0, import_promises8.readFile)(import_node_path6.default.join(cwd, "package.json"), "utf8");
|
|
1254
1469
|
} catch {
|
|
1255
1470
|
return names;
|
|
1256
1471
|
}
|
|
@@ -1275,12 +1490,12 @@ async function readDirectDepNames(cwd) {
|
|
|
1275
1490
|
}
|
|
1276
1491
|
return names;
|
|
1277
1492
|
}
|
|
1278
|
-
var
|
|
1493
|
+
var import_promises8, import_node_path6, NON_PACKAGE_KEYS, NON_REGISTRY_PROTOCOLS;
|
|
1279
1494
|
var init_yarn = __esm({
|
|
1280
1495
|
"src/parsers/yarn.ts"() {
|
|
1281
1496
|
"use strict";
|
|
1282
|
-
|
|
1283
|
-
|
|
1497
|
+
import_promises8 = require("fs/promises");
|
|
1498
|
+
import_node_path6 = __toESM(require("path"), 1);
|
|
1284
1499
|
init_types();
|
|
1285
1500
|
init_report();
|
|
1286
1501
|
NON_PACKAGE_KEYS = /* @__PURE__ */ new Set(["__metadata"]);
|
|
@@ -1292,7 +1507,7 @@ var init_yarn = __esm({
|
|
|
1292
1507
|
async function detectLockfile(cwd) {
|
|
1293
1508
|
const probed = await Promise.all(
|
|
1294
1509
|
LOCKFILE_CANDIDATES.map(async (candidate) => {
|
|
1295
|
-
const filePath =
|
|
1510
|
+
const filePath = import_node_path7.default.join(cwd, candidate.filename);
|
|
1296
1511
|
return { ...candidate, filePath, present: await exists(filePath) };
|
|
1297
1512
|
})
|
|
1298
1513
|
);
|
|
@@ -1403,7 +1618,7 @@ function staleWarning(source, missing) {
|
|
|
1403
1618
|
async function presentLockfiles(cwd) {
|
|
1404
1619
|
const probed = await Promise.all(
|
|
1405
1620
|
LOCKFILE_CANDIDATES.map(async (candidate) => {
|
|
1406
|
-
const filePath =
|
|
1621
|
+
const filePath = import_node_path7.default.join(cwd, candidate.filename);
|
|
1407
1622
|
return { ...candidate, filePath, present: await exists(filePath) };
|
|
1408
1623
|
})
|
|
1409
1624
|
);
|
|
@@ -1427,18 +1642,18 @@ async function runStrategy(detected, cwd, report) {
|
|
|
1427
1642
|
}
|
|
1428
1643
|
async function exists(filePath) {
|
|
1429
1644
|
try {
|
|
1430
|
-
await (0,
|
|
1645
|
+
await (0, import_promises9.access)(filePath);
|
|
1431
1646
|
return true;
|
|
1432
1647
|
} catch {
|
|
1433
1648
|
return false;
|
|
1434
1649
|
}
|
|
1435
1650
|
}
|
|
1436
|
-
var
|
|
1651
|
+
var import_promises9, import_node_path7, LOCKFILE_CANDIDATES;
|
|
1437
1652
|
var init_parsers = __esm({
|
|
1438
1653
|
"src/parsers/index.ts"() {
|
|
1439
1654
|
"use strict";
|
|
1440
|
-
|
|
1441
|
-
|
|
1655
|
+
import_promises9 = require("fs/promises");
|
|
1656
|
+
import_node_path7 = __toESM(require("path"), 1);
|
|
1442
1657
|
init_types();
|
|
1443
1658
|
init_consistency();
|
|
1444
1659
|
init_bun();
|
|
@@ -2879,12 +3094,12 @@ function isRegexForm(pattern) {
|
|
|
2879
3094
|
const parts = REGEX_FORM.exec(pattern);
|
|
2880
3095
|
return parts !== null && REGEX_SYNTAX.test(parts[1]);
|
|
2881
3096
|
}
|
|
2882
|
-
function pathMatches(pattern,
|
|
3097
|
+
function pathMatches(pattern, path8) {
|
|
2883
3098
|
if (isRegexForm(pattern)) {
|
|
2884
3099
|
const re = safeRegExp(pattern);
|
|
2885
|
-
return re ? re.test(
|
|
3100
|
+
return re ? re.test(path8) : false;
|
|
2886
3101
|
}
|
|
2887
|
-
const target = foldPathForScope(
|
|
3102
|
+
const target = foldPathForScope(path8);
|
|
2888
3103
|
const folded = foldPathForScope(pattern);
|
|
2889
3104
|
if (folded.includes("*")) {
|
|
2890
3105
|
const trailing = folded.endsWith("/*");
|
|
@@ -4426,7 +4641,7 @@ async function installEgressGuard({ shouldBlock, onBlock, onSkip, dnsScreen = tr
|
|
|
4426
4641
|
for (const moduleName of ["node:http", "node:https"]) {
|
|
4427
4642
|
try {
|
|
4428
4643
|
const mod = await import(moduleName);
|
|
4429
|
-
const restore = patchHttpModule(mod.default ?? mod, block, screen);
|
|
4644
|
+
const restore = patchHttpModule(mod.default ?? mod, block, screen, skip);
|
|
4430
4645
|
if (restore) restores.push(restore);
|
|
4431
4646
|
} catch {
|
|
4432
4647
|
}
|
|
@@ -4440,7 +4655,7 @@ async function installEgressGuard({ shouldBlock, onBlock, onSkip, dnsScreen = tr
|
|
|
4440
4655
|
}
|
|
4441
4656
|
};
|
|
4442
4657
|
}
|
|
4443
|
-
function patchHttpModule(http, block, screen) {
|
|
4658
|
+
function patchHttpModule(http, block, screen, skip) {
|
|
4444
4659
|
if (!http || typeof http.request !== "function" || http.__patchstackGuarded) return null;
|
|
4445
4660
|
const originalRequest = http.request;
|
|
4446
4661
|
const originalGet = http.get;
|
|
@@ -4451,7 +4666,7 @@ function patchHttpModule(http, block, screen) {
|
|
|
4451
4666
|
}
|
|
4452
4667
|
if (target && screen && target.host && screen.isIP(target.host) === 0 && !screen.isExempt(target.host)) {
|
|
4453
4668
|
try {
|
|
4454
|
-
args = withScreeningLookup(args, target, block, screen.lookup);
|
|
4669
|
+
args = withScreeningLookup(args, target, block, screen.lookup, skip);
|
|
4455
4670
|
} catch {
|
|
4456
4671
|
}
|
|
4457
4672
|
}
|
|
@@ -4483,8 +4698,8 @@ function extractHttpTarget(args) {
|
|
|
4483
4698
|
const host = normalizeHost(first.hostname || first.host) || "localhost";
|
|
4484
4699
|
const protocol = first.protocol || "http:";
|
|
4485
4700
|
const port = first.port ? `:${first.port}` : "";
|
|
4486
|
-
const
|
|
4487
|
-
return { url: `${protocol}//${host}${port}${
|
|
4701
|
+
const path8 = first.path || "/";
|
|
4702
|
+
return { url: `${protocol}//${host}${port}${path8}`, host, method: first.method || "GET" };
|
|
4488
4703
|
}
|
|
4489
4704
|
} catch {
|
|
4490
4705
|
}
|
|
@@ -4504,7 +4719,9 @@ function screenResolved(addresses, target, block) {
|
|
|
4504
4719
|
}
|
|
4505
4720
|
return null;
|
|
4506
4721
|
}
|
|
4507
|
-
function withScreeningLookup(args, target, block, lookup) {
|
|
4722
|
+
function withScreeningLookup(args, target, block, lookup, skip) {
|
|
4723
|
+
const own = callerLookup(args);
|
|
4724
|
+
const resolve = typeof own === "function" ? own : lookup;
|
|
4508
4725
|
const screeningLookup = (hostname, options, callback) => {
|
|
4509
4726
|
let opts = options;
|
|
4510
4727
|
let cb = callback;
|
|
@@ -4513,29 +4730,43 @@ function withScreeningLookup(args, target, block, lookup) {
|
|
|
4513
4730
|
opts = {};
|
|
4514
4731
|
}
|
|
4515
4732
|
if (!opts || typeof opts !== "object") opts = {};
|
|
4733
|
+
const handOver = (list) => {
|
|
4734
|
+
if (opts.all) return cb(null, list);
|
|
4735
|
+
const first = list[0];
|
|
4736
|
+
if (!first) return cb(new Error(`Patchstack: could not resolve ${hostname}`));
|
|
4737
|
+
return cb(null, first.address, first.family);
|
|
4738
|
+
};
|
|
4739
|
+
let answered = false;
|
|
4516
4740
|
try {
|
|
4517
|
-
|
|
4741
|
+
resolve(hostname, { ...opts, all: true }, (err, addresses) => {
|
|
4742
|
+
answered = true;
|
|
4518
4743
|
if (err) return cb(err);
|
|
4519
4744
|
const list = Array.isArray(addresses) ? addresses : [];
|
|
4520
|
-
|
|
4745
|
+
let blocked;
|
|
4746
|
+
try {
|
|
4747
|
+
blocked = screenResolved(list, target, block);
|
|
4748
|
+
} catch {
|
|
4749
|
+
skip("screen-failed", { host: hostname });
|
|
4750
|
+
return handOver(list);
|
|
4751
|
+
}
|
|
4521
4752
|
if (blocked) {
|
|
4522
4753
|
return cb(new Error(`Patchstack blocked an outbound request to a disallowed address: ${target.host} resolved to ${blocked}`));
|
|
4523
4754
|
}
|
|
4524
|
-
|
|
4525
|
-
const first = list[0];
|
|
4526
|
-
if (!first) return cb(new Error(`Patchstack: could not resolve ${hostname}`));
|
|
4527
|
-
return cb(null, first.address, first.family);
|
|
4755
|
+
return handOver(list);
|
|
4528
4756
|
});
|
|
4529
|
-
} catch {
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
} catch {
|
|
4533
|
-
cb(new Error(`Patchstack: lookup failed for ${hostname}`));
|
|
4534
|
-
}
|
|
4757
|
+
} catch (err) {
|
|
4758
|
+
if (answered) throw err;
|
|
4759
|
+
return cb(err);
|
|
4535
4760
|
}
|
|
4536
4761
|
};
|
|
4537
4762
|
return injectLookupOption(args, screeningLookup);
|
|
4538
4763
|
}
|
|
4764
|
+
function callerLookup(args) {
|
|
4765
|
+
for (const arg of args) {
|
|
4766
|
+
if (arg && typeof arg === "object" && !(arg instanceof URL) && typeof arg.lookup === "function") return arg.lookup;
|
|
4767
|
+
}
|
|
4768
|
+
return void 0;
|
|
4769
|
+
}
|
|
4539
4770
|
function injectLookupOption(args, lookup) {
|
|
4540
4771
|
const first = args[0];
|
|
4541
4772
|
if (first && typeof first === "object" && !(first instanceof URL)) {
|
|
@@ -4833,12 +5064,12 @@ var fsMod;
|
|
|
4833
5064
|
async function loadFs() {
|
|
4834
5065
|
if (fsMod !== void 0) return fsMod;
|
|
4835
5066
|
try {
|
|
4836
|
-
const [fs,
|
|
5067
|
+
const [fs, path8] = await Promise.all([import("fs"), import("path")]);
|
|
4837
5068
|
fsMod = {
|
|
4838
5069
|
readFileSync: fs.readFileSync,
|
|
4839
5070
|
writeFileSync: fs.writeFileSync,
|
|
4840
5071
|
mkdirSync: fs.mkdirSync,
|
|
4841
|
-
join:
|
|
5072
|
+
join: path8.join
|
|
4842
5073
|
};
|
|
4843
5074
|
} catch {
|
|
4844
5075
|
fsMod = null;
|
|
@@ -5132,13 +5363,13 @@ var MAX_CAPTURED_VALUE_CHARS = 512;
|
|
|
5132
5363
|
var MAX_QUERY_KEYS = 10;
|
|
5133
5364
|
var MAX_METHOD_CHARS = 16;
|
|
5134
5365
|
var MAX_BODY_BYTES2 = 64 * 1024;
|
|
5135
|
-
function queryKeysOf(
|
|
5136
|
-
if (typeof
|
|
5137
|
-
const start =
|
|
5366
|
+
function queryKeysOf(path8) {
|
|
5367
|
+
if (typeof path8 !== "string") return { keys: [], total: 0 };
|
|
5368
|
+
const start = path8.indexOf("?");
|
|
5138
5369
|
if (start === -1) return { keys: [], total: 0 };
|
|
5139
5370
|
let names;
|
|
5140
5371
|
try {
|
|
5141
|
-
names = [...new URLSearchParams(
|
|
5372
|
+
names = [...new URLSearchParams(path8.slice(start + 1)).keys()];
|
|
5142
5373
|
} catch {
|
|
5143
5374
|
return { keys: [], total: 0 };
|
|
5144
5375
|
}
|
|
@@ -5277,10 +5508,10 @@ function revisionOf(rule) {
|
|
|
5277
5508
|
if (typeof revision === "number" && Number.isFinite(revision)) return String(revision);
|
|
5278
5509
|
return null;
|
|
5279
5510
|
}
|
|
5280
|
-
function routeOf(
|
|
5281
|
-
if (typeof
|
|
5282
|
-
const cut =
|
|
5283
|
-
return cut === -1 ?
|
|
5511
|
+
function routeOf(path8) {
|
|
5512
|
+
if (typeof path8 !== "string" || path8 === "") return null;
|
|
5513
|
+
const cut = path8.search(/[?#]/);
|
|
5514
|
+
return cut === -1 ? path8 : path8.slice(0, cut);
|
|
5284
5515
|
}
|
|
5285
5516
|
function createDetectionReporter(opts) {
|
|
5286
5517
|
const siteUuid = opts.siteUuid ?? process.env?.PATCHSTACK_SITE_UUID;
|
|
@@ -6116,8 +6347,17 @@ function createServerFnGuard({ protection }) {
|
|
|
6116
6347
|
return { rule: body.rule, message: body.message || "Blocked by Patchstack" };
|
|
6117
6348
|
};
|
|
6118
6349
|
}
|
|
6350
|
+
function listOption(value, fallback, name, onError) {
|
|
6351
|
+
if (value === void 0) return fallback;
|
|
6352
|
+
if (Array.isArray(value)) return value;
|
|
6353
|
+
notify(onError, new Error(`patchstack: \`${name}\` has to be an array; the value configured is ignored`), "onError");
|
|
6354
|
+
return fallback;
|
|
6355
|
+
}
|
|
6119
6356
|
async function createProtection(options = {}) {
|
|
6120
6357
|
const onError = options.onError;
|
|
6358
|
+
const allowHosts = listOption(options.allowHosts, [], "allowHosts", onError);
|
|
6359
|
+
const configuredResponseRules = listOption(options.responseRules, DEFAULT_RESPONSE_RULES, "responseRules", onError);
|
|
6360
|
+
const configuredEgressRules = listOption(options.egressRules, DEFAULT_EGRESS_RULES, "egressRules", onError);
|
|
6121
6361
|
const userOnDetect = options.onDetect ?? defaultOnDetect;
|
|
6122
6362
|
const apiKey = await resolveApiKey(options);
|
|
6123
6363
|
const firewallLog = apiKey && telemetryEnabled() && options.reportFirewallLog !== false ? createFirewallLogReporter({
|
|
@@ -6217,9 +6457,9 @@ async function createProtection(options = {}) {
|
|
|
6217
6457
|
const applyBundle = (delivered) => {
|
|
6218
6458
|
const incoming = delivered.firewall ?? [];
|
|
6219
6459
|
requestRules = byPhase(incoming, "request");
|
|
6220
|
-
responseRules = [...
|
|
6460
|
+
responseRules = [...configuredResponseRules, ...byPhase(incoming, "response")];
|
|
6221
6461
|
screenCap = responseScreenCap(responseRules);
|
|
6222
|
-
egressRules = [...
|
|
6462
|
+
egressRules = [...configuredEgressRules, ...byPhase(incoming, "egress")];
|
|
6223
6463
|
engine = new RuleEngine({
|
|
6224
6464
|
firewall: requestRules,
|
|
6225
6465
|
whitelists: delivered.whitelists,
|
|
@@ -6688,7 +6928,7 @@ async function createProtection(options = {}) {
|
|
|
6688
6928
|
return origEnd(cb);
|
|
6689
6929
|
};
|
|
6690
6930
|
};
|
|
6691
|
-
const allow = new Set(
|
|
6931
|
+
const allow = new Set(allowHosts.map((h) => String(h).toLowerCase()));
|
|
6692
6932
|
const egressShouldBlock = (url, host, method) => {
|
|
6693
6933
|
if (host && allow.has(host.toLowerCase())) return false;
|
|
6694
6934
|
let egressPath = null;
|
|
@@ -6740,10 +6980,17 @@ async function createProtection(options = {}) {
|
|
|
6740
6980
|
return { request: requestRules, response: responseRules, egress: egressRules };
|
|
6741
6981
|
},
|
|
6742
6982
|
/**
|
|
6743
|
-
* Enforcement coverage: how often the guard
|
|
6744
|
-
* `<phase>:<reason>` (e.g. `response:body-cap`, `request:body-cap`,
|
|
6745
|
-
* `egress:resolver-failed`
|
|
6746
|
-
*
|
|
6983
|
+
* Enforcement coverage: how often the guard failed open rather than inspecting, in the cases it can
|
|
6984
|
+
* observe itself — keyed `<phase>:<reason>` (e.g. `response:body-cap`, `request:body-cap`,
|
|
6985
|
+
* `response:live-stream`, `egress:resolver-failed` for a fetch call whose address could not be
|
|
6986
|
+
* resolved). "Always-on" is not "always inspected", so surface this (or pass `onSkip`) rather than
|
|
6987
|
+
* assuming coverage.
|
|
6988
|
+
*
|
|
6989
|
+
* Not a total, in two ways. An outbound call can miss the address check without being counted:
|
|
6990
|
+
* whether it resolved through the screening resolver depends on the agent and the socket pool,
|
|
6991
|
+
* which this guard cannot observe. And a resolver failure on the node path appears nowhere here
|
|
6992
|
+
* because it is not a bypass — that resolver is the connection's, so the call fails instead.
|
|
6993
|
+
* Zero skips means nothing the guard can see was bypassed, not that nothing was.
|
|
6747
6994
|
*/
|
|
6748
6995
|
coverage() {
|
|
6749
6996
|
return { skipped: { ...skipCounts } };
|
|
@@ -6876,7 +7123,7 @@ async function createProtection(options = {}) {
|
|
|
6876
7123
|
// same skip accounting as the request/response phases.
|
|
6877
7124
|
onSkip: ({ reason, detail }) => recordSkip("egress", reason, detail),
|
|
6878
7125
|
dnsScreen: options.screenDns !== false,
|
|
6879
|
-
allowHosts
|
|
7126
|
+
allowHosts
|
|
6880
7127
|
});
|
|
6881
7128
|
}
|
|
6882
7129
|
const cwd = options.cwd ?? (typeof process !== "undefined" ? process.cwd() : void 0);
|
|
@@ -7378,20 +7625,20 @@ function requestMetaFromContext(reqCtx) {
|
|
|
7378
7625
|
}
|
|
7379
7626
|
function requestMeta(shaped, request) {
|
|
7380
7627
|
const client = shaped?._clientIp ?? { ip: null, source: "unavailable" };
|
|
7381
|
-
let
|
|
7628
|
+
let path8 = null;
|
|
7382
7629
|
let method = null;
|
|
7383
7630
|
let userAgent = null;
|
|
7384
7631
|
if (request) {
|
|
7385
7632
|
try {
|
|
7386
7633
|
const u = new URL(request.url);
|
|
7387
|
-
|
|
7634
|
+
path8 = u.pathname + u.search;
|
|
7388
7635
|
} catch {
|
|
7389
|
-
|
|
7636
|
+
path8 = typeof request.url === "string" ? request.url : null;
|
|
7390
7637
|
}
|
|
7391
7638
|
method = request.method ?? null;
|
|
7392
7639
|
userAgent = request.headers?.get?.("user-agent") ?? null;
|
|
7393
7640
|
}
|
|
7394
|
-
return { method, path:
|
|
7641
|
+
return { method, path: path8, ip: client.ip, clientIpSource: client.source, userAgent, event: eventFor(request) };
|
|
7395
7642
|
}
|
|
7396
7643
|
function nodeRequestMeta(req, client) {
|
|
7397
7644
|
if (!req) return {};
|