@validate.qa/runner 1.0.100 → 1.0.102
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/README.md +3 -0
- package/dist/{chunk-MBKGTELT.mjs → chunk-3JM6XTSZ.mjs} +1862 -530
- package/dist/{chunk-2XD63NU5.mjs → chunk-4WLP3RWN.mjs} +941 -86
- package/dist/{chunk-MHFP3QXU.mjs → chunk-5DFBEL72.mjs} +1 -1
- package/dist/{chunk-II7QW7MZ.mjs → chunk-BXLEPT4M.mjs} +31 -18
- package/dist/{chunk-MAO6ADAO.mjs → chunk-CBVHPYVJ.mjs} +62 -2
- package/dist/{chunk-5P4ANVDY.mjs → chunk-DN3O6EYT.mjs} +43073 -23357
- package/dist/{chunk-GIIIVO2W.mjs → chunk-E2RRAE27.mjs} +155 -11
- package/dist/chunk-E2VEMBQQ.mjs +291 -0
- package/dist/{chunk-HMUC3DKS.mjs → chunk-HJ6FKHMM.mjs} +2281 -2
- package/dist/{chunk-WDXLAZ2J.mjs → chunk-PWFWMVZU.mjs} +3 -2
- package/dist/{chunk-6NIT2CCH.mjs → chunk-ZZYBRIBT.mjs} +18 -12
- package/dist/cli.js +139723 -114754
- package/dist/cli.mjs +536 -247
- package/dist/credential-mcp-server.js +1761 -358
- package/dist/credential-mcp-server.mjs +3 -3
- package/dist/discovery-mcp-server.js +1874 -379
- package/dist/discovery-mcp-server.mjs +4 -4
- package/dist/dist-52M5RDZ6.mjs +17 -0
- package/dist/inbox-mcp-server.js +1902 -505
- package/dist/inbox-mcp-server.mjs +3 -3
- package/dist/route-seed-worker.js +3976 -356
- package/dist/route-seed-worker.mjs +14 -7
- package/dist/run-write-guard-preload.js +2908 -572
- package/dist/run-write-guard-preload.mjs +5 -285
- package/dist/run-write-guard.js +2908 -572
- package/dist/run-write-guard.mjs +3 -3
- package/dist/spec-driver.js +1 -1
- package/dist/spec-driver.mjs +1 -1
- package/package.json +4 -3
- package/dist/dist-DFNLMH6L.mjs +0 -16
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dist_exports,
|
|
3
3
|
init_dist
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3JM6XTSZ.mjs";
|
|
5
5
|
import {
|
|
6
6
|
__commonJS,
|
|
7
7
|
__require,
|
|
@@ -21,8 +21,12 @@ var require_survey_network_guard = __commonJS({
|
|
|
21
21
|
exports.maskedPath = maskedPath;
|
|
22
22
|
exports.bodyKeysOf = bodyKeysOf;
|
|
23
23
|
exports.graphqlDocumentIsQueryOnly = graphqlDocumentIsQueryOnly;
|
|
24
|
+
exports.graphqlOperationRoots = graphqlOperationRoots;
|
|
24
25
|
exports.isTokenRefreshWrite = isTokenRefreshWrite;
|
|
25
26
|
exports.isSignInPathWrite = isSignInPathWrite;
|
|
27
|
+
exports.graphqlUrlCarriesQuery = graphqlUrlCarriesQuery;
|
|
28
|
+
exports.graphqlUrlQueriesAreReads = graphqlUrlQueriesAreReads;
|
|
29
|
+
exports.graphqlGetMayWrite = graphqlGetMayWrite;
|
|
26
30
|
exports.writeShapedGet = writeShapedGet;
|
|
27
31
|
exports.decideSurveyRequest = decideSurveyRequest;
|
|
28
32
|
exports.installSurveyNetworkGuard = installSurveyNetworkGuard;
|
|
@@ -299,6 +303,100 @@ var require_survey_network_guard = __commonJS({
|
|
|
299
303
|
}
|
|
300
304
|
return depth === 0 && parens === 0 && objects === 0 && sawOperation;
|
|
301
305
|
}
|
|
306
|
+
function graphqlOperationRoots(text) {
|
|
307
|
+
const tokens = lexGraphql(text);
|
|
308
|
+
if (!tokens)
|
|
309
|
+
return null;
|
|
310
|
+
const ops = [];
|
|
311
|
+
let depth = 0;
|
|
312
|
+
let parens = 0;
|
|
313
|
+
let objects = 0;
|
|
314
|
+
let defStart = true;
|
|
315
|
+
let kind = null;
|
|
316
|
+
let current = null;
|
|
317
|
+
let prev = "";
|
|
318
|
+
for (const tok of tokens) {
|
|
319
|
+
const last = prev;
|
|
320
|
+
prev = tok;
|
|
321
|
+
if (tok === "(") {
|
|
322
|
+
parens++;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (tok === ")") {
|
|
326
|
+
parens--;
|
|
327
|
+
if (parens < 0 || objects !== 0)
|
|
328
|
+
return null;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (parens > 0) {
|
|
332
|
+
if (tok === "{")
|
|
333
|
+
objects++;
|
|
334
|
+
else if (tok === "}") {
|
|
335
|
+
objects--;
|
|
336
|
+
if (objects < 0)
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (tok === "{") {
|
|
342
|
+
if (depth === 0) {
|
|
343
|
+
if (defStart)
|
|
344
|
+
kind = "query";
|
|
345
|
+
else if (kind === null)
|
|
346
|
+
return null;
|
|
347
|
+
current = kind === "fragment" ? null : { kind, roots: [] };
|
|
348
|
+
if (current)
|
|
349
|
+
ops.push(current);
|
|
350
|
+
defStart = false;
|
|
351
|
+
}
|
|
352
|
+
depth++;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (tok === "}") {
|
|
356
|
+
depth--;
|
|
357
|
+
if (depth < 0)
|
|
358
|
+
return null;
|
|
359
|
+
if (depth === 0) {
|
|
360
|
+
defStart = true;
|
|
361
|
+
kind = null;
|
|
362
|
+
current = null;
|
|
363
|
+
}
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (depth === 0) {
|
|
367
|
+
if (!/^[_A-Za-z]/.test(tok)) {
|
|
368
|
+
if (tok === "@")
|
|
369
|
+
continue;
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
if (defStart) {
|
|
373
|
+
if (tok !== "query" && tok !== "mutation" && tok !== "fragment")
|
|
374
|
+
return null;
|
|
375
|
+
kind = tok;
|
|
376
|
+
defStart = false;
|
|
377
|
+
}
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (depth > 1 || !current)
|
|
381
|
+
continue;
|
|
382
|
+
if (/^[_A-Za-z]/.test(tok)) {
|
|
383
|
+
if (last === "@")
|
|
384
|
+
continue;
|
|
385
|
+
if (last === ":") {
|
|
386
|
+
if (current.roots.length === 0)
|
|
387
|
+
return null;
|
|
388
|
+
current.roots[current.roots.length - 1] = tok;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
current.roots.push(tok);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (tok === ":" || tok === "@")
|
|
395
|
+
continue;
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
return depth === 0 && parens === 0 && objects === 0 ? ops : null;
|
|
399
|
+
}
|
|
302
400
|
function isGraphqlQueryOnly(postData) {
|
|
303
401
|
if (!postData || postData.length > MAX_BODY_CHARS)
|
|
304
402
|
return false;
|
|
@@ -336,6 +434,34 @@ var require_survey_network_guard = __commonJS({
|
|
|
336
434
|
"remove"
|
|
337
435
|
]);
|
|
338
436
|
var NOT_A_SESSION_END = (segs) => !segs.some((seg) => SESSION_END_SEGMENTS.has(seg));
|
|
437
|
+
var DESTRUCTIVE_WORDS = /* @__PURE__ */ new Set([
|
|
438
|
+
"delete",
|
|
439
|
+
"remove",
|
|
440
|
+
"destroy",
|
|
441
|
+
"deactivate",
|
|
442
|
+
"terminate",
|
|
443
|
+
"purge",
|
|
444
|
+
"wipe",
|
|
445
|
+
"erase",
|
|
446
|
+
"cancel",
|
|
447
|
+
"close",
|
|
448
|
+
"revoke",
|
|
449
|
+
"unlink",
|
|
450
|
+
"disconnect"
|
|
451
|
+
]);
|
|
452
|
+
var DESTRUCTIVE_PREFIXES = ["delete", "remove", "destroy", "deactivate", "terminate", "purge"];
|
|
453
|
+
function hasDestructiveSegment(rawSegs) {
|
|
454
|
+
return rawSegs.some((raw) => {
|
|
455
|
+
let seg = raw;
|
|
456
|
+
try {
|
|
457
|
+
seg = decodeURIComponent(raw);
|
|
458
|
+
} catch {
|
|
459
|
+
}
|
|
460
|
+
const words = seg.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/);
|
|
461
|
+
return words.some((word) => DESTRUCTIVE_WORDS.has(word) || DESTRUCTIVE_PREFIXES.some((p) => word.startsWith(p)));
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
var rawPathSegments = (pathname) => pathname.split("/").map((seg) => seg.split(";")[0]).filter(Boolean);
|
|
339
465
|
var pathSegments = (pathname) => pathname.toLowerCase().split("/").map((seg) => seg.split(";")[0]).filter(Boolean);
|
|
340
466
|
function isTokenRefreshWrite(method, pathname, recentAction = false) {
|
|
341
467
|
if (method.toUpperCase() !== "POST")
|
|
@@ -352,6 +478,8 @@ var require_survey_network_guard = __commonJS({
|
|
|
352
478
|
var AUTH_PAGE_PATH_RE = /(?:^|\/)(?:log-?in|sign-?in|sign_in|auth|oauth2?|sso|callback)(?:\/|$)/;
|
|
353
479
|
var SIGN_IN_SEGMENT_RE = /^(?:log-?in|sign-?in|sign_in)$/;
|
|
354
480
|
var CALLBACK_ROOT_SEGMENTS = /* @__PURE__ */ new Set(["auth", "oauth", "oauth2", "sso"]);
|
|
481
|
+
var CALLBACK_PROTOCOL_SEGMENTS = /* @__PURE__ */ new Set(["oidc", "openid", "openid-connect", "openidconnect", "saml", "saml2", "oauth", "oauth2"]);
|
|
482
|
+
var API_VERSION_SEGMENT_RE = /^v[0-9]{1,3}$/;
|
|
355
483
|
var PROVIDER_SEGMENT_RE = /^[a-z][a-z0-9_-]{0,39}$/;
|
|
356
484
|
var NOT_A_PROVIDER_SEGMENTS = /* @__PURE__ */ new Set([
|
|
357
485
|
"link",
|
|
@@ -390,14 +518,21 @@ var require_survey_network_guard = __commonJS({
|
|
|
390
518
|
if (!NOT_A_SESSION_END(segs))
|
|
391
519
|
return false;
|
|
392
520
|
if (AUTH_END_PATH_RE.test(`/${segs.join("/")}`))
|
|
393
|
-
return
|
|
521
|
+
return !hasDestructiveSegment(rawPathSegments(pathname).slice(0, -1));
|
|
522
|
+
let top = segs[0] === "api" ? 1 : 0;
|
|
523
|
+
if (top < segs.length && API_VERSION_SEGMENT_RE.test(segs[top]))
|
|
524
|
+
top += 1;
|
|
525
|
+
const below = segs.length - top;
|
|
526
|
+
const isProvider = (seg) => PROVIDER_SEGMENT_RE.test(seg) && !NOT_A_PROVIDER_SEGMENTS.has(seg);
|
|
394
527
|
const provider = segs[segs.length - 1];
|
|
395
|
-
const authRootAtTop =
|
|
396
|
-
if (segs.length >= 3 && authRootAtTop && CALLBACK_ROOT_SEGMENTS.has(segs[segs.length - 3]) &&
|
|
528
|
+
const authRootAtTop = below === 3 || segs[segs.length - 4] === "api" && segs[segs.length - 3] === "auth";
|
|
529
|
+
if (segs.length >= 3 && authRootAtTop && CALLBACK_ROOT_SEGMENTS.has(segs[segs.length - 3]) && isProvider(provider)) {
|
|
397
530
|
const step = segs[segs.length - 2];
|
|
398
531
|
if (step === "callback" || step === "signin")
|
|
399
532
|
return true;
|
|
400
533
|
}
|
|
534
|
+
if (below === 4 && CALLBACK_ROOT_SEGMENTS.has(segs[top]) && CALLBACK_PROTOCOL_SEGMENTS.has(segs[top + 1]) && isProvider(segs[top + 2]) && segs[top + 3] === "callback")
|
|
535
|
+
return true;
|
|
401
536
|
if (segs.length < 2 || segs[segs.length - 1] !== "callback")
|
|
402
537
|
return false;
|
|
403
538
|
const parent = segs[segs.length - 2];
|
|
@@ -424,7 +559,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
424
559
|
try {
|
|
425
560
|
const a = new URL(url);
|
|
426
561
|
const b = new URL(appBaseUrl);
|
|
427
|
-
const path = (u) => u.pathname
|
|
562
|
+
const path = (u) => (0, shared_1.stripTrailingSlashes)(u.pathname);
|
|
428
563
|
return a.origin === b.origin && path(a) === path(b);
|
|
429
564
|
} catch {
|
|
430
565
|
return false;
|
|
@@ -446,9 +581,18 @@ var require_survey_network_guard = __commonJS({
|
|
|
446
581
|
const bodyHash = (0, node_crypto_1.createHash)("sha256").update(req.postData ?? "").digest("hex").slice(0, 16);
|
|
447
582
|
return `${record.method} ${url.hostname.toLowerCase()}${url.pathname} ${bodyHash}`;
|
|
448
583
|
}
|
|
449
|
-
function
|
|
450
|
-
|
|
451
|
-
|
|
584
|
+
function graphqlUrlCarriesQuery(params) {
|
|
585
|
+
return params.has("query");
|
|
586
|
+
}
|
|
587
|
+
function graphqlUrlQueriesAreReads(params) {
|
|
588
|
+
return params.getAll("query").every((doc) => doc.trim() !== "" && graphqlDocumentIsQueryOnly(doc));
|
|
589
|
+
}
|
|
590
|
+
function graphqlGetMayWrite(url, body) {
|
|
591
|
+
const docs = url.searchParams.getAll("query");
|
|
592
|
+
return docs.length > 0 && (typeof body === "string" && body.length > 0 || docs.some((doc) => !graphqlDocumentIsQueryOnly(doc)));
|
|
593
|
+
}
|
|
594
|
+
function isGraphqlGetWrite(url, s, body) {
|
|
595
|
+
return isGraphqlEndpoint(url, s) && graphqlGetMayWrite(url, body);
|
|
452
596
|
}
|
|
453
597
|
exports.WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
454
598
|
"cancel",
|
|
@@ -652,7 +796,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
652
796
|
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
653
797
|
return { action: "allow", rule: readMethod ? "read" : "non-http" };
|
|
654
798
|
const appSite = isAppSite(url.hostname, s.appBaseUrl);
|
|
655
|
-
const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s);
|
|
799
|
+
const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s, req.postData);
|
|
656
800
|
const inActionWindow = s.lastActionAt > 0 && s.now - s.lastActionAt <= ACTION_NAVIGATION_WINDOW_MS;
|
|
657
801
|
const typedLoad = s.agentLoad === true && req.resourceType === "document";
|
|
658
802
|
const writeShaped = method === "GET" && appSite && !graphqlGet && (inActionWindow || typedLoad) ? writeShapedGet(req.url, {
|
|
@@ -686,7 +830,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
686
830
|
return { action: "allow", rule: "extra-pattern", reason: extra.reason, record };
|
|
687
831
|
if (graphqlGet)
|
|
688
832
|
return { action: "block", rule: "app-write", record };
|
|
689
|
-
if (method === "POST" && isGraphqlEndpoint(url, s) && isGraphqlQueryOnly(req.postData))
|
|
833
|
+
if (method === "POST" && isGraphqlEndpoint(url, s) && graphqlUrlQueriesAreReads(url.searchParams) && isGraphqlQueryOnly(req.postData))
|
|
690
834
|
return { action: "allow", rule: "graphql-query", record };
|
|
691
835
|
const passive = s.lastNavigationAt > 0 && (s.lastActionAt === 0 || s.lastNavigationAt >= s.lastActionAt) && s.now - s.lastNavigationAt <= s.passiveWindowMs;
|
|
692
836
|
const signature = learnableSignature(req, url, record, s);
|
|
@@ -718,7 +862,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
718
862
|
const re = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
|
|
719
863
|
return { reason, test: (hostPath) => re.test(hostPath) };
|
|
720
864
|
}
|
|
721
|
-
const p = pattern.trim().toLowerCase()
|
|
865
|
+
const p = (0, shared_1.stripTrailingSlashes)(pattern.trim().toLowerCase());
|
|
722
866
|
if (p.includes("/"))
|
|
723
867
|
return { reason, test: (hostPath) => hostPath.toLowerCase() === p || hostPath.toLowerCase().startsWith(`${p}/`) };
|
|
724
868
|
return { reason, test: (hostPath) => {
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import {
|
|
2
|
+
require_run_write_guard
|
|
3
|
+
} from "./chunk-4WLP3RWN.mjs";
|
|
4
|
+
import {
|
|
5
|
+
require_survey_network_guard
|
|
6
|
+
} from "./chunk-E2RRAE27.mjs";
|
|
7
|
+
import {
|
|
8
|
+
__commonJS,
|
|
9
|
+
__require
|
|
10
|
+
} from "./chunk-WQXBGQU4.mjs";
|
|
11
|
+
|
|
12
|
+
// ../runner-core/dist/services/run-write-guard-preload.js
|
|
13
|
+
var require_run_write_guard_preload = __commonJS({
|
|
14
|
+
"../runner-core/dist/services/run-write-guard-preload.js"(exports) {
|
|
15
|
+
"use strict";
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.VERIFIED_PLAYWRIGHT_CORE = exports.installRunWriteGuard = exports.installNodeFetchGuard = exports.clearRunWriteRelay = exports.addRunWriteBrokerToken = exports.addRunWriteAuthHandler = void 0;
|
|
18
|
+
exports.describeBuiltRequest = describeBuiltRequest;
|
|
19
|
+
exports.installHttpGuard = installHttpGuard;
|
|
20
|
+
exports.installPlaywrightContextGuard = installPlaywrightContextGuard;
|
|
21
|
+
exports.startRunWriteGuardPreload = startRunWriteGuardPreload;
|
|
22
|
+
var node_module_1 = __require("module");
|
|
23
|
+
var node_path_1 = __require("path");
|
|
24
|
+
var survey_network_guard_js_1 = require_survey_network_guard();
|
|
25
|
+
var run_write_guard_js_1 = require_run_write_guard();
|
|
26
|
+
var run_write_guard_js_2 = require_run_write_guard();
|
|
27
|
+
Object.defineProperty(exports, "addRunWriteAuthHandler", { enumerable: true, get: function() {
|
|
28
|
+
return run_write_guard_js_2.addRunWriteAuthHandler;
|
|
29
|
+
} });
|
|
30
|
+
Object.defineProperty(exports, "addRunWriteBrokerToken", { enumerable: true, get: function() {
|
|
31
|
+
return run_write_guard_js_2.addRunWriteBrokerToken;
|
|
32
|
+
} });
|
|
33
|
+
Object.defineProperty(exports, "clearRunWriteRelay", { enumerable: true, get: function() {
|
|
34
|
+
return run_write_guard_js_2.clearRunWriteRelay;
|
|
35
|
+
} });
|
|
36
|
+
Object.defineProperty(exports, "installNodeFetchGuard", { enumerable: true, get: function() {
|
|
37
|
+
return run_write_guard_js_2.installNodeFetchGuard;
|
|
38
|
+
} });
|
|
39
|
+
Object.defineProperty(exports, "installRunWriteGuard", { enumerable: true, get: function() {
|
|
40
|
+
return run_write_guard_js_2.installRunWriteGuard;
|
|
41
|
+
} });
|
|
42
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
43
|
+
var MAX_DECIDED_BODY_BYTES = 1024 * 1024;
|
|
44
|
+
function describeBuiltRequest(req) {
|
|
45
|
+
const method = typeof req.method === "string" && req.method ? req.method.toUpperCase() : "GET";
|
|
46
|
+
const protocol = req.protocol === "https:" ? "https:" : "http:";
|
|
47
|
+
const path = typeof req.path === "string" && req.path.startsWith("/") ? req.path : null;
|
|
48
|
+
if (path === null)
|
|
49
|
+
throw new Error("unusable request path");
|
|
50
|
+
if (typeof req.socketPath === "string" && req.socketPath) {
|
|
51
|
+
return { method, url: `${protocol}//unix-socket.invalid${path}` };
|
|
52
|
+
}
|
|
53
|
+
if (typeof req.host !== "string" || !req.host)
|
|
54
|
+
throw new Error("unusable request host");
|
|
55
|
+
const host = req.host.includes(":") && !req.host.startsWith("[") ? `[${req.host}]` : req.host;
|
|
56
|
+
return { method, url: new URL(`${protocol}//${host}${path}`).href };
|
|
57
|
+
}
|
|
58
|
+
function headerString(value) {
|
|
59
|
+
if (typeof value === "string")
|
|
60
|
+
return value;
|
|
61
|
+
if (Array.isArray(value))
|
|
62
|
+
return value.map(String).join(", ");
|
|
63
|
+
if (typeof value === "number")
|
|
64
|
+
return String(value);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
function installHttpGuard(policy, opts, mod) {
|
|
68
|
+
if (policy.mode === "off")
|
|
69
|
+
return void 0;
|
|
70
|
+
const original = mod.request;
|
|
71
|
+
if (typeof original !== "function" || original.__vqaWriteGuarded)
|
|
72
|
+
return void 0;
|
|
73
|
+
const guard = new run_write_guard_js_1.RunWriteGuard(policy, opts);
|
|
74
|
+
const wrapped = function guardedRequest(...args) {
|
|
75
|
+
const req = original.apply(this, args);
|
|
76
|
+
const fields = req;
|
|
77
|
+
const connectedTo = { host: fields.host, socketPath: fields.socketPath, protocol: fields.protocol };
|
|
78
|
+
const chunks = [];
|
|
79
|
+
let bytes = 0;
|
|
80
|
+
let state = "pending";
|
|
81
|
+
const held = () => state === "held";
|
|
82
|
+
const originalWrite = req.write.bind(req);
|
|
83
|
+
const originalEnd = req.end.bind(req);
|
|
84
|
+
const originalFlushHeaders = req.flushHeaders.bind(req);
|
|
85
|
+
const take = (chunk, encoding) => {
|
|
86
|
+
if (chunk === void 0 || chunk === null || typeof chunk === "function")
|
|
87
|
+
return;
|
|
88
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : chunk instanceof Uint8Array ? Buffer.from(chunk) : Buffer.from(String(chunk), typeof encoding === "string" ? encoding : "utf8");
|
|
89
|
+
chunks.push(buf);
|
|
90
|
+
bytes += buf.length;
|
|
91
|
+
};
|
|
92
|
+
const decide = (withBody) => {
|
|
93
|
+
const builtLine = typeof fields._header === "string" ? /^([A-Z]+) (\S+) HTTP\//.exec(fields._header) : null;
|
|
94
|
+
let described = null;
|
|
95
|
+
try {
|
|
96
|
+
described = describeBuiltRequest({
|
|
97
|
+
...connectedTo,
|
|
98
|
+
method: builtLine ? builtLine[1] : fields.method,
|
|
99
|
+
path: builtLine ? builtLine[2] : fields.path
|
|
100
|
+
});
|
|
101
|
+
} catch {
|
|
102
|
+
described = null;
|
|
103
|
+
}
|
|
104
|
+
const moved = fields.host !== connectedTo.host || fields.socketPath !== connectedTo.socketPath || fields.protocol !== connectedTo.protocol;
|
|
105
|
+
let decision;
|
|
106
|
+
try {
|
|
107
|
+
if (!described || moved || typeof fields._header === "string" && !builtLine)
|
|
108
|
+
throw new Error("undescribable request");
|
|
109
|
+
const host = new URL(described.url).hostname.toLowerCase();
|
|
110
|
+
decision = LOOPBACK_HOSTS.has(host) && !policy.appSites.includes(host) ? { action: "allow", rule: "non-http" } : guard.decide({
|
|
111
|
+
method: described.method,
|
|
112
|
+
url: described.url,
|
|
113
|
+
postData: withBody ? Buffer.concat(chunks).toString("utf8") : null,
|
|
114
|
+
contentType: headerString(req.getHeader("content-type")),
|
|
115
|
+
amzTarget: headerString(req.getHeader("x-amz-target"))
|
|
116
|
+
});
|
|
117
|
+
} catch {
|
|
118
|
+
decision = {
|
|
119
|
+
action: "write",
|
|
120
|
+
rule: "decision-error",
|
|
121
|
+
record: { method: described?.method ?? "REQUEST", host: "", path: described ? (0, survey_network_guard_js_1.maskedPath)(described.url) : "(undescribable)", keys: [], rule: "decision-error" }
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (decision.action === "write" && decision.record) {
|
|
125
|
+
guard.note(decision.record);
|
|
126
|
+
if (guard.holds(decision.rule)) {
|
|
127
|
+
state = "held";
|
|
128
|
+
chunks.length = 0;
|
|
129
|
+
req.destroy(new Error(`blocked by the run write guard: ${decision.record.method} ${decision.record.host}${decision.record.path}`));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
state = "allowed";
|
|
134
|
+
for (const buf of chunks.splice(0))
|
|
135
|
+
originalWrite(buf);
|
|
136
|
+
};
|
|
137
|
+
req.write = (chunk, encoding, cb) => {
|
|
138
|
+
const done = typeof encoding === "function" ? encoding : cb;
|
|
139
|
+
if (state === "allowed")
|
|
140
|
+
return originalWrite(chunk, encoding, cb);
|
|
141
|
+
if (state === "pending") {
|
|
142
|
+
take(chunk, encoding);
|
|
143
|
+
if (bytes > MAX_DECIDED_BODY_BYTES)
|
|
144
|
+
decide(false);
|
|
145
|
+
}
|
|
146
|
+
if (held()) {
|
|
147
|
+
if (done)
|
|
148
|
+
process.nextTick(done, new Error("blocked by the run write guard"));
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (done)
|
|
152
|
+
process.nextTick(done);
|
|
153
|
+
return true;
|
|
154
|
+
};
|
|
155
|
+
req.flushHeaders = () => {
|
|
156
|
+
if (state === "allowed")
|
|
157
|
+
originalFlushHeaders();
|
|
158
|
+
};
|
|
159
|
+
req.end = (chunk, encoding, cb) => {
|
|
160
|
+
if (state === "allowed")
|
|
161
|
+
return originalEnd(chunk, encoding, cb);
|
|
162
|
+
if (state === "held")
|
|
163
|
+
return req;
|
|
164
|
+
const done = [chunk, encoding, cb].find((a) => typeof a === "function");
|
|
165
|
+
if (typeof chunk !== "function")
|
|
166
|
+
take(chunk, typeof encoding === "function" ? void 0 : encoding);
|
|
167
|
+
decide(bytes <= MAX_DECIDED_BODY_BYTES);
|
|
168
|
+
if (held())
|
|
169
|
+
return req;
|
|
170
|
+
return originalEnd(done);
|
|
171
|
+
};
|
|
172
|
+
return req;
|
|
173
|
+
};
|
|
174
|
+
Object.defineProperty(wrapped, "__vqaWriteGuarded", { value: true });
|
|
175
|
+
const wrappedGet = function guardedGet(...args) {
|
|
176
|
+
const req = wrapped.apply(this, args);
|
|
177
|
+
req.end();
|
|
178
|
+
return req;
|
|
179
|
+
};
|
|
180
|
+
Object.defineProperty(wrappedGet, "__vqaWriteGuarded", { value: true });
|
|
181
|
+
const install = (name, value) => {
|
|
182
|
+
if (policy.mode === "block") {
|
|
183
|
+
Object.defineProperty(mod, name, { value, writable: false, configurable: false, enumerable: true });
|
|
184
|
+
} else {
|
|
185
|
+
mod[name] = value;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
install("request", wrapped);
|
|
189
|
+
if (typeof mod.get === "function")
|
|
190
|
+
install("get", wrappedGet);
|
|
191
|
+
return guard;
|
|
192
|
+
}
|
|
193
|
+
exports.VERIFIED_PLAYWRIGHT_CORE = /^1\.64\./;
|
|
194
|
+
function installPlaywrightContextGuard(policy, opts, from = __filename) {
|
|
195
|
+
if (policy.mode === "off")
|
|
196
|
+
return true;
|
|
197
|
+
let pw;
|
|
198
|
+
let version = "";
|
|
199
|
+
try {
|
|
200
|
+
const req = (0, node_module_1.createRequire)(from);
|
|
201
|
+
let base = from;
|
|
202
|
+
try {
|
|
203
|
+
base = (0, node_path_1.dirname)(req.resolve("@playwright/test/package.json"));
|
|
204
|
+
} catch {
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
base = (0, node_path_1.dirname)((0, node_module_1.createRequire)(`${base}/`).resolve("playwright/package.json"));
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
const fromPlaywright = (0, node_module_1.createRequire)(`${base}/`);
|
|
211
|
+
version = String(fromPlaywright("playwright-core/package.json").version ?? "");
|
|
212
|
+
pw = fromPlaywright("playwright-core");
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
if (!exports.VERIFIED_PLAYWRIGHT_CORE.test(version)) {
|
|
217
|
+
if (policy.mode === "block")
|
|
218
|
+
return false;
|
|
219
|
+
opts.onLog?.(`[run-write-guard] observe: playwright-core ${version || "(unknown)"} is not a version the context hook was verified on`);
|
|
220
|
+
}
|
|
221
|
+
const add = pw?._instrumentation?.addListener;
|
|
222
|
+
if (typeof add !== "function")
|
|
223
|
+
return false;
|
|
224
|
+
add.call(pw._instrumentation, {
|
|
225
|
+
// Block mode: the context's service workers are off (design §4), whatever the test asked for.
|
|
226
|
+
// Playwright passes the options object it builds the context from (browser.newContext,
|
|
227
|
+
// launchPersistentContext); a context whose options cannot be set is still routed below.
|
|
228
|
+
runBeforeCreateBrowserContext: async (options) => {
|
|
229
|
+
if (options && typeof options === "object")
|
|
230
|
+
Object.assign(options, (0, run_write_guard_js_1.runWriteContextOptions)(policy));
|
|
231
|
+
},
|
|
232
|
+
runAfterCreateBrowserContext: async (context) => {
|
|
233
|
+
await (0, run_write_guard_js_1.installRunWriteGuard)(context, policy, opts);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
function startRunWriteGuardPreload(env = process.env) {
|
|
239
|
+
const raw = env[run_write_guard_js_1.RUN_WRITE_POLICY_ENV];
|
|
240
|
+
if (!raw)
|
|
241
|
+
return [];
|
|
242
|
+
let policy = null;
|
|
243
|
+
try {
|
|
244
|
+
policy = (0, run_write_guard_js_1.parseRunWritePolicy)(JSON.parse(raw));
|
|
245
|
+
} catch {
|
|
246
|
+
policy = null;
|
|
247
|
+
}
|
|
248
|
+
if (!policy) {
|
|
249
|
+
console.log(`${run_write_guard_js_1.RUN_WRITE_UNAVAILABLE_LINE}the run write policy could not be read`);
|
|
250
|
+
return ["policy"];
|
|
251
|
+
}
|
|
252
|
+
if (policy.mode === "off")
|
|
253
|
+
return [];
|
|
254
|
+
const names = (env[run_write_guard_js_1.RUN_WRITE_SIGNIN_VARS_ENV] ?? "").split(",").map((n) => n.trim()).filter((n) => /^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(n)).slice(0, 16);
|
|
255
|
+
const signInSecrets = names.map((n) => env[n]).filter((v) => typeof v === "string");
|
|
256
|
+
const opts = { onLog: (line) => console.log(line), signInSecrets };
|
|
257
|
+
const missing = [];
|
|
258
|
+
try {
|
|
259
|
+
(0, run_write_guard_js_1.installNodeFetchGuard)(policy, opts);
|
|
260
|
+
} catch {
|
|
261
|
+
missing.push("fetch");
|
|
262
|
+
}
|
|
263
|
+
const nodeRequire = (0, node_module_1.createRequire)(__filename);
|
|
264
|
+
try {
|
|
265
|
+
installHttpGuard(policy, opts, nodeRequire("node:http"));
|
|
266
|
+
} catch {
|
|
267
|
+
missing.push("http");
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
installHttpGuard(policy, opts, nodeRequire("node:https"));
|
|
271
|
+
} catch {
|
|
272
|
+
missing.push("https");
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
(0, node_module_1.syncBuiltinESMExports)();
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
if (!installPlaywrightContextGuard(policy, opts))
|
|
279
|
+
missing.push("browser contexts");
|
|
280
|
+
for (const piece of missing)
|
|
281
|
+
console.log(`${run_write_guard_js_1.RUN_WRITE_UNAVAILABLE_LINE}the preload could not guard ${piece}`);
|
|
282
|
+
return missing;
|
|
283
|
+
}
|
|
284
|
+
if (process.env[run_write_guard_js_1.RUN_WRITE_POLICY_ENV])
|
|
285
|
+
startRunWriteGuardPreload();
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
export {
|
|
290
|
+
require_run_write_guard_preload
|
|
291
|
+
};
|