@validate.qa/runner 1.0.100 → 1.0.101

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.
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  require_survey_network_guard
3
- } from "./chunk-GIIIVO2W.mjs";
3
+ } from "./chunk-SN3PN4G4.mjs";
4
4
  import {
5
5
  dist_exports,
6
6
  init_dist
7
- } from "./chunk-MBKGTELT.mjs";
7
+ } from "./chunk-IGXGSDE3.mjs";
8
8
  import {
9
9
  __commonJS,
10
10
  __require,
@@ -365,6 +365,7 @@ var require_run_write_guard = __commonJS({
365
365
  exports.addRunWriteAuthHandler = addRunWriteAuthHandler;
366
366
  exports.runWriteContextOptions = runWriteContextOptions;
367
367
  exports.clearRunWriteRelay = clearRunWriteRelay;
368
+ exports.runWriteGuardHopBytesInFlight = runWriteGuardHopBytesInFlight;
368
369
  exports.installRunWriteGuard = installRunWriteGuard;
369
370
  exports.installNodeFetchGuard = installNodeFetchGuard;
370
371
  exports.runWriteGuardRefusal = runWriteGuardRefusal;
@@ -376,7 +377,11 @@ var require_run_write_guard = __commonJS({
376
377
  exports.safeWritePatternFor = safeWritePatternFor;
377
378
  var node_async_hooks_1 = __require("async_hooks");
378
379
  var node_crypto_1 = __require("crypto");
380
+ var node_http_1 = __require("http");
381
+ var node_https_1 = __require("https");
379
382
  var node_path_1 = __require("path");
383
+ var node_stream_1 = __require("stream");
384
+ var node_zlib_1 = __require("zlib");
380
385
  var shared_1 = (init_dist(), __toCommonJS(dist_exports));
381
386
  var survey_network_guard_js_1 = require_survey_network_guard();
382
387
  var bounded_output_js_1 = require_bounded_output();
@@ -424,12 +429,13 @@ var require_run_write_guard = __commonJS({
424
429
  allowPatterns,
425
430
  ignoredAllowPatterns,
426
431
  recordOnlyRules,
427
- reason: v.reason.slice(0, MAX_REASON_CHARS)
432
+ reason: v.reason.slice(0, MAX_REASON_CHARS),
433
+ ...v.pwAgentsGuard === "off" ? { pwAgentsGuard: "off" } : {}
428
434
  };
429
435
  }
430
436
  var policyScope = new node_async_hooks_1.AsyncLocalStorage();
431
437
  var MIN_SIGN_IN_SECRET_CHARS = 6;
432
- exports.SIGN_IN_SECRET_VARIABLE = /PASS(?:WORD|CODE|PHRASE)?$/i;
438
+ exports.SIGN_IN_SECRET_VARIABLE = /(?:PASS(?:WORD|CODE|PHRASE)?|[A-Z0-9]_PASSWD|[A-Z0-9]_PWD)$/i;
433
439
  function signInSecretsOf(vars) {
434
440
  if (!vars || typeof vars !== "object")
435
441
  return [];
@@ -446,6 +452,7 @@ var require_run_write_guard = __commonJS({
446
452
  }
447
453
  var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
448
454
  var MAX_BODY_CHARS = 262144;
455
+ var MAX_SIGN_IN_LOG_LINES = 20;
449
456
  var CAPTCHA_HOSTS = ["recaptcha.net", "hcaptcha.com", "challenges.cloudflare.com"];
450
457
  var SEARCH_POST_HOSTS = ["algolia.net", "algolianet.com", "algolia.io"];
451
458
  var PAYMENT_HOSTS = [
@@ -471,7 +478,7 @@ var require_run_write_guard = __commonJS({
471
478
  var SEARCH_LAST_SEGMENTS = /* @__PURE__ */ new Set(["search", "autocomplete", "suggest", "suggestions", "typeahead"]);
472
479
  var PROVIDER_SIGN_UP_PATH = /(?:\/accounts:signup|\/dbconnections\/signup|\/auth\/v1\/signup|\/v1\/client\/sign_ups)(?:[/?]|$)/i;
473
480
  var FIRST_PARTY_SIGN_UP_SEGMENTS = /* @__PURE__ */ new Set(["signup", "sign-up", "sign_up", "register", "registration", "create-account", "createaccount", "sign_ups"]);
474
- var NOT_A_SIGN_IN_SEGMENTS = /* @__PURE__ */ new Set([
481
+ var NOT_A_SIGN_IN_WORDS = /* @__PURE__ */ new Set([
475
482
  "delete",
476
483
  "destroy",
477
484
  "remove",
@@ -480,19 +487,29 @@ var require_run_write_guard = __commonJS({
480
487
  "cancel",
481
488
  "password",
482
489
  "passwords",
483
- "change-password",
484
- "change_password",
485
490
  "reset",
486
- "reset-password",
487
491
  "update",
488
492
  "logout",
489
493
  "signout",
490
- "sign-out",
491
494
  "revoke",
492
495
  "transfer",
493
- "withdraw"
496
+ "withdraw",
497
+ "disable",
498
+ "enable",
499
+ "unlink",
500
+ "change",
501
+ "erase",
502
+ "purge",
503
+ "wipe",
504
+ "terminate",
505
+ "suspend",
506
+ "leave",
507
+ "disconnect",
508
+ "deauthorize"
494
509
  ]);
495
- var SIGN_IN_SHAPED_SEGMENTS = /* @__PURE__ */ new Set([
510
+ var ACCOUNT_CHANGE_WORDS = /* @__PURE__ */ new Set(["email", "mfa", "2fa", "totp", "otp"]);
511
+ var ACCOUNT_CHANGE_CONTEXT = /* @__PURE__ */ new Set(["account", "owner", "ownership", "invite"]);
512
+ var SIGN_IN_LAST_SEGMENTS = /* @__PURE__ */ new Set([
496
513
  "session",
497
514
  "sessions",
498
515
  "login",
@@ -502,25 +519,196 @@ var require_run_write_guard = __commonJS({
502
519
  "signin",
503
520
  "sign-in",
504
521
  "sign_in",
505
- "auth",
506
522
  "authenticate",
507
523
  "authentication",
508
- "token",
509
- "tokens",
510
- "oauth",
511
- "oauth2",
512
- "sso",
513
- "identity",
514
- "credentials"
524
+ "auth"
515
525
  ]);
516
- var GRAPHQL_SIGN_IN = /"operationName"\s*:\s*"(?:log_?in|sign_?in|authenticate|login\w{0,40}|signin\w{0,40})"|\bmutation\b[^{]{0,200}\{\s*(?:log_?in|sign_?in|authenticate|login\w{0,40}|signin\w{0,40})\s*[({]/i;
517
- function isCredentialSignInShape(pathname, postData) {
518
- const segs = segmentsOf(pathname);
519
- if (segs.some((seg) => NOT_A_SIGN_IN_SEGMENTS.has(seg)))
526
+ var SIGN_IN_VERB_SEGMENTS = /* @__PURE__ */ new Set(["login", "log-in", "log_in", "signin", "sign-in", "sign_in"]);
527
+ var SIGN_IN_CREDENTIAL_KINDS = /* @__PURE__ */ new Set(["email", "username", "phone", "password"]);
528
+ var SIGN_IN_TAILS = /* @__PURE__ */ new Set(["callback/credentials", "auth/local", "session/new", "sessions/new"]);
529
+ var PASSWORD_GRANT = /(?:^|[&{,\s"])grant_type"?\s*[=:]\s*"?password(?=["&,}\s]|$)/;
530
+ var NEW_CREDENTIAL_STEM = "(?:pass(?:word|wd)?|pwd)";
531
+ 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)?)`;
532
+ var NEW_CREDENTIAL_NAME = new RegExp(`^${NEW_CREDENTIAL_WORD}$`, "i");
533
+ var NEW_CREDENTIAL_KEY = new RegExp(`(?:^|["&{,\\s[])${NEW_CREDENTIAL_WORD}(?:["\\]]?\\s*[:=]|"\\s*(?:;|$))`, "im");
534
+ var PASSWORD_KEY_WORDS = /* @__PURE__ */ new Set(["password", "passwd", "pwd", "pass"]);
535
+ var PASSWORD_KEY_QUALIFIERS = /* @__PURE__ */ new Set([
536
+ "new",
537
+ "old",
538
+ "current",
539
+ "confirm",
540
+ "confirmed",
541
+ "confirmation",
542
+ "repeat",
543
+ "repeated",
544
+ "verify",
545
+ "verification",
546
+ "again",
547
+ "retype",
548
+ "check",
549
+ "2"
550
+ ]);
551
+ function isPasswordKey(key) {
552
+ const words = wordsOf(key);
553
+ return words.some((w) => PASSWORD_KEY_WORDS.has(w)) && words.every((w) => PASSWORD_KEY_WORDS.has(w) || PASSWORD_KEY_QUALIFIERS.has(w));
554
+ }
555
+ function couldBePassword(value) {
556
+ if (value === void 0)
557
+ return true;
558
+ if (typeof value === "number")
559
+ return Number.isFinite(value);
560
+ if (typeof value !== "string")
561
+ return false;
562
+ const v = value.trim();
563
+ return v !== "" && !/^(?:true|false|on|off|yes|no|null)$/i.test(v);
564
+ }
565
+ var MAX_CREDENTIAL_KEYS = 200;
566
+ function credentialKeysOf(body, contentType) {
567
+ const type = (contentType ?? "").toLowerCase();
568
+ const keys = [];
569
+ try {
570
+ if (type.includes("json") || /^\s*[[{]/.test(body)) {
571
+ const stack = [{ value: JSON.parse(body), under: null }];
572
+ while (stack.length > 0 && keys.length < MAX_CREDENTIAL_KEYS) {
573
+ const { value, under } = stack.pop();
574
+ if (Array.isArray(value))
575
+ stack.push(...value.slice(0, MAX_CREDENTIAL_KEYS).map((inner) => ({ value: inner, under: null })));
576
+ else if (value && typeof value === "object") {
577
+ for (const [key, inner] of Object.entries(value).slice(0, MAX_CREDENTIAL_KEYS)) {
578
+ const name = under === null ? key : `${under}_${key}`;
579
+ keys.push({ name, value: inner });
580
+ if (Array.isArray(inner) && isPasswordKey(name)) {
581
+ for (const item of inner.slice(0, MAX_CREDENTIAL_KEYS))
582
+ if (item === null || typeof item !== "object")
583
+ keys.push({ name, value: item });
584
+ }
585
+ const object = inner !== null && typeof inner === "object" && !Array.isArray(inner);
586
+ stack.push({ value: inner, under: object && isPasswordKey(name) ? name : null });
587
+ }
588
+ }
589
+ }
590
+ } else if (type.includes("multipart/form-data")) {
591
+ for (const m of body.matchAll(/(?:^|[;\s])name="([^"]{1,200})"/g))
592
+ if (keys.push({ name: m[1], value: void 0 }) >= MAX_CREDENTIAL_KEYS)
593
+ break;
594
+ } else if (type.includes("x-www-form-urlencoded")) {
595
+ for (const [name, value] of new URLSearchParams(body))
596
+ if (keys.push({ name, value }) >= MAX_CREDENTIAL_KEYS)
597
+ break;
598
+ }
599
+ } catch {
600
+ }
601
+ return keys.slice(0, MAX_CREDENTIAL_KEYS).map(({ name, value }) => {
602
+ const bare = name.replace(/\[\]$/, "");
603
+ const parts = bare.match(/[^[\]]+/g);
604
+ if (bare.endsWith("]") && parts && parts.length > 1) {
605
+ const inner = parts[parts.length - 1];
606
+ const outer = parts[parts.length - 2];
607
+ if (isPasswordKey(outer)) {
608
+ if (/^\d+$/.test(inner))
609
+ return { name: outer, value };
610
+ if (isPasswordKey(inner) || NEW_CREDENTIAL_NAME.test(inner))
611
+ return { name: inner, value };
612
+ return { name: `${outer}_${inner}`, value };
613
+ }
614
+ }
615
+ return { name: /\[([^[\]]*)\]$/.exec(bare)?.[1] ?? bare, value };
616
+ });
617
+ }
618
+ function namesNewCredential(body, contentType) {
619
+ if (NEW_CREDENTIAL_KEY.test(body))
620
+ return true;
621
+ const type = contentType ?? (/^\s*[[{]/.test(body) ? null : "application/x-www-form-urlencoded");
622
+ const keys = credentialKeysOf(body, type);
623
+ if (keys.some(({ name }) => NEW_CREDENTIAL_NAME.test(name)))
624
+ return true;
625
+ const found = keys.filter(({ name, value }) => isPasswordKey(name) && couldBePassword(value));
626
+ const isBare = (name) => wordsOf(name).every((w) => PASSWORD_KEY_WORDS.has(w));
627
+ const named = new Set(found.filter(({ name }) => !isBare(name)).map(({ name }) => name.toLowerCase()));
628
+ const bare = new Set(found.filter(({ name }) => isBare(name)).map(({ name, value }) => value === void 0 ? `name:${name.toLowerCase()}` : `value:${String(value)}`));
629
+ return named.size + Math.min(bare.size, 2) >= 2;
630
+ }
631
+ var GRAPHQL_SIGN_IN_NAME = /^(?:log_?in|sign_?in|authenticate|login\w{0,40}|signin\w{0,40})$/i;
632
+ function wordsOf(segment) {
633
+ return segment.replace(/([a-z])(?=[A-Z0-9])/g, "$1 ").toLowerCase().split(/[\s._/-]+/).filter(Boolean);
634
+ }
635
+ function rawSegmentsOf(pathname) {
636
+ return pathname.split("/").map((seg) => {
637
+ const raw = seg.split(";")[0];
638
+ try {
639
+ return decodeURIComponent(raw);
640
+ } catch {
641
+ return raw;
642
+ }
643
+ }).filter(Boolean);
644
+ }
645
+ function changesSomething(segments, opts = {}) {
646
+ const words = segments.flatMap(wordsOf);
647
+ const joined = segments.map((seg) => wordsOf(seg).join(""));
648
+ const hard = (w) => NOT_A_SIGN_IN_WORDS.has(w) && !(opts.passwordIsCredential && (w === "password" || w === "passwords"));
649
+ if (words.some(hard) || joined.some(hard))
650
+ return true;
651
+ return words.some((w) => ACCOUNT_CHANGE_WORDS.has(w)) && words.some((w) => ACCOUNT_CHANGE_CONTEXT.has(w));
652
+ }
653
+ function isGraphqlSignInName(name) {
654
+ return GRAPHQL_SIGN_IN_NAME.test(name) && !changesSomething([name], { passwordIsCredential: true });
655
+ }
656
+ function graphqlSignIn(body) {
657
+ let parsed;
658
+ try {
659
+ parsed = JSON.parse(body);
660
+ } catch {
661
+ parsed = { query: body };
662
+ }
663
+ const entries = Array.isArray(parsed) ? parsed : [parsed];
664
+ let signIns = 0;
665
+ for (const entry of entries) {
666
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
667
+ return null;
668
+ const { query, operationName } = entry;
669
+ if (typeof query !== "string") {
670
+ if (!Array.isArray(parsed) && query === void 0 && typeof operationName === "string" && isGraphqlSignInName(operationName))
671
+ return "persisted";
672
+ return null;
673
+ }
674
+ const ops = (0, survey_network_guard_js_1.graphqlOperationRoots)(query);
675
+ if (!ops)
676
+ return null;
677
+ for (const op of ops) {
678
+ if (op.kind === "query")
679
+ continue;
680
+ if (typeof operationName === "string" && changesSomething([operationName], { passwordIsCredential: true }))
681
+ return null;
682
+ const roots = op.roots.filter((root) => root !== "__typename");
683
+ if (roots.length === 0 || !roots.every(isGraphqlSignInName))
684
+ return null;
685
+ signIns += roots.length;
686
+ }
687
+ }
688
+ return signIns > 0 ? "document" : null;
689
+ }
690
+ function isCredentialSignInShape(pathname, search, postData, contentType) {
691
+ if (typeof postData === "string" && postData.length > MAX_BODY_CHARS)
692
+ return false;
693
+ const body = typeof postData === "string" && postData.length <= MAX_BODY_CHARS ? postData : null;
694
+ if (body !== null && namesNewCredential(body, contentType))
520
695
  return false;
521
- if (segs.some((seg) => SIGN_IN_SHAPED_SEGMENTS.has(seg)))
696
+ const raw = rawSegmentsOf(pathname);
697
+ const segs = raw.map((seg) => seg.toLowerCase());
698
+ const last = segs[segs.length - 1] ?? "";
699
+ const prev = segs[segs.length - 2] ?? "";
700
+ if (SIGN_IN_VERB_SEGMENTS.has(prev) && SIGN_IN_CREDENTIAL_KINDS.has(last))
701
+ return !changesSomething(raw, { passwordIsCredential: true });
702
+ if (changesSomething(raw))
703
+ return false;
704
+ if (SIGN_IN_LAST_SEGMENTS.has(last) || SIGN_IN_TAILS.has(`${prev}/${last}`))
522
705
  return true;
523
- return isGraphqlPath(pathname) && typeof postData === "string" && postData.length <= MAX_BODY_CHARS && GRAPHQL_SIGN_IN.test(postData);
706
+ if (last === "token")
707
+ return search.get("grant_type") === "password" || body !== null && PASSWORD_GRANT.test(body);
708
+ if (!isGraphqlPath(pathname) || body === null || (0, survey_network_guard_js_1.graphqlUrlCarriesQuery)(search))
709
+ return false;
710
+ const graphql = graphqlSignIn(body);
711
+ return graphql === "persisted" ? graphql : graphql === "document";
524
712
  }
525
713
  function bodyHasSignInSecret(body, secrets) {
526
714
  if (!body || body.length > MAX_BODY_CHARS || secrets.length === 0)
@@ -548,7 +736,7 @@ var require_run_write_guard = __commonJS({
548
736
  var COGNITO_HOSTED_UI_RE = /^\/(?:oauth2\/(?:token|authorize|userinfo|idpresponse)|saml2\/idpresponse|login|logincontinue)$/;
549
737
  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
738
  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))$/;
739
+ 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
740
  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
741
  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
742
  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 +802,26 @@ var require_run_write_guard = __commonJS({
614
802
  return GENERIC_IDP_AUTH_RE.test(path);
615
803
  }
616
804
  function isAuthProviderWrite(method, host, pathname, amzTarget, search) {
805
+ if (!isPlainPost(method, search))
806
+ return false;
807
+ if (!(0, shared_1.isAuthProviderHost)(host))
808
+ return false;
809
+ return isAuthProviderSignIn(host.toLowerCase(), pathname, amzTarget, search);
810
+ }
811
+ function isPlainPost(method, search) {
617
812
  if (method.toUpperCase() !== "POST")
618
813
  return false;
619
814
  const tunnelled = search.get("_method");
620
- if (tunnelled !== null && tunnelled.toUpperCase() !== "POST")
815
+ return tunnelled === null || tunnelled.toUpperCase() === "POST";
816
+ }
817
+ function isAuth0CustomDomainSignIn(method, pathname, search) {
818
+ if (!isPlainPost(method, search))
621
819
  return false;
622
- if (!(0, shared_1.isAuthProviderHost)(host))
820
+ const path = pathname.toLowerCase();
821
+ if (!AUTH0_AUTH_RE.test(path))
623
822
  return false;
624
- return isAuthProviderSignIn(host.toLowerCase(), pathname, amzTarget, search);
823
+ const needsState = path.startsWith("/u/") || path === "/authorize" || path === "/authorize/resume";
824
+ return !needsState || (search.get("state") ?? "") !== "";
625
825
  }
626
826
  function isSignUp(host, pathname, amzTarget) {
627
827
  if (PROVIDER_SIGN_UP_PATH.test(pathname))
@@ -747,8 +947,7 @@ var require_run_write_guard = __commonJS({
747
947
  const host = url.hostname.toLowerCase().replace(/\.$/, "");
748
948
  const pathname = url.pathname;
749
949
  if (READ_METHODS.has(method)) {
750
- const doc = method === "GET" && isGraphqlPath(pathname) ? url.searchParams.get("query") : null;
751
- if (doc !== null && !(0, survey_network_guard_js_1.graphqlDocumentIsQueryOnly)(doc)) {
950
+ if (method === "GET" && isGraphqlPath(pathname) && (0, survey_network_guard_js_1.graphqlGetMayWrite)(url, facts.postData)) {
752
951
  return { action: "write", rule: "graphql-get-mutation", record: describe(facts, url, "graphql-get-mutation") };
753
952
  }
754
953
  if (method !== "OPTIONS" && isAppSite(host, policy)) {
@@ -781,15 +980,17 @@ var require_run_write_guard = __commonJS({
781
980
  return { action: "allow", rule: "auth-provider" };
782
981
  if (appSite && (0, survey_network_guard_js_1.isSignInPathWrite)(method, pathname))
783
982
  return { action: "allow", rule: "sign-in" };
784
- if (appSite && method === "POST" && facts.brokerTokenInBody && !segmentsOf(pathname).some((seg) => NOT_A_SIGN_IN_SEGMENTS.has(seg))) {
983
+ if (appSite && isAuth0CustomDomainSignIn(method, pathname, url.searchParams))
984
+ return { action: "allow", rule: "sign-in" };
985
+ if (appSite && method === "POST" && facts.brokerTokenInBody && !changesSomething(rawSegmentsOf(pathname))) {
785
986
  return { action: "allow", rule: "broker-sign-in" };
786
987
  }
787
988
  if (appSite && isEvidencedTokenRefresh(method, pathname, facts.postData))
788
989
  return { action: "allow", rule: "token-refresh" };
789
- if (appSite && method === "POST" && facts.signInSecretInBody && isCredentialSignInShape(pathname, facts.postData)) {
790
- return { action: "allow", rule: "credential-sign-in" };
791
- }
792
- if (method === "POST" && isGraphqlPath(pathname) && isGraphqlQueryOnlyBody(facts.postData))
990
+ const signIn = appSite && method === "POST" && facts.signInSecretInBody ? isCredentialSignInShape(pathname, url.searchParams, facts.postData, facts.contentType) : false;
991
+ if (signIn)
992
+ return { action: "allow", rule: "credential-sign-in", ...signIn === "persisted" ? { persisted: true } : {} };
993
+ if (method === "POST" && isGraphqlPath(pathname) && !(0, survey_network_guard_js_1.graphqlUrlCarriesQuery)(url.searchParams) && isGraphqlQueryOnlyBody(facts.postData))
793
994
  return { action: "allow", rule: "graphql-query" };
794
995
  if (method === "POST" && hostIs(host, SEARCH_POST_HOSTS))
795
996
  return { action: "allow", rule: "search-post" };
@@ -834,6 +1035,8 @@ var require_run_write_guard = __commonJS({
834
1035
  recordOnly;
835
1036
  log;
836
1037
  signInSecrets;
1038
+ /** The deadline of one request the guard sends itself, all its hops together (sendCheckingRedirect). */
1039
+ redirectCheckTimeoutMs;
837
1040
  /** Tokens the Google sign-in broker handed this context, each until its expiry (epoch ms). */
838
1041
  brokerTokens = [];
839
1042
  /** Origins of the Firebase Auth handlers the broker registered for this context. */
@@ -842,12 +1045,16 @@ var require_run_write_guard = __commonJS({
842
1045
  relayIds = /* @__PURE__ */ new Set();
843
1046
  relayed = 0;
844
1047
  relayRejected = 0;
1048
+ /** Credential sign-ins already said in the run log (observe mode; method, host and path shape). */
1049
+ signInsLogged = /* @__PURE__ */ new Set();
845
1050
  constructor(policy, opts = {}) {
846
1051
  this.policy = policy;
847
1052
  this.compiled = policy.allowPatterns.map(compileAllowPattern).filter((p) => p !== null);
848
1053
  this.recordOnly = new Set((policy.recordOnlyRules ?? exports.DEFAULT_RECORD_ONLY_RULES).filter((rule) => exports.RECORD_ONLY_CAPABLE_RULES.includes(rule)));
849
1054
  this.log = opts.onLog;
850
1055
  this.signInSecrets = (opts.signInSecrets ?? []).filter((v) => typeof v === "string" && v.length >= MIN_SIGN_IN_SECRET_CHARS);
1056
+ const ms = opts.redirectCheckTimeoutMs;
1057
+ this.redirectCheckTimeoutMs = typeof ms === "number" && ms > 0 ? Math.min(ms, REDIRECT_CHECK_TIMEOUT_MS) : REDIRECT_CHECK_TIMEOUT_MS;
851
1058
  }
852
1059
  decide(facts) {
853
1060
  const signInSecretInBody = facts.signInSecretInBody ?? bodyHasSignInSecret(facts.postData, this.signInSecrets);
@@ -870,8 +1077,28 @@ var require_run_write_guard = __commonJS({
870
1077
  consumedBrokerTokens.add(brokerToken.value);
871
1078
  this.brokerTokens = this.brokerTokens.filter((t) => t !== brokerToken);
872
1079
  }
1080
+ if (decision.rule === "credential-sign-in" && this.policy.mode === "observe")
1081
+ this.noteCredentialSignIn(facts, decision.persisted === true);
873
1082
  return decision;
874
1083
  }
1084
+ /**
1085
+ * Observe mode says where each credential sign-in went, once per path and kind (the lead): the
1086
+ * method, host and masked path only, never the body, so the rule can be checked against real runs
1087
+ * before a site is blocked. A GraphQL persisted query is marked unverified.
1088
+ */
1089
+ noteCredentialSignIn(facts, persisted) {
1090
+ let host = "";
1091
+ try {
1092
+ host = new URL(facts.url).hostname.toLowerCase();
1093
+ } catch {
1094
+ return;
1095
+ }
1096
+ const line = `${facts.method.toUpperCase()} ${host}${(0, survey_network_guard_js_1.maskedPath)(facts.url)} [credential-sign-in${persisted ? " (persisted, unverified)" : ""}]`;
1097
+ if (this.signInsLogged.has(line) || this.signInsLogged.size >= MAX_SIGN_IN_LOG_LINES)
1098
+ return;
1099
+ this.signInsLogged.add(line);
1100
+ this.log?.(`[run-write-guard] allowed ${line}`);
1101
+ }
875
1102
  /**
876
1103
  * A code or ID token the Google sign-in broker hands this context: the first POST that carries it
877
1104
  * to the app in the next BROKER_TOKEN_TTL_MS is the sign-in. Anything not shaped like a Google code
@@ -931,8 +1158,9 @@ var require_run_write_guard = __commonJS({
931
1158
  }
932
1159
  /**
933
1160
  * A hop the guard fetched itself (sendCheckingRedirect) failed, so the request fails in the page.
934
- * Said in the run log, with the timeout when it was that: a slow allowed upload or stream is cut
935
- * at REDIRECT_CHECK_TIMEOUT_MS, and the page only sees a network error.
1161
+ * Said in the run log, with the reason when it was the guard's own limit: a slow allowed upload or
1162
+ * stream is cut at its deadline, an answer over MAX_GUARD_HOP_BYTES as it streams in, and the page
1163
+ * only sees a network error.
936
1164
  */
937
1165
  noteRedirectCheckFailed(facts, err, timeoutMs) {
938
1166
  let host = "";
@@ -940,8 +1168,8 @@ var require_run_write_guard = __commonJS({
940
1168
  host = new URL(facts.url).hostname.toLowerCase();
941
1169
  } catch {
942
1170
  }
943
- const timedOut = err instanceof Error && (err.name === "TimeoutError" || /\btimeout\b/i.test(err.message));
944
- const why = timedOut ? `the guard's redirect check timed out after ${Math.round(timeoutMs / 1e3)} s (REDIRECT_CHECK_TIMEOUT_MS)` : "the guard's redirect check failed";
1171
+ const reason = err instanceof GuardHopError ? err.reason : null;
1172
+ 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
1173
  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
1174
  }
947
1175
  /** A request still undecided when its page closed (see guardPageClose): it is failed in the page, so the browser never sends it. */
@@ -1389,66 +1617,321 @@ var require_run_write_guard = __commonJS({
1389
1617
  }
1390
1618
  }
1391
1619
  var MAX_GUARD_REDIRECTS = 5;
1392
- var REDIRECT_CHECK_TIMEOUT_MS = 6e4;
1620
+ var REDIRECT_CHECK_TIMEOUT_MS = 3e4;
1621
+ var MAX_GUARD_HOP_BYTES = 16 * 1024 * 1024;
1622
+ var MAX_GUARD_BUFFERED_BYTES = 128 * 1024 * 1024;
1623
+ var SEND_HTTP = node_http_1.request;
1624
+ var SEND_HTTPS = node_https_1.request;
1625
+ var HOP_ONLY_HEADERS = /* @__PURE__ */ new Set([
1626
+ "connection",
1627
+ "keep-alive",
1628
+ "proxy-connection",
1629
+ "transfer-encoding",
1630
+ "te",
1631
+ "trailer",
1632
+ "upgrade",
1633
+ "proxy-authorization",
1634
+ "proxy-authenticate",
1635
+ "host",
1636
+ "content-length"
1637
+ ]);
1638
+ var hopBytesInFlight = 0;
1639
+ var hopsInFlight = /* @__PURE__ */ new Set();
1640
+ var hopBudgetWaiters = [];
1641
+ function releaseHopBytes(hop, bytes) {
1642
+ hopsInFlight.delete(hop);
1643
+ hopBytesInFlight -= bytes;
1644
+ for (const wake of hopBudgetWaiters.splice(0))
1645
+ wake();
1646
+ }
1647
+ function runWriteGuardHopBytesInFlight() {
1648
+ return hopBytesInFlight;
1649
+ }
1650
+ var GuardHopError = class extends Error {
1651
+ reason;
1652
+ constructor(reason, message) {
1653
+ super(message);
1654
+ this.reason = reason;
1655
+ this.name = "GuardHopError";
1656
+ }
1657
+ };
1658
+ function sendGuardHop(hop) {
1659
+ return new Promise((resolve, reject) => {
1660
+ let url;
1661
+ try {
1662
+ url = new URL(hop.url);
1663
+ } catch (err) {
1664
+ reject(err);
1665
+ return;
1666
+ }
1667
+ const send = url.protocol === "https:" ? SEND_HTTPS : url.protocol === "http:" ? SEND_HTTP : null;
1668
+ if (!send) {
1669
+ reject(new Error(`the guard sends only http(s), not ${url.protocol}`));
1670
+ return;
1671
+ }
1672
+ const id = /* @__PURE__ */ Symbol("guard hop");
1673
+ hopsInFlight.add(id);
1674
+ let held = 0;
1675
+ let settled = false;
1676
+ let req = null;
1677
+ const chunks = [];
1678
+ const fail = (err) => {
1679
+ if (settled)
1680
+ return;
1681
+ settled = true;
1682
+ clearTimeout(timer);
1683
+ req?.destroy();
1684
+ releaseHopBytes(id, held);
1685
+ held = 0;
1686
+ reject(err);
1687
+ };
1688
+ const overCap = () => new GuardHopError("over-cap", `answer over ${MAX_GUARD_HOP_BYTES} bytes`);
1689
+ const timer = setTimeout(() => fail(new GuardHopError("timeout", "the guard hop passed its deadline")), Math.max(0, hop.deadline - Date.now()));
1690
+ try {
1691
+ req = send(url, { method: hop.method, headers: hop.headers, rejectUnauthorized: !hop.ignoreHTTPSErrors, agent: false }, (res) => {
1692
+ const encoding = String(res.headers["content-encoding"] ?? "").trim().toLowerCase();
1693
+ 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;
1694
+ const body = decoder ? (0, node_stream_1.pipeline)(res, decoder, (err) => {
1695
+ if (err)
1696
+ fail(err);
1697
+ }) : res;
1698
+ const parked = [];
1699
+ let paused = false;
1700
+ let ended = false;
1701
+ const take = (chunk) => {
1702
+ if (settled)
1703
+ return;
1704
+ if (paused) {
1705
+ parked.push(chunk);
1706
+ return;
1707
+ }
1708
+ if (held + chunk.length > MAX_GUARD_HOP_BYTES) {
1709
+ fail(overCap());
1710
+ return;
1711
+ }
1712
+ const oldest = hopsInFlight.values().next().value === id;
1713
+ const room = oldest ? MAX_GUARD_BUFFERED_BYTES : MAX_GUARD_BUFFERED_BYTES - MAX_GUARD_HOP_BYTES;
1714
+ if (hopBytesInFlight + chunk.length > room) {
1715
+ paused = true;
1716
+ body.pause();
1717
+ parked.push(chunk);
1718
+ hopBudgetWaiters.push(drain);
1719
+ return;
1720
+ }
1721
+ hopBytesInFlight += chunk.length;
1722
+ held += chunk.length;
1723
+ chunks.push(chunk);
1724
+ };
1725
+ const drain = () => {
1726
+ paused = false;
1727
+ const queued = parked.splice(0);
1728
+ for (let i = 0; i < queued.length; i++) {
1729
+ take(queued[i]);
1730
+ if (settled)
1731
+ return;
1732
+ if (paused) {
1733
+ parked.push(...queued.slice(i + 1));
1734
+ return;
1735
+ }
1736
+ }
1737
+ if (ended)
1738
+ finish();
1739
+ else
1740
+ body.resume();
1741
+ };
1742
+ body.on("data", take);
1743
+ body.on("error", (err) => fail(err));
1744
+ res.on("error", (err) => fail(err));
1745
+ body.on("end", () => {
1746
+ ended = true;
1747
+ if (!paused)
1748
+ finish();
1749
+ });
1750
+ const finish = () => {
1751
+ if (settled)
1752
+ return;
1753
+ settled = true;
1754
+ clearTimeout(timer);
1755
+ const kept = held;
1756
+ let released = false;
1757
+ const headers = [];
1758
+ for (let i = 0; i + 1 < res.rawHeaders.length; i += 2)
1759
+ headers.push([res.rawHeaders[i], res.rawHeaders[i + 1]]);
1760
+ resolve({
1761
+ status: res.statusCode ?? 0,
1762
+ headers,
1763
+ body: Buffer.concat(chunks, kept),
1764
+ decoded: decoder !== null,
1765
+ release: () => {
1766
+ if (!released) {
1767
+ released = true;
1768
+ releaseHopBytes(id, kept);
1769
+ }
1770
+ }
1771
+ });
1772
+ };
1773
+ });
1774
+ req.on("error", (err) => fail(err));
1775
+ req.end(hop.body ?? void 0);
1776
+ } catch (err) {
1777
+ fail(err instanceof Error ? err : new Error(String(err)));
1778
+ }
1779
+ });
1780
+ }
1781
+ function contextIgnoresHttpsErrors(context) {
1782
+ try {
1783
+ return context._options?.ignoreHTTPSErrors === true;
1784
+ } catch {
1785
+ return false;
1786
+ }
1787
+ }
1788
+ function hopRequestHeaders(pageHeaders, cookie) {
1789
+ const out = { accept: "*/*" };
1790
+ for (const [name, value] of Object.entries(pageHeaders)) {
1791
+ const key = name.toLowerCase();
1792
+ if (key.startsWith(":") || HOP_ONLY_HEADERS.has(key) || key === "cookie")
1793
+ continue;
1794
+ out[key] = value;
1795
+ }
1796
+ out["accept-encoding"] = "gzip, deflate, br";
1797
+ if (cookie)
1798
+ out.cookie = cookie;
1799
+ return out;
1800
+ }
1801
+ function cookiePairs(header) {
1802
+ return header.split(";").flatMap((part) => {
1803
+ const eq = part.indexOf("=");
1804
+ return eq > 0 ? [[part.slice(0, eq).trim(), part.slice(eq + 1).trim()]] : [];
1805
+ });
1806
+ }
1807
+ function hopCookie(pageCookie, contextCookies, setCookies) {
1808
+ const jar = /* @__PURE__ */ new Map();
1809
+ if (pageCookie)
1810
+ for (const [name, value] of cookiePairs(pageCookie))
1811
+ jar.set(name, value);
1812
+ else
1813
+ for (const c of contextCookies)
1814
+ jar.set(c.name, c.value);
1815
+ for (const line of setCookies) {
1816
+ const [pair, ...attrs] = line.split(";");
1817
+ const eq = pair.indexOf("=");
1818
+ if (eq <= 0)
1819
+ continue;
1820
+ const name = pair.slice(0, eq).trim();
1821
+ const gone = attrs.some((a) => {
1822
+ const [k, v = ""] = a.split("=");
1823
+ const key = k.trim().toLowerCase();
1824
+ return key === "max-age" && Number(v.trim()) <= 0 || key === "expires" && Date.parse(v.trim()) < Date.now();
1825
+ });
1826
+ if (gone)
1827
+ jar.delete(name);
1828
+ else
1829
+ jar.set(name, pair.slice(eq + 1).trim());
1830
+ }
1831
+ return [...jar].map(([name, value]) => `${name}=${value}`).join("; ");
1832
+ }
1833
+ function setCookiesOf(answer) {
1834
+ return answer.headers.filter(([name]) => name.toLowerCase() === "set-cookie").map(([, value]) => value);
1835
+ }
1836
+ function fulfillHeaders(answer, earlierSetCookies) {
1837
+ const out = {};
1838
+ const cookies = [...earlierSetCookies];
1839
+ for (const [name, value] of answer.headers) {
1840
+ const key = name.toLowerCase();
1841
+ if (HOP_ONLY_HEADERS.has(key) || answer.decoded && key === "content-encoding")
1842
+ continue;
1843
+ if (key === "set-cookie") {
1844
+ cookies.push(value);
1845
+ continue;
1846
+ }
1847
+ out[key] = key in out ? `${out[key]}, ${value}` : value;
1848
+ }
1849
+ if (cookies.length > 0)
1850
+ out["set-cookie"] = cookies.join("\n");
1851
+ return out;
1852
+ }
1393
1853
  var cutAtClose = /* @__PURE__ */ new WeakSet();
1394
- async function sendCheckingRedirect(route, guard) {
1395
- const first = factsOf(route.request());
1854
+ async function sendCheckingRedirect(route, guard, context) {
1855
+ const request = route.request();
1856
+ const first = factsOf(request);
1857
+ const deadline = Date.now() + guard.redirectCheckTimeoutMs;
1396
1858
  let facts = first;
1859
+ let pageHeaders = {};
1860
+ let postBody = null;
1861
+ const setCookies = [];
1397
1862
  for (let hop = 0; ; hop++) {
1398
- let response;
1863
+ let answer;
1399
1864
  try {
1400
- response = await route.fetch(hop === 0 ? { maxRedirects: 0, timeout: REDIRECT_CHECK_TIMEOUT_MS } : { url: facts.url, maxRedirects: 0, timeout: REDIRECT_CHECK_TIMEOUT_MS });
1865
+ if (hop === 0) {
1866
+ pageHeaders = request.headers();
1867
+ postBody = request.postDataBuffer();
1868
+ }
1869
+ const pageCookie = hop === 0 ? Object.entries(pageHeaders).find(([name]) => name.toLowerCase() === "cookie")?.[1] : void 0;
1870
+ const cookie = hopCookie(pageCookie, pageCookie ? [] : await context.cookies(facts.url), setCookies);
1871
+ answer = await sendGuardHop({
1872
+ url: facts.url,
1873
+ method: facts.method,
1874
+ headers: hopRequestHeaders(pageHeaders, cookie),
1875
+ body: postBody,
1876
+ ignoreHTTPSErrors: contextIgnoresHttpsErrors(context),
1877
+ deadline
1878
+ });
1401
1879
  } catch (err) {
1402
1880
  if (cutAtClose.has(route))
1403
1881
  return;
1404
- guard.noteRedirectCheckFailed(facts, err, REDIRECT_CHECK_TIMEOUT_MS);
1882
+ guard.noteRedirectCheckFailed(facts, err, guard.redirectCheckTimeoutMs);
1405
1883
  await route.abort("failed").catch(() => {
1406
1884
  });
1407
1885
  return;
1408
1886
  }
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
1887
  try {
1421
- decision = guard.decide(next);
1422
- } catch {
1423
- 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" } };
1424
- }
1425
- if (decision.action === "write") {
1426
- if (decision.record)
1427
- guard.note(decision.record);
1428
- if (guard.holds(decision.rule)) {
1888
+ if (cutAtClose.has(route))
1889
+ return;
1890
+ const status = answer.status;
1891
+ const location = answer.headers.find(([name]) => name.toLowerCase() === "location")?.[1];
1892
+ const next = REDIRECT_STATUSES.has(status) && location ? redirectFacts(facts, status, location) : null;
1893
+ if (!next) {
1894
+ await route.fulfill({ status, headers: fulfillHeaders(answer, setCookies), body: answer.body }).catch(() => {
1895
+ });
1896
+ return;
1897
+ }
1898
+ let decision;
1899
+ try {
1900
+ decision = guard.decide(next);
1901
+ } catch {
1902
+ 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" } };
1903
+ }
1904
+ if (decision.action === "write") {
1905
+ if (decision.record)
1906
+ guard.note(decision.record);
1907
+ if (guard.holds(decision.rule)) {
1908
+ await route.abort("blockedbyclient").catch(() => {
1909
+ });
1910
+ return;
1911
+ }
1912
+ }
1913
+ if (READ_METHODS.has(next.method.toUpperCase())) {
1914
+ await route.fulfill({ status, headers: { ...fulfillHeaders(answer, setCookies), location: next.url }, body: answer.body }).catch(() => {
1915
+ });
1916
+ return;
1917
+ }
1918
+ if (!sameOrigin(next.url, first.url)) {
1919
+ const target = new URL(next.url);
1920
+ guard.note(describe(next, target, isAppSite(target.hostname.toLowerCase(), guard.policy) ? "app-write" : "third-party-write"));
1429
1921
  await route.abort("blockedbyclient").catch(() => {
1430
1922
  });
1431
1923
  return;
1432
1924
  }
1925
+ if (hop + 1 >= MAX_GUARD_REDIRECTS) {
1926
+ await route.abort("failed").catch(() => {
1927
+ });
1928
+ return;
1929
+ }
1930
+ setCookies.push(...setCookiesOf(answer));
1931
+ facts = next;
1932
+ } finally {
1933
+ answer.release();
1433
1934
  }
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
1935
  }
1453
1936
  }
1454
1937
  function sameRouteUrl(a, b) {
@@ -1662,7 +2145,7 @@ var require_run_write_guard = __commonJS({
1662
2145
  }
1663
2146
  }
1664
2147
  if (decision.action === "allow" && !READ_METHODS.has(request.method().toUpperCase()) && checksRedirect(decision.rule, request.url(), guard.policy)) {
1665
- await sendCheckingRedirect(route, guard);
2148
+ await sendCheckingRedirect(route, guard, context);
1666
2149
  return;
1667
2150
  }
1668
2151
  await route.fallback().catch(() => {