@validate.qa/runner 1.0.101 → 1.0.103
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-SN3PN4G4.mjs → chunk-36AWDKPL.mjs} +6 -2
- package/dist/{chunk-CYWHPXNZ.mjs → chunk-7SH3COMQ.mjs} +1 -1
- package/dist/{chunk-BBTK6OX6.mjs → chunk-BECCGMU4.mjs} +383 -11
- package/dist/{chunk-4SZC5ZX3.mjs → chunk-CG75I3ZZ.mjs} +25477 -5374
- package/dist/{chunk-TPU42VH2.mjs → chunk-CMM4DOXC.mjs} +10 -5
- package/dist/{chunk-M3VZGHZQ.mjs → chunk-NNJYKR3O.mjs} +22 -11
- package/dist/{chunk-IGXGSDE3.mjs → chunk-OTKJZIOD.mjs} +1298 -384
- package/dist/{chunk-CO5KHIVX.mjs → chunk-TK5KEGNW.mjs} +1 -1
- package/dist/{chunk-FQUH2PU2.mjs → chunk-WVLBQDFP.mjs} +140 -7
- package/dist/{chunk-2OKZY3WN.mjs → chunk-YI3ZC2XI.mjs} +2 -2
- package/dist/{chunk-XZUUBMP3.mjs → chunk-YIM746WO.mjs} +1 -1
- package/dist/cli.js +140692 -118903
- package/dist/cli.mjs +362 -108
- package/dist/credential-mcp-server.js +1402 -350
- package/dist/credential-mcp-server.mjs +3 -3
- package/dist/discovery-mcp-server.js +1330 -401
- package/dist/discovery-mcp-server.mjs +4 -4
- package/dist/dist-2G7YTNH5.mjs +17 -0
- package/dist/inbox-mcp-server.js +1676 -629
- package/dist/inbox-mcp-server.mjs +3 -3
- package/dist/route-seed-worker.js +1244 -330
- package/dist/route-seed-worker.mjs +4 -4
- package/dist/run-write-guard-preload.js +1622 -332
- package/dist/run-write-guard-preload.mjs +4 -4
- package/dist/run-write-guard.js +1622 -332
- 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 +3 -2
- package/dist/dist-VTPBY2C4.mjs +0 -17
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Local test runner for the **[validate.qa](https://validate.qa)** AI testing platform.
|
|
4
4
|
|
|
5
|
+
> **Not sure this is the package you need?** See [Which one do I need?](https://validate.qa/docs/getting-started).
|
|
6
|
+
|
|
5
7
|
This package enables you to execute Playwright and Appium tests generated from your voice narrations directly on your local machine, securely connecting to your validate.qa cloud project.
|
|
6
8
|
|
|
7
9
|
**Features:**
|
|
@@ -373,6 +375,7 @@ Credentials are stored in `~/.validate.qa/credentials.json`:
|
|
|
373
375
|
| `POLL_INTERVAL_MS` | Poll interval | `2000` |
|
|
374
376
|
| `RUNS_DIR` | Directory for test run artifacts | `~/.validate.qa/runs` |
|
|
375
377
|
| `CHROME_USER_DATA_DIR` | Chrome profile path | Auto-detected |
|
|
378
|
+
| `RUNNER_RESULT_RETRY_BUDGET_MS` | How long a result post keeps retrying while the server is unreachable or answers 5xx (a redeploy); at least 10 s | `150000` |
|
|
376
379
|
|
|
377
380
|
### Example: Custom Configuration
|
|
378
381
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dist_exports,
|
|
3
3
|
init_dist
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OTKJZIOD.mjs";
|
|
5
5
|
import {
|
|
6
6
|
__commonJS,
|
|
7
7
|
__require,
|
|
@@ -25,6 +25,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
25
25
|
exports.isTokenRefreshWrite = isTokenRefreshWrite;
|
|
26
26
|
exports.isSignInPathWrite = isSignInPathWrite;
|
|
27
27
|
exports.graphqlUrlCarriesQuery = graphqlUrlCarriesQuery;
|
|
28
|
+
exports.graphqlUrlQueriesAreReads = graphqlUrlQueriesAreReads;
|
|
28
29
|
exports.graphqlGetMayWrite = graphqlGetMayWrite;
|
|
29
30
|
exports.writeShapedGet = writeShapedGet;
|
|
30
31
|
exports.decideSurveyRequest = decideSurveyRequest;
|
|
@@ -583,6 +584,9 @@ var require_survey_network_guard = __commonJS({
|
|
|
583
584
|
function graphqlUrlCarriesQuery(params) {
|
|
584
585
|
return params.has("query");
|
|
585
586
|
}
|
|
587
|
+
function graphqlUrlQueriesAreReads(params) {
|
|
588
|
+
return params.getAll("query").every((doc) => doc.trim() !== "" && graphqlDocumentIsQueryOnly(doc));
|
|
589
|
+
}
|
|
586
590
|
function graphqlGetMayWrite(url, body) {
|
|
587
591
|
const docs = url.searchParams.getAll("query");
|
|
588
592
|
return docs.length > 0 && (typeof body === "string" && body.length > 0 || docs.some((doc) => !graphqlDocumentIsQueryOnly(doc)));
|
|
@@ -826,7 +830,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
826
830
|
return { action: "allow", rule: "extra-pattern", reason: extra.reason, record };
|
|
827
831
|
if (graphqlGet)
|
|
828
832
|
return { action: "block", rule: "app-write", record };
|
|
829
|
-
if (method === "POST" && isGraphqlEndpoint(url, s) &&
|
|
833
|
+
if (method === "POST" && isGraphqlEndpoint(url, s) && graphqlUrlQueriesAreReads(url.searchParams) && isGraphqlQueryOnly(req.postData))
|
|
830
834
|
return { action: "allow", rule: "graphql-query", record };
|
|
831
835
|
const passive = s.lastNavigationAt > 0 && (s.lastActionAt === 0 || s.lastNavigationAt >= s.lastActionAt) && s.now - s.lastNavigationAt <= s.passiveWindowMs;
|
|
832
836
|
const signature = learnableSignature(req, url, record, s);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
require_survey_network_guard
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-36AWDKPL.mjs";
|
|
4
4
|
import {
|
|
5
5
|
dist_exports,
|
|
6
6
|
init_dist
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-OTKJZIOD.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;
|
|
@@ -389,6 +391,8 @@ var require_run_write_guard = __commonJS({
|
|
|
389
391
|
exports.RECORD_ONLY_CAPABLE_RULES = ["write-shaped-get"];
|
|
390
392
|
exports.DEFAULT_RECORD_ONLY_RULES = ["write-shaped-get"];
|
|
391
393
|
var MAX_APP_SITES = 16;
|
|
394
|
+
exports.MAX_IDP_HOSTS = 4;
|
|
395
|
+
var MAX_IDP_HINTS = 4;
|
|
392
396
|
exports.MAX_ALLOW_PATTERNS = 50;
|
|
393
397
|
exports.MAX_ALLOW_PATTERN_CHARS = 200;
|
|
394
398
|
var MAX_REASON_CHARS = 300;
|
|
@@ -416,6 +420,11 @@ var require_run_write_guard = __commonJS({
|
|
|
416
420
|
const e = x;
|
|
417
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) }] : [];
|
|
418
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
|
+
}) : [];
|
|
419
428
|
const recordOnlyRules = Array.isArray(v.recordOnlyRules) ? exports.RECORD_ONLY_CAPABLE_RULES.filter((rule) => v.recordOnlyRules.includes(rule)) : [...exports.DEFAULT_RECORD_ONLY_RULES];
|
|
420
429
|
if (typeof v.reason !== "string")
|
|
421
430
|
return null;
|
|
@@ -428,6 +437,9 @@ var require_run_write_guard = __commonJS({
|
|
|
428
437
|
appSites: appSites.map((s) => s.toLowerCase()),
|
|
429
438
|
allowPatterns,
|
|
430
439
|
ignoredAllowPatterns,
|
|
440
|
+
// Only when set: a policy without them parses exactly as before.
|
|
441
|
+
...idpHosts.length > 0 ? { idpHosts } : {},
|
|
442
|
+
...ignoredIdpHosts.length > 0 ? { ignoredIdpHosts } : {},
|
|
431
443
|
recordOnlyRules,
|
|
432
444
|
reason: v.reason.slice(0, MAX_REASON_CHARS),
|
|
433
445
|
...v.pwAgentsGuard === "off" ? { pwAgentsGuard: "off" } : {}
|
|
@@ -629,8 +641,153 @@ var require_run_write_guard = __commonJS({
|
|
|
629
641
|
return named.size + Math.min(bare.size, 2) >= 2;
|
|
630
642
|
}
|
|
631
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",
|
|
660
|
+
"token",
|
|
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
|
+
}
|
|
632
789
|
function wordsOf(segment) {
|
|
633
|
-
return segment.replace(/([a-z])(?=[A-Z0-9])/g, "$1 ").toLowerCase().split(/[\s
|
|
790
|
+
return segment.replace(/([a-z])(?=[A-Z0-9])/g, "$1 ").toLowerCase().split(/[\s.,_/-]+/).filter(Boolean);
|
|
634
791
|
}
|
|
635
792
|
function rawSegmentsOf(pathname) {
|
|
636
793
|
return pathname.split("/").map((seg) => {
|
|
@@ -651,7 +808,10 @@ var require_run_write_guard = __commonJS({
|
|
|
651
808
|
return words.some((w) => ACCOUNT_CHANGE_WORDS.has(w)) && words.some((w) => ACCOUNT_CHANGE_CONTEXT.has(w));
|
|
652
809
|
}
|
|
653
810
|
function isGraphqlSignInName(name) {
|
|
654
|
-
|
|
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 });
|
|
655
815
|
}
|
|
656
816
|
function graphqlSignIn(body) {
|
|
657
817
|
let parsed;
|
|
@@ -687,6 +847,173 @@ var require_run_write_guard = __commonJS({
|
|
|
687
847
|
}
|
|
688
848
|
return signIns > 0 ? "document" : null;
|
|
689
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",
|
|
891
|
+
"oauth",
|
|
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",
|
|
909
|
+
"sso",
|
|
910
|
+
"social",
|
|
911
|
+
"oidc",
|
|
912
|
+
"firebase"
|
|
913
|
+
]);
|
|
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
|
+
}
|
|
690
1017
|
function isCredentialSignInShape(pathname, search, postData, contentType) {
|
|
691
1018
|
if (typeof postData === "string" && postData.length > MAX_BODY_CHARS)
|
|
692
1019
|
return false;
|
|
@@ -900,6 +1227,26 @@ var require_run_write_guard = __commonJS({
|
|
|
900
1227
|
}).join("");
|
|
901
1228
|
return { method, host, path: new RegExp(`^${body}/?$`, "i") };
|
|
902
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
|
+
}
|
|
903
1250
|
function matchesAllowPatterns(method, host, pathname, policy, compiled) {
|
|
904
1251
|
const patterns = compiled ?? policy.allowPatterns.map(compileAllowPattern).filter((p) => p !== null);
|
|
905
1252
|
return patterns.some((p) => {
|
|
@@ -923,7 +1270,10 @@ var require_run_write_guard = __commonJS({
|
|
|
923
1270
|
return reason ? [{ pattern, reason }] : [];
|
|
924
1271
|
})
|
|
925
1272
|
];
|
|
926
|
-
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
|
+
];
|
|
927
1277
|
}
|
|
928
1278
|
function describe(facts, url, rule) {
|
|
929
1279
|
return {
|
|
@@ -975,6 +1325,8 @@ var require_run_write_guard = __commonJS({
|
|
|
975
1325
|
return { action: "write", rule: "sign-up", record: describe(facts, url, "sign-up") };
|
|
976
1326
|
if (isAuthProviderWrite(method, host, pathname, facts.amzTarget, url.searchParams))
|
|
977
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" };
|
|
978
1330
|
const appSite = isAppSite(host, policy);
|
|
979
1331
|
if (facts.registeredAuthHandler && pathname === exports.FIREBASE_AUTH_HANDLER_PATH)
|
|
980
1332
|
return { action: "allow", rule: "auth-provider" };
|
|
@@ -982,7 +1334,7 @@ var require_run_write_guard = __commonJS({
|
|
|
982
1334
|
return { action: "allow", rule: "sign-in" };
|
|
983
1335
|
if (appSite && isAuth0CustomDomainSignIn(method, pathname, url.searchParams))
|
|
984
1336
|
return { action: "allow", rule: "sign-in" };
|
|
985
|
-
if (appSite && method === "POST" && facts.brokerTokenInBody &&
|
|
1337
|
+
if (appSite && method === "POST" && facts.brokerTokenInBody && isBrokerSignInShape(pathname, url.searchParams, facts.postData, facts.contentType)) {
|
|
986
1338
|
return { action: "allow", rule: "broker-sign-in" };
|
|
987
1339
|
}
|
|
988
1340
|
if (appSite && isEvidencedTokenRefresh(method, pathname, facts.postData))
|
|
@@ -990,7 +1342,7 @@ var require_run_write_guard = __commonJS({
|
|
|
990
1342
|
const signIn = appSite && method === "POST" && facts.signInSecretInBody ? isCredentialSignInShape(pathname, url.searchParams, facts.postData, facts.contentType) : false;
|
|
991
1343
|
if (signIn)
|
|
992
1344
|
return { action: "allow", rule: "credential-sign-in", ...signIn === "persisted" ? { persisted: true } : {} };
|
|
993
|
-
if (method === "POST" && isGraphqlPath(pathname) &&
|
|
1345
|
+
if (method === "POST" && isGraphqlPath(pathname) && (0, survey_network_guard_js_1.graphqlUrlQueriesAreReads)(url.searchParams) && isGraphqlQueryOnlyBody(facts.postData))
|
|
994
1346
|
return { action: "allow", rule: "graphql-query" };
|
|
995
1347
|
if (method === "POST" && hostIs(host, SEARCH_POST_HOSTS))
|
|
996
1348
|
return { action: "allow", rule: "search-post" };
|
|
@@ -1047,6 +1399,8 @@ var require_run_write_guard = __commonJS({
|
|
|
1047
1399
|
relayRejected = 0;
|
|
1048
1400
|
/** Credential sign-ins already said in the run log (observe mode; method, host and path shape). */
|
|
1049
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();
|
|
1050
1404
|
constructor(policy, opts = {}) {
|
|
1051
1405
|
this.policy = policy;
|
|
1052
1406
|
this.compiled = policy.allowPatterns.map(compileAllowPattern).filter((p) => p !== null);
|
|
@@ -1144,6 +1498,22 @@ var require_run_write_guard = __commonJS({
|
|
|
1144
1498
|
const verb = held ? "held" : "saw";
|
|
1145
1499
|
const why = record.writeShaped ? `${record.rule}, ${record.writeShaped}` : record.rule;
|
|
1146
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`);
|
|
1147
1517
|
}
|
|
1148
1518
|
/**
|
|
1149
1519
|
* A page sent a message over a WebSocket to the app's site. The guard cannot tell a read from a
|
|
@@ -1951,8 +2321,9 @@ var require_run_write_guard = __commonJS({
|
|
|
1951
2321
|
};
|
|
1952
2322
|
Object.assign(context, {
|
|
1953
2323
|
route: async (url, handler, options) => {
|
|
1954
|
-
await route(url, handler, options);
|
|
2324
|
+
const disposable = await route(url, handler, options);
|
|
1955
2325
|
others.push({ url, handler });
|
|
2326
|
+
return disposable;
|
|
1956
2327
|
},
|
|
1957
2328
|
unroute: async (url, handler) => {
|
|
1958
2329
|
if (handler === void 0 && sameRouteUrl(url, GUARD_ROUTE)) {
|
|
@@ -2285,6 +2656,7 @@ var require_run_write_guard = __commonJS({
|
|
|
2285
2656
|
}
|
|
2286
2657
|
function buildRunWriteGuardHook(policy, modulePath, signInSecretNames = [], opts = {}) {
|
|
2287
2658
|
const withContext = opts.browserContext !== false;
|
|
2659
|
+
const hookTest = opts.hookTest ?? "test";
|
|
2288
2660
|
const names = signInSecretNames.filter((n) => /^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(n)).slice(0, 16);
|
|
2289
2661
|
return `
|
|
2290
2662
|
import { createRequire as __qaWriteGuardRequire } from 'node:module';
|
|
@@ -2307,7 +2679,7 @@ try {
|
|
|
2307
2679
|
} catch (err) {
|
|
2308
2680
|
__qaWriteGuardError = err;
|
|
2309
2681
|
}
|
|
2310
|
-
|
|
2682
|
+
${hookTest}.beforeEach(async (${withContext ? "{ context }" : ""}) => {
|
|
2311
2683
|
try {
|
|
2312
2684
|
if (__qaWriteGuardError) throw __qaWriteGuardError;
|
|
2313
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"}
|
|
@@ -2316,7 +2688,7 @@ test.beforeEach(async (${withContext ? "{ context }" : ""}) => {
|
|
|
2316
2688
|
${policy.mode === "block" ? "throw err;" : "// observe: never fail the test"}
|
|
2317
2689
|
}
|
|
2318
2690
|
});${withContext && policy.mode === "block" ? `
|
|
2319
|
-
|
|
2691
|
+
${hookTest}.afterEach(async ({ context }) => {
|
|
2320
2692
|
try { if (__qaWriteGuard) await __qaWriteGuard.clearRunWriteRelay(context); } catch { /* the context closes next */ }
|
|
2321
2693
|
});` : ""}
|
|
2322
2694
|
`;
|