@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,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
require_survey_network_guard
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-E2RRAE27.mjs";
|
|
4
4
|
import {
|
|
5
5
|
dist_exports,
|
|
6
6
|
init_dist
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-3JM6XTSZ.mjs";
|
|
8
8
|
import {
|
|
9
9
|
__commonJS,
|
|
10
10
|
__require,
|
|
@@ -348,7 +348,7 @@ var require_run_write_guard = __commonJS({
|
|
|
348
348
|
"../runner-core/dist/services/run-write-guard.js"(exports) {
|
|
349
349
|
"use strict";
|
|
350
350
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
351
|
-
exports.RUN_WRITE_RELAYED_LINE = exports.WRITE_GUARD_UNAVAILABLE_ERROR_PREFIX = exports.WRITE_HELD_ERROR_PREFIX = exports.RUN_WRITE_UNAVAILABLE_LINE = exports.RUN_WRITE_SAW_LINE = exports.RUN_WRITE_HELD_LINE = exports.RUN_WRITE_GUARD_LINE_PREFIX = exports.RUN_WRITE_SIGNIN_VARS_ENV = exports.RUN_WRITE_POLICY_ENV = exports.NO_KEEPALIVE_INIT_SCRIPT = exports.RUN_WRITE_LEAVING_EVENT = exports.RUN_WRITE_RELAY_KEY = exports.RUN_WRITE_DROPPED_BINDING = exports.RUN_WRITE_BLOCK_CONTEXT_OPTIONS = exports.WEBSOCKET_NOT_GUARDED = exports.RunWriteGuard = exports.BROKER_TOKEN_TTL_MS = exports.MAX_RUN_WRITE_RECORDS = exports.SHARED_SUFFIX_PATTERN_REASON = exports.FIREBASE_AUTH_HANDLER_PATH = exports.SIGN_IN_SECRET_VARIABLE = exports.MAX_ALLOW_PATTERN_CHARS = exports.MAX_ALLOW_PATTERNS = exports.DEFAULT_RECORD_ONLY_RULES = exports.RECORD_ONLY_CAPABLE_RULES = exports.RUN_WRITE_POLICY_VERSION = void 0;
|
|
351
|
+
exports.RUN_WRITE_RELAYED_LINE = exports.WRITE_GUARD_UNAVAILABLE_ERROR_PREFIX = exports.WRITE_HELD_ERROR_PREFIX = exports.RUN_WRITE_UNAVAILABLE_LINE = exports.RUN_WRITE_SAW_LINE = exports.RUN_WRITE_HELD_LINE = exports.RUN_WRITE_GUARD_LINE_PREFIX = exports.RUN_WRITE_SIGNIN_VARS_ENV = exports.RUN_WRITE_POLICY_ENV = exports.NO_KEEPALIVE_INIT_SCRIPT = exports.RUN_WRITE_LEAVING_EVENT = exports.RUN_WRITE_RELAY_KEY = exports.RUN_WRITE_DROPPED_BINDING = exports.RUN_WRITE_BLOCK_CONTEXT_OPTIONS = exports.WEBSOCKET_NOT_GUARDED = exports.RunWriteGuard = exports.BROKER_TOKEN_TTL_MS = exports.MAX_RUN_WRITE_RECORDS = exports.SHARED_SUFFIX_PATTERN_REASON = exports.FIREBASE_AUTH_HANDLER_PATH = exports.SIGN_IN_SECRET_VARIABLE = exports.MAX_ALLOW_PATTERN_CHARS = exports.MAX_ALLOW_PATTERNS = exports.MAX_IDP_HOSTS = exports.DEFAULT_RECORD_ONLY_RULES = exports.RECORD_ONLY_CAPABLE_RULES = exports.RUN_WRITE_POLICY_VERSION = void 0;
|
|
352
352
|
exports.parseRunWritePolicy = parseRunWritePolicy;
|
|
353
353
|
exports.signInSecretsOf = signInSecretsOf;
|
|
354
354
|
exports.withRunWritePolicyScope = withRunWritePolicyScope;
|
|
@@ -357,6 +357,8 @@ var require_run_write_guard = __commonJS({
|
|
|
357
357
|
exports.bodyHasSignInSecret = bodyHasSignInSecret;
|
|
358
358
|
exports.compileAllowPattern = compileAllowPattern;
|
|
359
359
|
exports.allowPatternProblem = allowPatternProblem;
|
|
360
|
+
exports.normaliseIdpHost = normaliseIdpHost;
|
|
361
|
+
exports.idpHostProblem = idpHostProblem;
|
|
360
362
|
exports.runWritePatternNotes = runWritePatternNotes;
|
|
361
363
|
exports.decideRunWrite = decideRunWrite;
|
|
362
364
|
exports.isGoogleBrokerToken = isGoogleBrokerToken;
|
|
@@ -365,6 +367,7 @@ var require_run_write_guard = __commonJS({
|
|
|
365
367
|
exports.addRunWriteAuthHandler = addRunWriteAuthHandler;
|
|
366
368
|
exports.runWriteContextOptions = runWriteContextOptions;
|
|
367
369
|
exports.clearRunWriteRelay = clearRunWriteRelay;
|
|
370
|
+
exports.runWriteGuardHopBytesInFlight = runWriteGuardHopBytesInFlight;
|
|
368
371
|
exports.installRunWriteGuard = installRunWriteGuard;
|
|
369
372
|
exports.installNodeFetchGuard = installNodeFetchGuard;
|
|
370
373
|
exports.runWriteGuardRefusal = runWriteGuardRefusal;
|
|
@@ -376,7 +379,11 @@ var require_run_write_guard = __commonJS({
|
|
|
376
379
|
exports.safeWritePatternFor = safeWritePatternFor;
|
|
377
380
|
var node_async_hooks_1 = __require("async_hooks");
|
|
378
381
|
var node_crypto_1 = __require("crypto");
|
|
382
|
+
var node_http_1 = __require("http");
|
|
383
|
+
var node_https_1 = __require("https");
|
|
379
384
|
var node_path_1 = __require("path");
|
|
385
|
+
var node_stream_1 = __require("stream");
|
|
386
|
+
var node_zlib_1 = __require("zlib");
|
|
380
387
|
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
381
388
|
var survey_network_guard_js_1 = require_survey_network_guard();
|
|
382
389
|
var bounded_output_js_1 = require_bounded_output();
|
|
@@ -384,6 +391,8 @@ var require_run_write_guard = __commonJS({
|
|
|
384
391
|
exports.RECORD_ONLY_CAPABLE_RULES = ["write-shaped-get"];
|
|
385
392
|
exports.DEFAULT_RECORD_ONLY_RULES = ["write-shaped-get"];
|
|
386
393
|
var MAX_APP_SITES = 16;
|
|
394
|
+
exports.MAX_IDP_HOSTS = 4;
|
|
395
|
+
var MAX_IDP_HINTS = 4;
|
|
387
396
|
exports.MAX_ALLOW_PATTERNS = 50;
|
|
388
397
|
exports.MAX_ALLOW_PATTERN_CHARS = 200;
|
|
389
398
|
var MAX_REASON_CHARS = 300;
|
|
@@ -411,6 +420,11 @@ var require_run_write_guard = __commonJS({
|
|
|
411
420
|
const e = x;
|
|
412
421
|
return e && typeof e.pattern === "string" && typeof e.reason === "string" ? [{ pattern: e.pattern.slice(0, exports.MAX_ALLOW_PATTERN_CHARS), reason: e.reason.slice(0, MAX_REASON_CHARS) }] : [];
|
|
413
422
|
}) : [];
|
|
423
|
+
const idpHosts = Array.isArray(v.idpHosts) ? [...new Set(v.idpHosts.filter((h) => typeof h === "string" && normaliseIdpHost(h) === h && idpHostProblem(h) === null))].slice(0, exports.MAX_IDP_HOSTS) : [];
|
|
424
|
+
const ignoredIdpHosts = Array.isArray(v.ignoredIdpHosts) ? v.ignoredIdpHosts.slice(0, exports.MAX_ALLOW_PATTERNS).flatMap((x) => {
|
|
425
|
+
const e = x;
|
|
426
|
+
return e && typeof e.pattern === "string" && typeof e.reason === "string" ? [{ pattern: e.pattern.slice(0, exports.MAX_ALLOW_PATTERN_CHARS), reason: e.reason.slice(0, MAX_REASON_CHARS) }] : [];
|
|
427
|
+
}) : [];
|
|
414
428
|
const recordOnlyRules = Array.isArray(v.recordOnlyRules) ? exports.RECORD_ONLY_CAPABLE_RULES.filter((rule) => v.recordOnlyRules.includes(rule)) : [...exports.DEFAULT_RECORD_ONLY_RULES];
|
|
415
429
|
if (typeof v.reason !== "string")
|
|
416
430
|
return null;
|
|
@@ -423,13 +437,17 @@ var require_run_write_guard = __commonJS({
|
|
|
423
437
|
appSites: appSites.map((s) => s.toLowerCase()),
|
|
424
438
|
allowPatterns,
|
|
425
439
|
ignoredAllowPatterns,
|
|
440
|
+
// Only when set: a policy without them parses exactly as before.
|
|
441
|
+
...idpHosts.length > 0 ? { idpHosts } : {},
|
|
442
|
+
...ignoredIdpHosts.length > 0 ? { ignoredIdpHosts } : {},
|
|
426
443
|
recordOnlyRules,
|
|
427
|
-
reason: v.reason.slice(0, MAX_REASON_CHARS)
|
|
444
|
+
reason: v.reason.slice(0, MAX_REASON_CHARS),
|
|
445
|
+
...v.pwAgentsGuard === "off" ? { pwAgentsGuard: "off" } : {}
|
|
428
446
|
};
|
|
429
447
|
}
|
|
430
448
|
var policyScope = new node_async_hooks_1.AsyncLocalStorage();
|
|
431
449
|
var MIN_SIGN_IN_SECRET_CHARS = 6;
|
|
432
|
-
exports.SIGN_IN_SECRET_VARIABLE = /PASS(?:WORD|CODE|PHRASE)
|
|
450
|
+
exports.SIGN_IN_SECRET_VARIABLE = /(?:PASS(?:WORD|CODE|PHRASE)?|[A-Z0-9]_PASSWD|[A-Z0-9]_PWD)$/i;
|
|
433
451
|
function signInSecretsOf(vars) {
|
|
434
452
|
if (!vars || typeof vars !== "object")
|
|
435
453
|
return [];
|
|
@@ -446,6 +464,7 @@ var require_run_write_guard = __commonJS({
|
|
|
446
464
|
}
|
|
447
465
|
var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
448
466
|
var MAX_BODY_CHARS = 262144;
|
|
467
|
+
var MAX_SIGN_IN_LOG_LINES = 20;
|
|
449
468
|
var CAPTCHA_HOSTS = ["recaptcha.net", "hcaptcha.com", "challenges.cloudflare.com"];
|
|
450
469
|
var SEARCH_POST_HOSTS = ["algolia.net", "algolianet.com", "algolia.io"];
|
|
451
470
|
var PAYMENT_HOSTS = [
|
|
@@ -471,7 +490,7 @@ var require_run_write_guard = __commonJS({
|
|
|
471
490
|
var SEARCH_LAST_SEGMENTS = /* @__PURE__ */ new Set(["search", "autocomplete", "suggest", "suggestions", "typeahead"]);
|
|
472
491
|
var PROVIDER_SIGN_UP_PATH = /(?:\/accounts:signup|\/dbconnections\/signup|\/auth\/v1\/signup|\/v1\/client\/sign_ups)(?:[/?]|$)/i;
|
|
473
492
|
var FIRST_PARTY_SIGN_UP_SEGMENTS = /* @__PURE__ */ new Set(["signup", "sign-up", "sign_up", "register", "registration", "create-account", "createaccount", "sign_ups"]);
|
|
474
|
-
var
|
|
493
|
+
var NOT_A_SIGN_IN_WORDS = /* @__PURE__ */ new Set([
|
|
475
494
|
"delete",
|
|
476
495
|
"destroy",
|
|
477
496
|
"remove",
|
|
@@ -480,19 +499,29 @@ var require_run_write_guard = __commonJS({
|
|
|
480
499
|
"cancel",
|
|
481
500
|
"password",
|
|
482
501
|
"passwords",
|
|
483
|
-
"change-password",
|
|
484
|
-
"change_password",
|
|
485
502
|
"reset",
|
|
486
|
-
"reset-password",
|
|
487
503
|
"update",
|
|
488
504
|
"logout",
|
|
489
505
|
"signout",
|
|
490
|
-
"sign-out",
|
|
491
506
|
"revoke",
|
|
492
507
|
"transfer",
|
|
493
|
-
"withdraw"
|
|
508
|
+
"withdraw",
|
|
509
|
+
"disable",
|
|
510
|
+
"enable",
|
|
511
|
+
"unlink",
|
|
512
|
+
"change",
|
|
513
|
+
"erase",
|
|
514
|
+
"purge",
|
|
515
|
+
"wipe",
|
|
516
|
+
"terminate",
|
|
517
|
+
"suspend",
|
|
518
|
+
"leave",
|
|
519
|
+
"disconnect",
|
|
520
|
+
"deauthorize"
|
|
494
521
|
]);
|
|
495
|
-
var
|
|
522
|
+
var ACCOUNT_CHANGE_WORDS = /* @__PURE__ */ new Set(["email", "mfa", "2fa", "totp", "otp"]);
|
|
523
|
+
var ACCOUNT_CHANGE_CONTEXT = /* @__PURE__ */ new Set(["account", "owner", "ownership", "invite"]);
|
|
524
|
+
var SIGN_IN_LAST_SEGMENTS = /* @__PURE__ */ new Set([
|
|
496
525
|
"session",
|
|
497
526
|
"sessions",
|
|
498
527
|
"login",
|
|
@@ -502,25 +531,511 @@ var require_run_write_guard = __commonJS({
|
|
|
502
531
|
"signin",
|
|
503
532
|
"sign-in",
|
|
504
533
|
"sign_in",
|
|
505
|
-
"auth",
|
|
506
534
|
"authenticate",
|
|
507
535
|
"authentication",
|
|
536
|
+
"auth"
|
|
537
|
+
]);
|
|
538
|
+
var SIGN_IN_VERB_SEGMENTS = /* @__PURE__ */ new Set(["login", "log-in", "log_in", "signin", "sign-in", "sign_in"]);
|
|
539
|
+
var SIGN_IN_CREDENTIAL_KINDS = /* @__PURE__ */ new Set(["email", "username", "phone", "password"]);
|
|
540
|
+
var SIGN_IN_TAILS = /* @__PURE__ */ new Set(["callback/credentials", "auth/local", "session/new", "sessions/new"]);
|
|
541
|
+
var PASSWORD_GRANT = /(?:^|[&{,\s"])grant_type"?\s*[=:]\s*"?password(?=["&,}\s]|$)/;
|
|
542
|
+
var NEW_CREDENTIAL_STEM = "(?:pass(?:word|wd)?|pwd)";
|
|
543
|
+
var NEW_CREDENTIAL_WORD = `(?:(?:new|old|current|confirm|repeat|verify|retype)[_-]?(?:${NEW_CREDENTIAL_STEM}|email)|${NEW_CREDENTIAL_STEM}[_-]?(?:new|old|current|confirm(?:ation)?|repeat(?:ed)?|verify|verification|again|retype|check|confirmed|2)|email[_-]?confirm(?:ation)?)`;
|
|
544
|
+
var NEW_CREDENTIAL_NAME = new RegExp(`^${NEW_CREDENTIAL_WORD}$`, "i");
|
|
545
|
+
var NEW_CREDENTIAL_KEY = new RegExp(`(?:^|["&{,\\s[])${NEW_CREDENTIAL_WORD}(?:["\\]]?\\s*[:=]|"\\s*(?:;|$))`, "im");
|
|
546
|
+
var PASSWORD_KEY_WORDS = /* @__PURE__ */ new Set(["password", "passwd", "pwd", "pass"]);
|
|
547
|
+
var PASSWORD_KEY_QUALIFIERS = /* @__PURE__ */ new Set([
|
|
548
|
+
"new",
|
|
549
|
+
"old",
|
|
550
|
+
"current",
|
|
551
|
+
"confirm",
|
|
552
|
+
"confirmed",
|
|
553
|
+
"confirmation",
|
|
554
|
+
"repeat",
|
|
555
|
+
"repeated",
|
|
556
|
+
"verify",
|
|
557
|
+
"verification",
|
|
558
|
+
"again",
|
|
559
|
+
"retype",
|
|
560
|
+
"check",
|
|
561
|
+
"2"
|
|
562
|
+
]);
|
|
563
|
+
function isPasswordKey(key) {
|
|
564
|
+
const words = wordsOf(key);
|
|
565
|
+
return words.some((w) => PASSWORD_KEY_WORDS.has(w)) && words.every((w) => PASSWORD_KEY_WORDS.has(w) || PASSWORD_KEY_QUALIFIERS.has(w));
|
|
566
|
+
}
|
|
567
|
+
function couldBePassword(value) {
|
|
568
|
+
if (value === void 0)
|
|
569
|
+
return true;
|
|
570
|
+
if (typeof value === "number")
|
|
571
|
+
return Number.isFinite(value);
|
|
572
|
+
if (typeof value !== "string")
|
|
573
|
+
return false;
|
|
574
|
+
const v = value.trim();
|
|
575
|
+
return v !== "" && !/^(?:true|false|on|off|yes|no|null)$/i.test(v);
|
|
576
|
+
}
|
|
577
|
+
var MAX_CREDENTIAL_KEYS = 200;
|
|
578
|
+
function credentialKeysOf(body, contentType) {
|
|
579
|
+
const type = (contentType ?? "").toLowerCase();
|
|
580
|
+
const keys = [];
|
|
581
|
+
try {
|
|
582
|
+
if (type.includes("json") || /^\s*[[{]/.test(body)) {
|
|
583
|
+
const stack = [{ value: JSON.parse(body), under: null }];
|
|
584
|
+
while (stack.length > 0 && keys.length < MAX_CREDENTIAL_KEYS) {
|
|
585
|
+
const { value, under } = stack.pop();
|
|
586
|
+
if (Array.isArray(value))
|
|
587
|
+
stack.push(...value.slice(0, MAX_CREDENTIAL_KEYS).map((inner) => ({ value: inner, under: null })));
|
|
588
|
+
else if (value && typeof value === "object") {
|
|
589
|
+
for (const [key, inner] of Object.entries(value).slice(0, MAX_CREDENTIAL_KEYS)) {
|
|
590
|
+
const name = under === null ? key : `${under}_${key}`;
|
|
591
|
+
keys.push({ name, value: inner });
|
|
592
|
+
if (Array.isArray(inner) && isPasswordKey(name)) {
|
|
593
|
+
for (const item of inner.slice(0, MAX_CREDENTIAL_KEYS))
|
|
594
|
+
if (item === null || typeof item !== "object")
|
|
595
|
+
keys.push({ name, value: item });
|
|
596
|
+
}
|
|
597
|
+
const object = inner !== null && typeof inner === "object" && !Array.isArray(inner);
|
|
598
|
+
stack.push({ value: inner, under: object && isPasswordKey(name) ? name : null });
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
} else if (type.includes("multipart/form-data")) {
|
|
603
|
+
for (const m of body.matchAll(/(?:^|[;\s])name="([^"]{1,200})"/g))
|
|
604
|
+
if (keys.push({ name: m[1], value: void 0 }) >= MAX_CREDENTIAL_KEYS)
|
|
605
|
+
break;
|
|
606
|
+
} else if (type.includes("x-www-form-urlencoded")) {
|
|
607
|
+
for (const [name, value] of new URLSearchParams(body))
|
|
608
|
+
if (keys.push({ name, value }) >= MAX_CREDENTIAL_KEYS)
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
613
|
+
return keys.slice(0, MAX_CREDENTIAL_KEYS).map(({ name, value }) => {
|
|
614
|
+
const bare = name.replace(/\[\]$/, "");
|
|
615
|
+
const parts = bare.match(/[^[\]]+/g);
|
|
616
|
+
if (bare.endsWith("]") && parts && parts.length > 1) {
|
|
617
|
+
const inner = parts[parts.length - 1];
|
|
618
|
+
const outer = parts[parts.length - 2];
|
|
619
|
+
if (isPasswordKey(outer)) {
|
|
620
|
+
if (/^\d+$/.test(inner))
|
|
621
|
+
return { name: outer, value };
|
|
622
|
+
if (isPasswordKey(inner) || NEW_CREDENTIAL_NAME.test(inner))
|
|
623
|
+
return { name: inner, value };
|
|
624
|
+
return { name: `${outer}_${inner}`, value };
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return { name: /\[([^[\]]*)\]$/.exec(bare)?.[1] ?? bare, value };
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
function namesNewCredential(body, contentType) {
|
|
631
|
+
if (NEW_CREDENTIAL_KEY.test(body))
|
|
632
|
+
return true;
|
|
633
|
+
const type = contentType ?? (/^\s*[[{]/.test(body) ? null : "application/x-www-form-urlencoded");
|
|
634
|
+
const keys = credentialKeysOf(body, type);
|
|
635
|
+
if (keys.some(({ name }) => NEW_CREDENTIAL_NAME.test(name)))
|
|
636
|
+
return true;
|
|
637
|
+
const found = keys.filter(({ name, value }) => isPasswordKey(name) && couldBePassword(value));
|
|
638
|
+
const isBare = (name) => wordsOf(name).every((w) => PASSWORD_KEY_WORDS.has(w));
|
|
639
|
+
const named = new Set(found.filter(({ name }) => !isBare(name)).map(({ name }) => name.toLowerCase()));
|
|
640
|
+
const bare = new Set(found.filter(({ name }) => isBare(name)).map(({ name, value }) => value === void 0 ? `name:${name.toLowerCase()}` : `value:${String(value)}`));
|
|
641
|
+
return named.size + Math.min(bare.size, 2) >= 2;
|
|
642
|
+
}
|
|
643
|
+
var GRAPHQL_SIGN_IN_NAME = /^(?:log_?in|sign_?in|authenticate|login\w{0,40}|signin\w{0,40})$/i;
|
|
644
|
+
var GRAPHQL_SIGN_IN_VERBS = ["login", "signin", "authenticate"];
|
|
645
|
+
var GRAPHQL_SIGN_IN_PHRASE_WORDS = /* @__PURE__ */ new Set(["with", "via", "by", "using"]);
|
|
646
|
+
var GRAPHQL_SIGN_IN_TAIL_WORDS = /* @__PURE__ */ new Set([
|
|
647
|
+
"user",
|
|
648
|
+
"mutation",
|
|
649
|
+
"mfa",
|
|
650
|
+
"otp",
|
|
651
|
+
"totp",
|
|
652
|
+
"sso",
|
|
653
|
+
"anonymous",
|
|
654
|
+
"anonymously",
|
|
655
|
+
"email",
|
|
656
|
+
"password",
|
|
657
|
+
"phone",
|
|
658
|
+
"number",
|
|
659
|
+
"code",
|
|
508
660
|
"token",
|
|
509
|
-
"
|
|
661
|
+
"refresh",
|
|
662
|
+
"custom",
|
|
663
|
+
"api",
|
|
664
|
+
"key",
|
|
665
|
+
"magic",
|
|
666
|
+
"credentials",
|
|
667
|
+
"username",
|
|
668
|
+
"credential",
|
|
669
|
+
"sms",
|
|
670
|
+
"pin",
|
|
671
|
+
"jwt",
|
|
672
|
+
"mobile",
|
|
673
|
+
"session",
|
|
674
|
+
"ticket",
|
|
675
|
+
"qr",
|
|
676
|
+
"biometric",
|
|
677
|
+
"biometrics",
|
|
678
|
+
"wallet",
|
|
679
|
+
"identifier",
|
|
680
|
+
"address",
|
|
681
|
+
"local",
|
|
682
|
+
"login",
|
|
683
|
+
"userid",
|
|
684
|
+
"passkey",
|
|
685
|
+
"webauthn",
|
|
686
|
+
"idp",
|
|
687
|
+
"oauth",
|
|
688
|
+
"oidc",
|
|
689
|
+
"saml",
|
|
690
|
+
"ldap",
|
|
691
|
+
"google",
|
|
692
|
+
"apple",
|
|
693
|
+
"github",
|
|
694
|
+
"gitlab",
|
|
695
|
+
"facebook",
|
|
696
|
+
"microsoft",
|
|
697
|
+
"azure",
|
|
698
|
+
"twitter",
|
|
699
|
+
"linkedin",
|
|
700
|
+
"discord",
|
|
701
|
+
"slack",
|
|
702
|
+
"twitch",
|
|
703
|
+
"keycloak",
|
|
704
|
+
"okta",
|
|
705
|
+
"ethereum",
|
|
706
|
+
"firebase",
|
|
707
|
+
"cognito",
|
|
708
|
+
"apikey",
|
|
709
|
+
"appleid",
|
|
710
|
+
"auth0",
|
|
711
|
+
"faceid",
|
|
712
|
+
"touchid",
|
|
713
|
+
"twofactor",
|
|
714
|
+
"backupcode",
|
|
715
|
+
"recoverycode",
|
|
716
|
+
"rememberme"
|
|
717
|
+
]);
|
|
718
|
+
var GRAPHQL_SIGN_IN_SUBJECT_WORDS = /* @__PURE__ */ new Set(["user", "customer", "admin", "member", "staff", "account"]);
|
|
719
|
+
var GRAPHQL_SIGN_IN_JOINERS = /* @__PURE__ */ new Set(["and", "or"]);
|
|
720
|
+
var GRAPHQL_SIGN_IN_JOINED_KINDS = /* @__PURE__ */ new Set([
|
|
721
|
+
"github",
|
|
722
|
+
"gitlab",
|
|
723
|
+
"linkedin",
|
|
724
|
+
"webauthn",
|
|
725
|
+
"passkey",
|
|
726
|
+
"apikey",
|
|
727
|
+
"idp",
|
|
728
|
+
"appleid",
|
|
729
|
+
"auth0",
|
|
730
|
+
"faceid",
|
|
731
|
+
"touchid",
|
|
732
|
+
"twofactor",
|
|
733
|
+
"backupcode",
|
|
734
|
+
"recoverycode",
|
|
735
|
+
"rememberme",
|
|
736
|
+
"username",
|
|
737
|
+
"userid"
|
|
738
|
+
]);
|
|
739
|
+
function joinSplitKinds(words) {
|
|
740
|
+
const out = [];
|
|
741
|
+
for (let i = 0; i < words.length; i++) {
|
|
742
|
+
let joined = 0;
|
|
743
|
+
for (let n = Math.min(3, words.length - i); n >= 2; n--) {
|
|
744
|
+
const part = words.slice(i, i + n);
|
|
745
|
+
if (!part.some((w) => GRAPHQL_SIGN_IN_JOINERS.has(w)) && GRAPHQL_SIGN_IN_JOINED_KINDS.has(part.join(""))) {
|
|
746
|
+
joined = n;
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (joined) {
|
|
751
|
+
out.push(words.slice(i, i + joined).join(""));
|
|
752
|
+
i += joined - 1;
|
|
753
|
+
} else
|
|
754
|
+
out.push(words[i]);
|
|
755
|
+
}
|
|
756
|
+
return out;
|
|
757
|
+
}
|
|
758
|
+
var GRAPHQL_SIGN_IN_LINK_KINDS = /* @__PURE__ */ new Set(["magic", "email"]);
|
|
759
|
+
function graphqlSignInTail(name) {
|
|
760
|
+
const words = wordsOf(name.replace(/(?<=[A-Z0-9][A-Z0-9])(?=[A-Z][a-z])/g, " "));
|
|
761
|
+
const head = words[0] === "log" || words[0] === "sign" ? words[1] === "in" ? [`${words[0]}in`, ...words.slice(2)] : null : words;
|
|
762
|
+
if (!head || head.length === 0)
|
|
763
|
+
return null;
|
|
764
|
+
const verb = GRAPHQL_SIGN_IN_VERBS.find((v) => head[0].startsWith(v));
|
|
765
|
+
if (!verb)
|
|
766
|
+
return null;
|
|
767
|
+
const glued = head[0].slice(verb.length);
|
|
768
|
+
return glued ? [glued, ...head.slice(1)] : head.slice(1);
|
|
769
|
+
}
|
|
770
|
+
function isGraphqlSignInTail(tail) {
|
|
771
|
+
const words = joinSplitKinds(tail);
|
|
772
|
+
let start = 0;
|
|
773
|
+
while (start < words.length && GRAPHQL_SIGN_IN_SUBJECT_WORDS.has(words[start]))
|
|
774
|
+
start++;
|
|
775
|
+
if (start === words.length)
|
|
776
|
+
return true;
|
|
777
|
+
let kinds = words.slice(start);
|
|
778
|
+
const phrase = kinds.findIndex((w) => GRAPHQL_SIGN_IN_PHRASE_WORDS.has(w));
|
|
779
|
+
if (phrase >= 0) {
|
|
780
|
+
if (phrase === kinds.length - 1 || phrase > 0 && GRAPHQL_SIGN_IN_JOINERS.has(kinds[phrase - 1]) || GRAPHQL_SIGN_IN_JOINERS.has(kinds[phrase + 1]))
|
|
781
|
+
return false;
|
|
782
|
+
kinds = [...kinds.slice(0, phrase), ...kinds.slice(phrase + 1)];
|
|
783
|
+
}
|
|
784
|
+
if (kinds.length === 0)
|
|
785
|
+
return false;
|
|
786
|
+
const isKind = (w, before) => GRAPHQL_SIGN_IN_TAIL_WORDS.has(w) || /^v\d{1,3}$/.test(w) || /^\d?fa$/.test(w) || w === "link" && before !== void 0 && GRAPHQL_SIGN_IN_LINK_KINDS.has(before);
|
|
787
|
+
return kinds.every((w, i) => GRAPHQL_SIGN_IN_JOINERS.has(w) ? i > 0 && i < kinds.length - 1 && !GRAPHQL_SIGN_IN_JOINERS.has(kinds[i - 1]) : isKind(w, i === phrase ? void 0 : kinds[i - 1]));
|
|
788
|
+
}
|
|
789
|
+
function wordsOf(segment) {
|
|
790
|
+
return segment.replace(/([a-z])(?=[A-Z0-9])/g, "$1 ").toLowerCase().split(/[\s.,_/-]+/).filter(Boolean);
|
|
791
|
+
}
|
|
792
|
+
function rawSegmentsOf(pathname) {
|
|
793
|
+
return pathname.split("/").map((seg) => {
|
|
794
|
+
const raw = seg.split(";")[0];
|
|
795
|
+
try {
|
|
796
|
+
return decodeURIComponent(raw);
|
|
797
|
+
} catch {
|
|
798
|
+
return raw;
|
|
799
|
+
}
|
|
800
|
+
}).filter(Boolean);
|
|
801
|
+
}
|
|
802
|
+
function changesSomething(segments, opts = {}) {
|
|
803
|
+
const words = segments.flatMap(wordsOf);
|
|
804
|
+
const joined = segments.map((seg) => wordsOf(seg).join(""));
|
|
805
|
+
const hard = (w) => NOT_A_SIGN_IN_WORDS.has(w) && !(opts.passwordIsCredential && (w === "password" || w === "passwords"));
|
|
806
|
+
if (words.some(hard) || joined.some(hard))
|
|
807
|
+
return true;
|
|
808
|
+
return words.some((w) => ACCOUNT_CHANGE_WORDS.has(w)) && words.some((w) => ACCOUNT_CHANGE_CONTEXT.has(w));
|
|
809
|
+
}
|
|
810
|
+
function isGraphqlSignInName(name) {
|
|
811
|
+
if (!GRAPHQL_SIGN_IN_NAME.test(name))
|
|
812
|
+
return false;
|
|
813
|
+
const tail = graphqlSignInTail(name);
|
|
814
|
+
return tail !== null && isGraphqlSignInTail(tail) && !changesSomething([name], { passwordIsCredential: true });
|
|
815
|
+
}
|
|
816
|
+
function graphqlSignIn(body) {
|
|
817
|
+
let parsed;
|
|
818
|
+
try {
|
|
819
|
+
parsed = JSON.parse(body);
|
|
820
|
+
} catch {
|
|
821
|
+
parsed = { query: body };
|
|
822
|
+
}
|
|
823
|
+
const entries = Array.isArray(parsed) ? parsed : [parsed];
|
|
824
|
+
let signIns = 0;
|
|
825
|
+
for (const entry of entries) {
|
|
826
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
827
|
+
return null;
|
|
828
|
+
const { query, operationName } = entry;
|
|
829
|
+
if (typeof query !== "string") {
|
|
830
|
+
if (!Array.isArray(parsed) && query === void 0 && typeof operationName === "string" && isGraphqlSignInName(operationName))
|
|
831
|
+
return "persisted";
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
const ops = (0, survey_network_guard_js_1.graphqlOperationRoots)(query);
|
|
835
|
+
if (!ops)
|
|
836
|
+
return null;
|
|
837
|
+
for (const op of ops) {
|
|
838
|
+
if (op.kind === "query")
|
|
839
|
+
continue;
|
|
840
|
+
if (typeof operationName === "string" && changesSomething([operationName], { passwordIsCredential: true }))
|
|
841
|
+
return null;
|
|
842
|
+
const roots = op.roots.filter((root) => root !== "__typename");
|
|
843
|
+
if (roots.length === 0 || !roots.every(isGraphqlSignInName))
|
|
844
|
+
return null;
|
|
845
|
+
signIns += roots.length;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return signIns > 0 ? "document" : null;
|
|
849
|
+
}
|
|
850
|
+
var NOT_A_BROKER_SIGN_IN_WORDS = /* @__PURE__ */ new Set([
|
|
851
|
+
"add",
|
|
852
|
+
"book",
|
|
853
|
+
"booking",
|
|
854
|
+
"bookings",
|
|
855
|
+
"order",
|
|
856
|
+
"orders",
|
|
857
|
+
"reserve",
|
|
858
|
+
"reservation",
|
|
859
|
+
"reservations",
|
|
860
|
+
"submit",
|
|
861
|
+
"send",
|
|
862
|
+
"invite",
|
|
863
|
+
"invites",
|
|
864
|
+
"invitation",
|
|
865
|
+
"invitations",
|
|
866
|
+
"checkout",
|
|
867
|
+
"purchase",
|
|
868
|
+
"purchases",
|
|
869
|
+
"buy",
|
|
870
|
+
"pay",
|
|
871
|
+
"payment",
|
|
872
|
+
"payments",
|
|
873
|
+
"cart",
|
|
874
|
+
"subscribe",
|
|
875
|
+
"subscription",
|
|
876
|
+
"subscriptions",
|
|
877
|
+
"publish",
|
|
878
|
+
"upload",
|
|
879
|
+
"charge",
|
|
880
|
+
"charges",
|
|
881
|
+
"refund",
|
|
882
|
+
"refunds"
|
|
883
|
+
]);
|
|
884
|
+
var BROKER_MAKE_VERBS = /* @__PURE__ */ new Set(["create", "generate"]);
|
|
885
|
+
var BROKER_CREDENTIAL_WORDS = /* @__PURE__ */ new Set(["session", "sessions", "login", "logins", "signin", "sign", "token", "tokens", "jwt", "jwts"]);
|
|
886
|
+
var BROKER_CREDENTIAL_QUALIFIERS = /* @__PURE__ */ new Set([
|
|
887
|
+
"access",
|
|
888
|
+
"refresh",
|
|
889
|
+
"id",
|
|
890
|
+
"auth",
|
|
510
891
|
"oauth",
|
|
511
|
-
"
|
|
892
|
+
"bearer",
|
|
893
|
+
"in",
|
|
894
|
+
"new",
|
|
895
|
+
"user",
|
|
896
|
+
"customer",
|
|
897
|
+
"with",
|
|
898
|
+
"from",
|
|
899
|
+
"via",
|
|
900
|
+
"for",
|
|
901
|
+
"by",
|
|
902
|
+
"and",
|
|
903
|
+
"mutation",
|
|
904
|
+
"google",
|
|
905
|
+
"apple",
|
|
906
|
+
"facebook",
|
|
907
|
+
"github",
|
|
908
|
+
"microsoft",
|
|
512
909
|
"sso",
|
|
513
|
-
"
|
|
514
|
-
"
|
|
910
|
+
"social",
|
|
911
|
+
"oidc",
|
|
912
|
+
"firebase"
|
|
515
913
|
]);
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
914
|
+
function namesOnlyACredential(words) {
|
|
915
|
+
const rest = words.filter((w) => !BROKER_MAKE_VERBS.has(w));
|
|
916
|
+
return rest.some((w) => BROKER_CREDENTIAL_WORDS.has(w)) && rest.every((w) => BROKER_CREDENTIAL_WORDS.has(w) || BROKER_CREDENTIAL_QUALIFIERS.has(w));
|
|
917
|
+
}
|
|
918
|
+
function actsBesidesBrokerSignIn(segments) {
|
|
919
|
+
return segments.some((seg, i) => {
|
|
920
|
+
const words = wordsOf(seg);
|
|
921
|
+
if (words.some((w) => NOT_A_BROKER_SIGN_IN_WORDS.has(w)) || NOT_A_BROKER_SIGN_IN_WORDS.has(words.join("")))
|
|
922
|
+
return true;
|
|
923
|
+
if (!words.some((w) => BROKER_MAKE_VERBS.has(w)))
|
|
924
|
+
return false;
|
|
925
|
+
if (namesOnlyACredential(words))
|
|
926
|
+
return false;
|
|
927
|
+
return !(words.length === 1 && i > 0 && i === segments.length - 1 && namesOnlyACredential(wordsOf(segments[i - 1])));
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
function brokerRefuses(segments) {
|
|
931
|
+
return changesSomething(segments) || actsBesidesBrokerSignIn(segments);
|
|
932
|
+
}
|
|
933
|
+
var BROKER_SIGN_IN_NAME_WORDS = /* @__PURE__ */ new Set(["login", "signin", "sign", "auth", "authenticate", "google", "oauth", "sso", "social", "session"]);
|
|
934
|
+
function isBrokerSignInName(name) {
|
|
935
|
+
return typeof name === "string" && wordsOf(name).some((w) => BROKER_SIGN_IN_NAME_WORDS.has(w)) && !brokerRefuses([name]);
|
|
936
|
+
}
|
|
937
|
+
function graphqlBrokerSignIn(body) {
|
|
938
|
+
let parsed;
|
|
939
|
+
try {
|
|
940
|
+
parsed = JSON.parse(body);
|
|
941
|
+
} catch {
|
|
942
|
+
parsed = { query: body };
|
|
943
|
+
}
|
|
944
|
+
const entries = Array.isArray(parsed) ? parsed : [parsed];
|
|
945
|
+
let mutationRoots = 0;
|
|
946
|
+
for (const entry of entries) {
|
|
947
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
948
|
+
return false;
|
|
949
|
+
const { query, operationName } = entry;
|
|
950
|
+
const record = entry;
|
|
951
|
+
const ids = PERSISTED_ID_KEYS.map((key) => record[key]).filter((n) => n !== void 0);
|
|
952
|
+
const names = operationName === void 0 ? ids : [operationName, ...ids];
|
|
953
|
+
if (ids.length > 0 && !names.every(isBrokerSignInName))
|
|
954
|
+
return false;
|
|
955
|
+
if (typeof query !== "string") {
|
|
956
|
+
return !Array.isArray(parsed) && query === void 0 && names.length > 0 && names.every(isBrokerSignInName);
|
|
957
|
+
}
|
|
958
|
+
const ops = (0, survey_network_guard_js_1.graphqlOperationRoots)(query);
|
|
959
|
+
if (!ops)
|
|
960
|
+
return false;
|
|
961
|
+
for (const op of ops) {
|
|
962
|
+
if (op.kind === "query")
|
|
963
|
+
continue;
|
|
964
|
+
if (typeof operationName === "string" && brokerRefuses([operationName]))
|
|
965
|
+
return false;
|
|
966
|
+
const roots = op.roots.filter((root) => root !== "__typename");
|
|
967
|
+
if (roots.some((root) => brokerRefuses([root])))
|
|
968
|
+
return false;
|
|
969
|
+
mutationRoots += roots.length;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return mutationRoots === 1;
|
|
973
|
+
}
|
|
974
|
+
var GRAPHQL_DOCUMENT_START = /^[\s,]*(?:[{#]|(?:query|mutation|subscription|fragment)\b)/;
|
|
975
|
+
function looksLikeGraphqlDocument(text) {
|
|
976
|
+
return GRAPHQL_DOCUMENT_START.test(text) || (0, survey_network_guard_js_1.graphqlOperationRoots)(text) !== null;
|
|
977
|
+
}
|
|
978
|
+
var PERSISTED_ID_KEYS = ["id", "documentId", "doc_id", "queryId"];
|
|
979
|
+
var GRAPHQL_ENVELOPE_FIELDS = ["variables", "operationName", "documentId", "doc_id", "queryId", "sha256Hash", "hash", "extensions", "operations"];
|
|
980
|
+
function hasGraphqlEnvelope(has, queryIsString) {
|
|
981
|
+
return queryIsString || GRAPHQL_ENVELOPE_FIELDS.some(has);
|
|
982
|
+
}
|
|
983
|
+
function carriesGraphql(search, body, contentType) {
|
|
984
|
+
if (search.getAll("query").some(looksLikeGraphqlDocument))
|
|
985
|
+
return true;
|
|
986
|
+
if (/^\s*application\/graphql\b/i.test(contentType ?? ""))
|
|
987
|
+
return true;
|
|
988
|
+
if (/multipart\//i.test(contentType ?? "") || /^\s*--/.test(body)) {
|
|
989
|
+
const parts = new Set([...body.matchAll(/\bname\s*=\s*"?([\w-]+)/gi)].map((m) => m[1]));
|
|
990
|
+
return hasGraphqlEnvelope((field) => parts.has(field), parts.has("query"));
|
|
991
|
+
}
|
|
992
|
+
let parsed;
|
|
993
|
+
try {
|
|
994
|
+
parsed = JSON.parse(body);
|
|
995
|
+
} catch {
|
|
996
|
+
if (looksLikeGraphqlDocument(body))
|
|
997
|
+
return true;
|
|
998
|
+
const form = new URLSearchParams(body);
|
|
999
|
+
return hasGraphqlEnvelope((field) => form.has(field), form.has("query"));
|
|
1000
|
+
}
|
|
1001
|
+
if (Array.isArray(parsed))
|
|
1002
|
+
return true;
|
|
1003
|
+
if (!parsed || typeof parsed !== "object")
|
|
1004
|
+
return false;
|
|
1005
|
+
const record = parsed;
|
|
1006
|
+
return hasGraphqlEnvelope((field) => record[field] !== void 0, typeof record.query === "string");
|
|
1007
|
+
}
|
|
1008
|
+
function isBrokerSignInShape(pathname, search, postData, contentType) {
|
|
1009
|
+
if (brokerRefuses(rawSegmentsOf(pathname)))
|
|
1010
|
+
return false;
|
|
1011
|
+
if (!isGraphqlPath(pathname) && !(typeof postData === "string" && carriesGraphql(search, postData, contentType)))
|
|
1012
|
+
return true;
|
|
1013
|
+
if ((0, survey_network_guard_js_1.graphqlUrlCarriesQuery)(search) || PERSISTED_ID_KEYS.some((key) => search.has(key)) || typeof postData !== "string" || postData.length > MAX_BODY_CHARS)
|
|
1014
|
+
return false;
|
|
1015
|
+
return graphqlBrokerSignIn(postData);
|
|
1016
|
+
}
|
|
1017
|
+
function isCredentialSignInShape(pathname, search, postData, contentType) {
|
|
1018
|
+
if (typeof postData === "string" && postData.length > MAX_BODY_CHARS)
|
|
1019
|
+
return false;
|
|
1020
|
+
const body = typeof postData === "string" && postData.length <= MAX_BODY_CHARS ? postData : null;
|
|
1021
|
+
if (body !== null && namesNewCredential(body, contentType))
|
|
520
1022
|
return false;
|
|
521
|
-
|
|
1023
|
+
const raw = rawSegmentsOf(pathname);
|
|
1024
|
+
const segs = raw.map((seg) => seg.toLowerCase());
|
|
1025
|
+
const last = segs[segs.length - 1] ?? "";
|
|
1026
|
+
const prev = segs[segs.length - 2] ?? "";
|
|
1027
|
+
if (SIGN_IN_VERB_SEGMENTS.has(prev) && SIGN_IN_CREDENTIAL_KINDS.has(last))
|
|
1028
|
+
return !changesSomething(raw, { passwordIsCredential: true });
|
|
1029
|
+
if (changesSomething(raw))
|
|
1030
|
+
return false;
|
|
1031
|
+
if (SIGN_IN_LAST_SEGMENTS.has(last) || SIGN_IN_TAILS.has(`${prev}/${last}`))
|
|
522
1032
|
return true;
|
|
523
|
-
|
|
1033
|
+
if (last === "token")
|
|
1034
|
+
return search.get("grant_type") === "password" || body !== null && PASSWORD_GRANT.test(body);
|
|
1035
|
+
if (!isGraphqlPath(pathname) || body === null || (0, survey_network_guard_js_1.graphqlUrlCarriesQuery)(search))
|
|
1036
|
+
return false;
|
|
1037
|
+
const graphql = graphqlSignIn(body);
|
|
1038
|
+
return graphql === "persisted" ? graphql : graphql === "document";
|
|
524
1039
|
}
|
|
525
1040
|
function bodyHasSignInSecret(body, secrets) {
|
|
526
1041
|
if (!body || body.length > MAX_BODY_CHARS || secrets.length === 0)
|
|
@@ -548,7 +1063,7 @@ var require_run_write_guard = __commonJS({
|
|
|
548
1063
|
var COGNITO_HOSTED_UI_RE = /^\/(?:oauth2\/(?:token|authorize|userinfo|idpresponse)|saml2\/idpresponse|login|logincontinue)$/;
|
|
549
1064
|
var CLERK_FAPI_RE = /^\/v1\/(?:client(?:\/(?:sign_ins(?:\/[^/]+\/(?:prepare_first_factor|attempt_first_factor|prepare_second_factor|attempt_second_factor))?|sessions\/[^/]+\/(?:touch|tokens(?:\/[^/]+)?)|handshake|sync|link))?|dev_browser|environment|oauth_callback)$/;
|
|
550
1065
|
var SUPABASE_AUTH_RE = /^\/auth\/v1\/(?:token|otp|magiclink|verify|authorize|callback|sso|saml\/acs|reauthenticate|factors\/[^/]+\/(?:challenge|verify))$/;
|
|
551
|
-
var AUTH0_AUTH_RE = /^\/(?:oauth\/(?:token|ro)|authorize(?:\/resume)?|co\/authenticate|usernamepassword\/(?:login|challenge)|login\/(?:callback|identifier|password)|passwordless\/(?:start|verify)|userinfo|mfa\/challenge|u\/(?:login(?:\/[^/]+)?|mfa-(?:otp|sms|email|voice|phone|push|webauthn-platform|webauthn-roaming|recovery-code)-challenge(?:-push|-new-code)?|mfa-login-options|mfa-detect-browser-capabilities|mfa-country-codes|consent))$/;
|
|
1066
|
+
var AUTH0_AUTH_RE = /^\/(?:oauth\/(?:token|ro)|authorize(?:\/resume)?|co\/authenticate|usernamepassword\/(?:login|challenge)|login\/(?:callback|identifier|password)|passwordless\/(?:start|verify)|userinfo|mfa\/challenge|u\/(?:login(?:\/[^/]+)?|mfa-(?:otp|sms|email|voice|phone|push|webauthn-platform|webauthn-roaming|recovery-code)-challenge(?:-push|-new-code)?|mfa-(?:sms|push|email)-list|mfa-login-options|mfa-detect-browser-capabilities|mfa-country-codes|consent))$/;
|
|
552
1067
|
var OKTA_AUTH_RE = /^\/(?:api\/v1\/(?:authn(?:\/(?:factors\/[^/]+\/verify|skip|cancel))?|sessions(?:\/me\/lifecycle\/refresh)?)|oauth2\/(?:[^/]+\/)?v1\/(?:token|authorize|introspect|userinfo|device\/authorize)|idp\/idx\/(?:introspect|identify(?:\/select)?|challenge(?:\/answer|\/poll)?|skip|cancel)|login\/[^/]+(?:\/[^/]+)?)$/;
|
|
553
1068
|
var MICROSOFT_AUTH_RE = /\/(?:oauth2\/(?:v2\.0\/)?(?:token|authorize|devicecode)|login|kmsi|getcredentialtype|sas\/(?:processauth|beginauth|endauth)|selfasserted|api\/combinedsigninandsignup\/[^/]+|resume|reprocess|federation\/oauth2)$/;
|
|
554
1069
|
var GOOGLE_ACCOUNTS_RE = /^\/(?:o\/oauth2\/(?:token|auth|v2\/auth|iframerpc|postmessagerelay|approval)|gsi\/[^/]+(?:\/[^/]+)?|_\/(?:signin|lookup|identitysigninui|accountssigninui)\/.+|v3\/signin\/.+|signin\/.+|servicelogin|checkcookie|accountchooser)$/;
|
|
@@ -614,14 +1129,26 @@ var require_run_write_guard = __commonJS({
|
|
|
614
1129
|
return GENERIC_IDP_AUTH_RE.test(path);
|
|
615
1130
|
}
|
|
616
1131
|
function isAuthProviderWrite(method, host, pathname, amzTarget, search) {
|
|
1132
|
+
if (!isPlainPost(method, search))
|
|
1133
|
+
return false;
|
|
1134
|
+
if (!(0, shared_1.isAuthProviderHost)(host))
|
|
1135
|
+
return false;
|
|
1136
|
+
return isAuthProviderSignIn(host.toLowerCase(), pathname, amzTarget, search);
|
|
1137
|
+
}
|
|
1138
|
+
function isPlainPost(method, search) {
|
|
617
1139
|
if (method.toUpperCase() !== "POST")
|
|
618
1140
|
return false;
|
|
619
1141
|
const tunnelled = search.get("_method");
|
|
620
|
-
|
|
1142
|
+
return tunnelled === null || tunnelled.toUpperCase() === "POST";
|
|
1143
|
+
}
|
|
1144
|
+
function isAuth0CustomDomainSignIn(method, pathname, search) {
|
|
1145
|
+
if (!isPlainPost(method, search))
|
|
621
1146
|
return false;
|
|
622
|
-
|
|
1147
|
+
const path = pathname.toLowerCase();
|
|
1148
|
+
if (!AUTH0_AUTH_RE.test(path))
|
|
623
1149
|
return false;
|
|
624
|
-
|
|
1150
|
+
const needsState = path.startsWith("/u/") || path === "/authorize" || path === "/authorize/resume";
|
|
1151
|
+
return !needsState || (search.get("state") ?? "") !== "";
|
|
625
1152
|
}
|
|
626
1153
|
function isSignUp(host, pathname, amzTarget) {
|
|
627
1154
|
if (PROVIDER_SIGN_UP_PATH.test(pathname))
|
|
@@ -700,6 +1227,26 @@ var require_run_write_guard = __commonJS({
|
|
|
700
1227
|
}).join("");
|
|
701
1228
|
return { method, host, path: new RegExp(`^${body}/?$`, "i") };
|
|
702
1229
|
}
|
|
1230
|
+
function normaliseIdpHost(raw) {
|
|
1231
|
+
return raw.trim().toLowerCase().replace(/\.$/, "");
|
|
1232
|
+
}
|
|
1233
|
+
var WILDCARD_DNS_RE = /(?:^|\.)(?:nip\.io|sslip\.io|xip\.io|localtest\.me|lvh\.me|vcap\.me|traefik\.me|localho\.st|localhost\.direct|local\.gd|fuf\.me)$/;
|
|
1234
|
+
function idpHostProblem(host) {
|
|
1235
|
+
if (host.length > 253 || !/^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/.test(host))
|
|
1236
|
+
return "not a host name (no scheme, port, path or wildcard)";
|
|
1237
|
+
if (/^[0-9.]+$/.test(host))
|
|
1238
|
+
return "an IP address";
|
|
1239
|
+
if (/(?:^|\.)(?:localhost|local|internal)$/.test(host))
|
|
1240
|
+
return "a local name";
|
|
1241
|
+
if (WILDCARD_DNS_RE.test(host))
|
|
1242
|
+
return "a wildcard-DNS name (it can point at any address)";
|
|
1243
|
+
if ((0, shared_1.isSharedSuffix)(host))
|
|
1244
|
+
return exports.SHARED_SUFFIX_PATTERN_REASON;
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
function isIdpHost(url, host, policy) {
|
|
1248
|
+
return url.protocol === "https:" && url.port === "" && (policy.idpHosts ?? []).includes(host);
|
|
1249
|
+
}
|
|
703
1250
|
function matchesAllowPatterns(method, host, pathname, policy, compiled) {
|
|
704
1251
|
const patterns = compiled ?? policy.allowPatterns.map(compileAllowPattern).filter((p) => p !== null);
|
|
705
1252
|
return patterns.some((p) => {
|
|
@@ -723,7 +1270,10 @@ var require_run_write_guard = __commonJS({
|
|
|
723
1270
|
return reason ? [{ pattern, reason }] : [];
|
|
724
1271
|
})
|
|
725
1272
|
];
|
|
726
|
-
return
|
|
1273
|
+
return [
|
|
1274
|
+
...ignored.map(({ pattern, reason }) => `[run-write-guard] SAFE_WRITE_PATTERNS entry ${JSON.stringify(pattern)} ignored: ${reason}`),
|
|
1275
|
+
...(policy.ignoredIdpHosts ?? []).map(({ pattern, reason }) => `[run-write-guard] AUTH_PROVIDER_HOSTS entry ${JSON.stringify(pattern)} ignored: ${reason}`)
|
|
1276
|
+
];
|
|
727
1277
|
}
|
|
728
1278
|
function describe(facts, url, rule) {
|
|
729
1279
|
return {
|
|
@@ -747,8 +1297,7 @@ var require_run_write_guard = __commonJS({
|
|
|
747
1297
|
const host = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
748
1298
|
const pathname = url.pathname;
|
|
749
1299
|
if (READ_METHODS.has(method)) {
|
|
750
|
-
|
|
751
|
-
if (doc !== null && !(0, survey_network_guard_js_1.graphqlDocumentIsQueryOnly)(doc)) {
|
|
1300
|
+
if (method === "GET" && isGraphqlPath(pathname) && (0, survey_network_guard_js_1.graphqlGetMayWrite)(url, facts.postData)) {
|
|
752
1301
|
return { action: "write", rule: "graphql-get-mutation", record: describe(facts, url, "graphql-get-mutation") };
|
|
753
1302
|
}
|
|
754
1303
|
if (method !== "OPTIONS" && isAppSite(host, policy)) {
|
|
@@ -776,20 +1325,24 @@ var require_run_write_guard = __commonJS({
|
|
|
776
1325
|
return { action: "write", rule: "sign-up", record: describe(facts, url, "sign-up") };
|
|
777
1326
|
if (isAuthProviderWrite(method, host, pathname, facts.amzTarget, url.searchParams))
|
|
778
1327
|
return { action: "allow", rule: "auth-provider" };
|
|
1328
|
+
if (isIdpHost(url, host, policy) && isAuth0CustomDomainSignIn(method, pathname, url.searchParams))
|
|
1329
|
+
return { action: "allow", rule: "auth-provider" };
|
|
779
1330
|
const appSite = isAppSite(host, policy);
|
|
780
1331
|
if (facts.registeredAuthHandler && pathname === exports.FIREBASE_AUTH_HANDLER_PATH)
|
|
781
1332
|
return { action: "allow", rule: "auth-provider" };
|
|
782
1333
|
if (appSite && (0, survey_network_guard_js_1.isSignInPathWrite)(method, pathname))
|
|
783
1334
|
return { action: "allow", rule: "sign-in" };
|
|
784
|
-
if (appSite && method
|
|
1335
|
+
if (appSite && isAuth0CustomDomainSignIn(method, pathname, url.searchParams))
|
|
1336
|
+
return { action: "allow", rule: "sign-in" };
|
|
1337
|
+
if (appSite && method === "POST" && facts.brokerTokenInBody && isBrokerSignInShape(pathname, url.searchParams, facts.postData, facts.contentType)) {
|
|
785
1338
|
return { action: "allow", rule: "broker-sign-in" };
|
|
786
1339
|
}
|
|
787
1340
|
if (appSite && isEvidencedTokenRefresh(method, pathname, facts.postData))
|
|
788
1341
|
return { action: "allow", rule: "token-refresh" };
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (method === "POST" && isGraphqlPath(pathname) && isGraphqlQueryOnlyBody(facts.postData))
|
|
1342
|
+
const signIn = appSite && method === "POST" && facts.signInSecretInBody ? isCredentialSignInShape(pathname, url.searchParams, facts.postData, facts.contentType) : false;
|
|
1343
|
+
if (signIn)
|
|
1344
|
+
return { action: "allow", rule: "credential-sign-in", ...signIn === "persisted" ? { persisted: true } : {} };
|
|
1345
|
+
if (method === "POST" && isGraphqlPath(pathname) && (0, survey_network_guard_js_1.graphqlUrlQueriesAreReads)(url.searchParams) && isGraphqlQueryOnlyBody(facts.postData))
|
|
793
1346
|
return { action: "allow", rule: "graphql-query" };
|
|
794
1347
|
if (method === "POST" && hostIs(host, SEARCH_POST_HOSTS))
|
|
795
1348
|
return { action: "allow", rule: "search-post" };
|
|
@@ -834,6 +1387,8 @@ var require_run_write_guard = __commonJS({
|
|
|
834
1387
|
recordOnly;
|
|
835
1388
|
log;
|
|
836
1389
|
signInSecrets;
|
|
1390
|
+
/** The deadline of one request the guard sends itself, all its hops together (sendCheckingRedirect). */
|
|
1391
|
+
redirectCheckTimeoutMs;
|
|
837
1392
|
/** Tokens the Google sign-in broker handed this context, each until its expiry (epoch ms). */
|
|
838
1393
|
brokerTokens = [];
|
|
839
1394
|
/** Origins of the Firebase Auth handlers the broker registered for this context. */
|
|
@@ -842,12 +1397,18 @@ var require_run_write_guard = __commonJS({
|
|
|
842
1397
|
relayIds = /* @__PURE__ */ new Set();
|
|
843
1398
|
relayed = 0;
|
|
844
1399
|
relayRejected = 0;
|
|
1400
|
+
/** Credential sign-ins already said in the run log (observe mode; method, host and path shape). */
|
|
1401
|
+
signInsLogged = /* @__PURE__ */ new Set();
|
|
1402
|
+
/** Hosts already named in the run log as a possible AUTH_PROVIDER_HOSTS entry (at most MAX_IDP_HINTS). */
|
|
1403
|
+
idpHintsLogged = /* @__PURE__ */ new Set();
|
|
845
1404
|
constructor(policy, opts = {}) {
|
|
846
1405
|
this.policy = policy;
|
|
847
1406
|
this.compiled = policy.allowPatterns.map(compileAllowPattern).filter((p) => p !== null);
|
|
848
1407
|
this.recordOnly = new Set((policy.recordOnlyRules ?? exports.DEFAULT_RECORD_ONLY_RULES).filter((rule) => exports.RECORD_ONLY_CAPABLE_RULES.includes(rule)));
|
|
849
1408
|
this.log = opts.onLog;
|
|
850
1409
|
this.signInSecrets = (opts.signInSecrets ?? []).filter((v) => typeof v === "string" && v.length >= MIN_SIGN_IN_SECRET_CHARS);
|
|
1410
|
+
const ms = opts.redirectCheckTimeoutMs;
|
|
1411
|
+
this.redirectCheckTimeoutMs = typeof ms === "number" && ms > 0 ? Math.min(ms, REDIRECT_CHECK_TIMEOUT_MS) : REDIRECT_CHECK_TIMEOUT_MS;
|
|
851
1412
|
}
|
|
852
1413
|
decide(facts) {
|
|
853
1414
|
const signInSecretInBody = facts.signInSecretInBody ?? bodyHasSignInSecret(facts.postData, this.signInSecrets);
|
|
@@ -870,8 +1431,28 @@ var require_run_write_guard = __commonJS({
|
|
|
870
1431
|
consumedBrokerTokens.add(brokerToken.value);
|
|
871
1432
|
this.brokerTokens = this.brokerTokens.filter((t) => t !== brokerToken);
|
|
872
1433
|
}
|
|
1434
|
+
if (decision.rule === "credential-sign-in" && this.policy.mode === "observe")
|
|
1435
|
+
this.noteCredentialSignIn(facts, decision.persisted === true);
|
|
873
1436
|
return decision;
|
|
874
1437
|
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Observe mode says where each credential sign-in went, once per path and kind (the lead): the
|
|
1440
|
+
* method, host and masked path only, never the body, so the rule can be checked against real runs
|
|
1441
|
+
* before a site is blocked. A GraphQL persisted query is marked unverified.
|
|
1442
|
+
*/
|
|
1443
|
+
noteCredentialSignIn(facts, persisted) {
|
|
1444
|
+
let host = "";
|
|
1445
|
+
try {
|
|
1446
|
+
host = new URL(facts.url).hostname.toLowerCase();
|
|
1447
|
+
} catch {
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
const line = `${facts.method.toUpperCase()} ${host}${(0, survey_network_guard_js_1.maskedPath)(facts.url)} [credential-sign-in${persisted ? " (persisted, unverified)" : ""}]`;
|
|
1451
|
+
if (this.signInsLogged.has(line) || this.signInsLogged.size >= MAX_SIGN_IN_LOG_LINES)
|
|
1452
|
+
return;
|
|
1453
|
+
this.signInsLogged.add(line);
|
|
1454
|
+
this.log?.(`[run-write-guard] allowed ${line}`);
|
|
1455
|
+
}
|
|
875
1456
|
/**
|
|
876
1457
|
* A code or ID token the Google sign-in broker hands this context: the first POST that carries it
|
|
877
1458
|
* to the app in the next BROKER_TOKEN_TTL_MS is the sign-in. Anything not shaped like a Google code
|
|
@@ -917,6 +1498,22 @@ var require_run_write_guard = __commonJS({
|
|
|
917
1498
|
const verb = held ? "held" : "saw";
|
|
918
1499
|
const why = record.writeShaped ? `${record.rule}, ${record.writeShaped}` : record.rule;
|
|
919
1500
|
this.log?.(`[run-write-guard] ${verb} ${record.method} ${record.host}${record.path}${record.keys.length ? ` (${record.keys.join(", ")})` : ""} [${why}]`);
|
|
1501
|
+
this.hintIdpHost(record);
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* An Auth0 sign-in step (by path) posted to another site was a third-party write: the tenant may
|
|
1505
|
+
* answer on a custom domain of its own. Said once per host, for at most MAX_IDP_HINTS hosts, so a
|
|
1506
|
+
* person knows AUTH_PROVIDER_HOSTS exists; the guard never names the host itself.
|
|
1507
|
+
*/
|
|
1508
|
+
hintIdpHost(record) {
|
|
1509
|
+
if (record.rule !== "third-party-write" || record.method !== "POST")
|
|
1510
|
+
return;
|
|
1511
|
+
if (this.idpHintsLogged.has(record.host) || this.idpHintsLogged.size >= MAX_IDP_HINTS)
|
|
1512
|
+
return;
|
|
1513
|
+
if ((this.policy.idpHosts ?? []).includes(record.host) || !AUTH0_AUTH_RE.test(record.path.toLowerCase()))
|
|
1514
|
+
return;
|
|
1515
|
+
this.idpHintsLogged.add(record.host);
|
|
1516
|
+
this.log?.(`[run-write-guard] note: ${record.host} looks like an Auth0 sign-in on another site; if it is the app's own sign-in, a project admin can name it in AUTH_PROVIDER_HOSTS in Test Data`);
|
|
920
1517
|
}
|
|
921
1518
|
/**
|
|
922
1519
|
* A page sent a message over a WebSocket to the app's site. The guard cannot tell a read from a
|
|
@@ -931,8 +1528,9 @@ var require_run_write_guard = __commonJS({
|
|
|
931
1528
|
}
|
|
932
1529
|
/**
|
|
933
1530
|
* A hop the guard fetched itself (sendCheckingRedirect) failed, so the request fails in the page.
|
|
934
|
-
* Said in the run log, with the
|
|
935
|
-
* at
|
|
1531
|
+
* Said in the run log, with the reason when it was the guard's own limit: a slow allowed upload or
|
|
1532
|
+
* stream is cut at its deadline, an answer over MAX_GUARD_HOP_BYTES as it streams in, and the page
|
|
1533
|
+
* only sees a network error.
|
|
936
1534
|
*/
|
|
937
1535
|
noteRedirectCheckFailed(facts, err, timeoutMs) {
|
|
938
1536
|
let host = "";
|
|
@@ -940,8 +1538,8 @@ var require_run_write_guard = __commonJS({
|
|
|
940
1538
|
host = new URL(facts.url).hostname.toLowerCase();
|
|
941
1539
|
} catch {
|
|
942
1540
|
}
|
|
943
|
-
const
|
|
944
|
-
const why =
|
|
1541
|
+
const reason = err instanceof GuardHopError ? err.reason : null;
|
|
1542
|
+
const why = reason === "timeout" ? `the guard's redirect check timed out after ${Math.round(timeoutMs / 1e3)} s (REDIRECT_CHECK_TIMEOUT_MS)` : reason === "over-cap" ? `the answer was over ${MAX_GUARD_HOP_BYTES / (1024 * 1024)} MB (MAX_GUARD_HOP_BYTES), cut as it came in` : "the guard's redirect check failed";
|
|
945
1543
|
this.log?.(`[run-write-guard] note: ${facts.method.toUpperCase()} ${host}${(0, survey_network_guard_js_1.maskedPath)(facts.url)} failed in the page: ${why}`);
|
|
946
1544
|
}
|
|
947
1545
|
/** A request still undecided when its page closed (see guardPageClose): it is failed in the page, so the browser never sends it. */
|
|
@@ -1389,66 +1987,321 @@ var require_run_write_guard = __commonJS({
|
|
|
1389
1987
|
}
|
|
1390
1988
|
}
|
|
1391
1989
|
var MAX_GUARD_REDIRECTS = 5;
|
|
1392
|
-
var REDIRECT_CHECK_TIMEOUT_MS =
|
|
1990
|
+
var REDIRECT_CHECK_TIMEOUT_MS = 3e4;
|
|
1991
|
+
var MAX_GUARD_HOP_BYTES = 16 * 1024 * 1024;
|
|
1992
|
+
var MAX_GUARD_BUFFERED_BYTES = 128 * 1024 * 1024;
|
|
1993
|
+
var SEND_HTTP = node_http_1.request;
|
|
1994
|
+
var SEND_HTTPS = node_https_1.request;
|
|
1995
|
+
var HOP_ONLY_HEADERS = /* @__PURE__ */ new Set([
|
|
1996
|
+
"connection",
|
|
1997
|
+
"keep-alive",
|
|
1998
|
+
"proxy-connection",
|
|
1999
|
+
"transfer-encoding",
|
|
2000
|
+
"te",
|
|
2001
|
+
"trailer",
|
|
2002
|
+
"upgrade",
|
|
2003
|
+
"proxy-authorization",
|
|
2004
|
+
"proxy-authenticate",
|
|
2005
|
+
"host",
|
|
2006
|
+
"content-length"
|
|
2007
|
+
]);
|
|
2008
|
+
var hopBytesInFlight = 0;
|
|
2009
|
+
var hopsInFlight = /* @__PURE__ */ new Set();
|
|
2010
|
+
var hopBudgetWaiters = [];
|
|
2011
|
+
function releaseHopBytes(hop, bytes) {
|
|
2012
|
+
hopsInFlight.delete(hop);
|
|
2013
|
+
hopBytesInFlight -= bytes;
|
|
2014
|
+
for (const wake of hopBudgetWaiters.splice(0))
|
|
2015
|
+
wake();
|
|
2016
|
+
}
|
|
2017
|
+
function runWriteGuardHopBytesInFlight() {
|
|
2018
|
+
return hopBytesInFlight;
|
|
2019
|
+
}
|
|
2020
|
+
var GuardHopError = class extends Error {
|
|
2021
|
+
reason;
|
|
2022
|
+
constructor(reason, message) {
|
|
2023
|
+
super(message);
|
|
2024
|
+
this.reason = reason;
|
|
2025
|
+
this.name = "GuardHopError";
|
|
2026
|
+
}
|
|
2027
|
+
};
|
|
2028
|
+
function sendGuardHop(hop) {
|
|
2029
|
+
return new Promise((resolve, reject) => {
|
|
2030
|
+
let url;
|
|
2031
|
+
try {
|
|
2032
|
+
url = new URL(hop.url);
|
|
2033
|
+
} catch (err) {
|
|
2034
|
+
reject(err);
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
const send = url.protocol === "https:" ? SEND_HTTPS : url.protocol === "http:" ? SEND_HTTP : null;
|
|
2038
|
+
if (!send) {
|
|
2039
|
+
reject(new Error(`the guard sends only http(s), not ${url.protocol}`));
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
const id = /* @__PURE__ */ Symbol("guard hop");
|
|
2043
|
+
hopsInFlight.add(id);
|
|
2044
|
+
let held = 0;
|
|
2045
|
+
let settled = false;
|
|
2046
|
+
let req = null;
|
|
2047
|
+
const chunks = [];
|
|
2048
|
+
const fail = (err) => {
|
|
2049
|
+
if (settled)
|
|
2050
|
+
return;
|
|
2051
|
+
settled = true;
|
|
2052
|
+
clearTimeout(timer);
|
|
2053
|
+
req?.destroy();
|
|
2054
|
+
releaseHopBytes(id, held);
|
|
2055
|
+
held = 0;
|
|
2056
|
+
reject(err);
|
|
2057
|
+
};
|
|
2058
|
+
const overCap = () => new GuardHopError("over-cap", `answer over ${MAX_GUARD_HOP_BYTES} bytes`);
|
|
2059
|
+
const timer = setTimeout(() => fail(new GuardHopError("timeout", "the guard hop passed its deadline")), Math.max(0, hop.deadline - Date.now()));
|
|
2060
|
+
try {
|
|
2061
|
+
req = send(url, { method: hop.method, headers: hop.headers, rejectUnauthorized: !hop.ignoreHTTPSErrors, agent: false }, (res) => {
|
|
2062
|
+
const encoding = String(res.headers["content-encoding"] ?? "").trim().toLowerCase();
|
|
2063
|
+
const decoder = encoding === "gzip" || encoding === "x-gzip" ? (0, node_zlib_1.createGunzip)({ flush: node_zlib_1.constants.Z_SYNC_FLUSH, finishFlush: node_zlib_1.constants.Z_SYNC_FLUSH }) : encoding === "br" ? (0, node_zlib_1.createBrotliDecompress)({ flush: node_zlib_1.constants.BROTLI_OPERATION_FLUSH, finishFlush: node_zlib_1.constants.BROTLI_OPERATION_FLUSH }) : encoding === "deflate" ? (0, node_zlib_1.createInflate)() : null;
|
|
2064
|
+
const body = decoder ? (0, node_stream_1.pipeline)(res, decoder, (err) => {
|
|
2065
|
+
if (err)
|
|
2066
|
+
fail(err);
|
|
2067
|
+
}) : res;
|
|
2068
|
+
const parked = [];
|
|
2069
|
+
let paused = false;
|
|
2070
|
+
let ended = false;
|
|
2071
|
+
const take = (chunk) => {
|
|
2072
|
+
if (settled)
|
|
2073
|
+
return;
|
|
2074
|
+
if (paused) {
|
|
2075
|
+
parked.push(chunk);
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
if (held + chunk.length > MAX_GUARD_HOP_BYTES) {
|
|
2079
|
+
fail(overCap());
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
const oldest = hopsInFlight.values().next().value === id;
|
|
2083
|
+
const room = oldest ? MAX_GUARD_BUFFERED_BYTES : MAX_GUARD_BUFFERED_BYTES - MAX_GUARD_HOP_BYTES;
|
|
2084
|
+
if (hopBytesInFlight + chunk.length > room) {
|
|
2085
|
+
paused = true;
|
|
2086
|
+
body.pause();
|
|
2087
|
+
parked.push(chunk);
|
|
2088
|
+
hopBudgetWaiters.push(drain);
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
hopBytesInFlight += chunk.length;
|
|
2092
|
+
held += chunk.length;
|
|
2093
|
+
chunks.push(chunk);
|
|
2094
|
+
};
|
|
2095
|
+
const drain = () => {
|
|
2096
|
+
paused = false;
|
|
2097
|
+
const queued = parked.splice(0);
|
|
2098
|
+
for (let i = 0; i < queued.length; i++) {
|
|
2099
|
+
take(queued[i]);
|
|
2100
|
+
if (settled)
|
|
2101
|
+
return;
|
|
2102
|
+
if (paused) {
|
|
2103
|
+
parked.push(...queued.slice(i + 1));
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (ended)
|
|
2108
|
+
finish();
|
|
2109
|
+
else
|
|
2110
|
+
body.resume();
|
|
2111
|
+
};
|
|
2112
|
+
body.on("data", take);
|
|
2113
|
+
body.on("error", (err) => fail(err));
|
|
2114
|
+
res.on("error", (err) => fail(err));
|
|
2115
|
+
body.on("end", () => {
|
|
2116
|
+
ended = true;
|
|
2117
|
+
if (!paused)
|
|
2118
|
+
finish();
|
|
2119
|
+
});
|
|
2120
|
+
const finish = () => {
|
|
2121
|
+
if (settled)
|
|
2122
|
+
return;
|
|
2123
|
+
settled = true;
|
|
2124
|
+
clearTimeout(timer);
|
|
2125
|
+
const kept = held;
|
|
2126
|
+
let released = false;
|
|
2127
|
+
const headers = [];
|
|
2128
|
+
for (let i = 0; i + 1 < res.rawHeaders.length; i += 2)
|
|
2129
|
+
headers.push([res.rawHeaders[i], res.rawHeaders[i + 1]]);
|
|
2130
|
+
resolve({
|
|
2131
|
+
status: res.statusCode ?? 0,
|
|
2132
|
+
headers,
|
|
2133
|
+
body: Buffer.concat(chunks, kept),
|
|
2134
|
+
decoded: decoder !== null,
|
|
2135
|
+
release: () => {
|
|
2136
|
+
if (!released) {
|
|
2137
|
+
released = true;
|
|
2138
|
+
releaseHopBytes(id, kept);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
};
|
|
2143
|
+
});
|
|
2144
|
+
req.on("error", (err) => fail(err));
|
|
2145
|
+
req.end(hop.body ?? void 0);
|
|
2146
|
+
} catch (err) {
|
|
2147
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
function contextIgnoresHttpsErrors(context) {
|
|
2152
|
+
try {
|
|
2153
|
+
return context._options?.ignoreHTTPSErrors === true;
|
|
2154
|
+
} catch {
|
|
2155
|
+
return false;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
function hopRequestHeaders(pageHeaders, cookie) {
|
|
2159
|
+
const out = { accept: "*/*" };
|
|
2160
|
+
for (const [name, value] of Object.entries(pageHeaders)) {
|
|
2161
|
+
const key = name.toLowerCase();
|
|
2162
|
+
if (key.startsWith(":") || HOP_ONLY_HEADERS.has(key) || key === "cookie")
|
|
2163
|
+
continue;
|
|
2164
|
+
out[key] = value;
|
|
2165
|
+
}
|
|
2166
|
+
out["accept-encoding"] = "gzip, deflate, br";
|
|
2167
|
+
if (cookie)
|
|
2168
|
+
out.cookie = cookie;
|
|
2169
|
+
return out;
|
|
2170
|
+
}
|
|
2171
|
+
function cookiePairs(header) {
|
|
2172
|
+
return header.split(";").flatMap((part) => {
|
|
2173
|
+
const eq = part.indexOf("=");
|
|
2174
|
+
return eq > 0 ? [[part.slice(0, eq).trim(), part.slice(eq + 1).trim()]] : [];
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
function hopCookie(pageCookie, contextCookies, setCookies) {
|
|
2178
|
+
const jar = /* @__PURE__ */ new Map();
|
|
2179
|
+
if (pageCookie)
|
|
2180
|
+
for (const [name, value] of cookiePairs(pageCookie))
|
|
2181
|
+
jar.set(name, value);
|
|
2182
|
+
else
|
|
2183
|
+
for (const c of contextCookies)
|
|
2184
|
+
jar.set(c.name, c.value);
|
|
2185
|
+
for (const line of setCookies) {
|
|
2186
|
+
const [pair, ...attrs] = line.split(";");
|
|
2187
|
+
const eq = pair.indexOf("=");
|
|
2188
|
+
if (eq <= 0)
|
|
2189
|
+
continue;
|
|
2190
|
+
const name = pair.slice(0, eq).trim();
|
|
2191
|
+
const gone = attrs.some((a) => {
|
|
2192
|
+
const [k, v = ""] = a.split("=");
|
|
2193
|
+
const key = k.trim().toLowerCase();
|
|
2194
|
+
return key === "max-age" && Number(v.trim()) <= 0 || key === "expires" && Date.parse(v.trim()) < Date.now();
|
|
2195
|
+
});
|
|
2196
|
+
if (gone)
|
|
2197
|
+
jar.delete(name);
|
|
2198
|
+
else
|
|
2199
|
+
jar.set(name, pair.slice(eq + 1).trim());
|
|
2200
|
+
}
|
|
2201
|
+
return [...jar].map(([name, value]) => `${name}=${value}`).join("; ");
|
|
2202
|
+
}
|
|
2203
|
+
function setCookiesOf(answer) {
|
|
2204
|
+
return answer.headers.filter(([name]) => name.toLowerCase() === "set-cookie").map(([, value]) => value);
|
|
2205
|
+
}
|
|
2206
|
+
function fulfillHeaders(answer, earlierSetCookies) {
|
|
2207
|
+
const out = {};
|
|
2208
|
+
const cookies = [...earlierSetCookies];
|
|
2209
|
+
for (const [name, value] of answer.headers) {
|
|
2210
|
+
const key = name.toLowerCase();
|
|
2211
|
+
if (HOP_ONLY_HEADERS.has(key) || answer.decoded && key === "content-encoding")
|
|
2212
|
+
continue;
|
|
2213
|
+
if (key === "set-cookie") {
|
|
2214
|
+
cookies.push(value);
|
|
2215
|
+
continue;
|
|
2216
|
+
}
|
|
2217
|
+
out[key] = key in out ? `${out[key]}, ${value}` : value;
|
|
2218
|
+
}
|
|
2219
|
+
if (cookies.length > 0)
|
|
2220
|
+
out["set-cookie"] = cookies.join("\n");
|
|
2221
|
+
return out;
|
|
2222
|
+
}
|
|
1393
2223
|
var cutAtClose = /* @__PURE__ */ new WeakSet();
|
|
1394
|
-
async function sendCheckingRedirect(route, guard) {
|
|
1395
|
-
const
|
|
2224
|
+
async function sendCheckingRedirect(route, guard, context) {
|
|
2225
|
+
const request = route.request();
|
|
2226
|
+
const first = factsOf(request);
|
|
2227
|
+
const deadline = Date.now() + guard.redirectCheckTimeoutMs;
|
|
1396
2228
|
let facts = first;
|
|
2229
|
+
let pageHeaders = {};
|
|
2230
|
+
let postBody = null;
|
|
2231
|
+
const setCookies = [];
|
|
1397
2232
|
for (let hop = 0; ; hop++) {
|
|
1398
|
-
let
|
|
2233
|
+
let answer;
|
|
1399
2234
|
try {
|
|
1400
|
-
|
|
2235
|
+
if (hop === 0) {
|
|
2236
|
+
pageHeaders = request.headers();
|
|
2237
|
+
postBody = request.postDataBuffer();
|
|
2238
|
+
}
|
|
2239
|
+
const pageCookie = hop === 0 ? Object.entries(pageHeaders).find(([name]) => name.toLowerCase() === "cookie")?.[1] : void 0;
|
|
2240
|
+
const cookie = hopCookie(pageCookie, pageCookie ? [] : await context.cookies(facts.url), setCookies);
|
|
2241
|
+
answer = await sendGuardHop({
|
|
2242
|
+
url: facts.url,
|
|
2243
|
+
method: facts.method,
|
|
2244
|
+
headers: hopRequestHeaders(pageHeaders, cookie),
|
|
2245
|
+
body: postBody,
|
|
2246
|
+
ignoreHTTPSErrors: contextIgnoresHttpsErrors(context),
|
|
2247
|
+
deadline
|
|
2248
|
+
});
|
|
1401
2249
|
} catch (err) {
|
|
1402
2250
|
if (cutAtClose.has(route))
|
|
1403
2251
|
return;
|
|
1404
|
-
guard.noteRedirectCheckFailed(facts, err,
|
|
2252
|
+
guard.noteRedirectCheckFailed(facts, err, guard.redirectCheckTimeoutMs);
|
|
1405
2253
|
await route.abort("failed").catch(() => {
|
|
1406
2254
|
});
|
|
1407
2255
|
return;
|
|
1408
2256
|
}
|
|
1409
|
-
if (cutAtClose.has(route))
|
|
1410
|
-
return;
|
|
1411
|
-
const status = response.status();
|
|
1412
|
-
const location = response.headers()["location"];
|
|
1413
|
-
const next = REDIRECT_STATUSES.has(status) && location ? redirectFacts(facts, status, location) : null;
|
|
1414
|
-
if (!next) {
|
|
1415
|
-
await route.fulfill({ response }).catch(() => {
|
|
1416
|
-
});
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1419
|
-
let decision;
|
|
1420
2257
|
try {
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
if (
|
|
1427
|
-
|
|
1428
|
-
|
|
2258
|
+
if (cutAtClose.has(route))
|
|
2259
|
+
return;
|
|
2260
|
+
const status = answer.status;
|
|
2261
|
+
const location = answer.headers.find(([name]) => name.toLowerCase() === "location")?.[1];
|
|
2262
|
+
const next = REDIRECT_STATUSES.has(status) && location ? redirectFacts(facts, status, location) : null;
|
|
2263
|
+
if (!next) {
|
|
2264
|
+
await route.fulfill({ status, headers: fulfillHeaders(answer, setCookies), body: answer.body }).catch(() => {
|
|
2265
|
+
});
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
let decision;
|
|
2269
|
+
try {
|
|
2270
|
+
decision = guard.decide(next);
|
|
2271
|
+
} catch {
|
|
2272
|
+
decision = { action: "write", rule: "decision-error", record: { method: next.method, host: "", path: (0, survey_network_guard_js_1.maskedPath)(next.url), keys: [], rule: "decision-error" } };
|
|
2273
|
+
}
|
|
2274
|
+
if (decision.action === "write") {
|
|
2275
|
+
if (decision.record)
|
|
2276
|
+
guard.note(decision.record);
|
|
2277
|
+
if (guard.holds(decision.rule)) {
|
|
2278
|
+
await route.abort("blockedbyclient").catch(() => {
|
|
2279
|
+
});
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
if (READ_METHODS.has(next.method.toUpperCase())) {
|
|
2284
|
+
await route.fulfill({ status, headers: { ...fulfillHeaders(answer, setCookies), location: next.url }, body: answer.body }).catch(() => {
|
|
2285
|
+
});
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
if (!sameOrigin(next.url, first.url)) {
|
|
2289
|
+
const target = new URL(next.url);
|
|
2290
|
+
guard.note(describe(next, target, isAppSite(target.hostname.toLowerCase(), guard.policy) ? "app-write" : "third-party-write"));
|
|
1429
2291
|
await route.abort("blockedbyclient").catch(() => {
|
|
1430
2292
|
});
|
|
1431
2293
|
return;
|
|
1432
2294
|
}
|
|
2295
|
+
if (hop + 1 >= MAX_GUARD_REDIRECTS) {
|
|
2296
|
+
await route.abort("failed").catch(() => {
|
|
2297
|
+
});
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
setCookies.push(...setCookiesOf(answer));
|
|
2301
|
+
facts = next;
|
|
2302
|
+
} finally {
|
|
2303
|
+
answer.release();
|
|
1433
2304
|
}
|
|
1434
|
-
if (READ_METHODS.has(next.method.toUpperCase())) {
|
|
1435
|
-
await route.fulfill({ response, headers: { ...response.headers(), location: next.url } }).catch(() => {
|
|
1436
|
-
});
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
if (!sameOrigin(next.url, first.url)) {
|
|
1440
|
-
const target = new URL(next.url);
|
|
1441
|
-
guard.note(describe(next, target, isAppSite(target.hostname.toLowerCase(), guard.policy) ? "app-write" : "third-party-write"));
|
|
1442
|
-
await route.abort("blockedbyclient").catch(() => {
|
|
1443
|
-
});
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
if (hop + 1 >= MAX_GUARD_REDIRECTS) {
|
|
1447
|
-
await route.abort("failed").catch(() => {
|
|
1448
|
-
});
|
|
1449
|
-
return;
|
|
1450
|
-
}
|
|
1451
|
-
facts = next;
|
|
1452
2305
|
}
|
|
1453
2306
|
}
|
|
1454
2307
|
function sameRouteUrl(a, b) {
|
|
@@ -1468,8 +2321,9 @@ var require_run_write_guard = __commonJS({
|
|
|
1468
2321
|
};
|
|
1469
2322
|
Object.assign(context, {
|
|
1470
2323
|
route: async (url, handler, options) => {
|
|
1471
|
-
await route(url, handler, options);
|
|
2324
|
+
const disposable = await route(url, handler, options);
|
|
1472
2325
|
others.push({ url, handler });
|
|
2326
|
+
return disposable;
|
|
1473
2327
|
},
|
|
1474
2328
|
unroute: async (url, handler) => {
|
|
1475
2329
|
if (handler === void 0 && sameRouteUrl(url, GUARD_ROUTE)) {
|
|
@@ -1662,7 +2516,7 @@ var require_run_write_guard = __commonJS({
|
|
|
1662
2516
|
}
|
|
1663
2517
|
}
|
|
1664
2518
|
if (decision.action === "allow" && !READ_METHODS.has(request.method().toUpperCase()) && checksRedirect(decision.rule, request.url(), guard.policy)) {
|
|
1665
|
-
await sendCheckingRedirect(route, guard);
|
|
2519
|
+
await sendCheckingRedirect(route, guard, context);
|
|
1666
2520
|
return;
|
|
1667
2521
|
}
|
|
1668
2522
|
await route.fallback().catch(() => {
|
|
@@ -1802,6 +2656,7 @@ var require_run_write_guard = __commonJS({
|
|
|
1802
2656
|
}
|
|
1803
2657
|
function buildRunWriteGuardHook(policy, modulePath, signInSecretNames = [], opts = {}) {
|
|
1804
2658
|
const withContext = opts.browserContext !== false;
|
|
2659
|
+
const hookTest = opts.hookTest ?? "test";
|
|
1805
2660
|
const names = signInSecretNames.filter((n) => /^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(n)).slice(0, 16);
|
|
1806
2661
|
return `
|
|
1807
2662
|
import { createRequire as __qaWriteGuardRequire } from 'node:module';
|
|
@@ -1824,7 +2679,7 @@ try {
|
|
|
1824
2679
|
} catch (err) {
|
|
1825
2680
|
__qaWriteGuardError = err;
|
|
1826
2681
|
}
|
|
1827
|
-
|
|
2682
|
+
${hookTest}.beforeEach(async (${withContext ? "{ context }" : ""}) => {
|
|
1828
2683
|
try {
|
|
1829
2684
|
if (__qaWriteGuardError) throw __qaWriteGuardError;
|
|
1830
2685
|
${withContext ? "await __qaWriteGuard.installRunWriteGuard(context, __qaWriteGuardPolicy, { onLog: (line) => console.log(line), signInSecrets: __qaWriteGuardSecrets });" : "// an @api test: no browser context, only Node's fetch is guarded"}
|
|
@@ -1833,7 +2688,7 @@ test.beforeEach(async (${withContext ? "{ context }" : ""}) => {
|
|
|
1833
2688
|
${policy.mode === "block" ? "throw err;" : "// observe: never fail the test"}
|
|
1834
2689
|
}
|
|
1835
2690
|
});${withContext && policy.mode === "block" ? `
|
|
1836
|
-
|
|
2691
|
+
${hookTest}.afterEach(async ({ context }) => {
|
|
1837
2692
|
try { if (__qaWriteGuard) await __qaWriteGuard.clearRunWriteRelay(context); } catch { /* the context closes next */ }
|
|
1838
2693
|
});` : ""}
|
|
1839
2694
|
`;
|