@ai-ecoverse/slicc-browsers 1.22.1 → 1.23.0
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 +1 -1
- package/close-protected-shape.mjs +563 -0
- package/fixture-server.mjs +45 -0
- package/guard-runner.mjs +48 -7
- package/guard-vectors.json +405 -1
- package/package.json +3 -2
- package/schema/guard-vectors.schema.json +16 -0
package/README.md
CHANGED
|
@@ -239,7 +239,7 @@ You supply:
|
|
|
239
239
|
| `siteIsolationOff`, `inboundDelay`, `forwardAudit` | no site isolation; hold inbound; audit forwarded commands |
|
|
240
240
|
| `secureOrigin` | HTTPS origins (service workers) |
|
|
241
241
|
| `stockSetup`, `syntheticAutoAttach` | admit stock clients' setup calls; synthesize auto-attach |
|
|
242
|
-
| `closeProtected` | close a protected tab |
|
|
242
|
+
| `closeProtected` | close a protected tab, except live vault/seven-UI disguises |
|
|
243
243
|
|
|
244
244
|
### Reading the result
|
|
245
245
|
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { outcome, parseURL, waitFor } from './guard-driver.mjs';
|
|
2
|
+
import { createObserverTarget } from './observer-target.mjs';
|
|
3
|
+
|
|
4
|
+
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
const refused = (out) => !out.ok && /^slicc: refused:/.test(out.error?.message ?? '');
|
|
6
|
+
|
|
7
|
+
const peel = (text) => {
|
|
8
|
+
let u = parseURL(text);
|
|
9
|
+
for (
|
|
10
|
+
let i = 0;
|
|
11
|
+
u && ['blob', 'filesystem', 'view-source'].includes(u.protocol.slice(0, -1)) && i < 8;
|
|
12
|
+
i++
|
|
13
|
+
)
|
|
14
|
+
u = parseURL(u.href.slice(u.protocol.length));
|
|
15
|
+
return u;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const hostOf = (u) => {
|
|
19
|
+
const host = (u?.hostname ?? '').toLowerCase();
|
|
20
|
+
return host.endsWith('.') ? host.slice(0, -1) : host;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function keptDocument(text) {
|
|
24
|
+
const u = peel(text);
|
|
25
|
+
if (!u || !['http:', 'https:'].includes(u.protocol)) return false;
|
|
26
|
+
const host = hostOf(u);
|
|
27
|
+
if (host === 'vault.sliccy.ai' || (host.startsWith('vault-') && host.endsWith('.sliccy.ai')))
|
|
28
|
+
return true;
|
|
29
|
+
const path = u.pathname || '/';
|
|
30
|
+
return host === 'seven.sliccy.ai' && path.startsWith('/os/') && path !== '/os/callback.html';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function keptSecurityOrigin(url, origin) {
|
|
34
|
+
if (keptDocument(origin)) return true;
|
|
35
|
+
const u = parseURL(url);
|
|
36
|
+
const opaque =
|
|
37
|
+
u?.protocol === 'about:' && ['blank', 'srcdoc'].includes((u.pathname || '').toLowerCase());
|
|
38
|
+
if (!opaque) return false;
|
|
39
|
+
const o = peel(origin);
|
|
40
|
+
if (!o || !['http:', 'https:'].includes(o.protocol)) return false;
|
|
41
|
+
const host = hostOf(o);
|
|
42
|
+
return (
|
|
43
|
+
host === 'seven.sliccy.ai' ||
|
|
44
|
+
host === 'vault.sliccy.ai' ||
|
|
45
|
+
(host.startsWith('vault-') && host.endsWith('.sliccy.ai'))
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function unclosableShape({ url, documentURL, securityOrigin, navigating, navigationURL }) {
|
|
50
|
+
if (navigating && (!navigationURL || keptDocument(navigationURL))) return true;
|
|
51
|
+
const error =
|
|
52
|
+
documentURL === 'chrome-error://chromewebdata/' ||
|
|
53
|
+
(typeof documentURL === 'string' && documentURL.startsWith('chrome-error:'));
|
|
54
|
+
const keptNow =
|
|
55
|
+
keptDocument(url) ||
|
|
56
|
+
keptDocument(documentURL) ||
|
|
57
|
+
keptSecurityOrigin(documentURL ?? url, securityOrigin ?? '');
|
|
58
|
+
if (!keptNow) return false;
|
|
59
|
+
if (!error) return true;
|
|
60
|
+
return keptDocument(documentURL) || keptSecurityOrigin(documentURL, securityOrigin ?? '');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function lockChecks({
|
|
64
|
+
client,
|
|
65
|
+
reader,
|
|
66
|
+
session,
|
|
67
|
+
targetId,
|
|
68
|
+
result,
|
|
69
|
+
until,
|
|
70
|
+
closeOnly,
|
|
71
|
+
skipEvaluate,
|
|
72
|
+
attachRefused,
|
|
73
|
+
}) {
|
|
74
|
+
if (!closeOnly) {
|
|
75
|
+
if (session && !skipEvaluate) {
|
|
76
|
+
result.evaluateRefused = refused(
|
|
77
|
+
await outcome(
|
|
78
|
+
client.send(
|
|
79
|
+
'Runtime.evaluate',
|
|
80
|
+
{ expression: 'document.title', returnByValue: true },
|
|
81
|
+
session
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
result.attachRefused = attachRefused;
|
|
87
|
+
}
|
|
88
|
+
await measureClose({ client, reader, targetId, result, until });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function measureClose({ client, reader, targetId, result, until }) {
|
|
92
|
+
const close = await outcome(client.send('Target.closeTarget', { targetId }));
|
|
93
|
+
const gone = await until(
|
|
94
|
+
async () =>
|
|
95
|
+
!(await reader.send('Target.getTargets')).targetInfos.some((i) => i.targetId === targetId)
|
|
96
|
+
);
|
|
97
|
+
result.closed = gone;
|
|
98
|
+
result.closeRefused = refused(close) && !gone;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function attachClient(client, targetId) {
|
|
102
|
+
const attached = await outcome(client.send('Target.attachToTarget', { targetId, flatten: true }));
|
|
103
|
+
const session = attached.result?.sessionId;
|
|
104
|
+
if (session) {
|
|
105
|
+
await outcome(client.send('Page.enable', {}, session));
|
|
106
|
+
await outcome(client.send('Runtime.enable', {}, session));
|
|
107
|
+
}
|
|
108
|
+
return { session, attachRefused: refused(attached) };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function observerPage(reader, url, notes, internalSessions) {
|
|
112
|
+
const opened = await outcome(
|
|
113
|
+
createObserverTarget(reader, { url }, notes).then(async (created) => {
|
|
114
|
+
const { sessionId } = await reader.send('Target.attachToTarget', {
|
|
115
|
+
targetId: created.targetId,
|
|
116
|
+
flatten: true,
|
|
117
|
+
});
|
|
118
|
+
await reader.send('Page.enable', {}, sessionId);
|
|
119
|
+
await reader.send('Runtime.enable', {}, sessionId);
|
|
120
|
+
return { targetId: created.targetId, sessionId };
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
if (!opened.ok) return opened;
|
|
124
|
+
internalSessions.add(opened.result.sessionId);
|
|
125
|
+
return opened;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function noteShape(notes, label, evidence) {
|
|
129
|
+
notes.push(`${label}: ${JSON.stringify(evidence)}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function shapeViewSource({ reader, shapedURL, notes, internalSessions, until }) {
|
|
133
|
+
const opened = await observerPage(reader, 'about:blank', notes, internalSessions);
|
|
134
|
+
if (!opened.ok) return opened;
|
|
135
|
+
const { targetId, sessionId } = opened.result;
|
|
136
|
+
await reader.send('Page.navigate', { url: shapedURL }, sessionId);
|
|
137
|
+
let info;
|
|
138
|
+
const live = await until(async () => {
|
|
139
|
+
({ targetInfo: info } = await reader.send('Target.getTargetInfo', { targetId }));
|
|
140
|
+
return info.url === shapedURL || info.url.startsWith('view-source:');
|
|
141
|
+
});
|
|
142
|
+
noteShape(notes, 'view-source', {
|
|
143
|
+
targetId,
|
|
144
|
+
type: info?.type,
|
|
145
|
+
url: info?.url,
|
|
146
|
+
});
|
|
147
|
+
return { ok: true, result: { targetId, sessionId, live, shaped: live } };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function shapeBlob({ reader, origin, tag, notes, internalSessions, until }) {
|
|
151
|
+
const page = `${origin}/blob-nav?n=${encodeURIComponent(tag)}`;
|
|
152
|
+
const opened = await observerPage(reader, page, notes, internalSessions);
|
|
153
|
+
if (!opened.ok) return opened;
|
|
154
|
+
const { targetId, sessionId } = opened.result;
|
|
155
|
+
let info;
|
|
156
|
+
const live = await until(async () => {
|
|
157
|
+
({ targetInfo: info } = await reader.send('Target.getTargetInfo', { targetId }));
|
|
158
|
+
return info.url.startsWith('blob:');
|
|
159
|
+
});
|
|
160
|
+
noteShape(notes, 'blob', { targetId, type: info?.type, url: info?.url });
|
|
161
|
+
return { ok: true, result: { targetId, sessionId, live, shaped: live } };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function shapeUserinfo({ reader, shapedURL, notes, internalSessions, until }) {
|
|
165
|
+
const opened = await observerPage(reader, 'about:blank', notes, internalSessions);
|
|
166
|
+
if (!opened.ok) return opened;
|
|
167
|
+
const { targetId, sessionId } = opened.result;
|
|
168
|
+
await reader.send('Page.navigate', { url: shapedURL }, sessionId);
|
|
169
|
+
let info;
|
|
170
|
+
let frame;
|
|
171
|
+
const live = await until(async () => {
|
|
172
|
+
({
|
|
173
|
+
frameTree: { frame },
|
|
174
|
+
} = await reader.send('Page.getFrameTree', {}, sessionId));
|
|
175
|
+
({ targetInfo: info } = await reader.send('Target.getTargetInfo', { targetId }));
|
|
176
|
+
return (
|
|
177
|
+
info.url.includes('@') &&
|
|
178
|
+
info.url.includes('/os/') &&
|
|
179
|
+
frame.url.includes('@') &&
|
|
180
|
+
!frame.unreachableUrl
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
noteShape(notes, 'userinfo', {
|
|
184
|
+
targetId,
|
|
185
|
+
type: info?.type,
|
|
186
|
+
url: info?.url,
|
|
187
|
+
frameUrl: frame?.url,
|
|
188
|
+
hasUserinfo: Boolean(info?.url?.includes('@')),
|
|
189
|
+
});
|
|
190
|
+
if (!live && info?.url?.includes('/os/') && !info.url.includes('@'))
|
|
191
|
+
return {
|
|
192
|
+
ok: true,
|
|
193
|
+
result: { incomplete: 'userinfo stripped from top-level target URL' },
|
|
194
|
+
};
|
|
195
|
+
return { ok: true, result: { targetId, sessionId, live, shaped: live } };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const raced = (promise, ms) => Promise.race([promise, pause(ms).then(() => undefined)]);
|
|
199
|
+
|
|
200
|
+
async function shapeNavigating({
|
|
201
|
+
reader,
|
|
202
|
+
ordinary,
|
|
203
|
+
destination,
|
|
204
|
+
notes,
|
|
205
|
+
internalSessions,
|
|
206
|
+
hold,
|
|
207
|
+
releaseSlow,
|
|
208
|
+
tag,
|
|
209
|
+
}) {
|
|
210
|
+
const started = Date.now();
|
|
211
|
+
const waitBudget = 8000;
|
|
212
|
+
const start = `${ordinary}/blank?n=close-nav-start`;
|
|
213
|
+
notes.push(`navigating.start: destination=${destination} budgetMs=${waitBudget}`);
|
|
214
|
+
notes.push('navigating.fetch: skipped; fixture /slow holds Document (no Fetch.enable)');
|
|
215
|
+
const opened = await raced(observerPage(reader, start, notes, internalSessions), 3000);
|
|
216
|
+
if (!opened?.ok)
|
|
217
|
+
return {
|
|
218
|
+
ok: true,
|
|
219
|
+
result: {
|
|
220
|
+
incomplete: `navigating shape did not appear: observer setup ${opened?.error?.message ?? 'timed out'}`,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
const { targetId, sessionId } = opened.result;
|
|
224
|
+
hold.targetId = targetId;
|
|
225
|
+
const sequence = [];
|
|
226
|
+
let cursor = reader.events.length;
|
|
227
|
+
let stopped = false;
|
|
228
|
+
const elapsed = () => Date.now() - started;
|
|
229
|
+
const readInfo = async () => {
|
|
230
|
+
const info = await raced(reader.send('Target.getTargetInfo', { targetId }), 400);
|
|
231
|
+
const listed = await raced(reader.send('Target.getTargets'), 400);
|
|
232
|
+
const row = listed?.targetInfos?.find((i) => i.targetId === targetId);
|
|
233
|
+
return info?.targetInfo ?? row;
|
|
234
|
+
};
|
|
235
|
+
const snap = async (label) => {
|
|
236
|
+
const targetInfo = await readInfo();
|
|
237
|
+
notes.push(
|
|
238
|
+
`navigating.${label}: ${JSON.stringify({
|
|
239
|
+
atMs: elapsed(),
|
|
240
|
+
targetId,
|
|
241
|
+
type: targetInfo?.type,
|
|
242
|
+
url: targetInfo?.url,
|
|
243
|
+
title: targetInfo?.title,
|
|
244
|
+
attached: targetInfo?.attached,
|
|
245
|
+
openerId: targetInfo?.openerId,
|
|
246
|
+
pendingUrl: destination,
|
|
247
|
+
observerSession: sessionId,
|
|
248
|
+
sequence: sequence.slice(-12),
|
|
249
|
+
})}`
|
|
250
|
+
);
|
|
251
|
+
return targetInfo;
|
|
252
|
+
};
|
|
253
|
+
hold.release = async () => {
|
|
254
|
+
stopped = true;
|
|
255
|
+
releaseSlow?.(tag);
|
|
256
|
+
await pause(20);
|
|
257
|
+
};
|
|
258
|
+
hold.abort = hold.release;
|
|
259
|
+
await snap('0s');
|
|
260
|
+
const pump = (async () => {
|
|
261
|
+
while (!stopped) {
|
|
262
|
+
for (const event of reader.events.slice(cursor)) {
|
|
263
|
+
cursor++;
|
|
264
|
+
if (
|
|
265
|
+
event.method === 'Fetch.requestPaused' ||
|
|
266
|
+
event.method === 'Page.frameStartedNavigating' ||
|
|
267
|
+
event.method === 'Page.frameRequestedNavigation' ||
|
|
268
|
+
event.method === 'Page.frameNavigated'
|
|
269
|
+
) {
|
|
270
|
+
const row = {
|
|
271
|
+
atMs: elapsed(),
|
|
272
|
+
method: event.method,
|
|
273
|
+
sessionId: event.sessionId,
|
|
274
|
+
url: event.params.request?.url ?? event.params.url ?? event.params.frame?.url,
|
|
275
|
+
resourceType: event.params.resourceType,
|
|
276
|
+
requestStage: event.params.requestStage,
|
|
277
|
+
frameId: event.params.frameId ?? event.params.frame?.id,
|
|
278
|
+
};
|
|
279
|
+
sequence.push(row);
|
|
280
|
+
notes.push(`navigating.event: ${JSON.stringify(row)}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
await pause(5);
|
|
284
|
+
}
|
|
285
|
+
})();
|
|
286
|
+
await raced(reader.send('Page.enable', {}, sessionId), 500);
|
|
287
|
+
notes.push(`navigating.navigate: not-awaited url=${destination} session=${sessionId}`);
|
|
288
|
+
reader
|
|
289
|
+
.send('Page.navigate', { url: destination }, sessionId)
|
|
290
|
+
.then((result) => {
|
|
291
|
+
notes.push(
|
|
292
|
+
`navigating.navigateResult: ${JSON.stringify({
|
|
293
|
+
atMs: elapsed(),
|
|
294
|
+
frameId: result?.frameId,
|
|
295
|
+
loaderId: result?.loaderId,
|
|
296
|
+
errorText: result?.errorText,
|
|
297
|
+
})}`
|
|
298
|
+
);
|
|
299
|
+
})
|
|
300
|
+
.catch((error) => {
|
|
301
|
+
notes.push(`navigating.navigateSettled: ${error?.message ?? error}`);
|
|
302
|
+
});
|
|
303
|
+
const marks = [1000, 2000, 5000];
|
|
304
|
+
let mark = 0;
|
|
305
|
+
let targetInfo;
|
|
306
|
+
let pending = false;
|
|
307
|
+
const deadline = started + waitBudget;
|
|
308
|
+
while (Date.now() < deadline && !pending) {
|
|
309
|
+
pending = sequence.some(
|
|
310
|
+
(event) =>
|
|
311
|
+
(event.method === 'Page.frameStartedNavigating' ||
|
|
312
|
+
event.method === 'Page.frameRequestedNavigation' ||
|
|
313
|
+
event.method === 'Fetch.requestPaused') &&
|
|
314
|
+
(event.url === destination || String(event.url ?? '').includes('/slow'))
|
|
315
|
+
);
|
|
316
|
+
if (!pending) {
|
|
317
|
+
targetInfo = await readInfo();
|
|
318
|
+
pending = targetInfo?.url === destination || String(targetInfo?.url ?? '').includes('/slow');
|
|
319
|
+
}
|
|
320
|
+
while (mark < marks.length && elapsed() >= marks[mark]) {
|
|
321
|
+
await snap(`${marks[mark] / 1000}s`);
|
|
322
|
+
mark++;
|
|
323
|
+
}
|
|
324
|
+
if (!pending) await pause(25);
|
|
325
|
+
}
|
|
326
|
+
stopped = true;
|
|
327
|
+
await Promise.race([pump, pause(20)]);
|
|
328
|
+
targetInfo = targetInfo ?? (await readInfo());
|
|
329
|
+
const shaped = pending;
|
|
330
|
+
notes.push(
|
|
331
|
+
`navigating.done: ${JSON.stringify({
|
|
332
|
+
atMs: elapsed(),
|
|
333
|
+
shaped,
|
|
334
|
+
url: targetInfo?.url,
|
|
335
|
+
title: targetInfo?.title,
|
|
336
|
+
attached: targetInfo?.attached,
|
|
337
|
+
pendingUrl: destination,
|
|
338
|
+
sequence: sequence.slice(-20),
|
|
339
|
+
})}`
|
|
340
|
+
);
|
|
341
|
+
if (!shaped) {
|
|
342
|
+
notes.push(`navigating.giveUp: no pending /slow within ${waitBudget}ms`);
|
|
343
|
+
return {
|
|
344
|
+
ok: true,
|
|
345
|
+
result: {
|
|
346
|
+
incomplete: `navigating shape did not appear: no pending /slow within ${waitBudget}ms`,
|
|
347
|
+
targetId,
|
|
348
|
+
sessionId,
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
ok: true,
|
|
354
|
+
result: {
|
|
355
|
+
targetId,
|
|
356
|
+
sessionId,
|
|
357
|
+
live: true,
|
|
358
|
+
shaped: true,
|
|
359
|
+
navigating: true,
|
|
360
|
+
navigationURL: destination,
|
|
361
|
+
closeOnly: true,
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function shapeBlankInherited({ reader, origin, tag, notes, internalSessions, until }) {
|
|
367
|
+
const page = `${origin}/blank-open?n=${encodeURIComponent(tag)}`;
|
|
368
|
+
const opened = await observerPage(reader, page, notes, internalSessions);
|
|
369
|
+
if (!opened.ok) return opened;
|
|
370
|
+
const parent = opened.result;
|
|
371
|
+
const before = new Set(
|
|
372
|
+
(await reader.send('Target.getTargets')).targetInfos.map((info) => info.targetId)
|
|
373
|
+
);
|
|
374
|
+
const openedBlank = await outcome(
|
|
375
|
+
reader.send(
|
|
376
|
+
'Runtime.evaluate',
|
|
377
|
+
{ expression: "window.open(''); undefined", returnByValue: true, userGesture: true },
|
|
378
|
+
parent.sessionId
|
|
379
|
+
)
|
|
380
|
+
);
|
|
381
|
+
if (!openedBlank.ok)
|
|
382
|
+
return {
|
|
383
|
+
ok: true,
|
|
384
|
+
result: {
|
|
385
|
+
incomplete: `window.open failed: ${openedBlank.error?.message ?? openedBlank.error}`,
|
|
386
|
+
parentId: parent.targetId,
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
let child;
|
|
390
|
+
const found = await until(async () => {
|
|
391
|
+
const listed = await reader.send('Target.getTargets');
|
|
392
|
+
child = listed.targetInfos.find(
|
|
393
|
+
(info) =>
|
|
394
|
+
!before.has(info.targetId) &&
|
|
395
|
+
info.type === 'page' &&
|
|
396
|
+
(info.url === 'about:blank' || info.url === '') &&
|
|
397
|
+
info.openerId === parent.targetId
|
|
398
|
+
);
|
|
399
|
+
return Boolean(child);
|
|
400
|
+
});
|
|
401
|
+
if (!found) {
|
|
402
|
+
noteShape(notes, 'blank-inherited', {
|
|
403
|
+
parentId: parent.targetId,
|
|
404
|
+
popup: null,
|
|
405
|
+
reason: 'no about:blank page target with opener',
|
|
406
|
+
});
|
|
407
|
+
return {
|
|
408
|
+
ok: true,
|
|
409
|
+
result: {
|
|
410
|
+
incomplete: 'about:blank popup with opener was not created',
|
|
411
|
+
parentId: parent.targetId,
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
const { sessionId } = await reader.send('Target.attachToTarget', {
|
|
416
|
+
targetId: child.targetId,
|
|
417
|
+
flatten: true,
|
|
418
|
+
});
|
|
419
|
+
internalSessions.add(sessionId);
|
|
420
|
+
await reader.send('Page.enable', {}, sessionId);
|
|
421
|
+
let securityOrigin;
|
|
422
|
+
try {
|
|
423
|
+
const { frameTree } = await reader.send('Page.getFrameTree', {}, sessionId);
|
|
424
|
+
securityOrigin = frameTree.frame.securityOrigin;
|
|
425
|
+
} catch {
|
|
426
|
+
securityOrigin = undefined;
|
|
427
|
+
}
|
|
428
|
+
noteShape(notes, 'blank-inherited', {
|
|
429
|
+
targetId: child.targetId,
|
|
430
|
+
type: child.type,
|
|
431
|
+
url: child.url,
|
|
432
|
+
openerId: child.openerId,
|
|
433
|
+
parentId: parent.targetId,
|
|
434
|
+
securityOrigin,
|
|
435
|
+
});
|
|
436
|
+
return {
|
|
437
|
+
ok: true,
|
|
438
|
+
result: {
|
|
439
|
+
targetId: child.targetId,
|
|
440
|
+
sessionId,
|
|
441
|
+
parentId: parent.targetId,
|
|
442
|
+
live: true,
|
|
443
|
+
shaped: true,
|
|
444
|
+
securityOrigin,
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export async function closeProtectedShape({
|
|
450
|
+
client,
|
|
451
|
+
reader,
|
|
452
|
+
ordinary,
|
|
453
|
+
protectedOrigin,
|
|
454
|
+
shape,
|
|
455
|
+
tag,
|
|
456
|
+
internalSessions = new Set(),
|
|
457
|
+
notes = [],
|
|
458
|
+
budget = 5000,
|
|
459
|
+
releaseSlow,
|
|
460
|
+
}) {
|
|
461
|
+
const until = (check) => waitFor(check, budget);
|
|
462
|
+
const result = {};
|
|
463
|
+
const hold = {};
|
|
464
|
+
let shaped;
|
|
465
|
+
const missing = async (reason) => {
|
|
466
|
+
hold.abort?.();
|
|
467
|
+
await hold.release?.();
|
|
468
|
+
const targetId = shaped?.result?.targetId ?? hold.targetId;
|
|
469
|
+
const parentId = shaped?.result?.parentId ?? hold.parentId;
|
|
470
|
+
if (targetId) await outcome(reader.send('Target.closeTarget', { targetId }));
|
|
471
|
+
if (parentId) await outcome(reader.send('Target.closeTarget', { targetId: parentId }));
|
|
472
|
+
return { ...result, incomplete: `close-protected-shape: ${reason}` };
|
|
473
|
+
};
|
|
474
|
+
if (!reader) return missing('missing unguarded reader');
|
|
475
|
+
const origin = protectedOrigin;
|
|
476
|
+
if (shape === 'view-source') {
|
|
477
|
+
shaped = await shapeViewSource({
|
|
478
|
+
reader,
|
|
479
|
+
shapedURL: `view-source:${origin}/doc?n=${encodeURIComponent(tag)}`,
|
|
480
|
+
notes,
|
|
481
|
+
internalSessions,
|
|
482
|
+
until,
|
|
483
|
+
});
|
|
484
|
+
} else if (shape === 'blob') {
|
|
485
|
+
shaped = await shapeBlob({ reader, origin, tag, notes, internalSessions, until });
|
|
486
|
+
} else if (shape === 'userinfo') {
|
|
487
|
+
const host = parseURL(origin)?.host;
|
|
488
|
+
const shapedURL = `${parseURL(origin)?.protocol}//user:pass@${host}/os/index.html?n=${encodeURIComponent(tag)}`;
|
|
489
|
+
shaped = await shapeUserinfo({ reader, shapedURL, notes, internalSessions, until });
|
|
490
|
+
} else if (shape === 'navigating') {
|
|
491
|
+
const navigating = shapeNavigating({
|
|
492
|
+
reader,
|
|
493
|
+
ordinary,
|
|
494
|
+
destination: `${origin}/slow?n=${encodeURIComponent(tag)}&ms=15000`,
|
|
495
|
+
notes,
|
|
496
|
+
internalSessions,
|
|
497
|
+
hold,
|
|
498
|
+
releaseSlow,
|
|
499
|
+
tag,
|
|
500
|
+
}).then((value) => ({ timedOut: false, value }));
|
|
501
|
+
let hardTimer;
|
|
502
|
+
const hard = new Promise((resolve) => {
|
|
503
|
+
hardTimer = setTimeout(() => resolve({ timedOut: true }), 9000);
|
|
504
|
+
});
|
|
505
|
+
const winner = await Promise.race([navigating, hard]);
|
|
506
|
+
clearTimeout(hardTimer);
|
|
507
|
+
if (winner.timedOut) {
|
|
508
|
+
notes.push('navigating.giveUp: hard 9s shape budget');
|
|
509
|
+
hold.abort?.();
|
|
510
|
+
await hold.release?.();
|
|
511
|
+
shaped = {
|
|
512
|
+
ok: true,
|
|
513
|
+
result: {
|
|
514
|
+
incomplete: 'navigating shape did not appear: hard 9s shape budget',
|
|
515
|
+
targetId: hold.targetId,
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
} else shaped = winner.value;
|
|
519
|
+
} else if (shape === 'blank-inherited') {
|
|
520
|
+
shaped = await shapeBlankInherited({ reader, origin, tag, notes, internalSessions, until });
|
|
521
|
+
} else {
|
|
522
|
+
return await missing(`unknown shape ${shape}`);
|
|
523
|
+
}
|
|
524
|
+
if (!shaped.ok)
|
|
525
|
+
return await missing(`observer setup failed: ${shaped.error?.message ?? shaped.error}`);
|
|
526
|
+
if (shaped.result.incomplete) return await missing(shaped.result.incomplete);
|
|
527
|
+
if (!shaped.result.live) return await missing(`shape ${shape} did not appear`);
|
|
528
|
+
Object.assign(result, {
|
|
529
|
+
shaped: shaped.result.shaped,
|
|
530
|
+
live: shaped.result.live,
|
|
531
|
+
});
|
|
532
|
+
const { targetId } = shaped.result;
|
|
533
|
+
const closeOnly = shaped.result.closeOnly === true;
|
|
534
|
+
const { session, attachRefused } = await attachClient(client, targetId);
|
|
535
|
+
if (!session) {
|
|
536
|
+
if (!closeOnly) result.attachRefused = attachRefused;
|
|
537
|
+
await measureClose({ client, reader, targetId, result, until });
|
|
538
|
+
await hold.release?.();
|
|
539
|
+
await outcome(reader.send('Target.closeTarget', { targetId }));
|
|
540
|
+
if (shaped.result.parentId)
|
|
541
|
+
await outcome(reader.send('Target.closeTarget', { targetId: shaped.result.parentId }));
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
await lockChecks({
|
|
546
|
+
client,
|
|
547
|
+
reader,
|
|
548
|
+
session,
|
|
549
|
+
targetId,
|
|
550
|
+
result,
|
|
551
|
+
until,
|
|
552
|
+
closeOnly,
|
|
553
|
+
skipEvaluate: true,
|
|
554
|
+
attachRefused,
|
|
555
|
+
});
|
|
556
|
+
return result;
|
|
557
|
+
} finally {
|
|
558
|
+
await hold.release?.();
|
|
559
|
+
await outcome(reader.send('Target.closeTarget', { targetId }));
|
|
560
|
+
if (shaped.result.parentId)
|
|
561
|
+
await outcome(reader.send('Target.closeTarget', { targetId: shaped.result.parentId }));
|
|
562
|
+
}
|
|
563
|
+
}
|
package/fixture-server.mjs
CHANGED
|
@@ -76,6 +76,16 @@ function route(req, url, send, html, redirects) {
|
|
|
76
76
|
{ sourcemap: '/listener.js.map' }
|
|
77
77
|
),
|
|
78
78
|
'/page': () => html('trigger', trigger(p)),
|
|
79
|
+
'/blob-nav': () =>
|
|
80
|
+
html(
|
|
81
|
+
'blob-nav',
|
|
82
|
+
`<p>blob-nav ${escape(p.get('n') ?? '')}</p><script>const b=URL.createObjectURL(new Blob(['<!doctype html><title>blob</title><p>blob ${escape(p.get('n') ?? '')}</p>'],{type:'text/html'}));location.replace(b);</script>`
|
|
83
|
+
),
|
|
84
|
+
'/blank': () => html('blank', `<p>blank ${escape(p.get('n') ?? '')}</p>`),
|
|
85
|
+
'/blank-open': () => html('blank-open', `<p>blank-open ${escape(p.get('n') ?? '')}</p>`),
|
|
86
|
+
'/os/': () => html('os', `<p>os ${escape(p.get('n') ?? '')}</p>`),
|
|
87
|
+
'/os/index.html': () => html('os', `<p>os ${escape(p.get('n') ?? '')}</p>`),
|
|
88
|
+
'/os/callback.html': () => html('os-callback', `<p>callback ${escape(p.get('n') ?? '')}</p>`),
|
|
79
89
|
'/api/cookies': () => send(200, req.headers.cookie ?? 'none', 'text/plain'),
|
|
80
90
|
'/plant': () =>
|
|
81
91
|
html('plant', '<script>localStorage.setItem("canary","planted")</script>', {
|
|
@@ -147,10 +157,31 @@ function requestURL(req, scheme) {
|
|
|
147
157
|
};
|
|
148
158
|
}
|
|
149
159
|
|
|
160
|
+
async function holdSlow({ req, res, url, p, html, entries, history, slows }) {
|
|
161
|
+
const n = p.get('n') ?? '';
|
|
162
|
+
const ms = Math.min(Math.max(Number(p.get('ms') ?? 15000) || 15000, 1), 60000);
|
|
163
|
+
const entry = {
|
|
164
|
+
time: Date.now(),
|
|
165
|
+
url: url.href,
|
|
166
|
+
method: req.method,
|
|
167
|
+
headers: { ...req.headers },
|
|
168
|
+
};
|
|
169
|
+
entries.push(entry);
|
|
170
|
+
history.push(entry);
|
|
171
|
+
await new Promise((resolve) => {
|
|
172
|
+
const timer = setTimeout(resolve, ms);
|
|
173
|
+
slows.set(n, { resolve, timer });
|
|
174
|
+
});
|
|
175
|
+
slows.delete(n);
|
|
176
|
+
if (res.writableEnded) return;
|
|
177
|
+
return html('slow', `<p>slow ${escape(n)}</p>`);
|
|
178
|
+
}
|
|
179
|
+
|
|
150
180
|
export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency } = {}) {
|
|
151
181
|
let entries = [];
|
|
152
182
|
const history = [];
|
|
153
183
|
const redirects = new Map();
|
|
184
|
+
const slows = new Map();
|
|
154
185
|
const redirectSecret = async ({ path, host = '', search = '', remove, ...value }) => {
|
|
155
186
|
if (
|
|
156
187
|
typeof path !== 'string' ||
|
|
@@ -162,6 +193,14 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
|
|
|
162
193
|
if (remove) redirects.delete(key);
|
|
163
194
|
else redirects.set(key, value);
|
|
164
195
|
};
|
|
196
|
+
const releaseSlow = (n) => {
|
|
197
|
+
const entry = slows.get(String(n ?? ''));
|
|
198
|
+
if (!entry) return false;
|
|
199
|
+
clearTimeout(entry.timer);
|
|
200
|
+
entry.resolve();
|
|
201
|
+
slows.delete(String(n ?? ''));
|
|
202
|
+
return true;
|
|
203
|
+
};
|
|
165
204
|
const scheme = tls ? 'https' : 'http';
|
|
166
205
|
const handler = async (req, res) => {
|
|
167
206
|
const url = requestURL(req, scheme);
|
|
@@ -183,6 +222,10 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
|
|
|
183
222
|
'text/html',
|
|
184
223
|
headers
|
|
185
224
|
);
|
|
225
|
+
if (req.method === 'GET' && url.pathname === '/slow') {
|
|
226
|
+
await holdSlow({ req, res, url, p, html, entries, history, slows });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
186
229
|
if (req.method === 'GET' && url.pathname === '/log') {
|
|
187
230
|
const from = Number(p.get('since') ?? 0);
|
|
188
231
|
if (!Number.isInteger(from) || from < 0 || from > history.length)
|
|
@@ -220,6 +263,7 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
|
|
|
220
263
|
return {
|
|
221
264
|
redirects,
|
|
222
265
|
redirectSecret,
|
|
266
|
+
releaseSlow,
|
|
223
267
|
port: address.port,
|
|
224
268
|
origin: `${scheme}://${host}:${address.port}`,
|
|
225
269
|
log: () => structuredClone(entries),
|
|
@@ -228,6 +272,7 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
|
|
|
228
272
|
},
|
|
229
273
|
close: () =>
|
|
230
274
|
new Promise((resolve, reject) => {
|
|
275
|
+
for (const n of [...slows.keys()]) releaseSlow(n);
|
|
231
276
|
server.closeAllConnections();
|
|
232
277
|
server.close((error) => (error ? reject(error) : resolve()));
|
|
233
278
|
}),
|
package/guard-runner.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { fixtureClient, prepareFixture } from './fixture-client.mjs';
|
|
|
3
3
|
export { fixtureClient };
|
|
4
4
|
|
|
5
5
|
import { bfcacheRestore } from './bfcache-restore.mjs';
|
|
6
|
+
import { closeProtectedShape } from './close-protected-shape.mjs';
|
|
6
7
|
import { extensionFrame } from './extension-frame.mjs';
|
|
7
8
|
import { DEFAULT_EXTENSION_WORLD_ID, extensionWorld } from './extension-world.mjs';
|
|
8
9
|
import { frameRead } from './frame-read.mjs';
|
|
@@ -194,6 +195,7 @@ class Execution {
|
|
|
194
195
|
this.unsupported = undefined;
|
|
195
196
|
this.preSent = [];
|
|
196
197
|
this.notes = [];
|
|
198
|
+
this.sharedNotes = Array.isArray(options.notes) ? options.notes : undefined;
|
|
197
199
|
this.answers = [];
|
|
198
200
|
this.internalSessions = new Set();
|
|
199
201
|
this.attachSettings = new Map();
|
|
@@ -222,6 +224,10 @@ class Execution {
|
|
|
222
224
|
note(message) {
|
|
223
225
|
if (this.notes.length >= 80) this.notes.shift();
|
|
224
226
|
Array.prototype.push.call(this.notes, message);
|
|
227
|
+
if (this.sharedNotes && this.sharedNotes !== this.notes) {
|
|
228
|
+
if (this.sharedNotes.length >= 80) this.sharedNotes.shift();
|
|
229
|
+
Array.prototype.push.call(this.sharedNotes, message);
|
|
230
|
+
}
|
|
225
231
|
}
|
|
226
232
|
|
|
227
233
|
installClientTrace() {
|
|
@@ -894,6 +900,7 @@ class Execution {
|
|
|
894
900
|
'redirectSecret',
|
|
895
901
|
'unparseable',
|
|
896
902
|
'errorPage',
|
|
903
|
+
'closeProtectedShape',
|
|
897
904
|
'extensionWorld',
|
|
898
905
|
'extensionFrame',
|
|
899
906
|
'frameRead',
|
|
@@ -902,9 +909,14 @@ class Execution {
|
|
|
902
909
|
if (probe && !out.ok) this.errors.push(`${probe}: ${out.error?.message ?? 'probe failed'}`);
|
|
903
910
|
if (step.client && !out.ok) this.errors.push(`client exit: ${out.error.message}`);
|
|
904
911
|
if (
|
|
905
|
-
[
|
|
906
|
-
|
|
907
|
-
|
|
912
|
+
[
|
|
913
|
+
'errorPage',
|
|
914
|
+
'closeProtectedShape',
|
|
915
|
+
'redirectSecret',
|
|
916
|
+
'extensionFrame',
|
|
917
|
+
'frameRead',
|
|
918
|
+
'bfcacheRestore',
|
|
919
|
+
].includes(probe) &&
|
|
908
920
|
out.ok &&
|
|
909
921
|
out.result?.incomplete
|
|
910
922
|
)
|
|
@@ -1000,9 +1012,14 @@ class Execution {
|
|
|
1000
1012
|
return;
|
|
1001
1013
|
}
|
|
1002
1014
|
if (step.client) return outcome(this.exit());
|
|
1003
|
-
if (
|
|
1004
|
-
|
|
1005
|
-
|
|
1015
|
+
if (
|
|
1016
|
+
step.redirectSecret ||
|
|
1017
|
+
step.errorPage ||
|
|
1018
|
+
step.closeProtectedShape ||
|
|
1019
|
+
step.spawnWorker ||
|
|
1020
|
+
step.probeSessions
|
|
1021
|
+
)
|
|
1022
|
+
return this.stepProbe(step, session);
|
|
1006
1023
|
if (step.bfcacheRestore)
|
|
1007
1024
|
return outcome(
|
|
1008
1025
|
bfcacheRestore({
|
|
@@ -1143,6 +1160,13 @@ class Execution {
|
|
|
1143
1160
|
}
|
|
1144
1161
|
}
|
|
1145
1162
|
|
|
1163
|
+
stepProbe(step, session) {
|
|
1164
|
+
if (step.redirectSecret || step.errorPage) return this.originProbe(step, session);
|
|
1165
|
+
if (step.closeProtectedShape) return this.runCloseProtectedShape(step);
|
|
1166
|
+
if (step.spawnWorker) return this.spawnWorker(step);
|
|
1167
|
+
return this.probeSessions(step);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1146
1170
|
async originProbe(step, session) {
|
|
1147
1171
|
if (step.redirectSecret) {
|
|
1148
1172
|
this.internalSessions.add(session);
|
|
@@ -1187,6 +1211,23 @@ class Execution {
|
|
|
1187
1211
|
);
|
|
1188
1212
|
}
|
|
1189
1213
|
|
|
1214
|
+
runCloseProtectedShape(step) {
|
|
1215
|
+
return outcome(
|
|
1216
|
+
closeProtectedShape({
|
|
1217
|
+
client: this.client,
|
|
1218
|
+
reader: this.reader,
|
|
1219
|
+
ordinary: this.vars.ordinary,
|
|
1220
|
+
protectedOrigin: this.vars.target,
|
|
1221
|
+
shape: step.closeProtectedShape,
|
|
1222
|
+
tag: this.vars.tag,
|
|
1223
|
+
internalSessions: this.internalSessions,
|
|
1224
|
+
notes: this.notes,
|
|
1225
|
+
budget: this.bound(5000),
|
|
1226
|
+
releaseSlow: this.options.fixture.releaseSlow,
|
|
1227
|
+
})
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1190
1231
|
spawnWorkerKind() {
|
|
1191
1232
|
return allSteps([...(this.vector.setup ?? []), ...(this.vector.trigger ?? [])]).find(
|
|
1192
1233
|
(step) => step.spawnWorker
|
|
@@ -2175,7 +2216,7 @@ const observerRequired = (check) => {
|
|
|
2175
2216
|
const needsObserver = (vector) =>
|
|
2176
2217
|
vector.control.via === 'unguarded' ||
|
|
2177
2218
|
allSteps([...(vector.setup ?? []), ...(vector.trigger ?? [])]).some(
|
|
2178
|
-
(step) => step.isolatedWorld || step.observer || step.spawnWorker
|
|
2219
|
+
(step) => step.isolatedWorld || step.observer || step.spawnWorker || step.closeProtectedShape
|
|
2179
2220
|
) ||
|
|
2180
2221
|
observerRequired(vector.liveCheck) ||
|
|
2181
2222
|
Object.values(vector.observe ?? {}).some(
|
package/guard-vectors.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"setVersion": "1.
|
|
3
|
+
"setVersion": "1.23.0",
|
|
4
4
|
"sources": [
|
|
5
5
|
{
|
|
6
6
|
"repo": "ai-ecoverse/slicc-cli",
|
|
@@ -2406,6 +2406,410 @@
|
|
|
2406
2406
|
"fullURL": false
|
|
2407
2407
|
}
|
|
2408
2408
|
},
|
|
2409
|
+
{
|
|
2410
|
+
"id": "C8.close-protected.view-source",
|
|
2411
|
+
"family": "C",
|
|
2412
|
+
"kind": "cdp",
|
|
2413
|
+
"scope": "any",
|
|
2414
|
+
"defaultContext": true,
|
|
2415
|
+
"requires": ["closeProtected"],
|
|
2416
|
+
"setup": [
|
|
2417
|
+
{
|
|
2418
|
+
"page": "tab",
|
|
2419
|
+
"url": "{ordinary}/blank"
|
|
2420
|
+
}
|
|
2421
|
+
],
|
|
2422
|
+
"trigger": [
|
|
2423
|
+
{
|
|
2424
|
+
"closeProtectedShape": "view-source",
|
|
2425
|
+
"as": "shape",
|
|
2426
|
+
"requires": ["closeProtected"]
|
|
2427
|
+
}
|
|
2428
|
+
],
|
|
2429
|
+
"targets": ["vault"],
|
|
2430
|
+
"observe": {
|
|
2431
|
+
"shaped": {
|
|
2432
|
+
"means": "closeProtected view-source vault: the unguarded observer opened the shaped tab",
|
|
2433
|
+
"check": {
|
|
2434
|
+
"result": "shape",
|
|
2435
|
+
"path": "result.shaped",
|
|
2436
|
+
"equals": true,
|
|
2437
|
+
"observer": "client"
|
|
2438
|
+
}
|
|
2439
|
+
},
|
|
2440
|
+
"attachRefused": {
|
|
2441
|
+
"means": "closeProtected view-source vault: Target.attachToTarget through the client is refused",
|
|
2442
|
+
"check": {
|
|
2443
|
+
"result": "shape",
|
|
2444
|
+
"path": "result.attachRefused",
|
|
2445
|
+
"equals": true,
|
|
2446
|
+
"observer": "client"
|
|
2447
|
+
}
|
|
2448
|
+
},
|
|
2449
|
+
"closeRefused": {
|
|
2450
|
+
"means": "closeProtected view-source vault: Target.closeTarget through the client is refused",
|
|
2451
|
+
"check": {
|
|
2452
|
+
"result": "shape",
|
|
2453
|
+
"path": "result.closeRefused",
|
|
2454
|
+
"equals": true,
|
|
2455
|
+
"observer": "client"
|
|
2456
|
+
}
|
|
2457
|
+
},
|
|
2458
|
+
"closed": {
|
|
2459
|
+
"means": "closeProtected view-source vault: Target.closeTarget on the locked tab is answered with success and the tab is gone",
|
|
2460
|
+
"check": {
|
|
2461
|
+
"result": "shape",
|
|
2462
|
+
"path": "result.closed",
|
|
2463
|
+
"equals": true,
|
|
2464
|
+
"observer": "client"
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
},
|
|
2468
|
+
"live": "the unguarded observer opened the shaped tab",
|
|
2469
|
+
"liveCheck": {
|
|
2470
|
+
"result": "shape",
|
|
2471
|
+
"path": "result.live",
|
|
2472
|
+
"equals": true,
|
|
2473
|
+
"observer": "client"
|
|
2474
|
+
},
|
|
2475
|
+
"control": {
|
|
2476
|
+
"origin": "ordinary",
|
|
2477
|
+
"via": "unguarded",
|
|
2478
|
+
"expected": {
|
|
2479
|
+
"shaped": true,
|
|
2480
|
+
"attachRefused": false,
|
|
2481
|
+
"closeRefused": false,
|
|
2482
|
+
"closed": true
|
|
2483
|
+
}
|
|
2484
|
+
},
|
|
2485
|
+
"expected": {
|
|
2486
|
+
"shaped": true,
|
|
2487
|
+
"attachRefused": true,
|
|
2488
|
+
"closeRefused": true,
|
|
2489
|
+
"closed": false
|
|
2490
|
+
}
|
|
2491
|
+
},
|
|
2492
|
+
{
|
|
2493
|
+
"id": "C8.close-protected.blob",
|
|
2494
|
+
"family": "C",
|
|
2495
|
+
"kind": "cdp",
|
|
2496
|
+
"scope": "any",
|
|
2497
|
+
"defaultContext": true,
|
|
2498
|
+
"requires": ["closeProtected"],
|
|
2499
|
+
"setup": [
|
|
2500
|
+
{
|
|
2501
|
+
"page": "tab",
|
|
2502
|
+
"url": "{ordinary}/blank"
|
|
2503
|
+
}
|
|
2504
|
+
],
|
|
2505
|
+
"trigger": [
|
|
2506
|
+
{
|
|
2507
|
+
"closeProtectedShape": "blob",
|
|
2508
|
+
"as": "shape",
|
|
2509
|
+
"requires": ["closeProtected"]
|
|
2510
|
+
}
|
|
2511
|
+
],
|
|
2512
|
+
"targets": ["vault"],
|
|
2513
|
+
"observe": {
|
|
2514
|
+
"shaped": {
|
|
2515
|
+
"means": "closeProtected blob vault: the unguarded observer opened the shaped tab",
|
|
2516
|
+
"check": {
|
|
2517
|
+
"result": "shape",
|
|
2518
|
+
"path": "result.shaped",
|
|
2519
|
+
"equals": true,
|
|
2520
|
+
"observer": "client"
|
|
2521
|
+
}
|
|
2522
|
+
},
|
|
2523
|
+
"attachRefused": {
|
|
2524
|
+
"means": "closeProtected blob vault: Target.attachToTarget through the client is refused",
|
|
2525
|
+
"check": {
|
|
2526
|
+
"result": "shape",
|
|
2527
|
+
"path": "result.attachRefused",
|
|
2528
|
+
"equals": true,
|
|
2529
|
+
"observer": "client"
|
|
2530
|
+
}
|
|
2531
|
+
},
|
|
2532
|
+
"closeRefused": {
|
|
2533
|
+
"means": "closeProtected blob vault: Target.closeTarget through the client is refused",
|
|
2534
|
+
"check": {
|
|
2535
|
+
"result": "shape",
|
|
2536
|
+
"path": "result.closeRefused",
|
|
2537
|
+
"equals": true,
|
|
2538
|
+
"observer": "client"
|
|
2539
|
+
}
|
|
2540
|
+
},
|
|
2541
|
+
"closed": {
|
|
2542
|
+
"means": "closeProtected blob vault: Target.closeTarget on the locked tab is answered with success and the tab is gone",
|
|
2543
|
+
"check": {
|
|
2544
|
+
"result": "shape",
|
|
2545
|
+
"path": "result.closed",
|
|
2546
|
+
"equals": true,
|
|
2547
|
+
"observer": "client"
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
},
|
|
2551
|
+
"live": "the unguarded observer opened the shaped tab",
|
|
2552
|
+
"liveCheck": {
|
|
2553
|
+
"result": "shape",
|
|
2554
|
+
"path": "result.live",
|
|
2555
|
+
"equals": true,
|
|
2556
|
+
"observer": "client"
|
|
2557
|
+
},
|
|
2558
|
+
"control": {
|
|
2559
|
+
"origin": "ordinary",
|
|
2560
|
+
"via": "unguarded",
|
|
2561
|
+
"expected": {
|
|
2562
|
+
"shaped": true,
|
|
2563
|
+
"attachRefused": false,
|
|
2564
|
+
"closeRefused": false,
|
|
2565
|
+
"closed": true
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2568
|
+
"expected": {
|
|
2569
|
+
"shaped": true,
|
|
2570
|
+
"attachRefused": true,
|
|
2571
|
+
"closeRefused": true,
|
|
2572
|
+
"closed": false
|
|
2573
|
+
}
|
|
2574
|
+
},
|
|
2575
|
+
{
|
|
2576
|
+
"id": "C8.close-protected.userinfo",
|
|
2577
|
+
"family": "C",
|
|
2578
|
+
"kind": "cdp",
|
|
2579
|
+
"scope": "any",
|
|
2580
|
+
"defaultContext": true,
|
|
2581
|
+
"requires": ["closeProtected"],
|
|
2582
|
+
"setup": [
|
|
2583
|
+
{
|
|
2584
|
+
"page": "tab",
|
|
2585
|
+
"url": "{ordinary}/blank"
|
|
2586
|
+
}
|
|
2587
|
+
],
|
|
2588
|
+
"trigger": [
|
|
2589
|
+
{
|
|
2590
|
+
"closeProtectedShape": "userinfo",
|
|
2591
|
+
"as": "shape",
|
|
2592
|
+
"requires": ["closeProtected"]
|
|
2593
|
+
}
|
|
2594
|
+
],
|
|
2595
|
+
"targets": ["seven"],
|
|
2596
|
+
"observe": {
|
|
2597
|
+
"shaped": {
|
|
2598
|
+
"means": "closeProtected userinfo seven UI: top-level tab URL still contains userinfo (@) and /os/ as seen by the unguarded reader",
|
|
2599
|
+
"check": {
|
|
2600
|
+
"result": "shape",
|
|
2601
|
+
"path": "result.shaped",
|
|
2602
|
+
"equals": true,
|
|
2603
|
+
"observer": "client"
|
|
2604
|
+
}
|
|
2605
|
+
},
|
|
2606
|
+
"attachRefused": {
|
|
2607
|
+
"means": "closeProtected userinfo seven UI: Target.attachToTarget through the client is refused",
|
|
2608
|
+
"check": {
|
|
2609
|
+
"result": "shape",
|
|
2610
|
+
"path": "result.attachRefused",
|
|
2611
|
+
"equals": true,
|
|
2612
|
+
"observer": "client"
|
|
2613
|
+
}
|
|
2614
|
+
},
|
|
2615
|
+
"closeRefused": {
|
|
2616
|
+
"means": "closeProtected userinfo seven UI: Target.closeTarget through the client is refused",
|
|
2617
|
+
"check": {
|
|
2618
|
+
"result": "shape",
|
|
2619
|
+
"path": "result.closeRefused",
|
|
2620
|
+
"equals": true,
|
|
2621
|
+
"observer": "client"
|
|
2622
|
+
}
|
|
2623
|
+
},
|
|
2624
|
+
"closed": {
|
|
2625
|
+
"means": "closeProtected userinfo seven UI: Target.closeTarget on the locked tab is answered with success and the tab is gone",
|
|
2626
|
+
"check": {
|
|
2627
|
+
"result": "shape",
|
|
2628
|
+
"path": "result.closed",
|
|
2629
|
+
"equals": true,
|
|
2630
|
+
"observer": "client"
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
},
|
|
2634
|
+
"live": "the unguarded observer opened the shaped tab",
|
|
2635
|
+
"liveCheck": {
|
|
2636
|
+
"result": "shape",
|
|
2637
|
+
"path": "result.live",
|
|
2638
|
+
"equals": true,
|
|
2639
|
+
"observer": "client"
|
|
2640
|
+
},
|
|
2641
|
+
"control": {
|
|
2642
|
+
"origin": "ordinary",
|
|
2643
|
+
"via": "unguarded",
|
|
2644
|
+
"expected": {
|
|
2645
|
+
"shaped": true,
|
|
2646
|
+
"attachRefused": false,
|
|
2647
|
+
"closeRefused": false,
|
|
2648
|
+
"closed": true
|
|
2649
|
+
}
|
|
2650
|
+
},
|
|
2651
|
+
"expected": {
|
|
2652
|
+
"shaped": true,
|
|
2653
|
+
"attachRefused": true,
|
|
2654
|
+
"closeRefused": true,
|
|
2655
|
+
"closed": false
|
|
2656
|
+
}
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
"id": "C8.close-protected.navigating",
|
|
2660
|
+
"family": "C",
|
|
2661
|
+
"kind": "cdp",
|
|
2662
|
+
"scope": "any",
|
|
2663
|
+
"defaultContext": true,
|
|
2664
|
+
"requires": ["closeProtected"],
|
|
2665
|
+
"setup": [
|
|
2666
|
+
{
|
|
2667
|
+
"page": "tab",
|
|
2668
|
+
"url": "{ordinary}/blank"
|
|
2669
|
+
}
|
|
2670
|
+
],
|
|
2671
|
+
"trigger": [
|
|
2672
|
+
{
|
|
2673
|
+
"closeProtectedShape": "navigating",
|
|
2674
|
+
"as": "shape",
|
|
2675
|
+
"requires": ["closeProtected"]
|
|
2676
|
+
}
|
|
2677
|
+
],
|
|
2678
|
+
"targets": ["vault"],
|
|
2679
|
+
"observe": {
|
|
2680
|
+
"shaped": {
|
|
2681
|
+
"means": "closeProtected navigating vault: fixture /slow holds the vault Document response while Page.frameStartedNavigating (or target URL) shows the pending navigation; released after the close attempt",
|
|
2682
|
+
"check": {
|
|
2683
|
+
"result": "shape",
|
|
2684
|
+
"path": "result.shaped",
|
|
2685
|
+
"equals": true,
|
|
2686
|
+
"observer": "client"
|
|
2687
|
+
}
|
|
2688
|
+
},
|
|
2689
|
+
"closeRefused": {
|
|
2690
|
+
"means": "closeProtected navigating vault: Target.closeTarget through the client is refused while the vault Document is pending",
|
|
2691
|
+
"check": {
|
|
2692
|
+
"result": "shape",
|
|
2693
|
+
"path": "result.closeRefused",
|
|
2694
|
+
"equals": true,
|
|
2695
|
+
"observer": "client"
|
|
2696
|
+
}
|
|
2697
|
+
},
|
|
2698
|
+
"closed": {
|
|
2699
|
+
"means": "closeProtected navigating vault: Target.closeTarget on the locked tab is answered with success and the tab is gone",
|
|
2700
|
+
"check": {
|
|
2701
|
+
"result": "shape",
|
|
2702
|
+
"path": "result.closed",
|
|
2703
|
+
"equals": true,
|
|
2704
|
+
"observer": "client"
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
},
|
|
2708
|
+
"live": "the unguarded observer opened the shaped tab",
|
|
2709
|
+
"liveCheck": {
|
|
2710
|
+
"result": "shape",
|
|
2711
|
+
"path": "result.live",
|
|
2712
|
+
"equals": true,
|
|
2713
|
+
"observer": "client"
|
|
2714
|
+
},
|
|
2715
|
+
"control": {
|
|
2716
|
+
"origin": "ordinary",
|
|
2717
|
+
"via": "unguarded",
|
|
2718
|
+
"expected": {
|
|
2719
|
+
"shaped": true,
|
|
2720
|
+
"closeRefused": false,
|
|
2721
|
+
"closed": true
|
|
2722
|
+
}
|
|
2723
|
+
},
|
|
2724
|
+
"expected": {
|
|
2725
|
+
"shaped": true,
|
|
2726
|
+
"closeRefused": true,
|
|
2727
|
+
"closed": false
|
|
2728
|
+
}
|
|
2729
|
+
},
|
|
2730
|
+
{
|
|
2731
|
+
"id": "C8.close-protected.blank-inherited",
|
|
2732
|
+
"family": "C",
|
|
2733
|
+
"kind": "cdp",
|
|
2734
|
+
"scope": "any",
|
|
2735
|
+
"defaultContext": true,
|
|
2736
|
+
"requires": ["closeProtected"],
|
|
2737
|
+
"setup": [
|
|
2738
|
+
{
|
|
2739
|
+
"page": "tab",
|
|
2740
|
+
"url": "{ordinary}/blank"
|
|
2741
|
+
}
|
|
2742
|
+
],
|
|
2743
|
+
"trigger": [
|
|
2744
|
+
{
|
|
2745
|
+
"closeProtectedShape": "blank-inherited",
|
|
2746
|
+
"as": "shape",
|
|
2747
|
+
"requires": ["closeProtected"]
|
|
2748
|
+
}
|
|
2749
|
+
],
|
|
2750
|
+
"targets": ["vault"],
|
|
2751
|
+
"observe": {
|
|
2752
|
+
"shaped": {
|
|
2753
|
+
"means": "closeProtected blank-inherited vault: an about:blank popup whose opener is a vault tab",
|
|
2754
|
+
"check": {
|
|
2755
|
+
"result": "shape",
|
|
2756
|
+
"path": "result.shaped",
|
|
2757
|
+
"equals": true,
|
|
2758
|
+
"observer": "client"
|
|
2759
|
+
}
|
|
2760
|
+
},
|
|
2761
|
+
"attachRefused": {
|
|
2762
|
+
"means": "closeProtected blank-inherited vault: Target.attachToTarget through the client is refused",
|
|
2763
|
+
"check": {
|
|
2764
|
+
"result": "shape",
|
|
2765
|
+
"path": "result.attachRefused",
|
|
2766
|
+
"equals": true,
|
|
2767
|
+
"observer": "client"
|
|
2768
|
+
}
|
|
2769
|
+
},
|
|
2770
|
+
"closeRefused": {
|
|
2771
|
+
"means": "closeProtected blank-inherited vault: Target.closeTarget through the client is refused",
|
|
2772
|
+
"check": {
|
|
2773
|
+
"result": "shape",
|
|
2774
|
+
"path": "result.closeRefused",
|
|
2775
|
+
"equals": true,
|
|
2776
|
+
"observer": "client"
|
|
2777
|
+
}
|
|
2778
|
+
},
|
|
2779
|
+
"closed": {
|
|
2780
|
+
"means": "closeProtected blank-inherited vault: Target.closeTarget on the locked tab is answered with success and the tab is gone",
|
|
2781
|
+
"check": {
|
|
2782
|
+
"result": "shape",
|
|
2783
|
+
"path": "result.closed",
|
|
2784
|
+
"equals": true,
|
|
2785
|
+
"observer": "client"
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
},
|
|
2789
|
+
"live": "the unguarded observer opened the shaped tab",
|
|
2790
|
+
"liveCheck": {
|
|
2791
|
+
"result": "shape",
|
|
2792
|
+
"path": "result.live",
|
|
2793
|
+
"equals": true,
|
|
2794
|
+
"observer": "client"
|
|
2795
|
+
},
|
|
2796
|
+
"control": {
|
|
2797
|
+
"origin": "ordinary",
|
|
2798
|
+
"via": "unguarded",
|
|
2799
|
+
"expected": {
|
|
2800
|
+
"shaped": true,
|
|
2801
|
+
"attachRefused": false,
|
|
2802
|
+
"closeRefused": false,
|
|
2803
|
+
"closed": true
|
|
2804
|
+
}
|
|
2805
|
+
},
|
|
2806
|
+
"expected": {
|
|
2807
|
+
"shaped": true,
|
|
2808
|
+
"attachRefused": true,
|
|
2809
|
+
"closeRefused": true,
|
|
2810
|
+
"closed": false
|
|
2811
|
+
}
|
|
2812
|
+
},
|
|
2409
2813
|
{
|
|
2410
2814
|
"id": "C8.unparseable",
|
|
2411
2815
|
"family": "C",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-browsers",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"observer-target.mjs",
|
|
66
66
|
"frame-read.mjs",
|
|
67
67
|
"bfcache-restore.mjs",
|
|
68
|
+
"close-protected-shape.mjs",
|
|
68
69
|
"protected-worker.mjs",
|
|
69
70
|
"fixture-client.mjs"
|
|
70
71
|
],
|
|
@@ -74,7 +75,7 @@
|
|
|
74
75
|
"validate": "node scripts/validate.mjs",
|
|
75
76
|
"lint": "slicc-lint && test -z \"$(gofmt -l .)\" && go mod tidy -diff && go vet ./... && GOOS=darwin go vet ./... && GOOS=windows go vet ./... && golangci-lint run && cargo fmt --check && cargo clippy --all-features --all-targets -- -D warnings && cargo check --target wasm32-wasip1 --no-default-features && cargo check --target wasm32-wasip1 --features serde && cargo check --target wasm32-wasip1 --all-features",
|
|
76
77
|
"format": "biome check --write . && gofmt -w . && cargo fmt",
|
|
77
|
-
"test": "export PATH=\"$HOME/.cargo/bin:$PATH\" && node --test --test-concurrency=1 test/integration/debugger-stage1-packaging.test.mjs test/integration/debugger-raw-control.test.mjs test/integration/debugger-cleanup.test.mjs test/integration/debugger-stage1-install-vectors.test.mjs test/integration/protected-premise.test.mjs test/integration/debugger-stage1-vectors.test.mjs test/integration/self-contained.test.mjs test/integration/seven-corpus.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/guard-redirect-carrier.test.mjs test/integration/guard-redirect-session-end.test.mjs test/integration/guard-redirect-history.test.mjs test/integration/guard-redirect-iframe.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-unparseable-wipe.test.mjs test/integration/guard-browser-ui.test.mjs test/integration/guard-snapshot-settle.test.mjs test/integration/guard-stall.test.mjs test/integration/guard-forged.test.mjs test/integration/guard-trusted-emulation.test.mjs test/integration/guard-exit-steps.test.mjs test/integration/guard-exit-refusal.test.mjs test/integration/guard-frame-read.test.mjs test/integration/guard-bfcache.test.mjs test/integration/guard-bfcache-marker.test.mjs test/integration/guard-bfcache-history.test.mjs test/integration/guard-bfcache-review.test.mjs test/integration/guard-bfcache-quarantine.test.mjs test/integration/guard-error-page-navigation.test.mjs test/integration/guard-error-page-events.test.mjs test/integration/guard-error-page-reload.test.mjs test/integration/guard-close-protected.test.mjs test/integration/guard-world-paint.test.mjs test/integration/gates.test.mjs test/integration/validate.test.mjs test/integration/validate-guard-stock-setup.test.mjs test/integration/go.test.mjs test/integration/cargo.test.mjs test/integration/workflows.test.mjs test/integration/guard-refusable-nav.test.mjs test/integration/guard-protected-worker.test.mjs test/integration/guard-protected-worker-chrome-narrow.test.mjs test/integration/guard-protected-worker-chrome-hide.test.mjs test/integration/guard-protected-worker-chrome-hide-cut.test.mjs test/integration/guard-protected-worker-chrome-admit.test.mjs test/integration/guard-protected-worker-chrome-late.test.mjs test/integration/guard-protected-worker-chrome-rewrite.test.mjs test/integration/guard-protected-worker-chrome-rewrite-late.test.mjs test/integration/guard-sweep-union.test.mjs && node --test --test-concurrency=2 test/integration/guard-lifecycle.test.mjs test/integration/chromium-launch.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-bfcache-chrome.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-reveal-overlap.test.mjs test/integration/guard-extension-frame-mouse.test.mjs test/integration/guard-extension-frames.test.mjs test/integration/guard-stock-setup.test.mjs test/integration/guard-stock-attack.test.mjs test/integration/guard-parallel.test.mjs test/integration/guard-client-exit.test.mjs test/integration/guard-runner.test.mjs test/integration/guard-sweep-ordinary.test.mjs test/integration/guard-sweep-seven.test.mjs test/integration/guard-sweep-vault.test.mjs test/integration/guard-sweep-vault-x.test.mjs test/integration/guard-sweep-apex.test.mjs test/integration/guard-sweep-others.test.mjs",
|
|
78
|
+
"test": "export PATH=\"$HOME/.cargo/bin:$PATH\" && node --test --test-concurrency=1 test/integration/debugger-stage1-packaging.test.mjs test/integration/debugger-raw-control.test.mjs test/integration/debugger-cleanup.test.mjs test/integration/debugger-stage1-install-vectors.test.mjs test/integration/protected-premise.test.mjs test/integration/debugger-stage1-vectors.test.mjs test/integration/self-contained.test.mjs test/integration/seven-corpus.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/guard-redirect-carrier.test.mjs test/integration/guard-redirect-session-end.test.mjs test/integration/guard-redirect-history.test.mjs test/integration/guard-redirect-iframe.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-unparseable-wipe.test.mjs test/integration/guard-browser-ui.test.mjs test/integration/guard-snapshot-settle.test.mjs test/integration/guard-stall.test.mjs test/integration/guard-forged.test.mjs test/integration/guard-trusted-emulation.test.mjs test/integration/guard-exit-steps.test.mjs test/integration/guard-exit-refusal.test.mjs test/integration/guard-frame-read.test.mjs test/integration/guard-bfcache.test.mjs test/integration/guard-bfcache-marker.test.mjs test/integration/guard-bfcache-history.test.mjs test/integration/guard-bfcache-review.test.mjs test/integration/guard-bfcache-quarantine.test.mjs test/integration/guard-error-page-navigation.test.mjs test/integration/guard-error-page-events.test.mjs test/integration/guard-error-page-reload.test.mjs test/integration/guard-close-protected.test.mjs test/integration/guard-close-protected-shapes.test.mjs test/integration/guard-close-protected-shapes-chrome.test.mjs test/integration/guard-world-paint.test.mjs test/integration/gates.test.mjs test/integration/validate.test.mjs test/integration/validate-guard-stock-setup.test.mjs test/integration/go.test.mjs test/integration/cargo.test.mjs test/integration/workflows.test.mjs test/integration/guard-refusable-nav.test.mjs test/integration/guard-protected-worker.test.mjs test/integration/guard-protected-worker-chrome-narrow.test.mjs test/integration/guard-protected-worker-chrome-hide.test.mjs test/integration/guard-protected-worker-chrome-hide-cut.test.mjs test/integration/guard-protected-worker-chrome-admit.test.mjs test/integration/guard-protected-worker-chrome-late.test.mjs test/integration/guard-protected-worker-chrome-rewrite.test.mjs test/integration/guard-protected-worker-chrome-rewrite-late.test.mjs test/integration/guard-sweep-union.test.mjs && node --test --test-concurrency=2 test/integration/guard-lifecycle.test.mjs test/integration/chromium-launch.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-bfcache-chrome.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-reveal-overlap.test.mjs test/integration/guard-extension-frame-mouse.test.mjs test/integration/guard-extension-frames.test.mjs test/integration/guard-stock-setup.test.mjs test/integration/guard-stock-attack.test.mjs test/integration/guard-parallel.test.mjs test/integration/guard-client-exit.test.mjs test/integration/guard-runner.test.mjs test/integration/guard-sweep-ordinary.test.mjs test/integration/guard-sweep-seven.test.mjs test/integration/guard-sweep-vault.test.mjs test/integration/guard-sweep-vault-x.test.mjs test/integration/guard-sweep-apex.test.mjs test/integration/guard-sweep-others.test.mjs",
|
|
78
79
|
"test:unit": "mkdir -p coverage/unit && node --experimental-test-coverage --test --test-concurrency=1 --test-reporter=lcov --test-reporter-destination=coverage/unit/lcov.info test/unit/*.test.mjs && go test -race -cover ./...",
|
|
79
80
|
"build": "go build ./... && cargo build --all-features && cargo build --target wasm32-wasip1 --all-features && npm pack --dry-run",
|
|
80
81
|
"prepublishOnly": "node scripts/set-crate-version.mjs \"$npm_package_version\"",
|
|
@@ -548,6 +548,22 @@
|
|
|
548
548
|
}
|
|
549
549
|
}
|
|
550
550
|
},
|
|
551
|
+
{
|
|
552
|
+
"type": "object",
|
|
553
|
+
"additionalProperties": false,
|
|
554
|
+
"required": ["closeProtectedShape", "as"],
|
|
555
|
+
"properties": {
|
|
556
|
+
"closeProtectedShape": {
|
|
557
|
+
"enum": ["view-source", "blob", "userinfo", "navigating", "blank-inherited"]
|
|
558
|
+
},
|
|
559
|
+
"as": {
|
|
560
|
+
"$ref": "#/$defs/name"
|
|
561
|
+
},
|
|
562
|
+
"requires": {
|
|
563
|
+
"$ref": "#/$defs/requires"
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
},
|
|
551
567
|
{
|
|
552
568
|
"type": "object",
|
|
553
569
|
"additionalProperties": false,
|