@ai-ecoverse/slicc-browsers 1.13.2 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/fixture-server.mjs +11 -1
- package/guard-driver.mjs +31 -4
- package/guard-runner.mjs +123 -34
- package/guard-vectors.json +2097 -187
- package/package.json +5 -5
- package/protected-error-page.mjs +35 -34
- package/redirect-secret.mjs +63 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-browsers",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.2",
|
|
4
4
|
"description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -61,19 +61,19 @@
|
|
|
61
61
|
"fixture-client.mjs"
|
|
62
62
|
],
|
|
63
63
|
"scripts": {
|
|
64
|
-
"prepare": "
|
|
64
|
+
"prepare": "slicc-install-hooks",
|
|
65
65
|
"corpus": "node scripts/corpus.mjs",
|
|
66
66
|
"validate": "node scripts/validate.mjs",
|
|
67
67
|
"lint": "slicc-lint && test -z \"$(gofmt -l .)\" && go mod tidy -diff && go vet ./... && golangci-lint run && cargo fmt --check && cargo clippy --all-features --all-targets -- -D warnings",
|
|
68
68
|
"format": "biome check --write . && gofmt -w . && cargo fmt",
|
|
69
|
-
"test": "npm run validate && node --test test/integration/self-contained.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/git-retry.test.mjs && go test -race -count=1 ./test/integration/... && cargo test --all-features",
|
|
69
|
+
"test": "npm run validate && node --test test/integration/self-contained.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/git-retry.test.mjs test/integration/dist-tag.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-stall.test.mjs && go test -race -count=1 ./test/integration/... && cargo test --all-features",
|
|
70
70
|
"test:unit": "go test -race -cover ./...",
|
|
71
71
|
"build": "go build ./... && cargo build --all-features && cargo build --target wasm32-wasip1 --all-features && npm pack --dry-run",
|
|
72
72
|
"trust": "npx fledgling sync -y --skip-publish",
|
|
73
73
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish",
|
|
74
74
|
"release": "semantic-release",
|
|
75
75
|
"test:guard": "npm run test:guard-integration && npm run test:guard-sweep",
|
|
76
|
-
"test:guard-integration": "node --test --test-concurrency=2 test/integration/guard-lifecycle.test.mjs test/integration/guard-fit.test.mjs test/integration/guard-chain.test.mjs test/integration/guard-validation.test.mjs test/integration/guard-liveness.test.mjs test/integration/guard-observations.test.mjs test/integration/guard-child-latency.test.mjs test/integration/guard-adoption.test.mjs test/integration/guard-scoping.test.mjs test/integration/guard-late-pause.test.mjs test/integration/guard-legs.test.mjs test/integration/guard-fetch-enable.test.mjs test/integration/guard-diagnostics.test.mjs test/integration/guard-chrome.test.mjs test/integration/guard-worlds.test.mjs test/integration/guard-world-discovery.test.mjs test/integration/guard-world-guesses.test.mjs test/integration/guard-unparseable.test.mjs test/integration/guard-redirect-secret.test.mjs test/integration/guard-error-page.test.mjs test/integration/guard-discovery.test.mjs test/integration/guard-browser-page.test.mjs test/integration/guard-scales.test.mjs test/integration/guard-shared-log.test.mjs test/integration/guard-pre-beacon.test.mjs test/integration/guard-world-pause.test.mjs test/integration/self-contained.test.mjs",
|
|
76
|
+
"test:guard-integration": "node --test --test-concurrency=2 test/integration/guard-lifecycle.test.mjs test/integration/guard-fit.test.mjs test/integration/guard-chain.test.mjs test/integration/guard-validation.test.mjs test/integration/guard-liveness.test.mjs test/integration/guard-observations.test.mjs test/integration/guard-child-latency.test.mjs test/integration/guard-adoption.test.mjs test/integration/guard-scoping.test.mjs test/integration/guard-late-pause.test.mjs test/integration/guard-legs.test.mjs test/integration/guard-fetch-enable.test.mjs test/integration/guard-diagnostics.test.mjs test/integration/guard-chrome.test.mjs test/integration/guard-worlds.test.mjs test/integration/guard-world-discovery.test.mjs test/integration/guard-world-guesses.test.mjs test/integration/guard-unparseable.test.mjs test/integration/guard-redirect-secret.test.mjs test/integration/guard-error-page.test.mjs test/integration/guard-discovery.test.mjs test/integration/guard-browser-page.test.mjs test/integration/guard-scales.test.mjs test/integration/guard-shared-log.test.mjs test/integration/guard-pre-beacon.test.mjs test/integration/guard-world-pause.test.mjs test/integration/guard-parallel.test.mjs test/integration/self-contained.test.mjs",
|
|
77
77
|
"test:guard-sweep": "node scripts/test-guards.mjs",
|
|
78
78
|
"test:gates": "node --test test/integration/gates.test.mjs",
|
|
79
79
|
"test:guard-target": "node --test test/integration/guard-runner.test.mjs",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"test:unit:guard": "mkdir -p coverage/unit && node --experimental-vm-modules --no-warnings --experimental-test-coverage --test --test-coverage-include=guard-runner.mjs --test-coverage-include=fixture-server.mjs --test-coverage-include=fixture-client.mjs --test-coverage-include=observer-target.mjs --test-coverage-include=protected-error-page.mjs --test-coverage-include=unparseable-url.mjs --test-coverage-include=scripts/browser-runner.mjs --test-coverage-include=scripts/validate.mjs --test-reporter=lcov --test-reporter-destination=coverage/unit/guard.lcov test/unit/guard-*.test.mjs && slicc-diff-cover coverage/unit/guard.lcov"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@ai-ecoverse/slicc-shared-web": "2.
|
|
84
|
+
"@ai-ecoverse/slicc-shared-web": "2.3.5",
|
|
85
85
|
"@biomejs/biome": "2.5.15",
|
|
86
86
|
"ajv": "8.20.0",
|
|
87
87
|
"ajv-formats": "3.0.1",
|
package/protected-error-page.mjs
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
import { outcome } from './guard-driver.mjs';
|
|
1
|
+
import { outcome, waitFor } from './guard-driver.mjs';
|
|
2
2
|
import { createObserverTarget } from './observer-target.mjs';
|
|
3
3
|
|
|
4
4
|
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
5
|
const refused = (out) => !out.ok && /^slicc: refused:/.test(out.error?.message ?? '');
|
|
6
6
|
|
|
7
|
-
async function until
|
|
8
|
-
const deadline = Date.now() + 5000;
|
|
9
|
-
do {
|
|
10
|
-
if (await check()) return true;
|
|
11
|
-
await pause(25);
|
|
12
|
-
} while (Date.now() < deadline);
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function loadedDocument(reader, observer, url) {
|
|
7
|
+
async function loadedDocument(reader, observer, url, until) {
|
|
17
8
|
return until(async () => {
|
|
18
9
|
const { frameTree } = await reader.send('Page.getFrameTree', {}, observer);
|
|
19
10
|
if (frameTree.frame.url !== url || frameTree.frame.unreachableUrl) return false;
|
|
@@ -89,6 +80,18 @@ async function redaction({
|
|
|
89
80
|
: 'not-supported';
|
|
90
81
|
}
|
|
91
82
|
|
|
83
|
+
async function seed(reader, observer, loaded, initial, allowed) {
|
|
84
|
+
const out = await outcome(
|
|
85
|
+
(async () => {
|
|
86
|
+
await reader.send('Page.enable', {}, observer);
|
|
87
|
+
if (!(await loaded(initial))) return 'history seed did not commit';
|
|
88
|
+
await reader.send('Page.navigate', { url: allowed }, observer);
|
|
89
|
+
if (!(await loaded(allowed))) return 'allowed document did not commit';
|
|
90
|
+
})()
|
|
91
|
+
);
|
|
92
|
+
return out.ok ? out.result : `observer setup failed: ${out.error.message}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
92
95
|
const secrets = {
|
|
93
96
|
all: (tag) => `/doc/path-secret-${tag}?query-secret=${tag}#fragment-secret-${tag}`,
|
|
94
97
|
query: (tag) => `/doc?query-secret=${tag}`,
|
|
@@ -107,10 +110,13 @@ export async function protectedErrorPage({
|
|
|
107
110
|
secret = 'all',
|
|
108
111
|
discovery = true,
|
|
109
112
|
notes = [],
|
|
113
|
+
budget = 5000,
|
|
110
114
|
}) {
|
|
111
115
|
const initial = `${protectedOrigin}/doc?n=${tag}-history`;
|
|
112
116
|
const failedURL = `${protectedOrigin}${secrets[secret](tag)}`;
|
|
113
117
|
const result = {};
|
|
118
|
+
const until = (check) => waitFor(check, budget);
|
|
119
|
+
const missing = (reason) => ({ ...result, incomplete: `protected-error-page: ${reason}` });
|
|
114
120
|
if (!discovery) notes.push('eventsRedacted: not-supported:discovery');
|
|
115
121
|
const start = client.events.length;
|
|
116
122
|
const { targetId } = await createObserverTarget(
|
|
@@ -126,7 +132,7 @@ export async function protectedErrorPage({
|
|
|
126
132
|
observer = sessionId;
|
|
127
133
|
internalSessions.add(observer);
|
|
128
134
|
const frame = async () => (await reader.send('Page.getFrameTree', {}, observer)).frameTree.frame;
|
|
129
|
-
const loaded = (url) => loadedDocument(reader, observer, url);
|
|
135
|
+
const loaded = (url) => loadedDocument(reader, observer, url, until);
|
|
130
136
|
let stopped = false;
|
|
131
137
|
const fail = (async () => {
|
|
132
138
|
while (!stopped) {
|
|
@@ -147,12 +153,8 @@ export async function protectedErrorPage({
|
|
|
147
153
|
}
|
|
148
154
|
})();
|
|
149
155
|
try {
|
|
150
|
-
await reader
|
|
151
|
-
if (
|
|
152
|
-
throw new Error('protected-error-page: history seed did not commit');
|
|
153
|
-
await reader.send('Page.navigate', { url: `${ordinary}/blank` }, observer);
|
|
154
|
-
if (!(await loaded(`${ordinary}/blank`)))
|
|
155
|
-
throw new Error('protected-error-page: allowed document did not commit');
|
|
156
|
+
const unseeded = await seed(reader, observer, loaded, initial, `${ordinary}/blank`);
|
|
157
|
+
if (unseeded) return missing(unseeded);
|
|
156
158
|
const attached = await outcome(
|
|
157
159
|
client.send('Target.attachToTarget', { targetId, flatten: true })
|
|
158
160
|
);
|
|
@@ -174,10 +176,11 @@ export async function protectedErrorPage({
|
|
|
174
176
|
{ expression: `setTimeout(()=>location.href=${JSON.stringify(failedURL)},0)` },
|
|
175
177
|
session
|
|
176
178
|
);
|
|
177
|
-
|
|
179
|
+
let evidence;
|
|
180
|
+
const live = await until(async () => {
|
|
178
181
|
const f = await frame();
|
|
179
182
|
const { targetInfo } = await reader.send('Target.getTargetInfo', { targetId });
|
|
180
|
-
|
|
183
|
+
evidence = { frame: f, targetInfo };
|
|
181
184
|
return (
|
|
182
185
|
f.url === 'chrome-error://chromewebdata/' &&
|
|
183
186
|
f.securityOrigin === '://' &&
|
|
@@ -185,11 +188,11 @@ export async function protectedErrorPage({
|
|
|
185
188
|
targetInfo.url === failedURL
|
|
186
189
|
);
|
|
187
190
|
});
|
|
188
|
-
|
|
189
|
-
if (!
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
result.evidence = evidence;
|
|
192
|
+
if (!live && side === 'control')
|
|
193
|
+
return missing(`missing error commit ${JSON.stringify(evidence)}`);
|
|
194
|
+
result.live = live;
|
|
195
|
+
if (!live) return result;
|
|
193
196
|
await redaction({ client, targetId, failedURL, protectedOrigin, start, result, discovery });
|
|
194
197
|
const beforeReload = (await frame()).loaderId;
|
|
195
198
|
result.reload = (await outcome(client.send('Page.reload', {}, session))).ok;
|
|
@@ -227,14 +230,14 @@ export async function protectedErrorPage({
|
|
|
227
230
|
failing = false;
|
|
228
231
|
const { entries, currentIndex } = await reader.send('Page.getNavigationHistory', {}, observer);
|
|
229
232
|
const index = entries.findIndex((e) => e.url === initial);
|
|
230
|
-
if (index < 0)
|
|
233
|
+
if (index < 0) return missing('missing protected history');
|
|
231
234
|
const beforeRestore = reader.events.length;
|
|
232
235
|
await reader.send(
|
|
233
236
|
'Runtime.evaluate',
|
|
234
237
|
{ expression: `setTimeout(() => history.go(${index - currentIndex}), 0)` },
|
|
235
238
|
observer
|
|
236
239
|
);
|
|
237
|
-
|
|
240
|
+
const restored = await until(() =>
|
|
238
241
|
reader.events
|
|
239
242
|
.slice(beforeRestore)
|
|
240
243
|
.some(
|
|
@@ -245,8 +248,8 @@ export async function protectedErrorPage({
|
|
|
245
248
|
e.params.frame.url === initial
|
|
246
249
|
)
|
|
247
250
|
);
|
|
248
|
-
if (!
|
|
249
|
-
|
|
251
|
+
if (!restored) return missing('real protected BFCache restore required');
|
|
252
|
+
result.restored = true;
|
|
250
253
|
await pause(100);
|
|
251
254
|
if (mode === 'sticky') {
|
|
252
255
|
await reader.send('Target.detachFromTarget', { sessionId: observer });
|
|
@@ -260,11 +263,9 @@ export async function protectedErrorPage({
|
|
|
260
263
|
observer
|
|
261
264
|
);
|
|
262
265
|
await reader.send('Page.navigate', { url: failedURL }, observer);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
`protected-error-page: missing sticky error ${JSON.stringify(await frame())}`
|
|
267
|
-
);
|
|
266
|
+
if (!(await until(async () => (await frame()).unreachableUrl === failedURL)))
|
|
267
|
+
return missing(`missing sticky error ${JSON.stringify(await frame())}`);
|
|
268
|
+
result.stickyError = true;
|
|
268
269
|
await pause(100);
|
|
269
270
|
}
|
|
270
271
|
await lockChecks({ client, session, targetId, result });
|
package/redirect-secret.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { load, outcome } from './guard-driver.mjs';
|
|
1
|
+
import { load, outcome, waitFor } from './guard-driver.mjs';
|
|
2
2
|
import { redirectFragments } from './redirect-fragments.mjs';
|
|
3
3
|
|
|
4
4
|
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -47,28 +47,33 @@ const headersTextMatch = (text, to) =>
|
|
|
47
47
|
)
|
|
48
48
|
);
|
|
49
49
|
|
|
50
|
-
export function containsSecret(value, marker) {
|
|
50
|
+
export function containsSecret(value, marker, seen = new Map()) {
|
|
51
51
|
const visit = (value, depth = 0) => {
|
|
52
52
|
if (typeof value === 'string') {
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
decoded.push(decodeURIComponent(value));
|
|
58
|
-
} catch {}
|
|
59
|
-
try {
|
|
60
|
-
decoded.push(JSON.parse(value));
|
|
61
|
-
} catch {}
|
|
62
|
-
try {
|
|
63
|
-
decoded.push(JSON.parse(`"${value}"`));
|
|
64
|
-
} catch {}
|
|
65
|
-
decoded.push(...decodedBase64(value));
|
|
66
|
-
return decoded.some((text) => text !== value && visit(text, depth + 1));
|
|
53
|
+
const key = `${depth}:${value}`;
|
|
54
|
+
if (!seen.has(key)) seen.set(key, text(value, depth));
|
|
55
|
+
return seen.get(key);
|
|
67
56
|
}
|
|
68
57
|
if (value && typeof value === 'object')
|
|
69
58
|
return Object.values(value).some((v) => visit(v, depth));
|
|
70
59
|
return false;
|
|
71
60
|
};
|
|
61
|
+
const text = (value, depth) => {
|
|
62
|
+
if (value.includes(marker)) return true;
|
|
63
|
+
if (depth >= 8) return false;
|
|
64
|
+
const decoded = [];
|
|
65
|
+
try {
|
|
66
|
+
decoded.push(decodeURIComponent(value));
|
|
67
|
+
} catch {}
|
|
68
|
+
try {
|
|
69
|
+
decoded.push(JSON.parse(value));
|
|
70
|
+
} catch {}
|
|
71
|
+
try {
|
|
72
|
+
decoded.push(JSON.parse(`"${value}"`));
|
|
73
|
+
} catch {}
|
|
74
|
+
decoded.push(...decodedBase64(value));
|
|
75
|
+
return decoded.some((text) => text !== value && visit(text, depth + 1));
|
|
76
|
+
};
|
|
72
77
|
return visit(value);
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -85,6 +90,7 @@ class RedirectProbe {
|
|
|
85
90
|
this.jobs = new Set();
|
|
86
91
|
this.networkBodies = new Set();
|
|
87
92
|
this.registrations = [];
|
|
93
|
+
this.seen = new Map();
|
|
88
94
|
this.protectedURLs = [
|
|
89
95
|
`https://seven.sliccy.ai/auth/callback?code=${encodeURIComponent(this.marker)}&state=${encodeURIComponent(this.marker)}`,
|
|
90
96
|
`https://vault.sliccy.ai/oauth/mcp-callback?code=${encodeURIComponent(this.marker)}`,
|
|
@@ -158,7 +164,23 @@ class RedirectProbe {
|
|
|
158
164
|
await load(this.reader, this.observer, `${this.ordinary}/doc?n=${this.tag}-${suffix}`);
|
|
159
165
|
}
|
|
160
166
|
|
|
161
|
-
|
|
167
|
+
contains(value) {
|
|
168
|
+
return containsSecret(value, this.marker, this.seen);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async scan(items) {
|
|
172
|
+
const flags = [];
|
|
173
|
+
let slice = Date.now();
|
|
174
|
+
for (const item of items) {
|
|
175
|
+
flags.push(this.contains(item));
|
|
176
|
+
if (Date.now() - slice < 10) continue;
|
|
177
|
+
await pause(0);
|
|
178
|
+
slice = Date.now();
|
|
179
|
+
}
|
|
180
|
+
return flags;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async observe() {
|
|
162
184
|
this.observer = (
|
|
163
185
|
await this.reader.send('Target.attachToTarget', { targetId: this.targetId, flatten: true })
|
|
164
186
|
).sessionId;
|
|
@@ -176,6 +198,9 @@ class RedirectProbe {
|
|
|
176
198
|
},
|
|
177
199
|
],
|
|
178
200
|
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async setup() {
|
|
179
204
|
for (const domain of ['Page', 'Network', 'Runtime', 'Log', 'Audits'])
|
|
180
205
|
await this.send(`${domain}.enable`);
|
|
181
206
|
await this.send('Fetch.enable', {
|
|
@@ -249,9 +274,9 @@ class RedirectProbe {
|
|
|
249
274
|
resourceContentAttempted: true,
|
|
250
275
|
snapshotAttempted: true,
|
|
251
276
|
domSnapshotAttempted: true,
|
|
252
|
-
ordinaryResource:
|
|
253
|
-
ordinarySnapshot:
|
|
254
|
-
ordinaryDOM:
|
|
277
|
+
ordinaryResource: this.contains(resource),
|
|
278
|
+
ordinarySnapshot: this.contains(snapshot),
|
|
279
|
+
ordinaryDOM: this.contains(dom),
|
|
255
280
|
});
|
|
256
281
|
}
|
|
257
282
|
|
|
@@ -262,9 +287,8 @@ class RedirectProbe {
|
|
|
262
287
|
await this.send('Runtime.evaluate', {
|
|
263
288
|
expression: `fetch(${JSON.stringify(this.holdURL)}).catch(()=>{});void 0`,
|
|
264
289
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (!this.held) throw new Error('redirect body: Fetch override pause missing');
|
|
290
|
+
if (!(await waitFor(() => Boolean(this.held), 5000, 10)))
|
|
291
|
+
throw new Error('redirect body: Fetch override pause missing');
|
|
268
292
|
const requestId = this.held.params.requestId;
|
|
269
293
|
const out = await this.send('Fetch.continueRequest', { requestId, url });
|
|
270
294
|
this.responses.push({
|
|
@@ -330,8 +354,8 @@ class RedirectProbe {
|
|
|
330
354
|
const dom = await this.send('DOMSnapshot.captureSnapshot', { computedStyles: [] });
|
|
331
355
|
this.responses.push({
|
|
332
356
|
iframeSnapshotAttempted: true,
|
|
333
|
-
ordinaryIframeSnapshot:
|
|
334
|
-
ordinaryIframeDOM:
|
|
357
|
+
ordinaryIframeSnapshot: this.contains(snapshot),
|
|
358
|
+
ordinaryIframeDOM: this.contains(dom),
|
|
335
359
|
});
|
|
336
360
|
}
|
|
337
361
|
|
|
@@ -345,11 +369,13 @@ class RedirectProbe {
|
|
|
345
369
|
});
|
|
346
370
|
const events = this.client.events.slice(this.eventsStart);
|
|
347
371
|
const messages = this.client.messages?.slice(this.messagesStart) ?? [];
|
|
372
|
+
const eventLeaks = await this.scan(events);
|
|
348
373
|
const ordinaryEvents = events.filter(
|
|
349
|
-
(event) =>
|
|
350
|
-
|
|
351
|
-
JSON.stringify(event).includes(new URL(this.plainURL).host)
|
|
374
|
+
(event, index) =>
|
|
375
|
+
eventLeaks[index] && JSON.stringify(event).includes(new URL(this.plainURL).host)
|
|
352
376
|
);
|
|
377
|
+
const replies = [...messages, ...this.responses];
|
|
378
|
+
const replyLeaks = await this.scan(replies);
|
|
353
379
|
const extra = events.filter(
|
|
354
380
|
(event) =>
|
|
355
381
|
event.sessionId === this.session &&
|
|
@@ -377,14 +403,14 @@ class RedirectProbe {
|
|
|
377
403
|
entry.url.includes(`/redirect-secret/${this.tag}`)
|
|
378
404
|
),
|
|
379
405
|
rawRecorded: this.messagesStart !== undefined && messages.length > events.length,
|
|
380
|
-
leakChannels:
|
|
381
|
-
.filter((
|
|
406
|
+
leakChannels: replies
|
|
407
|
+
.filter((_, index) => replyLeaks[index])
|
|
382
408
|
.map((message) =>
|
|
383
409
|
typeof message === 'string'
|
|
384
410
|
? (JSON.parse(message).method ?? 'reply')
|
|
385
411
|
: (message.method ?? 'reply')
|
|
386
412
|
),
|
|
387
|
-
secretHidden: !
|
|
413
|
+
secretHidden: !replyLeaks.includes(true),
|
|
388
414
|
sessionSurvives: survives.ok && survives.result?.result?.value === 'doc',
|
|
389
415
|
fetchBodyAttempted: attempted('Fetch.getResponseBody'),
|
|
390
416
|
networkBodyAttempted: attempted('Network.getResponseBody'),
|
|
@@ -396,7 +422,7 @@ class RedirectProbe {
|
|
|
396
422
|
events,
|
|
397
423
|
replies: this.responses,
|
|
398
424
|
protectedOrigins: this.protectedURLs.map((url) => new URL(url).origin),
|
|
399
|
-
contains: (value) =>
|
|
425
|
+
contains: (value) => this.contains(value),
|
|
400
426
|
}),
|
|
401
427
|
redirectLocation: ordinaryEvents.some(
|
|
402
428
|
(event) =>
|
|
@@ -472,6 +498,11 @@ export async function redirectSecret(options) {
|
|
|
472
498
|
throw new Error('redirectSecret fixture registration required');
|
|
473
499
|
const probe = new RedirectProbe(options);
|
|
474
500
|
try {
|
|
501
|
+
const observed = await outcome(probe.observe());
|
|
502
|
+
if (!observed.ok)
|
|
503
|
+
return {
|
|
504
|
+
incomplete: `redirect-secret: observer setup failed: ${observed.error.message}`,
|
|
505
|
+
};
|
|
475
506
|
await probe.setup();
|
|
476
507
|
if (options.mode === 'body') await probe.body();
|
|
477
508
|
else await probe.headers();
|