@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.
- package/dist/chunk-2OKZY3WN.mjs +291 -0
- package/dist/{chunk-5P4ANVDY.mjs → chunk-4SZC5ZX3.mjs} +5092 -760
- package/dist/{chunk-2XD63NU5.mjs → chunk-BBTK6OX6.mjs} +567 -84
- package/dist/{chunk-WDXLAZ2J.mjs → chunk-CO5KHIVX.mjs} +3 -2
- package/dist/{chunk-HMUC3DKS.mjs → chunk-CYWHPXNZ.mjs} +2281 -2
- package/dist/{chunk-MAO6ADAO.mjs → chunk-FQUH2PU2.mjs} +1 -1
- package/dist/{chunk-MBKGTELT.mjs → chunk-IGXGSDE3.mjs} +701 -283
- package/dist/{chunk-II7QW7MZ.mjs → chunk-M3VZGHZQ.mjs} +14 -11
- package/dist/{chunk-GIIIVO2W.mjs → chunk-SN3PN4G4.mjs} +151 -11
- package/dist/{chunk-6NIT2CCH.mjs → chunk-TPU42VH2.mjs} +10 -9
- package/dist/{chunk-MHFP3QXU.mjs → chunk-XZUUBMP3.mjs} +1 -1
- package/dist/cli.js +8994 -1015
- package/dist/cli.mjs +211 -170
- package/dist/credential-mcp-server.js +503 -79
- package/dist/credential-mcp-server.mjs +3 -3
- package/dist/discovery-mcp-server.js +654 -87
- package/dist/discovery-mcp-server.mjs +4 -4
- package/dist/dist-VTPBY2C4.mjs +17 -0
- package/dist/inbox-mcp-server.js +496 -73
- package/dist/inbox-mcp-server.mjs +3 -3
- package/dist/route-seed-worker.js +2780 -74
- package/dist/route-seed-worker.mjs +14 -7
- package/dist/run-write-guard-preload.js +1206 -160
- package/dist/run-write-guard-preload.mjs +5 -285
- package/dist/run-write-guard.js +1206 -160
- package/dist/run-write-guard.mjs +3 -3
- package/package.json +3 -3
- package/dist/dist-DFNLMH6L.mjs +0 -16
package/dist/run-write-guard.js
CHANGED
|
@@ -3027,7 +3027,7 @@ function unescapeQuotedBody(raw) {
|
|
|
3027
3027
|
return raw.replace(/\\(['"`\\])/g, "$1");
|
|
3028
3028
|
}
|
|
3029
3029
|
function extractRoleNameValue(line) {
|
|
3030
|
-
const m = line.match(new RegExp(`getByRole\\s*\\([^,]
|
|
3030
|
+
const m = line.match(new RegExp(`getByRole\\s*\\([^,]{1,200},\\s*\\{[^}]{0,200}?name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
|
|
3031
3031
|
if (!m)
|
|
3032
3032
|
return null;
|
|
3033
3033
|
const raw = m[1] ?? m[2] ?? m[3];
|
|
@@ -3735,6 +3735,8 @@ function stripCoverageComments(code) {
|
|
|
3735
3735
|
let copyFrom = 0;
|
|
3736
3736
|
let i = 0;
|
|
3737
3737
|
let quote = null;
|
|
3738
|
+
let prev = "";
|
|
3739
|
+
let noRegexBefore = 0;
|
|
3738
3740
|
while (i < code.length) {
|
|
3739
3741
|
const ch = code[i];
|
|
3740
3742
|
if (quote) {
|
|
@@ -3742,12 +3744,24 @@ function stripCoverageComments(code) {
|
|
|
3742
3744
|
i += 2;
|
|
3743
3745
|
continue;
|
|
3744
3746
|
}
|
|
3745
|
-
if (ch === quote || ch === "\n" && quote !== "`")
|
|
3747
|
+
if (ch === quote || ch === "\n" && quote !== "`") {
|
|
3746
3748
|
quote = null;
|
|
3749
|
+
prev = ch === "\n" ? prev : ch;
|
|
3750
|
+
}
|
|
3747
3751
|
i++;
|
|
3748
3752
|
continue;
|
|
3749
3753
|
}
|
|
3750
3754
|
const next = code[i + 1];
|
|
3755
|
+
if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
|
|
3756
|
+
const end = regexLiteralEnd3(code, i);
|
|
3757
|
+
if (end > 0) {
|
|
3758
|
+
i = end;
|
|
3759
|
+
prev = "a";
|
|
3760
|
+
continue;
|
|
3761
|
+
}
|
|
3762
|
+
const eol = code.indexOf("\n", i);
|
|
3763
|
+
noRegexBefore = eol === -1 ? code.length : eol;
|
|
3764
|
+
}
|
|
3751
3765
|
if (ch === "/" && next === "*") {
|
|
3752
3766
|
const end = code.indexOf("*/", i + 2);
|
|
3753
3767
|
const stop = end === -1 ? code.length : end + 2;
|
|
@@ -3766,11 +3780,39 @@ function stripCoverageComments(code) {
|
|
|
3766
3780
|
}
|
|
3767
3781
|
if (ch === '"' || ch === "'" || ch === "`")
|
|
3768
3782
|
quote = ch;
|
|
3783
|
+
if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
|
|
3784
|
+
prev = ch;
|
|
3769
3785
|
i++;
|
|
3770
3786
|
}
|
|
3771
3787
|
out.push(code.slice(copyFrom));
|
|
3772
3788
|
return out.join("");
|
|
3773
3789
|
}
|
|
3790
|
+
function regexLiteralEnd3(code, start) {
|
|
3791
|
+
let inClass = false;
|
|
3792
|
+
for (let j = start + 1; j < code.length; j++) {
|
|
3793
|
+
const c = code[j];
|
|
3794
|
+
if (c === "\n")
|
|
3795
|
+
return -1;
|
|
3796
|
+
if (c === "\\") {
|
|
3797
|
+
j++;
|
|
3798
|
+
continue;
|
|
3799
|
+
}
|
|
3800
|
+
if (inClass) {
|
|
3801
|
+
if (c === "]")
|
|
3802
|
+
inClass = false;
|
|
3803
|
+
continue;
|
|
3804
|
+
}
|
|
3805
|
+
if (c === "[")
|
|
3806
|
+
inClass = true;
|
|
3807
|
+
else if (c === "/") {
|
|
3808
|
+
let k = j + 1;
|
|
3809
|
+
while (k < code.length && isIdentChar(code[k]))
|
|
3810
|
+
k++;
|
|
3811
|
+
return k;
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
return -1;
|
|
3815
|
+
}
|
|
3774
3816
|
function isIdentChar(ch) {
|
|
3775
3817
|
if (!ch)
|
|
3776
3818
|
return false;
|
|
@@ -4428,7 +4470,7 @@ function assessTitleCoverage(title, code) {
|
|
|
4428
4470
|
const repeatedHit = [...repeated].some((term) => hay.includes(term));
|
|
4429
4471
|
return { covered: hit >= needed || repeatedHit, missingTerms };
|
|
4430
4472
|
}
|
|
4431
|
-
var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
|
|
4473
|
+
var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, REGEX_PRECEDER2, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
|
|
4432
4474
|
var init_expected_outcome_coverage = __esm({
|
|
4433
4475
|
"../shared/dist/expected-outcome-coverage.js"() {
|
|
4434
4476
|
"use strict";
|
|
@@ -4465,6 +4507,7 @@ var init_expected_outcome_coverage = __esm({
|
|
|
4465
4507
|
]);
|
|
4466
4508
|
MAX_ASSERTION_JOIN_CHARS = 8e3;
|
|
4467
4509
|
MAX_COVERAGE_LINE_CHARS = 2e3;
|
|
4510
|
+
REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
|
|
4468
4511
|
BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
|
|
4469
4512
|
cachedAssertionInput = null;
|
|
4470
4513
|
cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
|
|
@@ -4915,7 +4958,7 @@ function detectsErrorBannerAssertion(code) {
|
|
|
4915
4958
|
const cleaned = stripCommentsAndLogs(stripLoginScaffolding(code));
|
|
4916
4959
|
const lines = cleaned.split("\n");
|
|
4917
4960
|
const candidates = [];
|
|
4918
|
-
const positive = (stmt) => !
|
|
4961
|
+
const positive = (stmt) => !claimsAbsence(stmt);
|
|
4919
4962
|
const statements = liveExpectStatements(cleaned).map((s) => s.stmt);
|
|
4920
4963
|
for (const stmt of statements) {
|
|
4921
4964
|
if (positive(stmt))
|
|
@@ -5044,6 +5087,12 @@ function detectsObservedFailureAsExpected(code) {
|
|
|
5044
5087
|
return FAILURE_STATUS_ASSERT.some((re) => re.test(stmt)) || hasFailedListener && FAILED_LIST_ASSERT.test(stmt);
|
|
5045
5088
|
});
|
|
5046
5089
|
}
|
|
5090
|
+
function claimsAbsence(stmt) {
|
|
5091
|
+
const negated = NEGATED_ASSERT_NOT.test(stmt);
|
|
5092
|
+
const bare = negated ? stmt.replace(NOT_BEFORE_MATCHER, ".") : stmt;
|
|
5093
|
+
const absenceMatcher = /\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
|
|
5094
|
+
return negated !== absenceMatcher;
|
|
5095
|
+
}
|
|
5047
5096
|
function isVacuousErrorAbsence(stmt, presence) {
|
|
5048
5097
|
if (!ABSENCE_MATCHER.test(stmt))
|
|
5049
5098
|
return false;
|
|
@@ -5059,19 +5108,53 @@ function isChromeTarget(stmt) {
|
|
|
5059
5108
|
return CHROME_LOCATOR.test(stmt) && !CONTENT_ROLE.test(stmt) && !/getByText\s*\(/.test(stmt);
|
|
5060
5109
|
}
|
|
5061
5110
|
function countsAsContentText(stmt) {
|
|
5062
|
-
if (!TEXT_CONTENT_MATCHER.test(stmt))
|
|
5111
|
+
if (!TEXT_CONTENT_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt))
|
|
5063
5112
|
return false;
|
|
5064
5113
|
if (isSymbolOnlyArgument(stmt, TEXT_CONTENT_MATCHER))
|
|
5065
5114
|
return false;
|
|
5066
|
-
|
|
5115
|
+
return onContentTarget(stmt);
|
|
5116
|
+
}
|
|
5117
|
+
function onContentTarget(stmt) {
|
|
5118
|
+
return !isChromeTarget(stmt) || /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
|
|
5119
|
+
}
|
|
5120
|
+
function provesTextRemoved(stmt, presence) {
|
|
5121
|
+
const m = stmt.match(NEGATED_TEXT);
|
|
5122
|
+
if (!m || !onContentTarget(stmt))
|
|
5123
|
+
return false;
|
|
5124
|
+
const text = m[1] ?? m[2];
|
|
5125
|
+
if (text === void 0)
|
|
5067
5126
|
return true;
|
|
5068
|
-
|
|
5127
|
+
if (!/[\p{L}\p{N}]/u.test(text))
|
|
5128
|
+
return false;
|
|
5129
|
+
return !ERROR_TEXT_WORDS.test(text) || presence().includes(text);
|
|
5069
5130
|
}
|
|
5070
5131
|
function isDataContentRole(stmt) {
|
|
5071
5132
|
return CONTENT_ROLE.test(stmt) && !ANNOUNCE_ROLE.test(stmt);
|
|
5072
5133
|
}
|
|
5134
|
+
function destructuresRequest(code) {
|
|
5135
|
+
let readTo = -1;
|
|
5136
|
+
for (const m of code.matchAll(ASYNC_DESTRUCTURE_OPEN)) {
|
|
5137
|
+
const start = m.index + m[0].length;
|
|
5138
|
+
if (start <= readTo)
|
|
5139
|
+
continue;
|
|
5140
|
+
let end = start;
|
|
5141
|
+
while (end < code.length && code[end] !== "}" && code[end] !== ")")
|
|
5142
|
+
end++;
|
|
5143
|
+
readTo = end;
|
|
5144
|
+
if (end === code.length)
|
|
5145
|
+
return false;
|
|
5146
|
+
if (code[end] !== "}" || !REQUEST_WORD.test(code.slice(start, end)))
|
|
5147
|
+
continue;
|
|
5148
|
+
let close = end + 1;
|
|
5149
|
+
while (close < code.length && /\s/.test(code[close]))
|
|
5150
|
+
close++;
|
|
5151
|
+
if (code[close] === ")")
|
|
5152
|
+
return true;
|
|
5153
|
+
}
|
|
5154
|
+
return false;
|
|
5155
|
+
}
|
|
5073
5156
|
function isApiTest(code) {
|
|
5074
|
-
const usesRequestFixture =
|
|
5157
|
+
const usesRequestFixture = destructuresRequest(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
|
|
5075
5158
|
if (!usesRequestFixture)
|
|
5076
5159
|
return false;
|
|
5077
5160
|
const drivesUi = /\bpage\.(getBy\w+|locator|click|fill|goto|waitForURL|reload)\s*\(/.test(code) || /\.(click|fill|check|uncheck|selectOption|pressSequentially)\s*\(/.test(code);
|
|
@@ -5330,6 +5413,9 @@ function hasOnlyConstantAssertions(code) {
|
|
|
5330
5413
|
const { constant, real } = constantAssertions(code);
|
|
5331
5414
|
return constant.length > 0 && real === 0;
|
|
5332
5415
|
}
|
|
5416
|
+
function envValueKey(name) {
|
|
5417
|
+
return `\0env:${name}`;
|
|
5418
|
+
}
|
|
5333
5419
|
function enteredValues(stmts) {
|
|
5334
5420
|
const values = /* @__PURE__ */ new Map();
|
|
5335
5421
|
const navLines = [];
|
|
@@ -5338,24 +5424,55 @@ function enteredValues(stmts) {
|
|
|
5338
5424
|
navLines.push(startLine);
|
|
5339
5425
|
let m;
|
|
5340
5426
|
ENTERED_VALUE.lastIndex = 0;
|
|
5341
|
-
|
|
5342
|
-
const
|
|
5343
|
-
if (!
|
|
5344
|
-
values.set(value, startLine);
|
|
5345
|
-
|
|
5427
|
+
const add = (value) => {
|
|
5428
|
+
const lines = values.get(value);
|
|
5429
|
+
if (!lines)
|
|
5430
|
+
values.set(value, [startLine]);
|
|
5431
|
+
else if (lines[lines.length - 1] !== startLine)
|
|
5432
|
+
lines.push(startLine);
|
|
5433
|
+
};
|
|
5434
|
+
while (m = ENTERED_VALUE.exec(stmt))
|
|
5435
|
+
add((m[1] ?? m[2] ?? m[3]).toLowerCase());
|
|
5436
|
+
ENTERED_ENV.lastIndex = 0;
|
|
5437
|
+
while (m = ENTERED_ENV.exec(stmt))
|
|
5438
|
+
add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
|
|
5346
5439
|
}
|
|
5347
5440
|
return { values, navLines };
|
|
5348
5441
|
}
|
|
5349
|
-
function
|
|
5442
|
+
function countBelow(lines, line) {
|
|
5443
|
+
let lo = 0;
|
|
5444
|
+
let hi = lines.length;
|
|
5445
|
+
while (lo < hi) {
|
|
5446
|
+
const mid = lo + hi >> 1;
|
|
5447
|
+
if (lines[mid] < line)
|
|
5448
|
+
lo = mid + 1;
|
|
5449
|
+
else
|
|
5450
|
+
hi = mid;
|
|
5451
|
+
}
|
|
5452
|
+
return lo;
|
|
5453
|
+
}
|
|
5454
|
+
function enteredLinesOf(stmt, entered) {
|
|
5350
5455
|
const m = stmt.match(/\.toHaveValue\s*\(\s*(['"`])((?:(?!\1).)+)\1/);
|
|
5351
|
-
if (
|
|
5456
|
+
if (m)
|
|
5457
|
+
return entered.values.get(m[2].toLowerCase());
|
|
5458
|
+
const env = stmt.match(HAVE_VALUE_ENV);
|
|
5459
|
+
return env ? entered.values.get(envValueKey(env[1] ?? env[2])) : void 0;
|
|
5460
|
+
}
|
|
5461
|
+
function typedSinceLastNav(stmt, entered, startLine, lines = enteredLinesOf(stmt, entered)) {
|
|
5462
|
+
if (!lines || startLine === void 0)
|
|
5352
5463
|
return false;
|
|
5353
|
-
const
|
|
5354
|
-
if (
|
|
5464
|
+
const before = countBelow(lines, startLine);
|
|
5465
|
+
if (before === 0)
|
|
5355
5466
|
return false;
|
|
5356
|
-
|
|
5467
|
+
return countBelow(entered.navLines, startLine) === countBelow(entered.navLines, lines[before - 1] + 1);
|
|
5468
|
+
}
|
|
5469
|
+
function isEnteredValueEcho(stmt, entered, startLine) {
|
|
5470
|
+
const lines = enteredLinesOf(stmt, entered);
|
|
5471
|
+
if (!lines)
|
|
5472
|
+
return false;
|
|
5473
|
+
if (startLine === void 0 || lines[0] >= startLine)
|
|
5357
5474
|
return true;
|
|
5358
|
-
return
|
|
5475
|
+
return typedSinceLastNav(stmt, entered, startLine, lines);
|
|
5359
5476
|
}
|
|
5360
5477
|
function isAnyValueMatcher(stmt) {
|
|
5361
5478
|
if (/\.not\.toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/.test(stmt))
|
|
@@ -5445,6 +5562,43 @@ function hasAttributeTransition(stmts) {
|
|
|
5445
5562
|
return true;
|
|
5446
5563
|
return false;
|
|
5447
5564
|
}
|
|
5565
|
+
function comparesWithItself(subject, tail) {
|
|
5566
|
+
const m = SELF_COMPARE_MATCHER.exec(tail);
|
|
5567
|
+
if (!m)
|
|
5568
|
+
return false;
|
|
5569
|
+
const args = balancedArguments(tail, m[0].length, MATCHER_ARGS_MAX);
|
|
5570
|
+
if (args === null)
|
|
5571
|
+
return false;
|
|
5572
|
+
const squash2 = (x) => x.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
|
|
5573
|
+
const expected = squash2(args);
|
|
5574
|
+
return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
|
|
5575
|
+
}
|
|
5576
|
+
function asPlainAssertion(s) {
|
|
5577
|
+
const { stmt } = s;
|
|
5578
|
+
if (COUNT_AT_LEAST_ZERO.test(stmt))
|
|
5579
|
+
return [];
|
|
5580
|
+
const subject = extractExpectSubject(stmt);
|
|
5581
|
+
if (subject === null)
|
|
5582
|
+
return [s];
|
|
5583
|
+
const plain = subject.replace(/^await\s+/, "").trim();
|
|
5584
|
+
const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
|
|
5585
|
+
const tail = stmt.slice(subjectAt + subject.length).trimStart();
|
|
5586
|
+
const as = (rewritten) => [{ ...s, stmt: rewritten }];
|
|
5587
|
+
if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
|
|
5588
|
+
return [];
|
|
5589
|
+
if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
|
|
5590
|
+
return [];
|
|
5591
|
+
if (comparesWithItself(plain, tail))
|
|
5592
|
+
return [];
|
|
5593
|
+
const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
|
|
5594
|
+
if (visible && IS_VISIBLE_TRUE.test(tail))
|
|
5595
|
+
return as(`await expect(${visible[1]}).toBeVisible();`);
|
|
5596
|
+
if (/^page\s*\.\s*(?:url|title)\s*\(\s*\)$/.test(plain) && PAGE_VALUE_MATCHER.test(tail)) {
|
|
5597
|
+
const matcher = plain.includes("url") ? "toHaveURL" : "toHaveTitle";
|
|
5598
|
+
return as(`await expect(page)${tail.replace(/^\)\s*\.\s*\w+\s*\(/, `.${matcher}(`)}`);
|
|
5599
|
+
}
|
|
5600
|
+
return [s];
|
|
5601
|
+
}
|
|
5448
5602
|
function creditsOutcomeAlways(stmt, entered, startLine) {
|
|
5449
5603
|
if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
|
|
5450
5604
|
return false;
|
|
@@ -5518,6 +5672,8 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
|
|
|
5518
5672
|
continue;
|
|
5519
5673
|
if (isVacuousErrorAbsence(stmt, presence))
|
|
5520
5674
|
continue;
|
|
5675
|
+
if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
|
|
5676
|
+
continue;
|
|
5521
5677
|
if (creditsOutcomeAlways(stmt, entered, startLine))
|
|
5522
5678
|
return { kind: "durable" };
|
|
5523
5679
|
if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
|
|
@@ -5527,12 +5683,15 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
|
|
|
5527
5683
|
else
|
|
5528
5684
|
return { kind: "durable" };
|
|
5529
5685
|
}
|
|
5530
|
-
|
|
5686
|
+
const retyped = typedSinceLastNav(stmt, entered, startLine);
|
|
5687
|
+
if (!retyped && STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
|
|
5531
5688
|
return { kind: "durable" };
|
|
5532
|
-
if (provesStoredControlValue(stmt, startLine, persistence))
|
|
5689
|
+
if (!retyped && provesStoredControlValue(stmt, startLine, persistence))
|
|
5533
5690
|
return { kind: "durable" };
|
|
5534
5691
|
if (countsAsContentText(stmt))
|
|
5535
5692
|
return { kind: "durable" };
|
|
5693
|
+
if (provesTextRemoved(stmt, presence))
|
|
5694
|
+
return { kind: "durable" };
|
|
5536
5695
|
if (mutated && VISIBILITY_MATCHER.test(stmt)) {
|
|
5537
5696
|
const revealsText = GET_BY_TEXT.test(stmt) || HAS_TEXT_FILTER.test(stmt) || CONTENT_ROLE.test(stmt);
|
|
5538
5697
|
if (revealsText && !isChromeTarget(stmt)) {
|
|
@@ -5599,7 +5758,7 @@ function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMut
|
|
|
5599
5758
|
function validateAssertionStrength(code, opts = {}) {
|
|
5600
5759
|
const body = stripLoginScaffolding(code);
|
|
5601
5760
|
const lines = body.split("\n");
|
|
5602
|
-
const stmts = liveExpectStatements(stripCoverageComments(body));
|
|
5761
|
+
const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
|
|
5603
5762
|
const base = (extra) => ({
|
|
5604
5763
|
apiTest: false,
|
|
5605
5764
|
interacted: false,
|
|
@@ -5805,7 +5964,7 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
5805
5964
|
signals
|
|
5806
5965
|
};
|
|
5807
5966
|
}
|
|
5808
|
-
var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, IN_FLIGHT_TEXT, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
|
|
5967
|
+
var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, NEGATED_ASSERT_NOT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, VALUE_ABSENCE, OPTION_ABSENCE, NOT_BEFORE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, NEGATED_TEXT, ASYNC_DESTRUCTURE_OPEN, REQUEST_WORD, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, ENV_READ, ENTERED_ENV, HAVE_VALUE_ENV, IN_FLIGHT_TEXT, LOCATOR_VALUE_READ, LOCATOR_EXPR, ALWAYS_TRUE_ON_OBJECT, COUNT_AT_LEAST_ZERO, DEFINED_VALUE_READ, ALWAYS_DEFINED, SELF_COMPARE_MATCHER, IS_VISIBLE_TRUE, PAGE_VALUE_MATCHER, COUNT_ONE, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
|
|
5809
5968
|
var init_assertion_strength = __esm({
|
|
5810
5969
|
"../shared/dist/assertion-strength.js"() {
|
|
5811
5970
|
"use strict";
|
|
@@ -5830,8 +5989,8 @@ var init_assertion_strength = __esm({
|
|
|
5830
5989
|
// form field value
|
|
5831
5990
|
/\.toMatch\s*\(/,
|
|
5832
5991
|
// string/regex content match (e.g. textContent() vs /[2-9] selected/)
|
|
5833
|
-
|
|
5834
|
-
// a control became DISABLED — a gating/validation outcome
|
|
5992
|
+
/(?<!\.not\s*)\.\s*toBeDisabled\s*\(/,
|
|
5993
|
+
// a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
|
|
5835
5994
|
/\.not\.toBeVisible\s*\(/,
|
|
5836
5995
|
/\.toBeHidden\s*\(/
|
|
5837
5996
|
// something the action removed/closed (disappearance proof)
|
|
@@ -5839,7 +5998,7 @@ var init_assertion_strength = __esm({
|
|
|
5839
5998
|
FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
|
|
5840
5999
|
ANY_CLICK = /\.click\s*\(/;
|
|
5841
6000
|
NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
|
|
5842
|
-
URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(/;
|
|
6001
|
+
URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(|\bexpect\s*\(\s*(?:await\s+)?page\s*\.\s*url\s*\(\s*\)\s*\)|\bexpect\s*\.\s*poll\s*\(\s*(?:async\s*)?\(\s*\)\s*=>\s*(?:\{\s*return\s+)?page\s*\.\s*url\s*\(\s*\)/;
|
|
5843
6002
|
RELOAD = /\.reload\s*\(/;
|
|
5844
6003
|
EXPECT_STMT = /\bexpect\s*[.(]/;
|
|
5845
6004
|
BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
|
|
@@ -5868,6 +6027,7 @@ var init_assertion_strength = __esm({
|
|
|
5868
6027
|
INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
|
|
5869
6028
|
INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
|
|
5870
6029
|
NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
|
|
6030
|
+
NEGATED_ASSERT_NOT = /\.not\s*\.\s*to[A-Z]/;
|
|
5871
6031
|
CONST_CHAIN_MAX_DEPTH = 5;
|
|
5872
6032
|
TYPED_INPUT = /\.(fill|type|pressSequentially|setInputFiles)\s*\(/;
|
|
5873
6033
|
TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
|
|
@@ -5883,8 +6043,14 @@ var init_assertion_strength = __esm({
|
|
|
5883
6043
|
CATCH_ALL_PATTERN = /^(['"`])\*\*(?:\/\*)?\1$|^\/\.\*\/[a-z]*$/;
|
|
5884
6044
|
GUARD_HANDLER = /\bMUTATING\.has\s*\(|blockedbyclient|Read-only guard/;
|
|
5885
6045
|
ABSENCE_MATCHER = /\.not\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/;
|
|
6046
|
+
VALUE_ABSENCE = /\.(?:count\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*0\s*\)|toBeLessThan\s*\(\s*1\s*\))|isVisible\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*false\s*\)|toBeFalsy\s*\()|isHidden\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*true\s*\)|toBeTruthy\s*\())/;
|
|
6047
|
+
OPTION_ABSENCE = /\.(?:toBeVisible\s*\(\s*\{[^{}()]{0,200}\bvisible|toBeAttached\s*\(\s*\{[^{}()]{0,200}\battached)\s*:\s*false\b/;
|
|
6048
|
+
NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
|
|
5886
6049
|
ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
|
|
5887
6050
|
TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
|
|
6051
|
+
NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
|
|
6052
|
+
ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
|
|
6053
|
+
REQUEST_WORD = /\brequest\b/;
|
|
5888
6054
|
ROLE_NAME_RE = /getByRole\s*\(\s*['"`][a-z]+['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
|
|
5889
6055
|
INTERACT_VERB = /\.(click|fill|check|uncheck|setChecked|selectOption|pressSequentially)\s*\(/;
|
|
5890
6056
|
ROLE_SIG_RE = /getByRole\s*\(\s*['"`]([a-z]+)['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
|
|
@@ -5905,7 +6071,20 @@ var init_assertion_strength = __esm({
|
|
|
5905
6071
|
MATCHER_OPEN = /\s*(?:\.\s*(?:not|resolves|rejects)\s*)*\.\s*[A-Za-z_$][\w$]*\s*\(/y;
|
|
5906
6072
|
ENTERED_VALUE = /\.(?:fill|pressSequentially)\s*\(\s*['"`]([^'"`]+)['"`]|keyboard\.type\s*\(\s*['"`]([^'"`]+)['"`]|\.selectOption\s*\(\s*(?:\[\s*)?(?:\{\s*(?:label|value)\s*:\s*)?['"`]([^'"`]+)['"`]/g;
|
|
5907
6073
|
PAGE_NAV = /\.(?:goto|reload)\s*\(/;
|
|
6074
|
+
ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
|
|
6075
|
+
ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
|
|
6076
|
+
HAVE_VALUE_ENV = new RegExp(String.raw`\.toHaveValue\s*\(\s*${ENV_READ}`);
|
|
5908
6077
|
IN_FLIGHT_TEXT = /^(?:(?:sending|saving|loading|searching|checking|processing|submitting|preparing|updating|please wait|working|one moment)\b[^.]{0,40}\.{2,3}|\.{3}|…)$/i;
|
|
6078
|
+
LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
|
|
6079
|
+
LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
|
|
6080
|
+
ALWAYS_TRUE_ON_OBJECT = /^\)\s*\.\s*(?:toBeTruthy|toBeDefined|not\s*\.\s*(?:toBeNull|toBeUndefined|toBeFalsy))\s*\(/;
|
|
6081
|
+
COUNT_AT_LEAST_ZERO = /\.(?:count\s*\(\s*\)|length)[\s)]{0,10}\.(?:toBeGreaterThanOrEqual\s*\(\s*0\s*\)|toBeGreaterThan\s*\(\s*-\s*1\s*\))/;
|
|
6082
|
+
DEFINED_VALUE_READ = /\.\s*(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|title|url|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts)\s*\([^()]*\)$/;
|
|
6083
|
+
ALWAYS_DEFINED = /^\)\s*\.\s*(?:toBeDefined|not\s*\.\s*toBeUndefined)\s*\(/;
|
|
6084
|
+
SELF_COMPARE_MATCHER = /^\)\s*\.\s*(toBe|toEqual|toStrictEqual|toHaveCount)\s*\(/;
|
|
6085
|
+
IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
|
|
6086
|
+
PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
|
|
6087
|
+
COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
|
|
5909
6088
|
FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
|
|
5910
6089
|
CONTROL_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(|\.toHaveAttribute\s*\(\s*['"`]aria-checked['"`]/;
|
|
5911
6090
|
COPY_CONTROL = /\bcopy\b/i;
|
|
@@ -6114,7 +6293,7 @@ function reportedBugWhere() {
|
|
|
6114
6293
|
function bugReviewStatus(status) {
|
|
6115
6294
|
return status === "QUARANTINED" ? "confirmed" : "needs-review";
|
|
6116
6295
|
}
|
|
6117
|
-
var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
|
|
6296
|
+
var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, TEST_DID_NOT_RUN_ERROR, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
|
|
6118
6297
|
var init_run_classification = __esm({
|
|
6119
6298
|
"../shared/dist/run-classification.js"() {
|
|
6120
6299
|
"use strict";
|
|
@@ -6134,6 +6313,7 @@ var init_run_classification = __esm({
|
|
|
6134
6313
|
UNEXPECTED_PASS_ERROR_PREFIX = "UNEXPECTED_PASS:";
|
|
6135
6314
|
BUG_TEST_BROKEN_ERROR_PREFIX = "BUG_TEST_BROKEN:";
|
|
6136
6315
|
BUG_TEST_BROKEN_REASON = "bug test broken (not a bug verdict)";
|
|
6316
|
+
TEST_DID_NOT_RUN_ERROR = "Test did not run (a dependency failed or the run was interrupted)";
|
|
6137
6317
|
EMAIL_NOT_RECEIVED_PREFIXES = [
|
|
6138
6318
|
"VERIFICATION_EMAIL_NOT_RECEIVED:",
|
|
6139
6319
|
"VERIFICATION_EMAIL_HAS_NO_CODE:",
|
|
@@ -8875,6 +9055,23 @@ var init_auth_block_detector = __esm({
|
|
|
8875
9055
|
}
|
|
8876
9056
|
});
|
|
8877
9057
|
|
|
9058
|
+
// ../shared/dist/auth-session-stamp.js
|
|
9059
|
+
function parseAuthSessionStamp(value) {
|
|
9060
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
9061
|
+
return null;
|
|
9062
|
+
const { shared, role, generation } = value;
|
|
9063
|
+
if (shared !== true || role !== "producer" && role !== "follower")
|
|
9064
|
+
return null;
|
|
9065
|
+
return typeof generation === "string" && AUTH_SESSION_GENERATION_RE.test(generation) ? { shared: true, role, generation } : { shared: true, role };
|
|
9066
|
+
}
|
|
9067
|
+
var AUTH_SESSION_GENERATION_RE;
|
|
9068
|
+
var init_auth_session_stamp = __esm({
|
|
9069
|
+
"../shared/dist/auth-session-stamp.js"() {
|
|
9070
|
+
"use strict";
|
|
9071
|
+
AUTH_SESSION_GENERATION_RE = /^[0-9a-z]{1,16}$/;
|
|
9072
|
+
}
|
|
9073
|
+
});
|
|
9074
|
+
|
|
8878
9075
|
// ../shared/dist/auth-state-freshness.js
|
|
8879
9076
|
function isCsrfTokenName(name) {
|
|
8880
9077
|
return /csrf|xsrf|forgery|authenticity|requestverification/.test(name.toLowerCase());
|
|
@@ -10103,7 +10300,7 @@ var init_agentic_cli_runtime = __esm({
|
|
|
10103
10300
|
GROK_CLI_DEFAULT_MODEL = "grok-4.7";
|
|
10104
10301
|
GROK_CLI_DEFAULT_REASONING_EFFORT = "high";
|
|
10105
10302
|
CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-5-5";
|
|
10106
|
-
CLAUDE_CLI_DEFAULT_EFFORT = "
|
|
10303
|
+
CLAUDE_CLI_DEFAULT_EFFORT = "high";
|
|
10107
10304
|
MUSE_CLI_DEFAULT_MODEL = "muse-spark-1.3-contributor";
|
|
10108
10305
|
MUSE_CLI_DEFAULT_EFFORT = "high";
|
|
10109
10306
|
CODEX_CLI_DEFAULT_MODEL = "gpt-6.1-sol";
|
|
@@ -10154,9 +10351,25 @@ function isOpenCodeGoModelId(id) {
|
|
|
10154
10351
|
function hasOpenCodeGoPrefix(id) {
|
|
10155
10352
|
return (id?.trim() ?? "").startsWith(OPENCODE_GO_PREFIX);
|
|
10156
10353
|
}
|
|
10354
|
+
function hasOpenCodeGo2Prefix(id) {
|
|
10355
|
+
return (id?.trim() ?? "").startsWith(OPENCODE_GO_2_PREFIX);
|
|
10356
|
+
}
|
|
10157
10357
|
function openCodeGoWireModel(id) {
|
|
10158
10358
|
const trimmed = id?.trim() ?? "";
|
|
10159
|
-
|
|
10359
|
+
if (!isOpenCodeGoModelId(trimmed))
|
|
10360
|
+
return null;
|
|
10361
|
+
return trimmed.slice((hasOpenCodeGo2Prefix(trimmed) ? OPENCODE_GO_2_PREFIX : OPENCODE_GO_PREFIX).length);
|
|
10362
|
+
}
|
|
10363
|
+
function isZaiModelId(id) {
|
|
10364
|
+
const trimmed = id?.trim() ?? "";
|
|
10365
|
+
return ZAI_MODEL_PATTERN.test(trimmed) && !trimmed.includes("..");
|
|
10366
|
+
}
|
|
10367
|
+
function hasZaiPrefix(id) {
|
|
10368
|
+
return (id?.trim() ?? "").startsWith(ZAI_PREFIX);
|
|
10369
|
+
}
|
|
10370
|
+
function zaiWireModel(id) {
|
|
10371
|
+
const trimmed = id?.trim() ?? "";
|
|
10372
|
+
return isZaiModelId(trimmed) ? trimmed.slice(ZAI_PREFIX.length) : null;
|
|
10160
10373
|
}
|
|
10161
10374
|
function isEngineId(value) {
|
|
10162
10375
|
return CLI_ENGINE_MODEL_IDS.includes(value);
|
|
@@ -10254,7 +10467,7 @@ function museCliContributorMode(env = process.env) {
|
|
|
10254
10467
|
const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
|
|
10255
10468
|
return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
|
|
10256
10469
|
}
|
|
10257
|
-
var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_MODEL_PATTERN;
|
|
10470
|
+
var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_2_PREFIX, OPENCODE_GO_MODEL_PATTERN, ZAI_PREFIX, ZAI_MODEL_PATTERN;
|
|
10258
10471
|
var init_engine_spec = __esm({
|
|
10259
10472
|
"../shared/dist/engine-spec.js"() {
|
|
10260
10473
|
"use strict";
|
|
@@ -10313,7 +10526,10 @@ var init_engine_spec = __esm({
|
|
|
10313
10526
|
OPENROUTER_PREFIX = "openrouter:";
|
|
10314
10527
|
OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
|
|
10315
10528
|
OPENCODE_GO_PREFIX = "opencode-go:";
|
|
10316
|
-
|
|
10529
|
+
OPENCODE_GO_2_PREFIX = "opencode-go-2:";
|
|
10530
|
+
OPENCODE_GO_MODEL_PATTERN = /^opencode-go(?:-2)?:[a-z0-9][a-z0-9._-]{0,100}$/;
|
|
10531
|
+
ZAI_PREFIX = "zai:";
|
|
10532
|
+
ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
|
|
10317
10533
|
}
|
|
10318
10534
|
});
|
|
10319
10535
|
|
|
@@ -10598,6 +10814,8 @@ var init_api_model_catalog = __esm({
|
|
|
10598
10814
|
authCaptureModel: "Login capture",
|
|
10599
10815
|
plannerModel: "Scenario Planner",
|
|
10600
10816
|
plannerGateModel: "Planner Gate",
|
|
10817
|
+
pwAgentsHealerModel: "Test Healer (Playwright agents)",
|
|
10818
|
+
pwAgentsReviewModel: "Plan Review (Playwright agents)",
|
|
10601
10819
|
generationModel: "Code Generation",
|
|
10602
10820
|
reviewModel: "Code Review",
|
|
10603
10821
|
apiTestModel: "API Test Generator",
|
|
@@ -10622,11 +10840,6 @@ var init_api_model_catalog = __esm({
|
|
|
10622
10840
|
});
|
|
10623
10841
|
|
|
10624
10842
|
// ../shared/dist/provider-chains.js
|
|
10625
|
-
function isClaudeModel(id) {
|
|
10626
|
-
if (/claude|anthropic/i.test(id))
|
|
10627
|
-
return true;
|
|
10628
|
-
return API_MODEL_CATALOG.some((m) => m.provider === "anthropic" && (m.id === id || m.apiModel === id));
|
|
10629
|
-
}
|
|
10630
10843
|
function chainKey(modelId) {
|
|
10631
10844
|
const id = canonicalModelId(modelId);
|
|
10632
10845
|
if (!id)
|
|
@@ -10634,34 +10847,83 @@ function chainKey(modelId) {
|
|
|
10634
10847
|
const entry = API_MODEL_CATALOG.find((m) => m.id === id || m.apiModel === id);
|
|
10635
10848
|
return entry?.id ?? id;
|
|
10636
10849
|
}
|
|
10850
|
+
function has(map, key) {
|
|
10851
|
+
return Object.prototype.hasOwnProperty.call(map, key);
|
|
10852
|
+
}
|
|
10637
10853
|
function providerChainFor(modelId) {
|
|
10638
10854
|
const key = chainKey(modelId);
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10855
|
+
return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
|
|
10856
|
+
}
|
|
10857
|
+
function providerChainLeadsFor(modelId) {
|
|
10858
|
+
const key = chainKey(modelId);
|
|
10859
|
+
return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
|
|
10860
|
+
}
|
|
10861
|
+
function allChains() {
|
|
10862
|
+
return [...Object.entries(PROVIDER_CHAINS), ...Object.entries(PROVIDER_CHAIN_LEADS)];
|
|
10642
10863
|
}
|
|
10643
10864
|
function isProviderChainHopId(modelId) {
|
|
10644
10865
|
const id = modelId ?? "";
|
|
10645
|
-
return id !== "" &&
|
|
10866
|
+
return id !== "" && allChains().some(([, hops]) => hops.includes(id));
|
|
10646
10867
|
}
|
|
10647
10868
|
function providerChainPrimariesForHop(hopId) {
|
|
10648
10869
|
const id = hopId ?? "";
|
|
10649
10870
|
if (!id)
|
|
10650
10871
|
return [];
|
|
10651
|
-
return
|
|
10872
|
+
return allChains().filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
|
|
10873
|
+
}
|
|
10874
|
+
function providerChainHostOf(value, modelValue) {
|
|
10875
|
+
if (value === modelValue)
|
|
10876
|
+
return "own";
|
|
10877
|
+
if (hasZaiPrefix(value))
|
|
10878
|
+
return "zai";
|
|
10879
|
+
if (hasOpenCodeGo2Prefix(value))
|
|
10880
|
+
return "opencode-go-2";
|
|
10881
|
+
if (hasOpenCodeGoPrefix(value))
|
|
10882
|
+
return "opencode-go";
|
|
10883
|
+
if (hasOpenRouterPrefix(value))
|
|
10884
|
+
return "openrouter";
|
|
10885
|
+
return "own";
|
|
10886
|
+
}
|
|
10887
|
+
function providerHostPlan(modelId) {
|
|
10888
|
+
const key = chainKey(modelId);
|
|
10889
|
+
if (!key)
|
|
10890
|
+
return [];
|
|
10891
|
+
const own = { host: hasOpenRouterPrefix(key) ? "openrouter" : "own", value: key };
|
|
10892
|
+
const steps = [
|
|
10893
|
+
...providerChainLeadsFor(key).map((value) => ({ host: providerChainHostOf(value, key), value })),
|
|
10894
|
+
own,
|
|
10895
|
+
...providerChainFor(key).map((value) => ({ host: providerChainHostOf(value, key), value }))
|
|
10896
|
+
];
|
|
10897
|
+
const notOffered = Object.prototype.hasOwnProperty.call(PROVIDER_HOST_NOT_OFFERED, key) ? PROVIDER_HOST_NOT_OFFERED[key] : {};
|
|
10898
|
+
for (const [host, reason] of Object.entries(notOffered)) {
|
|
10899
|
+
const at = steps.findIndex((step) => step === own) + 1;
|
|
10900
|
+
steps.splice(at, 0, { host, value: null, notOffered: reason });
|
|
10901
|
+
}
|
|
10902
|
+
return steps;
|
|
10652
10903
|
}
|
|
10653
|
-
var PROVIDER_CHAINS;
|
|
10904
|
+
var PROVIDER_CHAINS, PROVIDER_CHAIN_LEADS, PROVIDER_HOST_NOT_OFFERED;
|
|
10654
10905
|
var init_provider_chains = __esm({
|
|
10655
10906
|
"../shared/dist/provider-chains.js"() {
|
|
10656
10907
|
"use strict";
|
|
10657
10908
|
init_api_model_catalog();
|
|
10909
|
+
init_engine_spec();
|
|
10658
10910
|
PROVIDER_CHAINS = {
|
|
10659
|
-
"minimax-m3": ["
|
|
10660
|
-
"minimax-m3.1-flash": ["
|
|
10911
|
+
"minimax-m3": ["opencode-go:minimax-m3", "openrouter:minimax/minimax-m3"],
|
|
10912
|
+
"minimax-m3.1-flash": ["opencode-go:minimax-m3.1-flash", "openrouter:minimax/minimax-m3.1-flash"],
|
|
10661
10913
|
"muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
|
|
10662
|
-
"
|
|
10663
|
-
"
|
|
10664
|
-
"
|
|
10914
|
+
"claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
|
|
10915
|
+
"gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
|
|
10916
|
+
"deepseek-flash": ["opencode-go-2:deepseek-v4.1-flash", "opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
|
|
10917
|
+
"deepseek-v4-pro": ["opencode-go-2:deepseek-v4-pro", "opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
|
|
10918
|
+
};
|
|
10919
|
+
PROVIDER_CHAIN_LEADS = {
|
|
10920
|
+
"openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go-2:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
|
|
10921
|
+
"openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go-2:glm-5.3", "opencode-go:glm-5.3"],
|
|
10922
|
+
"openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go-2:glm-5.2", "opencode-go:glm-5.2"]
|
|
10923
|
+
};
|
|
10924
|
+
PROVIDER_HOST_NOT_OFFERED = {
|
|
10925
|
+
"muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
|
|
10926
|
+
"gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
|
|
10665
10927
|
};
|
|
10666
10928
|
}
|
|
10667
10929
|
});
|
|
@@ -12620,7 +12882,7 @@ function stateLiteralsOf(line) {
|
|
|
12620
12882
|
const out = [];
|
|
12621
12883
|
for (const m of line.matchAll(STRING_LITERAL_RE)) {
|
|
12622
12884
|
const text = m[1] ?? m[2] ?? m[3] ?? "";
|
|
12623
|
-
if (
|
|
12885
|
+
if (PLACEHOLDER_ATTR_RE.test(text))
|
|
12624
12886
|
continue;
|
|
12625
12887
|
if (PLACEHOLDER_CALL_TAIL_RE.test(line.slice(Math.max(0, m.index - 40), m.index)))
|
|
12626
12888
|
continue;
|
|
@@ -12716,7 +12978,7 @@ function formatReadinessPollAdvisory(findings) {
|
|
|
12716
12978
|
const where = findings.length === 1 ? "One place" : `${findings.length} places`;
|
|
12717
12979
|
return `${where} re-check a background state once and assert the finished state at once (${short(first.line, 90)} then ${short(first.assertion, 90)}). ${first.reason}`;
|
|
12718
12980
|
}
|
|
12719
|
-
var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
|
|
12981
|
+
var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_ATTR_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
|
|
12720
12982
|
var init_readiness_poll = __esm({
|
|
12721
12983
|
"../shared/dist/readiness-poll.js"() {
|
|
12722
12984
|
"use strict";
|
|
@@ -12729,7 +12991,7 @@ var init_readiness_poll = __esm({
|
|
|
12729
12991
|
STRING_LITERAL_RE = /'((?:\\.|[^'\\\n]){0,300})'|"((?:\\.|[^"\\\n]){0,300})"|`((?:\\.|[^`\\\n]){0,300})`/g;
|
|
12730
12992
|
RECHECK_NAME_RE = /^\s{0,10}(?:check again|check (?:status|availability|now|for updates|progress)|re-?check(?: status)?|update status|see (?:latest|updates))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
|
|
12731
12993
|
RETRY_NAME_RE = /^\s{0,10}(?:refresh(?: (?:status|list|page|results|now))?|reload(?: (?:page|results|list))?|try again|retry|load (?:again|latest))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
|
|
12732
|
-
PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b[a-z]{3,
|
|
12994
|
+
PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b(?:saving|uploading|downloading|signing|logging|submitting|creating|updating|deleting|importing|exporting|publishing|connecting|redirecting|authenticating|installing|retrieving|fetching|analy[sz]ing|running|rendering|restarting|building|compiling|booting|provisioning|deploying|indexing|migrating|launching|initiali[sz]ing|optimi[sz]ing|converting|processing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}(?:starting|scanning|working|applying|finishing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}[a-z]{3,40}(?<!th)ing\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
|
|
12733
12995
|
LOCATOR_LINE_RE = /\.(?:getBy[A-Z]\w{0,30}|locator|toHaveText|toContainText)\s{0,20}\(/;
|
|
12734
12996
|
CLICK_RE = /\.(?:click|dblclick|tap)\s{0,20}\(/;
|
|
12735
12997
|
RELOAD_RE = /\bpage\.reload\s{0,20}\(/;
|
|
@@ -12743,6 +13005,7 @@ var init_readiness_poll = __esm({
|
|
|
12743
13005
|
TEST_END_RE = /^\s{0,20}\}\s{0,20}\)\s{0,20};?\s{0,20}$/;
|
|
12744
13006
|
TEST_START_RE = /\btest(?:\.\w{1,20})?\s{0,20}\(/;
|
|
12745
13007
|
POLL_OPEN_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}async\b|\bexpect\.poll\s{0,20}\(|\bfor\s{0,20}\(|\bwhile\s{0,20}\(|\bdo\s{0,20}\{/g;
|
|
13008
|
+
PLACEHOLDER_ATTR_RE = /placeholder\s{0,4}[*^$~|]?=/;
|
|
12746
13009
|
PLACEHOLDER_CALL_TAIL_RE = /getByPlaceholder\s{0,20}\(\s{0,20}$/;
|
|
12747
13010
|
POLL_SHAPE = "await expect(async () => { await page.reload(); await expect(<the finished state>).toBeVisible({ timeout: 1500 }); }).toPass({ timeout: 20000 });";
|
|
12748
13011
|
}
|
|
@@ -13144,6 +13407,15 @@ function computes(line) {
|
|
|
13144
13407
|
const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
|
|
13145
13408
|
return COMPUTE_RE.test(code) || RELATIONAL_RE.test(line);
|
|
13146
13409
|
}
|
|
13410
|
+
function identifiersIn(line) {
|
|
13411
|
+
const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX");
|
|
13412
|
+
return [code, ...[...line.matchAll(TEMPLATE_EXPR_RE)].map((m) => m[1])].flatMap((part) => part.match(IDENTIFIER_RE) ?? []);
|
|
13413
|
+
}
|
|
13414
|
+
function addTyped(typed, value) {
|
|
13415
|
+
const d = digitsOf(value);
|
|
13416
|
+
if (/\d/.test(d))
|
|
13417
|
+
typed.add(d);
|
|
13418
|
+
}
|
|
13147
13419
|
function reasonFor4(kind, label, value) {
|
|
13148
13420
|
const what = kind === "derived-value-shape-only" ? `The ${label} is checked only for its shape, so any amount passes, including a wrongly computed one.` : `The ${label} is checked against a number written into the test (${value}). That number was read off the page, so when the page computes it wrong the check still passes.`;
|
|
13149
13421
|
return `${what} Instead, ${COMPUTE_SHAPE} If the number is not computed from other values (a list price, an id, a fixed fee), leave it as it is. Keep the existing checks.`;
|
|
@@ -13155,25 +13427,32 @@ function analyzeDerivedValues(rawCode) {
|
|
|
13155
13427
|
for (const body of testBodies(rawCode)) {
|
|
13156
13428
|
if (findings.length >= MAX_FINDINGS4)
|
|
13157
13429
|
break;
|
|
13158
|
-
if (body.some(computes))
|
|
13159
|
-
continue;
|
|
13160
13430
|
const typed = /* @__PURE__ */ new Set();
|
|
13161
13431
|
const definitions = /* @__PURE__ */ new Map();
|
|
13432
|
+
const computed = /* @__PURE__ */ new Set();
|
|
13433
|
+
const usesComputed = (text) => identifiersIn(text).some((id) => computed.has(id));
|
|
13162
13434
|
for (const line of body) {
|
|
13163
13435
|
if (FILL_RE2.test(line) && !EXPECT_RE3.test(line)) {
|
|
13164
|
-
for (const m of line.matchAll(
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13436
|
+
for (const m of line.matchAll(QUOTED_RE))
|
|
13437
|
+
addTyped(typed, m[1]);
|
|
13438
|
+
const arg = FILL_ARG_RE.exec(line)?.[1];
|
|
13439
|
+
const literal = arg === void 0 ? null : LITERAL_VALUE_RE.exec(definitions.get(arg) ?? "");
|
|
13440
|
+
const value = literal?.slice(1).find((v) => v !== void 0);
|
|
13441
|
+
if (value !== void 0)
|
|
13442
|
+
addTyped(typed, value);
|
|
13169
13443
|
}
|
|
13170
13444
|
const def = DEFINITION_RE.exec(line);
|
|
13171
|
-
if (def)
|
|
13445
|
+
if (def) {
|
|
13172
13446
|
definitions.set(def[1], def[2]);
|
|
13447
|
+
if (computes(def[2]) || usesComputed(def[2]))
|
|
13448
|
+
computed.add(def[1]);
|
|
13449
|
+
}
|
|
13173
13450
|
}
|
|
13174
13451
|
for (const line of body) {
|
|
13175
13452
|
if (!EXPECT_RE3.test(line) || !VALUE_MATCHER_RE2.test(line) || /\.not\s{0,20}\./.test(line))
|
|
13176
13453
|
continue;
|
|
13454
|
+
if (computes(line) || usesComputed(line))
|
|
13455
|
+
continue;
|
|
13177
13456
|
const subject = EXPECT_SUBJECT_RE.exec(line)?.[1];
|
|
13178
13457
|
const label = DERIVED_WORD_RE.exec(`${line} ${(subject && definitions.get(subject)) ?? ""}`)?.[0];
|
|
13179
13458
|
if (!label)
|
|
@@ -13203,7 +13482,7 @@ function formatDerivedValueAdvisory(findings) {
|
|
|
13203
13482
|
const where = findings.length === 1 ? "One computed value is" : `${findings.length} computed values are`;
|
|
13204
13483
|
return `${where} checked against a copied number or only its shape (${short4(first.line, 90)}). ${first.reason}`;
|
|
13205
13484
|
}
|
|
13206
|
-
var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, COMPUTE_SHAPE;
|
|
13485
|
+
var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, TEMPLATE_EXPR_RE, IDENTIFIER_RE, QUOTED_RE, FILL_ARG_RE, LITERAL_VALUE_RE, COMPUTE_SHAPE;
|
|
13207
13486
|
var init_derived_value_check = __esm({
|
|
13208
13487
|
"../shared/dist/derived-value-check.js"() {
|
|
13209
13488
|
"use strict";
|
|
@@ -13223,6 +13502,11 @@ var init_derived_value_check = __esm({
|
|
|
13223
13502
|
DEFINITION_RE = /\b(?:const|let)\s{1,10}([\w$]{1,60})\s{0,10}=\s{0,10}([^\n]{0,300})/;
|
|
13224
13503
|
EXPECT_SUBJECT_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}([\w$]{1,60})/;
|
|
13225
13504
|
LITERAL_ARITH_RE = /\b\d[\d_.]{0,15}(?:\s{0,5}[*/+-]\s{0,5}\d[\d_.]{0,15}){1,8}\b/g;
|
|
13505
|
+
TEMPLATE_EXPR_RE = /\$\{([^{}\n]{0,200})\}/g;
|
|
13506
|
+
IDENTIFIER_RE = /[A-Za-z_$][\w$]{0,60}/g;
|
|
13507
|
+
QUOTED_RE = /['"`]([^'"`\n]{0,40})['"`]/g;
|
|
13508
|
+
FILL_ARG_RE = /\.(?:fill|type|pressSequentially|selectOption)\s{0,20}\(\s{0,20}([A-Za-z_$][\w$]{0,60})\s{0,20}[,)]/;
|
|
13509
|
+
LITERAL_VALUE_RE = /^\s{0,10}(?:'([^'\n]{0,40})'|"([^"\n]{0,40})"|`([^`$\n]{0,40})`|(\d[\d_,]{0,15}(?:\.\d{1,4})?))\s{0,10};?\s{0,10}$/;
|
|
13226
13510
|
COMPUTE_SHAPE = "compute the expected value in the test from its inputs (the values the test typed, or each input read from the page) and compare: const toNumber = (s) => Number(s.replace(/[^\\d.-]/g, '')); const unit = toNumber(await <unit price locator>.innerText()); then expect(toNumber(await <shown value locator>.innerText())).toBeCloseTo(unit * quantity, 2); for several lines, read them all and sum them with reduce. For a comma-decimal price like \u20AC1.234,56, drop the dots and turn the comma into a point before Number, since the replace above would read it as 1.23456.";
|
|
13227
13511
|
}
|
|
13228
13512
|
});
|
|
@@ -15257,6 +15541,99 @@ function formatStaleAfterSubmitAdvisory(rawCode) {
|
|
|
15257
15541
|
const more = described.length > 3 ? ` (+${described.length - 3} more)` : "";
|
|
15258
15542
|
return `stale-after-submit: ${described.slice(0, 3).join("; ")}${more}; these pass before the request lands and still pass if the server rejects or ignores the save. Wait for the result instead: page.waitForResponse around the click, or assert something the submit creates (the new row, the confirmation message, a path with an end anchor such as /\\/orders\\/\\d+$/)`;
|
|
15259
15543
|
}
|
|
15544
|
+
function regexLiteralEnd4(text, at) {
|
|
15545
|
+
let inClass = false;
|
|
15546
|
+
for (let i = at + 1; i < text.length; i++) {
|
|
15547
|
+
const ch = text[i];
|
|
15548
|
+
if (ch === "\n")
|
|
15549
|
+
return -1;
|
|
15550
|
+
if (ch === "\\") {
|
|
15551
|
+
i++;
|
|
15552
|
+
continue;
|
|
15553
|
+
}
|
|
15554
|
+
if (inClass) {
|
|
15555
|
+
if (ch === "]")
|
|
15556
|
+
inClass = false;
|
|
15557
|
+
continue;
|
|
15558
|
+
}
|
|
15559
|
+
if (ch === "[") {
|
|
15560
|
+
inClass = true;
|
|
15561
|
+
continue;
|
|
15562
|
+
}
|
|
15563
|
+
if (ch === "/")
|
|
15564
|
+
return i + 1;
|
|
15565
|
+
}
|
|
15566
|
+
return -1;
|
|
15567
|
+
}
|
|
15568
|
+
function startsWithRegexLiteral(text) {
|
|
15569
|
+
const t = text.trimStart();
|
|
15570
|
+
return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
|
|
15571
|
+
}
|
|
15572
|
+
function afterFirstArg(args) {
|
|
15573
|
+
let depth = 0;
|
|
15574
|
+
let quote = null;
|
|
15575
|
+
for (let i = 0; i < args.length; i++) {
|
|
15576
|
+
const ch = args[i];
|
|
15577
|
+
if (quote) {
|
|
15578
|
+
if (ch === "\\") {
|
|
15579
|
+
i++;
|
|
15580
|
+
continue;
|
|
15581
|
+
}
|
|
15582
|
+
if (ch === quote)
|
|
15583
|
+
quote = null;
|
|
15584
|
+
continue;
|
|
15585
|
+
}
|
|
15586
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
15587
|
+
quote = ch;
|
|
15588
|
+
continue;
|
|
15589
|
+
}
|
|
15590
|
+
if (ch === "(" || ch === "[" || ch === "{")
|
|
15591
|
+
depth++;
|
|
15592
|
+
else if (ch === ")" || ch === "]" || ch === "}")
|
|
15593
|
+
depth--;
|
|
15594
|
+
else if (ch === "," && depth === 0)
|
|
15595
|
+
return args.slice(i + 1);
|
|
15596
|
+
}
|
|
15597
|
+
return "";
|
|
15598
|
+
}
|
|
15599
|
+
function analyzeRegexMatchers(rawCode) {
|
|
15600
|
+
if (!rawCode || typeof rawCode !== "string")
|
|
15601
|
+
return [];
|
|
15602
|
+
const code = stripComments2(rawCode.slice(0, MAX_LINT_INPUT));
|
|
15603
|
+
const out = [];
|
|
15604
|
+
const re = /\bexpect(?:\.soft)?\s{0,20}\(/g;
|
|
15605
|
+
let m;
|
|
15606
|
+
let examined = 0;
|
|
15607
|
+
let line = 1;
|
|
15608
|
+
let lineAt2 = 0;
|
|
15609
|
+
while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
|
|
15610
|
+
const subj = readBalanced(code, m.index + m[0].length);
|
|
15611
|
+
if (!subj)
|
|
15612
|
+
continue;
|
|
15613
|
+
const mm = /^\s{0,20}(\.\s{0,20}not)?\s{0,20}\.\s{0,20}(\w{1,40})\s{0,20}\(/.exec(code.slice(subj.end + 1, subj.end + 1 + 80));
|
|
15614
|
+
if (!mm || !REGEX_EQUALITY_MATCHERS.has(mm[2]))
|
|
15615
|
+
continue;
|
|
15616
|
+
const args = readBalanced(code, subj.end + 1 + mm[0].length);
|
|
15617
|
+
if (!args)
|
|
15618
|
+
continue;
|
|
15619
|
+
const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
|
|
15620
|
+
if (!startsWithRegexLiteral(expected))
|
|
15621
|
+
continue;
|
|
15622
|
+
if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain" && (startsWithRegexLiteral(subj.text) || /^new\s+RegExp\b/.test(subj.text.trim())))
|
|
15623
|
+
continue;
|
|
15624
|
+
for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
|
|
15625
|
+
line++;
|
|
15626
|
+
lineAt2 = i + 1;
|
|
15627
|
+
}
|
|
15628
|
+
out.push({ line, matcher: mm[2], negated: mm[1] !== void 0 });
|
|
15629
|
+
}
|
|
15630
|
+
return out;
|
|
15631
|
+
}
|
|
15632
|
+
function formatRegexMatcherNote(findings) {
|
|
15633
|
+
const lines = findings.map((f) => `Line ${f.line}: .${f.negated ? "not." : ""}${f.matcher}(\u2026 /regex/ \u2026) ${f.negated ? "always passes (a regex never equals the value), so it checks nothing" : "can never pass: this matcher compares by equality and a regex never equals a value"}.`);
|
|
15634
|
+
return `${lines.join("\n")}
|
|
15635
|
+
A regex only works in toMatch, toHaveText, toContainText, toHaveValue and similar text matchers. For a DOM property read it first: expect(await loc.evaluate((n) => n.<prop>)).toMatch(/re/). Or give toHaveJSProperty the exact string (or boolean) the page shows. Keep every other line as-is.`;
|
|
15636
|
+
}
|
|
15260
15637
|
function lintTestQuality(rawCode, opts = {}) {
|
|
15261
15638
|
if (!rawCode || typeof rawCode !== "string")
|
|
15262
15639
|
return [];
|
|
@@ -15264,6 +15641,10 @@ function lintTestQuality(rawCode, opts = {}) {
|
|
|
15264
15641
|
return lintTestData(rawCode, { pageContacts: opts.pageContacts });
|
|
15265
15642
|
if (opts.only === "test-data-blocking")
|
|
15266
15643
|
return analyzeTestData(rawCode, { pageContacts: opts.pageContacts }).blocking;
|
|
15644
|
+
if (opts.only === "regex-matcher") {
|
|
15645
|
+
const found = analyzeRegexMatchers(rawCode);
|
|
15646
|
+
return found.length > 0 ? [formatRegexMatcherNote(found)] : [];
|
|
15647
|
+
}
|
|
15267
15648
|
const warnings = [];
|
|
15268
15649
|
if (rawCode.length > MAX_LINT_INPUT) {
|
|
15269
15650
|
warnings.push(`The code is ${Math.round(rawCode.length / 1024)} KB; the quality lint checked only the first ${MAX_LINT_INPUT / 1e3} KB. Split it into smaller tests (one behaviour per test).`);
|
|
@@ -15538,7 +15919,7 @@ function specItemsFromTestPlans(testPlans) {
|
|
|
15538
15919
|
}
|
|
15539
15920
|
return [...new Set(out)].slice(0, 200);
|
|
15540
15921
|
}
|
|
15541
|
-
var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, STOP, SYNONYMS, AREA_TOKENS;
|
|
15922
|
+
var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, MAX_REGEX_MATCHER_FINDINGS, REGEX_EQUALITY_MATCHERS, STOP, SYNONYMS, AREA_TOKENS;
|
|
15542
15923
|
var init_test_authoring_quality = __esm({
|
|
15543
15924
|
"../shared/dist/test-authoring-quality.js"() {
|
|
15544
15925
|
"use strict";
|
|
@@ -15770,6 +16151,8 @@ var init_test_authoring_quality = __esm({
|
|
|
15770
16151
|
};
|
|
15771
16152
|
LIVE_WRITE_NAME = /\b(?:submit|place|book|reserve|confirm|pay|send|checkout|order|register|sign\s?up|create account)\b/i;
|
|
15772
16153
|
BUNDLED_CHECKS_BEFORE_WRITE = 5;
|
|
16154
|
+
MAX_REGEX_MATCHER_FINDINGS = 10;
|
|
16155
|
+
REGEX_EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual", "toContain", "toHaveJSProperty"]);
|
|
15773
16156
|
STOP = /* @__PURE__ */ new Set([
|
|
15774
16157
|
"a",
|
|
15775
16158
|
"an",
|
|
@@ -15998,6 +16381,19 @@ var init_fence_runs = __esm({
|
|
|
15998
16381
|
}
|
|
15999
16382
|
});
|
|
16000
16383
|
|
|
16384
|
+
// ../shared/dist/trailing-slashes.js
|
|
16385
|
+
function stripTrailingSlashes(value) {
|
|
16386
|
+
let end = value.length;
|
|
16387
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47)
|
|
16388
|
+
end--;
|
|
16389
|
+
return end === value.length ? value : value.slice(0, end);
|
|
16390
|
+
}
|
|
16391
|
+
var init_trailing_slashes = __esm({
|
|
16392
|
+
"../shared/dist/trailing-slashes.js"() {
|
|
16393
|
+
"use strict";
|
|
16394
|
+
}
|
|
16395
|
+
});
|
|
16396
|
+
|
|
16001
16397
|
// ../shared/dist/survey-bug-signature.js
|
|
16002
16398
|
function normalizeWords(text) {
|
|
16003
16399
|
return text.toLowerCase().replace(UUID, " id ").replace(PREFIXED_ID, " id ").replace(HEX_RUN, (run) => /\d/.test(run) ? " id " : run).replace(NUMBER_RUN, " # ").replace(/[^\p{L}\p{N}#]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
@@ -16008,7 +16404,7 @@ function normalizeBugPath(route) {
|
|
|
16008
16404
|
if (hash >= 0 && path[hash + 1] !== "/")
|
|
16009
16405
|
path = path.slice(0, hash);
|
|
16010
16406
|
const segments = path.toLowerCase().split("/").map((segment) => /\d/.test(segment) ? ":id" : segment);
|
|
16011
|
-
const joined = segments.join("/")
|
|
16407
|
+
const joined = stripTrailingSlashes(segments.join("/"));
|
|
16012
16408
|
return joined || (path.startsWith("/") ? "/" : "");
|
|
16013
16409
|
}
|
|
16014
16410
|
function isGenericConsoleMessage(message) {
|
|
@@ -16142,6 +16538,7 @@ var SIGNATURE_SOURCE_MAX_CHARS, UUID, PREFIXED_ID, HEX_RUN, NUMBER_RUN, GENERIC_
|
|
|
16142
16538
|
var init_survey_bug_signature = __esm({
|
|
16143
16539
|
"../shared/dist/survey-bug-signature.js"() {
|
|
16144
16540
|
"use strict";
|
|
16541
|
+
init_trailing_slashes();
|
|
16145
16542
|
SIGNATURE_SOURCE_MAX_CHARS = 800;
|
|
16146
16543
|
UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g;
|
|
16147
16544
|
PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
|
|
@@ -17422,9 +17819,9 @@ function lastLocatorLabels(chain) {
|
|
|
17422
17819
|
if (t)
|
|
17423
17820
|
labels.push(t[1]);
|
|
17424
17821
|
}
|
|
17425
|
-
const
|
|
17426
|
-
if (
|
|
17427
|
-
labels.push(
|
|
17822
|
+
const has2 = optionText(last.args, HAS_TEXT_OPTION_RE);
|
|
17823
|
+
if (has2 !== null)
|
|
17824
|
+
labels.push(has2);
|
|
17428
17825
|
}
|
|
17429
17826
|
return labels.map((l) => l.replace(/^[\s^(?:]+/, "").trim());
|
|
17430
17827
|
}
|
|
@@ -18181,14 +18578,17 @@ var init_promotion_quality_gate = __esm({
|
|
|
18181
18578
|
SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
|
|
18182
18579
|
strengthCheck = {
|
|
18183
18580
|
id: "assertion-strength",
|
|
18184
|
-
run({ code, strengthGate, flagContradictedAdditions }) {
|
|
18581
|
+
run({ code, tags, strengthGate, flagContradictedAdditions }) {
|
|
18185
18582
|
const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
|
|
18186
18583
|
if (verdict.severity === "route-bug")
|
|
18187
18584
|
return [{ check: "route-bug" }];
|
|
18188
|
-
|
|
18585
|
+
const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
|
|
18586
|
+
if (verdict.severity !== "block" && !authDowngrade)
|
|
18189
18587
|
return [];
|
|
18190
18588
|
const revealed = strengthGate?.severity === "none" && strengthGate.revealEvidence === "revealed" && strengthGate.source !== "heal";
|
|
18191
|
-
|
|
18589
|
+
if (revealed)
|
|
18590
|
+
return [];
|
|
18591
|
+
return [{ check: authDowngrade ? "strength-downgrade-auth" : "strength-block" }];
|
|
18192
18592
|
}
|
|
18193
18593
|
};
|
|
18194
18594
|
STRING_CONST = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,60})\s{0,20}=\s{0,20}(?:'([^'\\\n]{0,300})'|"([^"\\\n]{0,300})"|`([^`\\$\n]{0,300})`)\s{0,20}[;\n]/g;
|
|
@@ -18595,6 +18995,7 @@ __export(dist_exports, {
|
|
|
18595
18995
|
AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
|
|
18596
18996
|
AUTH_PATH_SIGNATURE: () => AUTH_PATH_SIGNATURE,
|
|
18597
18997
|
AUTH_PROVIDER_HOST_SIGNATURES: () => AUTH_PROVIDER_HOST_SIGNATURES,
|
|
18998
|
+
AUTH_SESSION_GENERATION_RE: () => AUTH_SESSION_GENERATION_RE,
|
|
18598
18999
|
AUTH_STATE_EXPIRY_SKEW_MS: () => AUTH_STATE_EXPIRY_SKEW_MS,
|
|
18599
19000
|
AUTH_TAG_COOKIE_STATE: () => AUTH_TAG_COOKIE_STATE,
|
|
18600
19001
|
AUTH_TAG_LOGIN_BEARER: () => AUTH_TAG_LOGIN_BEARER,
|
|
@@ -18727,6 +19128,8 @@ __export(dist_exports, {
|
|
|
18727
19128
|
LIVE_FRAME_STALE_MS: () => LIVE_FRAME_STALE_MS,
|
|
18728
19129
|
LIVE_FRAME_UNFOCUSED_INTERVAL_MS: () => LIVE_FRAME_UNFOCUSED_INTERVAL_MS,
|
|
18729
19130
|
LIVE_THUMBNAIL_MAX_BASE64_BYTES: () => LIVE_THUMBNAIL_MAX_BASE64_BYTES,
|
|
19131
|
+
MAX_BALANCED_SCAN: () => MAX_BALANCED_SCAN,
|
|
19132
|
+
MAX_CALLS: () => MAX_CALLS,
|
|
18730
19133
|
MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
|
|
18731
19134
|
MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
|
|
18732
19135
|
MAX_EMAIL_ADDRESS_LENGTH: () => MAX_EMAIL_ADDRESS_LENGTH,
|
|
@@ -18771,6 +19174,7 @@ __export(dist_exports, {
|
|
|
18771
19174
|
MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
|
|
18772
19175
|
MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
|
|
18773
19176
|
NOREPLY_EMAIL: () => NOREPLY_EMAIL,
|
|
19177
|
+
OPENCODE_GO_2_PREFIX: () => OPENCODE_GO_2_PREFIX,
|
|
18774
19178
|
OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
|
|
18775
19179
|
OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
|
|
18776
19180
|
OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
|
|
@@ -18806,6 +19210,8 @@ __export(dist_exports, {
|
|
|
18806
19210
|
PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
|
|
18807
19211
|
PROMOTION_QUALITY_CHECKS: () => PROMOTION_QUALITY_CHECKS,
|
|
18808
19212
|
PROVIDER_CHAINS: () => PROVIDER_CHAINS,
|
|
19213
|
+
PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
|
|
19214
|
+
PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
|
|
18809
19215
|
PUBLIC_PLANS: () => PUBLIC_PLANS,
|
|
18810
19216
|
QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
|
|
18811
19217
|
READINESS_POLL_MAX_CODE_CHARS: () => READINESS_POLL_MAX_CODE_CHARS,
|
|
@@ -18844,6 +19250,7 @@ __export(dist_exports, {
|
|
|
18844
19250
|
SURVEY_NOTES_RENDER_MAX_CHARS: () => SURVEY_NOTES_RENDER_MAX_CHARS,
|
|
18845
19251
|
SURVEY_NOTE_KEYS: () => SURVEY_NOTE_KEYS,
|
|
18846
19252
|
SUSPICIOUS_PASS_TAG: () => SUSPICIOUS_PASS_TAG,
|
|
19253
|
+
TEST_DID_NOT_RUN_ERROR: () => TEST_DID_NOT_RUN_ERROR,
|
|
18847
19254
|
TEST_QUALITY_CONVENTIONS: () => TEST_QUALITY_CONVENTIONS,
|
|
18848
19255
|
TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
|
|
18849
19256
|
TEST_VARIABLE_SECRET_KEY_PATTERN: () => TEST_VARIABLE_SECRET_KEY_PATTERN,
|
|
@@ -18859,6 +19266,8 @@ __export(dist_exports, {
|
|
|
18859
19266
|
VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
|
|
18860
19267
|
WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
|
|
18861
19268
|
WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
|
|
19269
|
+
ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
|
|
19270
|
+
ZAI_PREFIX: () => ZAI_PREFIX,
|
|
18862
19271
|
accumulateStatements: () => accumulateStatements,
|
|
18863
19272
|
adoptBrokerFailover: () => adoptBrokerFailover,
|
|
18864
19273
|
aiCostToCreditCents: () => aiCostToCreditCents,
|
|
@@ -18867,6 +19276,7 @@ __export(dist_exports, {
|
|
|
18867
19276
|
analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
|
|
18868
19277
|
analyzePersistenceReread: () => analyzePersistenceReread,
|
|
18869
19278
|
analyzeReadinessPolling: () => analyzeReadinessPolling,
|
|
19279
|
+
analyzeRegexMatchers: () => analyzeRegexMatchers,
|
|
18870
19280
|
analyzeSharedAccountUse: () => analyzeSharedAccountUse,
|
|
18871
19281
|
analyzeStaleAfterSubmit: () => analyzeStaleAfterSubmit,
|
|
18872
19282
|
analyzeTestData: () => analyzeTestData,
|
|
@@ -19030,6 +19440,7 @@ __export(dist_exports, {
|
|
|
19030
19440
|
formatPersistenceRereadAdvisory: () => formatPersistenceRereadAdvisory,
|
|
19031
19441
|
formatPoolBudgetLine: () => formatPoolBudgetLine,
|
|
19032
19442
|
formatReadinessPollAdvisory: () => formatReadinessPollAdvisory,
|
|
19443
|
+
formatRegexMatcherNote: () => formatRegexMatcherNote,
|
|
19033
19444
|
formatStaleAfterSubmitAdvisory: () => formatStaleAfterSubmitAdvisory,
|
|
19034
19445
|
formatSuspiciousExpectation: () => formatSuspiciousExpectation,
|
|
19035
19446
|
formatUrlWaitAdvisory: () => formatUrlWaitAdvisory,
|
|
@@ -19068,12 +19479,14 @@ __export(dist_exports, {
|
|
|
19068
19479
|
hasEncodedRequestBody: () => hasEncodedRequestBody,
|
|
19069
19480
|
hasEngineSpecParts: () => hasEngineSpecParts,
|
|
19070
19481
|
hasOnlyConstantAssertions: () => hasOnlyConstantAssertions,
|
|
19482
|
+
hasOpenCodeGo2Prefix: () => hasOpenCodeGo2Prefix,
|
|
19071
19483
|
hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
|
|
19072
19484
|
hasOpenRouterPrefix: () => hasOpenRouterPrefix,
|
|
19073
19485
|
hasPlatformLocator: () => hasPlatformLocator,
|
|
19074
19486
|
hasReadOnlyMarker: () => hasReadOnlyMarker,
|
|
19075
19487
|
hasSurveyNotes: () => hasSurveyNotes,
|
|
19076
19488
|
hasTestFailContract: () => hasTestFailContract,
|
|
19489
|
+
hasZaiPrefix: () => hasZaiPrefix,
|
|
19077
19490
|
healMobileTestSteps: () => healMobileTestSteps,
|
|
19078
19491
|
healthRunWhere: () => healthRunWhere,
|
|
19079
19492
|
heldCodeRecord: () => heldCodeRecord,
|
|
@@ -19177,6 +19590,7 @@ __export(dist_exports, {
|
|
|
19177
19590
|
isValidSttProvider: () => isValidSttProvider,
|
|
19178
19591
|
isValidTtsModel: () => isValidTtsModel,
|
|
19179
19592
|
isValidTtsVoiceId: () => isValidTtsVoiceId,
|
|
19593
|
+
isZaiModelId: () => isZaiModelId,
|
|
19180
19594
|
jsEscapedForms: () => jsEscapedForms,
|
|
19181
19595
|
jsonStructureRefusalOf: () => jsonStructureRefusalOf,
|
|
19182
19596
|
jsonStructureVerify: () => jsonStructureVerify,
|
|
@@ -19225,6 +19639,7 @@ __export(dist_exports, {
|
|
|
19225
19639
|
parseApiCardModelUpdate: () => parseApiCardModelUpdate,
|
|
19226
19640
|
parseAppTimezone: () => parseAppTimezone,
|
|
19227
19641
|
parseAuditGroupScopeId: () => parseAuditGroupScopeId,
|
|
19642
|
+
parseAuthSessionStamp: () => parseAuthSessionStamp,
|
|
19228
19643
|
parseBoolFlag: () => parseBoolFlag,
|
|
19229
19644
|
parseDbPoolSize: () => parseDbPoolSize,
|
|
19230
19645
|
parseDeepgramListenResponse: () => parseDeepgramListenResponse,
|
|
@@ -19268,7 +19683,11 @@ __export(dist_exports, {
|
|
|
19268
19683
|
profileKeyRoute: () => profileKeyRoute,
|
|
19269
19684
|
projectAppHosts: () => projectAppHosts,
|
|
19270
19685
|
providerChainFor: () => providerChainFor,
|
|
19686
|
+
providerChainHostOf: () => providerChainHostOf,
|
|
19687
|
+
providerChainLeadsFor: () => providerChainLeadsFor,
|
|
19271
19688
|
providerChainPrimariesForHop: () => providerChainPrimariesForHop,
|
|
19689
|
+
providerHostPlan: () => providerHostPlan,
|
|
19690
|
+
readBalanced: () => readBalanced,
|
|
19272
19691
|
readChatFailover: () => readChatFailover,
|
|
19273
19692
|
readChatReasoningTokens: () => readChatReasoningTokens,
|
|
19274
19693
|
readChatUsage: () => readChatUsage,
|
|
@@ -19351,6 +19770,7 @@ __export(dist_exports, {
|
|
|
19351
19770
|
stripCoverageComments: () => stripCoverageComments,
|
|
19352
19771
|
stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
|
|
19353
19772
|
stripLoginScaffolding: () => stripLoginScaffolding,
|
|
19773
|
+
stripTrailingSlashes: () => stripTrailingSlashes,
|
|
19354
19774
|
strongerNeedsReviewSource: () => strongerNeedsReviewSource,
|
|
19355
19775
|
sttModelsForProvider: () => sttModelsForProvider,
|
|
19356
19776
|
summarizeHealth: () => summarizeHealth,
|
|
@@ -19378,7 +19798,8 @@ __export(dist_exports, {
|
|
|
19378
19798
|
withReviewSource: () => withReviewSource,
|
|
19379
19799
|
withoutNotChargedClaim: () => withoutNotChargedClaim,
|
|
19380
19800
|
withoutServerOnlySpendFields: () => withoutServerOnlySpendFields,
|
|
19381
|
-
withoutSurveyNotes: () => withoutSurveyNotes
|
|
19801
|
+
withoutSurveyNotes: () => withoutSurveyNotes,
|
|
19802
|
+
zaiWireModel: () => zaiWireModel
|
|
19382
19803
|
});
|
|
19383
19804
|
var init_dist = __esm({
|
|
19384
19805
|
"../shared/dist/index.js"() {
|
|
@@ -19420,6 +19841,7 @@ var init_dist = __esm({
|
|
|
19420
19841
|
init_admin_view_return_to();
|
|
19421
19842
|
init_auth_plan();
|
|
19422
19843
|
init_auth_block_detector();
|
|
19844
|
+
init_auth_session_stamp();
|
|
19423
19845
|
init_auth_state_freshness();
|
|
19424
19846
|
init_skip_coverage_detector();
|
|
19425
19847
|
init_feature_coverage();
|
|
@@ -19476,6 +19898,7 @@ var init_dist = __esm({
|
|
|
19476
19898
|
init_promotion_quality_gate();
|
|
19477
19899
|
init_regex_alternation();
|
|
19478
19900
|
init_page_evidence();
|
|
19901
|
+
init_trailing_slashes();
|
|
19479
19902
|
}
|
|
19480
19903
|
});
|
|
19481
19904
|
|
|
@@ -19492,8 +19915,11 @@ var require_survey_network_guard = __commonJS({
|
|
|
19492
19915
|
exports2.maskedPath = maskedPath;
|
|
19493
19916
|
exports2.bodyKeysOf = bodyKeysOf;
|
|
19494
19917
|
exports2.graphqlDocumentIsQueryOnly = graphqlDocumentIsQueryOnly;
|
|
19918
|
+
exports2.graphqlOperationRoots = graphqlOperationRoots;
|
|
19495
19919
|
exports2.isTokenRefreshWrite = isTokenRefreshWrite;
|
|
19496
19920
|
exports2.isSignInPathWrite = isSignInPathWrite;
|
|
19921
|
+
exports2.graphqlUrlCarriesQuery = graphqlUrlCarriesQuery;
|
|
19922
|
+
exports2.graphqlGetMayWrite = graphqlGetMayWrite;
|
|
19497
19923
|
exports2.writeShapedGet = writeShapedGet;
|
|
19498
19924
|
exports2.decideSurveyRequest = decideSurveyRequest;
|
|
19499
19925
|
exports2.installSurveyNetworkGuard = installSurveyNetworkGuard;
|
|
@@ -19770,6 +20196,100 @@ var require_survey_network_guard = __commonJS({
|
|
|
19770
20196
|
}
|
|
19771
20197
|
return depth === 0 && parens === 0 && objects === 0 && sawOperation;
|
|
19772
20198
|
}
|
|
20199
|
+
function graphqlOperationRoots(text) {
|
|
20200
|
+
const tokens2 = lexGraphql(text);
|
|
20201
|
+
if (!tokens2)
|
|
20202
|
+
return null;
|
|
20203
|
+
const ops = [];
|
|
20204
|
+
let depth = 0;
|
|
20205
|
+
let parens = 0;
|
|
20206
|
+
let objects = 0;
|
|
20207
|
+
let defStart = true;
|
|
20208
|
+
let kind = null;
|
|
20209
|
+
let current = null;
|
|
20210
|
+
let prev = "";
|
|
20211
|
+
for (const tok of tokens2) {
|
|
20212
|
+
const last = prev;
|
|
20213
|
+
prev = tok;
|
|
20214
|
+
if (tok === "(") {
|
|
20215
|
+
parens++;
|
|
20216
|
+
continue;
|
|
20217
|
+
}
|
|
20218
|
+
if (tok === ")") {
|
|
20219
|
+
parens--;
|
|
20220
|
+
if (parens < 0 || objects !== 0)
|
|
20221
|
+
return null;
|
|
20222
|
+
continue;
|
|
20223
|
+
}
|
|
20224
|
+
if (parens > 0) {
|
|
20225
|
+
if (tok === "{")
|
|
20226
|
+
objects++;
|
|
20227
|
+
else if (tok === "}") {
|
|
20228
|
+
objects--;
|
|
20229
|
+
if (objects < 0)
|
|
20230
|
+
return null;
|
|
20231
|
+
}
|
|
20232
|
+
continue;
|
|
20233
|
+
}
|
|
20234
|
+
if (tok === "{") {
|
|
20235
|
+
if (depth === 0) {
|
|
20236
|
+
if (defStart)
|
|
20237
|
+
kind = "query";
|
|
20238
|
+
else if (kind === null)
|
|
20239
|
+
return null;
|
|
20240
|
+
current = kind === "fragment" ? null : { kind, roots: [] };
|
|
20241
|
+
if (current)
|
|
20242
|
+
ops.push(current);
|
|
20243
|
+
defStart = false;
|
|
20244
|
+
}
|
|
20245
|
+
depth++;
|
|
20246
|
+
continue;
|
|
20247
|
+
}
|
|
20248
|
+
if (tok === "}") {
|
|
20249
|
+
depth--;
|
|
20250
|
+
if (depth < 0)
|
|
20251
|
+
return null;
|
|
20252
|
+
if (depth === 0) {
|
|
20253
|
+
defStart = true;
|
|
20254
|
+
kind = null;
|
|
20255
|
+
current = null;
|
|
20256
|
+
}
|
|
20257
|
+
continue;
|
|
20258
|
+
}
|
|
20259
|
+
if (depth === 0) {
|
|
20260
|
+
if (!/^[_A-Za-z]/.test(tok)) {
|
|
20261
|
+
if (tok === "@")
|
|
20262
|
+
continue;
|
|
20263
|
+
return null;
|
|
20264
|
+
}
|
|
20265
|
+
if (defStart) {
|
|
20266
|
+
if (tok !== "query" && tok !== "mutation" && tok !== "fragment")
|
|
20267
|
+
return null;
|
|
20268
|
+
kind = tok;
|
|
20269
|
+
defStart = false;
|
|
20270
|
+
}
|
|
20271
|
+
continue;
|
|
20272
|
+
}
|
|
20273
|
+
if (depth > 1 || !current)
|
|
20274
|
+
continue;
|
|
20275
|
+
if (/^[_A-Za-z]/.test(tok)) {
|
|
20276
|
+
if (last === "@")
|
|
20277
|
+
continue;
|
|
20278
|
+
if (last === ":") {
|
|
20279
|
+
if (current.roots.length === 0)
|
|
20280
|
+
return null;
|
|
20281
|
+
current.roots[current.roots.length - 1] = tok;
|
|
20282
|
+
continue;
|
|
20283
|
+
}
|
|
20284
|
+
current.roots.push(tok);
|
|
20285
|
+
continue;
|
|
20286
|
+
}
|
|
20287
|
+
if (tok === ":" || tok === "@")
|
|
20288
|
+
continue;
|
|
20289
|
+
return null;
|
|
20290
|
+
}
|
|
20291
|
+
return depth === 0 && parens === 0 && objects === 0 ? ops : null;
|
|
20292
|
+
}
|
|
19773
20293
|
function isGraphqlQueryOnly(postData) {
|
|
19774
20294
|
if (!postData || postData.length > MAX_BODY_CHARS)
|
|
19775
20295
|
return false;
|
|
@@ -19807,6 +20327,34 @@ var require_survey_network_guard = __commonJS({
|
|
|
19807
20327
|
"remove"
|
|
19808
20328
|
]);
|
|
19809
20329
|
var NOT_A_SESSION_END = (segs) => !segs.some((seg) => SESSION_END_SEGMENTS.has(seg));
|
|
20330
|
+
var DESTRUCTIVE_WORDS = /* @__PURE__ */ new Set([
|
|
20331
|
+
"delete",
|
|
20332
|
+
"remove",
|
|
20333
|
+
"destroy",
|
|
20334
|
+
"deactivate",
|
|
20335
|
+
"terminate",
|
|
20336
|
+
"purge",
|
|
20337
|
+
"wipe",
|
|
20338
|
+
"erase",
|
|
20339
|
+
"cancel",
|
|
20340
|
+
"close",
|
|
20341
|
+
"revoke",
|
|
20342
|
+
"unlink",
|
|
20343
|
+
"disconnect"
|
|
20344
|
+
]);
|
|
20345
|
+
var DESTRUCTIVE_PREFIXES = ["delete", "remove", "destroy", "deactivate", "terminate", "purge"];
|
|
20346
|
+
function hasDestructiveSegment(rawSegs) {
|
|
20347
|
+
return rawSegs.some((raw) => {
|
|
20348
|
+
let seg = raw;
|
|
20349
|
+
try {
|
|
20350
|
+
seg = decodeURIComponent(raw);
|
|
20351
|
+
} catch {
|
|
20352
|
+
}
|
|
20353
|
+
const words = seg.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/);
|
|
20354
|
+
return words.some((word) => DESTRUCTIVE_WORDS.has(word) || DESTRUCTIVE_PREFIXES.some((p) => word.startsWith(p)));
|
|
20355
|
+
});
|
|
20356
|
+
}
|
|
20357
|
+
var rawPathSegments = (pathname) => pathname.split("/").map((seg) => seg.split(";")[0]).filter(Boolean);
|
|
19810
20358
|
var pathSegments = (pathname) => pathname.toLowerCase().split("/").map((seg) => seg.split(";")[0]).filter(Boolean);
|
|
19811
20359
|
function isTokenRefreshWrite(method, pathname, recentAction = false) {
|
|
19812
20360
|
if (method.toUpperCase() !== "POST")
|
|
@@ -19823,6 +20371,8 @@ var require_survey_network_guard = __commonJS({
|
|
|
19823
20371
|
var AUTH_PAGE_PATH_RE = /(?:^|\/)(?:log-?in|sign-?in|sign_in|auth|oauth2?|sso|callback)(?:\/|$)/;
|
|
19824
20372
|
var SIGN_IN_SEGMENT_RE = /^(?:log-?in|sign-?in|sign_in)$/;
|
|
19825
20373
|
var CALLBACK_ROOT_SEGMENTS = /* @__PURE__ */ new Set(["auth", "oauth", "oauth2", "sso"]);
|
|
20374
|
+
var CALLBACK_PROTOCOL_SEGMENTS = /* @__PURE__ */ new Set(["oidc", "openid", "openid-connect", "openidconnect", "saml", "saml2", "oauth", "oauth2"]);
|
|
20375
|
+
var API_VERSION_SEGMENT_RE = /^v[0-9]{1,3}$/;
|
|
19826
20376
|
var PROVIDER_SEGMENT_RE = /^[a-z][a-z0-9_-]{0,39}$/;
|
|
19827
20377
|
var NOT_A_PROVIDER_SEGMENTS = /* @__PURE__ */ new Set([
|
|
19828
20378
|
"link",
|
|
@@ -19861,14 +20411,21 @@ var require_survey_network_guard = __commonJS({
|
|
|
19861
20411
|
if (!NOT_A_SESSION_END(segs))
|
|
19862
20412
|
return false;
|
|
19863
20413
|
if (AUTH_END_PATH_RE.test(`/${segs.join("/")}`))
|
|
19864
|
-
return
|
|
20414
|
+
return !hasDestructiveSegment(rawPathSegments(pathname).slice(0, -1));
|
|
20415
|
+
let top = segs[0] === "api" ? 1 : 0;
|
|
20416
|
+
if (top < segs.length && API_VERSION_SEGMENT_RE.test(segs[top]))
|
|
20417
|
+
top += 1;
|
|
20418
|
+
const below = segs.length - top;
|
|
20419
|
+
const isProvider = (seg) => PROVIDER_SEGMENT_RE.test(seg) && !NOT_A_PROVIDER_SEGMENTS.has(seg);
|
|
19865
20420
|
const provider = segs[segs.length - 1];
|
|
19866
|
-
const authRootAtTop =
|
|
19867
|
-
if (segs.length >= 3 && authRootAtTop && CALLBACK_ROOT_SEGMENTS.has(segs[segs.length - 3]) &&
|
|
20421
|
+
const authRootAtTop = below === 3 || segs[segs.length - 4] === "api" && segs[segs.length - 3] === "auth";
|
|
20422
|
+
if (segs.length >= 3 && authRootAtTop && CALLBACK_ROOT_SEGMENTS.has(segs[segs.length - 3]) && isProvider(provider)) {
|
|
19868
20423
|
const step = segs[segs.length - 2];
|
|
19869
20424
|
if (step === "callback" || step === "signin")
|
|
19870
20425
|
return true;
|
|
19871
20426
|
}
|
|
20427
|
+
if (below === 4 && CALLBACK_ROOT_SEGMENTS.has(segs[top]) && CALLBACK_PROTOCOL_SEGMENTS.has(segs[top + 1]) && isProvider(segs[top + 2]) && segs[top + 3] === "callback")
|
|
20428
|
+
return true;
|
|
19872
20429
|
if (segs.length < 2 || segs[segs.length - 1] !== "callback")
|
|
19873
20430
|
return false;
|
|
19874
20431
|
const parent = segs[segs.length - 2];
|
|
@@ -19895,7 +20452,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
19895
20452
|
try {
|
|
19896
20453
|
const a = new URL(url);
|
|
19897
20454
|
const b = new URL(appBaseUrl);
|
|
19898
|
-
const path = (u) => u.pathname
|
|
20455
|
+
const path = (u) => (0, shared_1.stripTrailingSlashes)(u.pathname);
|
|
19899
20456
|
return a.origin === b.origin && path(a) === path(b);
|
|
19900
20457
|
} catch {
|
|
19901
20458
|
return false;
|
|
@@ -19917,9 +20474,15 @@ var require_survey_network_guard = __commonJS({
|
|
|
19917
20474
|
const bodyHash = (0, node_crypto_1.createHash)("sha256").update(req.postData ?? "").digest("hex").slice(0, 16);
|
|
19918
20475
|
return `${record.method} ${url.hostname.toLowerCase()}${url.pathname} ${bodyHash}`;
|
|
19919
20476
|
}
|
|
19920
|
-
function
|
|
19921
|
-
|
|
19922
|
-
|
|
20477
|
+
function graphqlUrlCarriesQuery(params) {
|
|
20478
|
+
return params.has("query");
|
|
20479
|
+
}
|
|
20480
|
+
function graphqlGetMayWrite(url, body) {
|
|
20481
|
+
const docs = url.searchParams.getAll("query");
|
|
20482
|
+
return docs.length > 0 && (typeof body === "string" && body.length > 0 || docs.some((doc) => !graphqlDocumentIsQueryOnly(doc)));
|
|
20483
|
+
}
|
|
20484
|
+
function isGraphqlGetWrite(url, s, body) {
|
|
20485
|
+
return isGraphqlEndpoint(url, s) && graphqlGetMayWrite(url, body);
|
|
19923
20486
|
}
|
|
19924
20487
|
exports2.WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
19925
20488
|
"cancel",
|
|
@@ -20123,7 +20686,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
20123
20686
|
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
20124
20687
|
return { action: "allow", rule: readMethod ? "read" : "non-http" };
|
|
20125
20688
|
const appSite = isAppSite(url.hostname, s.appBaseUrl);
|
|
20126
|
-
const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s);
|
|
20689
|
+
const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s, req.postData);
|
|
20127
20690
|
const inActionWindow = s.lastActionAt > 0 && s.now - s.lastActionAt <= ACTION_NAVIGATION_WINDOW_MS;
|
|
20128
20691
|
const typedLoad = s.agentLoad === true && req.resourceType === "document";
|
|
20129
20692
|
const writeShaped = method === "GET" && appSite && !graphqlGet && (inActionWindow || typedLoad) ? writeShapedGet(req.url, {
|
|
@@ -20157,7 +20720,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
20157
20720
|
return { action: "allow", rule: "extra-pattern", reason: extra.reason, record };
|
|
20158
20721
|
if (graphqlGet)
|
|
20159
20722
|
return { action: "block", rule: "app-write", record };
|
|
20160
|
-
if (method === "POST" && isGraphqlEndpoint(url, s) && isGraphqlQueryOnly(req.postData))
|
|
20723
|
+
if (method === "POST" && isGraphqlEndpoint(url, s) && !graphqlUrlCarriesQuery(url.searchParams) && isGraphqlQueryOnly(req.postData))
|
|
20161
20724
|
return { action: "allow", rule: "graphql-query", record };
|
|
20162
20725
|
const passive = s.lastNavigationAt > 0 && (s.lastActionAt === 0 || s.lastNavigationAt >= s.lastActionAt) && s.now - s.lastNavigationAt <= s.passiveWindowMs;
|
|
20163
20726
|
const signature = learnableSignature(req, url, record, s);
|
|
@@ -20189,7 +20752,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
20189
20752
|
const re = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
|
|
20190
20753
|
return { reason, test: (hostPath) => re.test(hostPath) };
|
|
20191
20754
|
}
|
|
20192
|
-
const p = pattern.trim().toLowerCase()
|
|
20755
|
+
const p = (0, shared_1.stripTrailingSlashes)(pattern.trim().toLowerCase());
|
|
20193
20756
|
if (p.includes("/"))
|
|
20194
20757
|
return { reason, test: (hostPath) => hostPath.toLowerCase() === p || hostPath.toLowerCase().startsWith(`${p}/`) };
|
|
20195
20758
|
return { reason, test: (hostPath) => {
|
|
@@ -20854,6 +21417,7 @@ var require_run_write_guard = __commonJS({
|
|
|
20854
21417
|
exports2.addRunWriteAuthHandler = addRunWriteAuthHandler2;
|
|
20855
21418
|
exports2.runWriteContextOptions = runWriteContextOptions;
|
|
20856
21419
|
exports2.clearRunWriteRelay = clearRunWriteRelay2;
|
|
21420
|
+
exports2.runWriteGuardHopBytesInFlight = runWriteGuardHopBytesInFlight;
|
|
20857
21421
|
exports2.installRunWriteGuard = installRunWriteGuard2;
|
|
20858
21422
|
exports2.installNodeFetchGuard = installNodeFetchGuard2;
|
|
20859
21423
|
exports2.runWriteGuardRefusal = runWriteGuardRefusal;
|
|
@@ -20865,7 +21429,11 @@ var require_run_write_guard = __commonJS({
|
|
|
20865
21429
|
exports2.safeWritePatternFor = safeWritePatternFor;
|
|
20866
21430
|
var node_async_hooks_1 = require("async_hooks");
|
|
20867
21431
|
var node_crypto_1 = require("crypto");
|
|
21432
|
+
var node_http_1 = require("http");
|
|
21433
|
+
var node_https_1 = require("https");
|
|
20868
21434
|
var node_path_1 = require("path");
|
|
21435
|
+
var node_stream_1 = require("stream");
|
|
21436
|
+
var node_zlib_1 = require("zlib");
|
|
20869
21437
|
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
20870
21438
|
var survey_network_guard_js_1 = require_survey_network_guard();
|
|
20871
21439
|
var bounded_output_js_1 = require_bounded_output();
|
|
@@ -20913,12 +21481,13 @@ var require_run_write_guard = __commonJS({
|
|
|
20913
21481
|
allowPatterns,
|
|
20914
21482
|
ignoredAllowPatterns,
|
|
20915
21483
|
recordOnlyRules,
|
|
20916
|
-
reason: v.reason.slice(0, MAX_REASON_CHARS)
|
|
21484
|
+
reason: v.reason.slice(0, MAX_REASON_CHARS),
|
|
21485
|
+
...v.pwAgentsGuard === "off" ? { pwAgentsGuard: "off" } : {}
|
|
20917
21486
|
};
|
|
20918
21487
|
}
|
|
20919
21488
|
var policyScope = new node_async_hooks_1.AsyncLocalStorage();
|
|
20920
21489
|
var MIN_SIGN_IN_SECRET_CHARS = 6;
|
|
20921
|
-
exports2.SIGN_IN_SECRET_VARIABLE = /PASS(?:WORD|CODE|PHRASE)
|
|
21490
|
+
exports2.SIGN_IN_SECRET_VARIABLE = /(?:PASS(?:WORD|CODE|PHRASE)?|[A-Z0-9]_PASSWD|[A-Z0-9]_PWD)$/i;
|
|
20922
21491
|
function signInSecretsOf(vars) {
|
|
20923
21492
|
if (!vars || typeof vars !== "object")
|
|
20924
21493
|
return [];
|
|
@@ -20935,6 +21504,7 @@ var require_run_write_guard = __commonJS({
|
|
|
20935
21504
|
}
|
|
20936
21505
|
var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
20937
21506
|
var MAX_BODY_CHARS = 262144;
|
|
21507
|
+
var MAX_SIGN_IN_LOG_LINES = 20;
|
|
20938
21508
|
var CAPTCHA_HOSTS = ["recaptcha.net", "hcaptcha.com", "challenges.cloudflare.com"];
|
|
20939
21509
|
var SEARCH_POST_HOSTS = ["algolia.net", "algolianet.com", "algolia.io"];
|
|
20940
21510
|
var PAYMENT_HOSTS = [
|
|
@@ -20960,7 +21530,7 @@ var require_run_write_guard = __commonJS({
|
|
|
20960
21530
|
var SEARCH_LAST_SEGMENTS = /* @__PURE__ */ new Set(["search", "autocomplete", "suggest", "suggestions", "typeahead"]);
|
|
20961
21531
|
var PROVIDER_SIGN_UP_PATH = /(?:\/accounts:signup|\/dbconnections\/signup|\/auth\/v1\/signup|\/v1\/client\/sign_ups)(?:[/?]|$)/i;
|
|
20962
21532
|
var FIRST_PARTY_SIGN_UP_SEGMENTS = /* @__PURE__ */ new Set(["signup", "sign-up", "sign_up", "register", "registration", "create-account", "createaccount", "sign_ups"]);
|
|
20963
|
-
var
|
|
21533
|
+
var NOT_A_SIGN_IN_WORDS = /* @__PURE__ */ new Set([
|
|
20964
21534
|
"delete",
|
|
20965
21535
|
"destroy",
|
|
20966
21536
|
"remove",
|
|
@@ -20969,19 +21539,29 @@ var require_run_write_guard = __commonJS({
|
|
|
20969
21539
|
"cancel",
|
|
20970
21540
|
"password",
|
|
20971
21541
|
"passwords",
|
|
20972
|
-
"change-password",
|
|
20973
|
-
"change_password",
|
|
20974
21542
|
"reset",
|
|
20975
|
-
"reset-password",
|
|
20976
21543
|
"update",
|
|
20977
21544
|
"logout",
|
|
20978
21545
|
"signout",
|
|
20979
|
-
"sign-out",
|
|
20980
21546
|
"revoke",
|
|
20981
21547
|
"transfer",
|
|
20982
|
-
"withdraw"
|
|
21548
|
+
"withdraw",
|
|
21549
|
+
"disable",
|
|
21550
|
+
"enable",
|
|
21551
|
+
"unlink",
|
|
21552
|
+
"change",
|
|
21553
|
+
"erase",
|
|
21554
|
+
"purge",
|
|
21555
|
+
"wipe",
|
|
21556
|
+
"terminate",
|
|
21557
|
+
"suspend",
|
|
21558
|
+
"leave",
|
|
21559
|
+
"disconnect",
|
|
21560
|
+
"deauthorize"
|
|
20983
21561
|
]);
|
|
20984
|
-
var
|
|
21562
|
+
var ACCOUNT_CHANGE_WORDS = /* @__PURE__ */ new Set(["email", "mfa", "2fa", "totp", "otp"]);
|
|
21563
|
+
var ACCOUNT_CHANGE_CONTEXT = /* @__PURE__ */ new Set(["account", "owner", "ownership", "invite"]);
|
|
21564
|
+
var SIGN_IN_LAST_SEGMENTS = /* @__PURE__ */ new Set([
|
|
20985
21565
|
"session",
|
|
20986
21566
|
"sessions",
|
|
20987
21567
|
"login",
|
|
@@ -20991,25 +21571,196 @@ var require_run_write_guard = __commonJS({
|
|
|
20991
21571
|
"signin",
|
|
20992
21572
|
"sign-in",
|
|
20993
21573
|
"sign_in",
|
|
20994
|
-
"auth",
|
|
20995
21574
|
"authenticate",
|
|
20996
21575
|
"authentication",
|
|
20997
|
-
"
|
|
20998
|
-
"tokens",
|
|
20999
|
-
"oauth",
|
|
21000
|
-
"oauth2",
|
|
21001
|
-
"sso",
|
|
21002
|
-
"identity",
|
|
21003
|
-
"credentials"
|
|
21576
|
+
"auth"
|
|
21004
21577
|
]);
|
|
21005
|
-
var
|
|
21006
|
-
|
|
21007
|
-
|
|
21008
|
-
|
|
21578
|
+
var SIGN_IN_VERB_SEGMENTS = /* @__PURE__ */ new Set(["login", "log-in", "log_in", "signin", "sign-in", "sign_in"]);
|
|
21579
|
+
var SIGN_IN_CREDENTIAL_KINDS = /* @__PURE__ */ new Set(["email", "username", "phone", "password"]);
|
|
21580
|
+
var SIGN_IN_TAILS = /* @__PURE__ */ new Set(["callback/credentials", "auth/local", "session/new", "sessions/new"]);
|
|
21581
|
+
var PASSWORD_GRANT = /(?:^|[&{,\s"])grant_type"?\s*[=:]\s*"?password(?=["&,}\s]|$)/;
|
|
21582
|
+
var NEW_CREDENTIAL_STEM = "(?:pass(?:word|wd)?|pwd)";
|
|
21583
|
+
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)?)`;
|
|
21584
|
+
var NEW_CREDENTIAL_NAME = new RegExp(`^${NEW_CREDENTIAL_WORD}$`, "i");
|
|
21585
|
+
var NEW_CREDENTIAL_KEY = new RegExp(`(?:^|["&{,\\s[])${NEW_CREDENTIAL_WORD}(?:["\\]]?\\s*[:=]|"\\s*(?:;|$))`, "im");
|
|
21586
|
+
var PASSWORD_KEY_WORDS = /* @__PURE__ */ new Set(["password", "passwd", "pwd", "pass"]);
|
|
21587
|
+
var PASSWORD_KEY_QUALIFIERS = /* @__PURE__ */ new Set([
|
|
21588
|
+
"new",
|
|
21589
|
+
"old",
|
|
21590
|
+
"current",
|
|
21591
|
+
"confirm",
|
|
21592
|
+
"confirmed",
|
|
21593
|
+
"confirmation",
|
|
21594
|
+
"repeat",
|
|
21595
|
+
"repeated",
|
|
21596
|
+
"verify",
|
|
21597
|
+
"verification",
|
|
21598
|
+
"again",
|
|
21599
|
+
"retype",
|
|
21600
|
+
"check",
|
|
21601
|
+
"2"
|
|
21602
|
+
]);
|
|
21603
|
+
function isPasswordKey(key) {
|
|
21604
|
+
const words = wordsOf(key);
|
|
21605
|
+
return words.some((w) => PASSWORD_KEY_WORDS.has(w)) && words.every((w) => PASSWORD_KEY_WORDS.has(w) || PASSWORD_KEY_QUALIFIERS.has(w));
|
|
21606
|
+
}
|
|
21607
|
+
function couldBePassword(value) {
|
|
21608
|
+
if (value === void 0)
|
|
21609
|
+
return true;
|
|
21610
|
+
if (typeof value === "number")
|
|
21611
|
+
return Number.isFinite(value);
|
|
21612
|
+
if (typeof value !== "string")
|
|
21613
|
+
return false;
|
|
21614
|
+
const v = value.trim();
|
|
21615
|
+
return v !== "" && !/^(?:true|false|on|off|yes|no|null)$/i.test(v);
|
|
21616
|
+
}
|
|
21617
|
+
var MAX_CREDENTIAL_KEYS = 200;
|
|
21618
|
+
function credentialKeysOf(body, contentType) {
|
|
21619
|
+
const type = (contentType ?? "").toLowerCase();
|
|
21620
|
+
const keys = [];
|
|
21621
|
+
try {
|
|
21622
|
+
if (type.includes("json") || /^\s*[[{]/.test(body)) {
|
|
21623
|
+
const stack = [{ value: JSON.parse(body), under: null }];
|
|
21624
|
+
while (stack.length > 0 && keys.length < MAX_CREDENTIAL_KEYS) {
|
|
21625
|
+
const { value, under } = stack.pop();
|
|
21626
|
+
if (Array.isArray(value))
|
|
21627
|
+
stack.push(...value.slice(0, MAX_CREDENTIAL_KEYS).map((inner) => ({ value: inner, under: null })));
|
|
21628
|
+
else if (value && typeof value === "object") {
|
|
21629
|
+
for (const [key, inner] of Object.entries(value).slice(0, MAX_CREDENTIAL_KEYS)) {
|
|
21630
|
+
const name = under === null ? key : `${under}_${key}`;
|
|
21631
|
+
keys.push({ name, value: inner });
|
|
21632
|
+
if (Array.isArray(inner) && isPasswordKey(name)) {
|
|
21633
|
+
for (const item of inner.slice(0, MAX_CREDENTIAL_KEYS))
|
|
21634
|
+
if (item === null || typeof item !== "object")
|
|
21635
|
+
keys.push({ name, value: item });
|
|
21636
|
+
}
|
|
21637
|
+
const object = inner !== null && typeof inner === "object" && !Array.isArray(inner);
|
|
21638
|
+
stack.push({ value: inner, under: object && isPasswordKey(name) ? name : null });
|
|
21639
|
+
}
|
|
21640
|
+
}
|
|
21641
|
+
}
|
|
21642
|
+
} else if (type.includes("multipart/form-data")) {
|
|
21643
|
+
for (const m of body.matchAll(/(?:^|[;\s])name="([^"]{1,200})"/g))
|
|
21644
|
+
if (keys.push({ name: m[1], value: void 0 }) >= MAX_CREDENTIAL_KEYS)
|
|
21645
|
+
break;
|
|
21646
|
+
} else if (type.includes("x-www-form-urlencoded")) {
|
|
21647
|
+
for (const [name, value] of new URLSearchParams(body))
|
|
21648
|
+
if (keys.push({ name, value }) >= MAX_CREDENTIAL_KEYS)
|
|
21649
|
+
break;
|
|
21650
|
+
}
|
|
21651
|
+
} catch {
|
|
21652
|
+
}
|
|
21653
|
+
return keys.slice(0, MAX_CREDENTIAL_KEYS).map(({ name, value }) => {
|
|
21654
|
+
const bare = name.replace(/\[\]$/, "");
|
|
21655
|
+
const parts = bare.match(/[^[\]]+/g);
|
|
21656
|
+
if (bare.endsWith("]") && parts && parts.length > 1) {
|
|
21657
|
+
const inner = parts[parts.length - 1];
|
|
21658
|
+
const outer = parts[parts.length - 2];
|
|
21659
|
+
if (isPasswordKey(outer)) {
|
|
21660
|
+
if (/^\d+$/.test(inner))
|
|
21661
|
+
return { name: outer, value };
|
|
21662
|
+
if (isPasswordKey(inner) || NEW_CREDENTIAL_NAME.test(inner))
|
|
21663
|
+
return { name: inner, value };
|
|
21664
|
+
return { name: `${outer}_${inner}`, value };
|
|
21665
|
+
}
|
|
21666
|
+
}
|
|
21667
|
+
return { name: /\[([^[\]]*)\]$/.exec(bare)?.[1] ?? bare, value };
|
|
21668
|
+
});
|
|
21669
|
+
}
|
|
21670
|
+
function namesNewCredential(body, contentType) {
|
|
21671
|
+
if (NEW_CREDENTIAL_KEY.test(body))
|
|
21672
|
+
return true;
|
|
21673
|
+
const type = contentType ?? (/^\s*[[{]/.test(body) ? null : "application/x-www-form-urlencoded");
|
|
21674
|
+
const keys = credentialKeysOf(body, type);
|
|
21675
|
+
if (keys.some(({ name }) => NEW_CREDENTIAL_NAME.test(name)))
|
|
21676
|
+
return true;
|
|
21677
|
+
const found = keys.filter(({ name, value }) => isPasswordKey(name) && couldBePassword(value));
|
|
21678
|
+
const isBare = (name) => wordsOf(name).every((w) => PASSWORD_KEY_WORDS.has(w));
|
|
21679
|
+
const named = new Set(found.filter(({ name }) => !isBare(name)).map(({ name }) => name.toLowerCase()));
|
|
21680
|
+
const bare = new Set(found.filter(({ name }) => isBare(name)).map(({ name, value }) => value === void 0 ? `name:${name.toLowerCase()}` : `value:${String(value)}`));
|
|
21681
|
+
return named.size + Math.min(bare.size, 2) >= 2;
|
|
21682
|
+
}
|
|
21683
|
+
var GRAPHQL_SIGN_IN_NAME = /^(?:log_?in|sign_?in|authenticate|login\w{0,40}|signin\w{0,40})$/i;
|
|
21684
|
+
function wordsOf(segment) {
|
|
21685
|
+
return segment.replace(/([a-z])(?=[A-Z0-9])/g, "$1 ").toLowerCase().split(/[\s._/-]+/).filter(Boolean);
|
|
21686
|
+
}
|
|
21687
|
+
function rawSegmentsOf(pathname) {
|
|
21688
|
+
return pathname.split("/").map((seg) => {
|
|
21689
|
+
const raw = seg.split(";")[0];
|
|
21690
|
+
try {
|
|
21691
|
+
return decodeURIComponent(raw);
|
|
21692
|
+
} catch {
|
|
21693
|
+
return raw;
|
|
21694
|
+
}
|
|
21695
|
+
}).filter(Boolean);
|
|
21696
|
+
}
|
|
21697
|
+
function changesSomething(segments, opts = {}) {
|
|
21698
|
+
const words = segments.flatMap(wordsOf);
|
|
21699
|
+
const joined = segments.map((seg) => wordsOf(seg).join(""));
|
|
21700
|
+
const hard = (w) => NOT_A_SIGN_IN_WORDS.has(w) && !(opts.passwordIsCredential && (w === "password" || w === "passwords"));
|
|
21701
|
+
if (words.some(hard) || joined.some(hard))
|
|
21702
|
+
return true;
|
|
21703
|
+
return words.some((w) => ACCOUNT_CHANGE_WORDS.has(w)) && words.some((w) => ACCOUNT_CHANGE_CONTEXT.has(w));
|
|
21704
|
+
}
|
|
21705
|
+
function isGraphqlSignInName(name) {
|
|
21706
|
+
return GRAPHQL_SIGN_IN_NAME.test(name) && !changesSomething([name], { passwordIsCredential: true });
|
|
21707
|
+
}
|
|
21708
|
+
function graphqlSignIn(body) {
|
|
21709
|
+
let parsed;
|
|
21710
|
+
try {
|
|
21711
|
+
parsed = JSON.parse(body);
|
|
21712
|
+
} catch {
|
|
21713
|
+
parsed = { query: body };
|
|
21714
|
+
}
|
|
21715
|
+
const entries = Array.isArray(parsed) ? parsed : [parsed];
|
|
21716
|
+
let signIns = 0;
|
|
21717
|
+
for (const entry of entries) {
|
|
21718
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
21719
|
+
return null;
|
|
21720
|
+
const { query, operationName } = entry;
|
|
21721
|
+
if (typeof query !== "string") {
|
|
21722
|
+
if (!Array.isArray(parsed) && query === void 0 && typeof operationName === "string" && isGraphqlSignInName(operationName))
|
|
21723
|
+
return "persisted";
|
|
21724
|
+
return null;
|
|
21725
|
+
}
|
|
21726
|
+
const ops = (0, survey_network_guard_js_1.graphqlOperationRoots)(query);
|
|
21727
|
+
if (!ops)
|
|
21728
|
+
return null;
|
|
21729
|
+
for (const op of ops) {
|
|
21730
|
+
if (op.kind === "query")
|
|
21731
|
+
continue;
|
|
21732
|
+
if (typeof operationName === "string" && changesSomething([operationName], { passwordIsCredential: true }))
|
|
21733
|
+
return null;
|
|
21734
|
+
const roots = op.roots.filter((root) => root !== "__typename");
|
|
21735
|
+
if (roots.length === 0 || !roots.every(isGraphqlSignInName))
|
|
21736
|
+
return null;
|
|
21737
|
+
signIns += roots.length;
|
|
21738
|
+
}
|
|
21739
|
+
}
|
|
21740
|
+
return signIns > 0 ? "document" : null;
|
|
21741
|
+
}
|
|
21742
|
+
function isCredentialSignInShape(pathname, search, postData, contentType) {
|
|
21743
|
+
if (typeof postData === "string" && postData.length > MAX_BODY_CHARS)
|
|
21009
21744
|
return false;
|
|
21010
|
-
|
|
21745
|
+
const body = typeof postData === "string" && postData.length <= MAX_BODY_CHARS ? postData : null;
|
|
21746
|
+
if (body !== null && namesNewCredential(body, contentType))
|
|
21747
|
+
return false;
|
|
21748
|
+
const raw = rawSegmentsOf(pathname);
|
|
21749
|
+
const segs = raw.map((seg) => seg.toLowerCase());
|
|
21750
|
+
const last = segs[segs.length - 1] ?? "";
|
|
21751
|
+
const prev = segs[segs.length - 2] ?? "";
|
|
21752
|
+
if (SIGN_IN_VERB_SEGMENTS.has(prev) && SIGN_IN_CREDENTIAL_KINDS.has(last))
|
|
21753
|
+
return !changesSomething(raw, { passwordIsCredential: true });
|
|
21754
|
+
if (changesSomething(raw))
|
|
21755
|
+
return false;
|
|
21756
|
+
if (SIGN_IN_LAST_SEGMENTS.has(last) || SIGN_IN_TAILS.has(`${prev}/${last}`))
|
|
21011
21757
|
return true;
|
|
21012
|
-
|
|
21758
|
+
if (last === "token")
|
|
21759
|
+
return search.get("grant_type") === "password" || body !== null && PASSWORD_GRANT.test(body);
|
|
21760
|
+
if (!isGraphqlPath(pathname) || body === null || (0, survey_network_guard_js_1.graphqlUrlCarriesQuery)(search))
|
|
21761
|
+
return false;
|
|
21762
|
+
const graphql = graphqlSignIn(body);
|
|
21763
|
+
return graphql === "persisted" ? graphql : graphql === "document";
|
|
21013
21764
|
}
|
|
21014
21765
|
function bodyHasSignInSecret(body, secrets) {
|
|
21015
21766
|
if (!body || body.length > MAX_BODY_CHARS || secrets.length === 0)
|
|
@@ -21037,7 +21788,7 @@ var require_run_write_guard = __commonJS({
|
|
|
21037
21788
|
var COGNITO_HOSTED_UI_RE = /^\/(?:oauth2\/(?:token|authorize|userinfo|idpresponse)|saml2\/idpresponse|login|logincontinue)$/;
|
|
21038
21789
|
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)$/;
|
|
21039
21790
|
var SUPABASE_AUTH_RE = /^\/auth\/v1\/(?:token|otp|magiclink|verify|authorize|callback|sso|saml\/acs|reauthenticate|factors\/[^/]+\/(?:challenge|verify))$/;
|
|
21040
|
-
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))$/;
|
|
21791
|
+
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))$/;
|
|
21041
21792
|
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\/[^/]+(?:\/[^/]+)?)$/;
|
|
21042
21793
|
var MICROSOFT_AUTH_RE = /\/(?:oauth2\/(?:v2\.0\/)?(?:token|authorize|devicecode)|login|kmsi|getcredentialtype|sas\/(?:processauth|beginauth|endauth)|selfasserted|api\/combinedsigninandsignup\/[^/]+|resume|reprocess|federation\/oauth2)$/;
|
|
21043
21794
|
var GOOGLE_ACCOUNTS_RE = /^\/(?:o\/oauth2\/(?:token|auth|v2\/auth|iframerpc|postmessagerelay|approval)|gsi\/[^/]+(?:\/[^/]+)?|_\/(?:signin|lookup|identitysigninui|accountssigninui)\/.+|v3\/signin\/.+|signin\/.+|servicelogin|checkcookie|accountchooser)$/;
|
|
@@ -21103,14 +21854,26 @@ var require_run_write_guard = __commonJS({
|
|
|
21103
21854
|
return GENERIC_IDP_AUTH_RE.test(path);
|
|
21104
21855
|
}
|
|
21105
21856
|
function isAuthProviderWrite(method, host, pathname, amzTarget, search) {
|
|
21857
|
+
if (!isPlainPost(method, search))
|
|
21858
|
+
return false;
|
|
21859
|
+
if (!(0, shared_1.isAuthProviderHost)(host))
|
|
21860
|
+
return false;
|
|
21861
|
+
return isAuthProviderSignIn(host.toLowerCase(), pathname, amzTarget, search);
|
|
21862
|
+
}
|
|
21863
|
+
function isPlainPost(method, search) {
|
|
21106
21864
|
if (method.toUpperCase() !== "POST")
|
|
21107
21865
|
return false;
|
|
21108
21866
|
const tunnelled = search.get("_method");
|
|
21109
|
-
|
|
21867
|
+
return tunnelled === null || tunnelled.toUpperCase() === "POST";
|
|
21868
|
+
}
|
|
21869
|
+
function isAuth0CustomDomainSignIn(method, pathname, search) {
|
|
21870
|
+
if (!isPlainPost(method, search))
|
|
21110
21871
|
return false;
|
|
21111
|
-
|
|
21872
|
+
const path = pathname.toLowerCase();
|
|
21873
|
+
if (!AUTH0_AUTH_RE.test(path))
|
|
21112
21874
|
return false;
|
|
21113
|
-
|
|
21875
|
+
const needsState = path.startsWith("/u/") || path === "/authorize" || path === "/authorize/resume";
|
|
21876
|
+
return !needsState || (search.get("state") ?? "") !== "";
|
|
21114
21877
|
}
|
|
21115
21878
|
function isSignUp(host, pathname, amzTarget) {
|
|
21116
21879
|
if (PROVIDER_SIGN_UP_PATH.test(pathname))
|
|
@@ -21236,8 +21999,7 @@ var require_run_write_guard = __commonJS({
|
|
|
21236
21999
|
const host = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
21237
22000
|
const pathname = url.pathname;
|
|
21238
22001
|
if (READ_METHODS.has(method)) {
|
|
21239
|
-
|
|
21240
|
-
if (doc !== null && !(0, survey_network_guard_js_1.graphqlDocumentIsQueryOnly)(doc)) {
|
|
22002
|
+
if (method === "GET" && isGraphqlPath(pathname) && (0, survey_network_guard_js_1.graphqlGetMayWrite)(url, facts.postData)) {
|
|
21241
22003
|
return { action: "write", rule: "graphql-get-mutation", record: describe(facts, url, "graphql-get-mutation") };
|
|
21242
22004
|
}
|
|
21243
22005
|
if (method !== "OPTIONS" && isAppSite(host, policy)) {
|
|
@@ -21270,15 +22032,17 @@ var require_run_write_guard = __commonJS({
|
|
|
21270
22032
|
return { action: "allow", rule: "auth-provider" };
|
|
21271
22033
|
if (appSite && (0, survey_network_guard_js_1.isSignInPathWrite)(method, pathname))
|
|
21272
22034
|
return { action: "allow", rule: "sign-in" };
|
|
21273
|
-
if (appSite && method
|
|
22035
|
+
if (appSite && isAuth0CustomDomainSignIn(method, pathname, url.searchParams))
|
|
22036
|
+
return { action: "allow", rule: "sign-in" };
|
|
22037
|
+
if (appSite && method === "POST" && facts.brokerTokenInBody && !changesSomething(rawSegmentsOf(pathname))) {
|
|
21274
22038
|
return { action: "allow", rule: "broker-sign-in" };
|
|
21275
22039
|
}
|
|
21276
22040
|
if (appSite && isEvidencedTokenRefresh(method, pathname, facts.postData))
|
|
21277
22041
|
return { action: "allow", rule: "token-refresh" };
|
|
21278
|
-
|
|
21279
|
-
|
|
21280
|
-
|
|
21281
|
-
if (method === "POST" && isGraphqlPath(pathname) && isGraphqlQueryOnlyBody(facts.postData))
|
|
22042
|
+
const signIn = appSite && method === "POST" && facts.signInSecretInBody ? isCredentialSignInShape(pathname, url.searchParams, facts.postData, facts.contentType) : false;
|
|
22043
|
+
if (signIn)
|
|
22044
|
+
return { action: "allow", rule: "credential-sign-in", ...signIn === "persisted" ? { persisted: true } : {} };
|
|
22045
|
+
if (method === "POST" && isGraphqlPath(pathname) && !(0, survey_network_guard_js_1.graphqlUrlCarriesQuery)(url.searchParams) && isGraphqlQueryOnlyBody(facts.postData))
|
|
21282
22046
|
return { action: "allow", rule: "graphql-query" };
|
|
21283
22047
|
if (method === "POST" && hostIs(host, SEARCH_POST_HOSTS))
|
|
21284
22048
|
return { action: "allow", rule: "search-post" };
|
|
@@ -21323,6 +22087,8 @@ var require_run_write_guard = __commonJS({
|
|
|
21323
22087
|
recordOnly;
|
|
21324
22088
|
log;
|
|
21325
22089
|
signInSecrets;
|
|
22090
|
+
/** The deadline of one request the guard sends itself, all its hops together (sendCheckingRedirect). */
|
|
22091
|
+
redirectCheckTimeoutMs;
|
|
21326
22092
|
/** Tokens the Google sign-in broker handed this context, each until its expiry (epoch ms). */
|
|
21327
22093
|
brokerTokens = [];
|
|
21328
22094
|
/** Origins of the Firebase Auth handlers the broker registered for this context. */
|
|
@@ -21331,12 +22097,16 @@ var require_run_write_guard = __commonJS({
|
|
|
21331
22097
|
relayIds = /* @__PURE__ */ new Set();
|
|
21332
22098
|
relayed = 0;
|
|
21333
22099
|
relayRejected = 0;
|
|
22100
|
+
/** Credential sign-ins already said in the run log (observe mode; method, host and path shape). */
|
|
22101
|
+
signInsLogged = /* @__PURE__ */ new Set();
|
|
21334
22102
|
constructor(policy, opts = {}) {
|
|
21335
22103
|
this.policy = policy;
|
|
21336
22104
|
this.compiled = policy.allowPatterns.map(compileAllowPattern).filter((p) => p !== null);
|
|
21337
22105
|
this.recordOnly = new Set((policy.recordOnlyRules ?? exports2.DEFAULT_RECORD_ONLY_RULES).filter((rule) => exports2.RECORD_ONLY_CAPABLE_RULES.includes(rule)));
|
|
21338
22106
|
this.log = opts.onLog;
|
|
21339
22107
|
this.signInSecrets = (opts.signInSecrets ?? []).filter((v) => typeof v === "string" && v.length >= MIN_SIGN_IN_SECRET_CHARS);
|
|
22108
|
+
const ms = opts.redirectCheckTimeoutMs;
|
|
22109
|
+
this.redirectCheckTimeoutMs = typeof ms === "number" && ms > 0 ? Math.min(ms, REDIRECT_CHECK_TIMEOUT_MS) : REDIRECT_CHECK_TIMEOUT_MS;
|
|
21340
22110
|
}
|
|
21341
22111
|
decide(facts) {
|
|
21342
22112
|
const signInSecretInBody = facts.signInSecretInBody ?? bodyHasSignInSecret(facts.postData, this.signInSecrets);
|
|
@@ -21359,8 +22129,28 @@ var require_run_write_guard = __commonJS({
|
|
|
21359
22129
|
consumedBrokerTokens.add(brokerToken.value);
|
|
21360
22130
|
this.brokerTokens = this.brokerTokens.filter((t) => t !== brokerToken);
|
|
21361
22131
|
}
|
|
22132
|
+
if (decision.rule === "credential-sign-in" && this.policy.mode === "observe")
|
|
22133
|
+
this.noteCredentialSignIn(facts, decision.persisted === true);
|
|
21362
22134
|
return decision;
|
|
21363
22135
|
}
|
|
22136
|
+
/**
|
|
22137
|
+
* Observe mode says where each credential sign-in went, once per path and kind (the lead): the
|
|
22138
|
+
* method, host and masked path only, never the body, so the rule can be checked against real runs
|
|
22139
|
+
* before a site is blocked. A GraphQL persisted query is marked unverified.
|
|
22140
|
+
*/
|
|
22141
|
+
noteCredentialSignIn(facts, persisted) {
|
|
22142
|
+
let host = "";
|
|
22143
|
+
try {
|
|
22144
|
+
host = new URL(facts.url).hostname.toLowerCase();
|
|
22145
|
+
} catch {
|
|
22146
|
+
return;
|
|
22147
|
+
}
|
|
22148
|
+
const line = `${facts.method.toUpperCase()} ${host}${(0, survey_network_guard_js_1.maskedPath)(facts.url)} [credential-sign-in${persisted ? " (persisted, unverified)" : ""}]`;
|
|
22149
|
+
if (this.signInsLogged.has(line) || this.signInsLogged.size >= MAX_SIGN_IN_LOG_LINES)
|
|
22150
|
+
return;
|
|
22151
|
+
this.signInsLogged.add(line);
|
|
22152
|
+
this.log?.(`[run-write-guard] allowed ${line}`);
|
|
22153
|
+
}
|
|
21364
22154
|
/**
|
|
21365
22155
|
* A code or ID token the Google sign-in broker hands this context: the first POST that carries it
|
|
21366
22156
|
* to the app in the next BROKER_TOKEN_TTL_MS is the sign-in. Anything not shaped like a Google code
|
|
@@ -21420,8 +22210,9 @@ var require_run_write_guard = __commonJS({
|
|
|
21420
22210
|
}
|
|
21421
22211
|
/**
|
|
21422
22212
|
* A hop the guard fetched itself (sendCheckingRedirect) failed, so the request fails in the page.
|
|
21423
|
-
* Said in the run log, with the
|
|
21424
|
-
* at
|
|
22213
|
+
* Said in the run log, with the reason when it was the guard's own limit: a slow allowed upload or
|
|
22214
|
+
* stream is cut at its deadline, an answer over MAX_GUARD_HOP_BYTES as it streams in, and the page
|
|
22215
|
+
* only sees a network error.
|
|
21425
22216
|
*/
|
|
21426
22217
|
noteRedirectCheckFailed(facts, err, timeoutMs) {
|
|
21427
22218
|
let host = "";
|
|
@@ -21429,8 +22220,8 @@ var require_run_write_guard = __commonJS({
|
|
|
21429
22220
|
host = new URL(facts.url).hostname.toLowerCase();
|
|
21430
22221
|
} catch {
|
|
21431
22222
|
}
|
|
21432
|
-
const
|
|
21433
|
-
const why =
|
|
22223
|
+
const reason = err instanceof GuardHopError ? err.reason : null;
|
|
22224
|
+
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";
|
|
21434
22225
|
this.log?.(`[run-write-guard] note: ${facts.method.toUpperCase()} ${host}${(0, survey_network_guard_js_1.maskedPath)(facts.url)} failed in the page: ${why}`);
|
|
21435
22226
|
}
|
|
21436
22227
|
/** A request still undecided when its page closed (see guardPageClose): it is failed in the page, so the browser never sends it. */
|
|
@@ -21878,66 +22669,321 @@ var require_run_write_guard = __commonJS({
|
|
|
21878
22669
|
}
|
|
21879
22670
|
}
|
|
21880
22671
|
var MAX_GUARD_REDIRECTS = 5;
|
|
21881
|
-
var REDIRECT_CHECK_TIMEOUT_MS =
|
|
22672
|
+
var REDIRECT_CHECK_TIMEOUT_MS = 3e4;
|
|
22673
|
+
var MAX_GUARD_HOP_BYTES = 16 * 1024 * 1024;
|
|
22674
|
+
var MAX_GUARD_BUFFERED_BYTES = 128 * 1024 * 1024;
|
|
22675
|
+
var SEND_HTTP = node_http_1.request;
|
|
22676
|
+
var SEND_HTTPS = node_https_1.request;
|
|
22677
|
+
var HOP_ONLY_HEADERS = /* @__PURE__ */ new Set([
|
|
22678
|
+
"connection",
|
|
22679
|
+
"keep-alive",
|
|
22680
|
+
"proxy-connection",
|
|
22681
|
+
"transfer-encoding",
|
|
22682
|
+
"te",
|
|
22683
|
+
"trailer",
|
|
22684
|
+
"upgrade",
|
|
22685
|
+
"proxy-authorization",
|
|
22686
|
+
"proxy-authenticate",
|
|
22687
|
+
"host",
|
|
22688
|
+
"content-length"
|
|
22689
|
+
]);
|
|
22690
|
+
var hopBytesInFlight = 0;
|
|
22691
|
+
var hopsInFlight = /* @__PURE__ */ new Set();
|
|
22692
|
+
var hopBudgetWaiters = [];
|
|
22693
|
+
function releaseHopBytes(hop, bytes) {
|
|
22694
|
+
hopsInFlight.delete(hop);
|
|
22695
|
+
hopBytesInFlight -= bytes;
|
|
22696
|
+
for (const wake of hopBudgetWaiters.splice(0))
|
|
22697
|
+
wake();
|
|
22698
|
+
}
|
|
22699
|
+
function runWriteGuardHopBytesInFlight() {
|
|
22700
|
+
return hopBytesInFlight;
|
|
22701
|
+
}
|
|
22702
|
+
var GuardHopError = class extends Error {
|
|
22703
|
+
reason;
|
|
22704
|
+
constructor(reason, message) {
|
|
22705
|
+
super(message);
|
|
22706
|
+
this.reason = reason;
|
|
22707
|
+
this.name = "GuardHopError";
|
|
22708
|
+
}
|
|
22709
|
+
};
|
|
22710
|
+
function sendGuardHop(hop) {
|
|
22711
|
+
return new Promise((resolve, reject) => {
|
|
22712
|
+
let url;
|
|
22713
|
+
try {
|
|
22714
|
+
url = new URL(hop.url);
|
|
22715
|
+
} catch (err) {
|
|
22716
|
+
reject(err);
|
|
22717
|
+
return;
|
|
22718
|
+
}
|
|
22719
|
+
const send = url.protocol === "https:" ? SEND_HTTPS : url.protocol === "http:" ? SEND_HTTP : null;
|
|
22720
|
+
if (!send) {
|
|
22721
|
+
reject(new Error(`the guard sends only http(s), not ${url.protocol}`));
|
|
22722
|
+
return;
|
|
22723
|
+
}
|
|
22724
|
+
const id = /* @__PURE__ */ Symbol("guard hop");
|
|
22725
|
+
hopsInFlight.add(id);
|
|
22726
|
+
let held = 0;
|
|
22727
|
+
let settled = false;
|
|
22728
|
+
let req = null;
|
|
22729
|
+
const chunks = [];
|
|
22730
|
+
const fail = (err) => {
|
|
22731
|
+
if (settled)
|
|
22732
|
+
return;
|
|
22733
|
+
settled = true;
|
|
22734
|
+
clearTimeout(timer);
|
|
22735
|
+
req?.destroy();
|
|
22736
|
+
releaseHopBytes(id, held);
|
|
22737
|
+
held = 0;
|
|
22738
|
+
reject(err);
|
|
22739
|
+
};
|
|
22740
|
+
const overCap = () => new GuardHopError("over-cap", `answer over ${MAX_GUARD_HOP_BYTES} bytes`);
|
|
22741
|
+
const timer = setTimeout(() => fail(new GuardHopError("timeout", "the guard hop passed its deadline")), Math.max(0, hop.deadline - Date.now()));
|
|
22742
|
+
try {
|
|
22743
|
+
req = send(url, { method: hop.method, headers: hop.headers, rejectUnauthorized: !hop.ignoreHTTPSErrors, agent: false }, (res) => {
|
|
22744
|
+
const encoding = String(res.headers["content-encoding"] ?? "").trim().toLowerCase();
|
|
22745
|
+
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;
|
|
22746
|
+
const body = decoder ? (0, node_stream_1.pipeline)(res, decoder, (err) => {
|
|
22747
|
+
if (err)
|
|
22748
|
+
fail(err);
|
|
22749
|
+
}) : res;
|
|
22750
|
+
const parked = [];
|
|
22751
|
+
let paused = false;
|
|
22752
|
+
let ended = false;
|
|
22753
|
+
const take = (chunk) => {
|
|
22754
|
+
if (settled)
|
|
22755
|
+
return;
|
|
22756
|
+
if (paused) {
|
|
22757
|
+
parked.push(chunk);
|
|
22758
|
+
return;
|
|
22759
|
+
}
|
|
22760
|
+
if (held + chunk.length > MAX_GUARD_HOP_BYTES) {
|
|
22761
|
+
fail(overCap());
|
|
22762
|
+
return;
|
|
22763
|
+
}
|
|
22764
|
+
const oldest = hopsInFlight.values().next().value === id;
|
|
22765
|
+
const room = oldest ? MAX_GUARD_BUFFERED_BYTES : MAX_GUARD_BUFFERED_BYTES - MAX_GUARD_HOP_BYTES;
|
|
22766
|
+
if (hopBytesInFlight + chunk.length > room) {
|
|
22767
|
+
paused = true;
|
|
22768
|
+
body.pause();
|
|
22769
|
+
parked.push(chunk);
|
|
22770
|
+
hopBudgetWaiters.push(drain);
|
|
22771
|
+
return;
|
|
22772
|
+
}
|
|
22773
|
+
hopBytesInFlight += chunk.length;
|
|
22774
|
+
held += chunk.length;
|
|
22775
|
+
chunks.push(chunk);
|
|
22776
|
+
};
|
|
22777
|
+
const drain = () => {
|
|
22778
|
+
paused = false;
|
|
22779
|
+
const queued = parked.splice(0);
|
|
22780
|
+
for (let i = 0; i < queued.length; i++) {
|
|
22781
|
+
take(queued[i]);
|
|
22782
|
+
if (settled)
|
|
22783
|
+
return;
|
|
22784
|
+
if (paused) {
|
|
22785
|
+
parked.push(...queued.slice(i + 1));
|
|
22786
|
+
return;
|
|
22787
|
+
}
|
|
22788
|
+
}
|
|
22789
|
+
if (ended)
|
|
22790
|
+
finish();
|
|
22791
|
+
else
|
|
22792
|
+
body.resume();
|
|
22793
|
+
};
|
|
22794
|
+
body.on("data", take);
|
|
22795
|
+
body.on("error", (err) => fail(err));
|
|
22796
|
+
res.on("error", (err) => fail(err));
|
|
22797
|
+
body.on("end", () => {
|
|
22798
|
+
ended = true;
|
|
22799
|
+
if (!paused)
|
|
22800
|
+
finish();
|
|
22801
|
+
});
|
|
22802
|
+
const finish = () => {
|
|
22803
|
+
if (settled)
|
|
22804
|
+
return;
|
|
22805
|
+
settled = true;
|
|
22806
|
+
clearTimeout(timer);
|
|
22807
|
+
const kept = held;
|
|
22808
|
+
let released = false;
|
|
22809
|
+
const headers = [];
|
|
22810
|
+
for (let i = 0; i + 1 < res.rawHeaders.length; i += 2)
|
|
22811
|
+
headers.push([res.rawHeaders[i], res.rawHeaders[i + 1]]);
|
|
22812
|
+
resolve({
|
|
22813
|
+
status: res.statusCode ?? 0,
|
|
22814
|
+
headers,
|
|
22815
|
+
body: Buffer.concat(chunks, kept),
|
|
22816
|
+
decoded: decoder !== null,
|
|
22817
|
+
release: () => {
|
|
22818
|
+
if (!released) {
|
|
22819
|
+
released = true;
|
|
22820
|
+
releaseHopBytes(id, kept);
|
|
22821
|
+
}
|
|
22822
|
+
}
|
|
22823
|
+
});
|
|
22824
|
+
};
|
|
22825
|
+
});
|
|
22826
|
+
req.on("error", (err) => fail(err));
|
|
22827
|
+
req.end(hop.body ?? void 0);
|
|
22828
|
+
} catch (err) {
|
|
22829
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
22830
|
+
}
|
|
22831
|
+
});
|
|
22832
|
+
}
|
|
22833
|
+
function contextIgnoresHttpsErrors(context) {
|
|
22834
|
+
try {
|
|
22835
|
+
return context._options?.ignoreHTTPSErrors === true;
|
|
22836
|
+
} catch {
|
|
22837
|
+
return false;
|
|
22838
|
+
}
|
|
22839
|
+
}
|
|
22840
|
+
function hopRequestHeaders(pageHeaders, cookie) {
|
|
22841
|
+
const out = { accept: "*/*" };
|
|
22842
|
+
for (const [name, value] of Object.entries(pageHeaders)) {
|
|
22843
|
+
const key = name.toLowerCase();
|
|
22844
|
+
if (key.startsWith(":") || HOP_ONLY_HEADERS.has(key) || key === "cookie")
|
|
22845
|
+
continue;
|
|
22846
|
+
out[key] = value;
|
|
22847
|
+
}
|
|
22848
|
+
out["accept-encoding"] = "gzip, deflate, br";
|
|
22849
|
+
if (cookie)
|
|
22850
|
+
out.cookie = cookie;
|
|
22851
|
+
return out;
|
|
22852
|
+
}
|
|
22853
|
+
function cookiePairs(header) {
|
|
22854
|
+
return header.split(";").flatMap((part) => {
|
|
22855
|
+
const eq = part.indexOf("=");
|
|
22856
|
+
return eq > 0 ? [[part.slice(0, eq).trim(), part.slice(eq + 1).trim()]] : [];
|
|
22857
|
+
});
|
|
22858
|
+
}
|
|
22859
|
+
function hopCookie(pageCookie, contextCookies, setCookies) {
|
|
22860
|
+
const jar = /* @__PURE__ */ new Map();
|
|
22861
|
+
if (pageCookie)
|
|
22862
|
+
for (const [name, value] of cookiePairs(pageCookie))
|
|
22863
|
+
jar.set(name, value);
|
|
22864
|
+
else
|
|
22865
|
+
for (const c of contextCookies)
|
|
22866
|
+
jar.set(c.name, c.value);
|
|
22867
|
+
for (const line of setCookies) {
|
|
22868
|
+
const [pair, ...attrs] = line.split(";");
|
|
22869
|
+
const eq = pair.indexOf("=");
|
|
22870
|
+
if (eq <= 0)
|
|
22871
|
+
continue;
|
|
22872
|
+
const name = pair.slice(0, eq).trim();
|
|
22873
|
+
const gone = attrs.some((a) => {
|
|
22874
|
+
const [k, v = ""] = a.split("=");
|
|
22875
|
+
const key = k.trim().toLowerCase();
|
|
22876
|
+
return key === "max-age" && Number(v.trim()) <= 0 || key === "expires" && Date.parse(v.trim()) < Date.now();
|
|
22877
|
+
});
|
|
22878
|
+
if (gone)
|
|
22879
|
+
jar.delete(name);
|
|
22880
|
+
else
|
|
22881
|
+
jar.set(name, pair.slice(eq + 1).trim());
|
|
22882
|
+
}
|
|
22883
|
+
return [...jar].map(([name, value]) => `${name}=${value}`).join("; ");
|
|
22884
|
+
}
|
|
22885
|
+
function setCookiesOf(answer) {
|
|
22886
|
+
return answer.headers.filter(([name]) => name.toLowerCase() === "set-cookie").map(([, value]) => value);
|
|
22887
|
+
}
|
|
22888
|
+
function fulfillHeaders(answer, earlierSetCookies) {
|
|
22889
|
+
const out = {};
|
|
22890
|
+
const cookies = [...earlierSetCookies];
|
|
22891
|
+
for (const [name, value] of answer.headers) {
|
|
22892
|
+
const key = name.toLowerCase();
|
|
22893
|
+
if (HOP_ONLY_HEADERS.has(key) || answer.decoded && key === "content-encoding")
|
|
22894
|
+
continue;
|
|
22895
|
+
if (key === "set-cookie") {
|
|
22896
|
+
cookies.push(value);
|
|
22897
|
+
continue;
|
|
22898
|
+
}
|
|
22899
|
+
out[key] = key in out ? `${out[key]}, ${value}` : value;
|
|
22900
|
+
}
|
|
22901
|
+
if (cookies.length > 0)
|
|
22902
|
+
out["set-cookie"] = cookies.join("\n");
|
|
22903
|
+
return out;
|
|
22904
|
+
}
|
|
21882
22905
|
var cutAtClose = /* @__PURE__ */ new WeakSet();
|
|
21883
|
-
async function sendCheckingRedirect(route, guard) {
|
|
21884
|
-
const
|
|
22906
|
+
async function sendCheckingRedirect(route, guard, context) {
|
|
22907
|
+
const request = route.request();
|
|
22908
|
+
const first = factsOf(request);
|
|
22909
|
+
const deadline = Date.now() + guard.redirectCheckTimeoutMs;
|
|
21885
22910
|
let facts = first;
|
|
22911
|
+
let pageHeaders = {};
|
|
22912
|
+
let postBody = null;
|
|
22913
|
+
const setCookies = [];
|
|
21886
22914
|
for (let hop = 0; ; hop++) {
|
|
21887
|
-
let
|
|
22915
|
+
let answer;
|
|
21888
22916
|
try {
|
|
21889
|
-
|
|
22917
|
+
if (hop === 0) {
|
|
22918
|
+
pageHeaders = request.headers();
|
|
22919
|
+
postBody = request.postDataBuffer();
|
|
22920
|
+
}
|
|
22921
|
+
const pageCookie = hop === 0 ? Object.entries(pageHeaders).find(([name]) => name.toLowerCase() === "cookie")?.[1] : void 0;
|
|
22922
|
+
const cookie = hopCookie(pageCookie, pageCookie ? [] : await context.cookies(facts.url), setCookies);
|
|
22923
|
+
answer = await sendGuardHop({
|
|
22924
|
+
url: facts.url,
|
|
22925
|
+
method: facts.method,
|
|
22926
|
+
headers: hopRequestHeaders(pageHeaders, cookie),
|
|
22927
|
+
body: postBody,
|
|
22928
|
+
ignoreHTTPSErrors: contextIgnoresHttpsErrors(context),
|
|
22929
|
+
deadline
|
|
22930
|
+
});
|
|
21890
22931
|
} catch (err) {
|
|
21891
22932
|
if (cutAtClose.has(route))
|
|
21892
22933
|
return;
|
|
21893
|
-
guard.noteRedirectCheckFailed(facts, err,
|
|
22934
|
+
guard.noteRedirectCheckFailed(facts, err, guard.redirectCheckTimeoutMs);
|
|
21894
22935
|
await route.abort("failed").catch(() => {
|
|
21895
22936
|
});
|
|
21896
22937
|
return;
|
|
21897
22938
|
}
|
|
21898
|
-
if (cutAtClose.has(route))
|
|
21899
|
-
return;
|
|
21900
|
-
const status = response.status();
|
|
21901
|
-
const location = response.headers()["location"];
|
|
21902
|
-
const next = REDIRECT_STATUSES.has(status) && location ? redirectFacts(facts, status, location) : null;
|
|
21903
|
-
if (!next) {
|
|
21904
|
-
await route.fulfill({ response }).catch(() => {
|
|
21905
|
-
});
|
|
21906
|
-
return;
|
|
21907
|
-
}
|
|
21908
|
-
let decision;
|
|
21909
22939
|
try {
|
|
21910
|
-
|
|
21911
|
-
|
|
21912
|
-
|
|
21913
|
-
|
|
21914
|
-
|
|
21915
|
-
if (
|
|
21916
|
-
|
|
21917
|
-
|
|
22940
|
+
if (cutAtClose.has(route))
|
|
22941
|
+
return;
|
|
22942
|
+
const status = answer.status;
|
|
22943
|
+
const location = answer.headers.find(([name]) => name.toLowerCase() === "location")?.[1];
|
|
22944
|
+
const next = REDIRECT_STATUSES.has(status) && location ? redirectFacts(facts, status, location) : null;
|
|
22945
|
+
if (!next) {
|
|
22946
|
+
await route.fulfill({ status, headers: fulfillHeaders(answer, setCookies), body: answer.body }).catch(() => {
|
|
22947
|
+
});
|
|
22948
|
+
return;
|
|
22949
|
+
}
|
|
22950
|
+
let decision;
|
|
22951
|
+
try {
|
|
22952
|
+
decision = guard.decide(next);
|
|
22953
|
+
} catch {
|
|
22954
|
+
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" } };
|
|
22955
|
+
}
|
|
22956
|
+
if (decision.action === "write") {
|
|
22957
|
+
if (decision.record)
|
|
22958
|
+
guard.note(decision.record);
|
|
22959
|
+
if (guard.holds(decision.rule)) {
|
|
22960
|
+
await route.abort("blockedbyclient").catch(() => {
|
|
22961
|
+
});
|
|
22962
|
+
return;
|
|
22963
|
+
}
|
|
22964
|
+
}
|
|
22965
|
+
if (READ_METHODS.has(next.method.toUpperCase())) {
|
|
22966
|
+
await route.fulfill({ status, headers: { ...fulfillHeaders(answer, setCookies), location: next.url }, body: answer.body }).catch(() => {
|
|
22967
|
+
});
|
|
22968
|
+
return;
|
|
22969
|
+
}
|
|
22970
|
+
if (!sameOrigin(next.url, first.url)) {
|
|
22971
|
+
const target = new URL(next.url);
|
|
22972
|
+
guard.note(describe(next, target, isAppSite(target.hostname.toLowerCase(), guard.policy) ? "app-write" : "third-party-write"));
|
|
21918
22973
|
await route.abort("blockedbyclient").catch(() => {
|
|
21919
22974
|
});
|
|
21920
22975
|
return;
|
|
21921
22976
|
}
|
|
22977
|
+
if (hop + 1 >= MAX_GUARD_REDIRECTS) {
|
|
22978
|
+
await route.abort("failed").catch(() => {
|
|
22979
|
+
});
|
|
22980
|
+
return;
|
|
22981
|
+
}
|
|
22982
|
+
setCookies.push(...setCookiesOf(answer));
|
|
22983
|
+
facts = next;
|
|
22984
|
+
} finally {
|
|
22985
|
+
answer.release();
|
|
21922
22986
|
}
|
|
21923
|
-
if (READ_METHODS.has(next.method.toUpperCase())) {
|
|
21924
|
-
await route.fulfill({ response, headers: { ...response.headers(), location: next.url } }).catch(() => {
|
|
21925
|
-
});
|
|
21926
|
-
return;
|
|
21927
|
-
}
|
|
21928
|
-
if (!sameOrigin(next.url, first.url)) {
|
|
21929
|
-
const target = new URL(next.url);
|
|
21930
|
-
guard.note(describe(next, target, isAppSite(target.hostname.toLowerCase(), guard.policy) ? "app-write" : "third-party-write"));
|
|
21931
|
-
await route.abort("blockedbyclient").catch(() => {
|
|
21932
|
-
});
|
|
21933
|
-
return;
|
|
21934
|
-
}
|
|
21935
|
-
if (hop + 1 >= MAX_GUARD_REDIRECTS) {
|
|
21936
|
-
await route.abort("failed").catch(() => {
|
|
21937
|
-
});
|
|
21938
|
-
return;
|
|
21939
|
-
}
|
|
21940
|
-
facts = next;
|
|
21941
22987
|
}
|
|
21942
22988
|
}
|
|
21943
22989
|
function sameRouteUrl(a, b) {
|
|
@@ -22151,7 +23197,7 @@ var require_run_write_guard = __commonJS({
|
|
|
22151
23197
|
}
|
|
22152
23198
|
}
|
|
22153
23199
|
if (decision.action === "allow" && !READ_METHODS.has(request.method().toUpperCase()) && checksRedirect(decision.rule, request.url(), guard.policy)) {
|
|
22154
|
-
await sendCheckingRedirect(route, guard);
|
|
23200
|
+
await sendCheckingRedirect(route, guard, context);
|
|
22155
23201
|
return;
|
|
22156
23202
|
}
|
|
22157
23203
|
await route.fallback().catch(() => {
|