@ai-ecoverse/slicc-browsers 1.22.1 → 1.24.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 +3 -3
- package/close-protected-shape.mjs +563 -0
- package/fixture-server.mjs +64 -0
- package/guard-runner.mjs +80 -8
- package/guard-vectors.json +606 -1
- package/package.json +6 -4
- package/protected-parent-iframes.mjs +567 -0
- package/schema/guard-vectors.schema.json +32 -0
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>', {
|
|
@@ -91,6 +101,25 @@ function route(req, url, send, html, redirects) {
|
|
|
91
101
|
`<script>navigator.sendBeacon(${JSON.stringify(pre)})</script><iframe src="${escape(to)}"></iframe>`
|
|
92
102
|
);
|
|
93
103
|
},
|
|
104
|
+
'/oopif-nest': () => {
|
|
105
|
+
const mid = p.get('mid') ?? 'about:blank';
|
|
106
|
+
return html(
|
|
107
|
+
'oopif-nest',
|
|
108
|
+
`<p>oopif-nest ${escape(p.get('n') ?? '')}</p><iframe src="${escape(mid)}"></iframe>`
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
'/oopif-mid': () => {
|
|
112
|
+
const inner = p.get('inner') ?? 'about:blank';
|
|
113
|
+
return html(
|
|
114
|
+
'oopif-mid',
|
|
115
|
+
`<p>oopif-mid ${escape(p.get('n') ?? '')}</p><iframe src="${escape(inner)}"></iframe>`
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
'/oopif-srcdoc': () =>
|
|
119
|
+
html(
|
|
120
|
+
'oopif-srcdoc',
|
|
121
|
+
`<p>oopif-srcdoc ${escape(p.get('n') ?? '')}</p><iframe sandbox="allow-scripts allow-forms" srcdoc="${escape('<!doctype html><title>srcdoc</title><p>ordinary srcdoc</p>')}"></iframe>`
|
|
122
|
+
),
|
|
94
123
|
'/bfcache': () => {
|
|
95
124
|
const marker = JSON.stringify(`bfcache-marker-${p.get('n') ?? ''}`).replaceAll(
|
|
96
125
|
'<',
|
|
@@ -147,10 +176,31 @@ function requestURL(req, scheme) {
|
|
|
147
176
|
};
|
|
148
177
|
}
|
|
149
178
|
|
|
179
|
+
async function holdSlow({ req, res, url, p, html, entries, history, slows }) {
|
|
180
|
+
const n = p.get('n') ?? '';
|
|
181
|
+
const ms = Math.min(Math.max(Number(p.get('ms') ?? 15000) || 15000, 1), 60000);
|
|
182
|
+
const entry = {
|
|
183
|
+
time: Date.now(),
|
|
184
|
+
url: url.href,
|
|
185
|
+
method: req.method,
|
|
186
|
+
headers: { ...req.headers },
|
|
187
|
+
};
|
|
188
|
+
entries.push(entry);
|
|
189
|
+
history.push(entry);
|
|
190
|
+
await new Promise((resolve) => {
|
|
191
|
+
const timer = setTimeout(resolve, ms);
|
|
192
|
+
slows.set(n, { resolve, timer });
|
|
193
|
+
});
|
|
194
|
+
slows.delete(n);
|
|
195
|
+
if (res.writableEnded) return;
|
|
196
|
+
return html('slow', `<p>slow ${escape(n)}</p>`);
|
|
197
|
+
}
|
|
198
|
+
|
|
150
199
|
export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency } = {}) {
|
|
151
200
|
let entries = [];
|
|
152
201
|
const history = [];
|
|
153
202
|
const redirects = new Map();
|
|
203
|
+
const slows = new Map();
|
|
154
204
|
const redirectSecret = async ({ path, host = '', search = '', remove, ...value }) => {
|
|
155
205
|
if (
|
|
156
206
|
typeof path !== 'string' ||
|
|
@@ -162,6 +212,14 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
|
|
|
162
212
|
if (remove) redirects.delete(key);
|
|
163
213
|
else redirects.set(key, value);
|
|
164
214
|
};
|
|
215
|
+
const releaseSlow = (n) => {
|
|
216
|
+
const entry = slows.get(String(n ?? ''));
|
|
217
|
+
if (!entry) return false;
|
|
218
|
+
clearTimeout(entry.timer);
|
|
219
|
+
entry.resolve();
|
|
220
|
+
slows.delete(String(n ?? ''));
|
|
221
|
+
return true;
|
|
222
|
+
};
|
|
165
223
|
const scheme = tls ? 'https' : 'http';
|
|
166
224
|
const handler = async (req, res) => {
|
|
167
225
|
const url = requestURL(req, scheme);
|
|
@@ -183,6 +241,10 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
|
|
|
183
241
|
'text/html',
|
|
184
242
|
headers
|
|
185
243
|
);
|
|
244
|
+
if (req.method === 'GET' && url.pathname === '/slow') {
|
|
245
|
+
await holdSlow({ req, res, url, p, html, entries, history, slows });
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
186
248
|
if (req.method === 'GET' && url.pathname === '/log') {
|
|
187
249
|
const from = Number(p.get('since') ?? 0);
|
|
188
250
|
if (!Number.isInteger(from) || from < 0 || from > history.length)
|
|
@@ -220,6 +282,7 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
|
|
|
220
282
|
return {
|
|
221
283
|
redirects,
|
|
222
284
|
redirectSecret,
|
|
285
|
+
releaseSlow,
|
|
223
286
|
port: address.port,
|
|
224
287
|
origin: `${scheme}://${host}:${address.port}`,
|
|
225
288
|
log: () => structuredClone(entries),
|
|
@@ -228,6 +291,7 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
|
|
|
228
291
|
},
|
|
229
292
|
close: () =>
|
|
230
293
|
new Promise((resolve, reject) => {
|
|
294
|
+
for (const n of [...slows.keys()]) releaseSlow(n);
|
|
231
295
|
server.closeAllConnections();
|
|
232
296
|
server.close((error) => (error ? reject(error) : resolve()));
|
|
233
297
|
}),
|
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';
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
} from './guard-driver.mjs';
|
|
32
33
|
import { createObserverTarget, discoveryViolation } from './observer-target.mjs';
|
|
33
34
|
import { protectedErrorPage } from './protected-error-page.mjs';
|
|
35
|
+
import { protectedParentIframes } from './protected-parent-iframes.mjs';
|
|
34
36
|
import {
|
|
35
37
|
leftoverPersistentWorker,
|
|
36
38
|
persistentWorkerTypes,
|
|
@@ -194,6 +196,7 @@ class Execution {
|
|
|
194
196
|
this.unsupported = undefined;
|
|
195
197
|
this.preSent = [];
|
|
196
198
|
this.notes = [];
|
|
199
|
+
this.sharedNotes = Array.isArray(options.notes) ? options.notes : undefined;
|
|
197
200
|
this.answers = [];
|
|
198
201
|
this.internalSessions = new Set();
|
|
199
202
|
this.attachSettings = new Map();
|
|
@@ -222,6 +225,10 @@ class Execution {
|
|
|
222
225
|
note(message) {
|
|
223
226
|
if (this.notes.length >= 80) this.notes.shift();
|
|
224
227
|
Array.prototype.push.call(this.notes, message);
|
|
228
|
+
if (this.sharedNotes && this.sharedNotes !== this.notes) {
|
|
229
|
+
if (this.sharedNotes.length >= 80) this.sharedNotes.shift();
|
|
230
|
+
Array.prototype.push.call(this.sharedNotes, message);
|
|
231
|
+
}
|
|
225
232
|
}
|
|
226
233
|
|
|
227
234
|
installClientTrace() {
|
|
@@ -894,6 +901,8 @@ class Execution {
|
|
|
894
901
|
'redirectSecret',
|
|
895
902
|
'unparseable',
|
|
896
903
|
'errorPage',
|
|
904
|
+
'closeProtectedShape',
|
|
905
|
+
'protectedParentIframes',
|
|
897
906
|
'extensionWorld',
|
|
898
907
|
'extensionFrame',
|
|
899
908
|
'frameRead',
|
|
@@ -902,9 +911,15 @@ class Execution {
|
|
|
902
911
|
if (probe && !out.ok) this.errors.push(`${probe}: ${out.error?.message ?? 'probe failed'}`);
|
|
903
912
|
if (step.client && !out.ok) this.errors.push(`client exit: ${out.error.message}`);
|
|
904
913
|
if (
|
|
905
|
-
[
|
|
906
|
-
|
|
907
|
-
|
|
914
|
+
[
|
|
915
|
+
'errorPage',
|
|
916
|
+
'closeProtectedShape',
|
|
917
|
+
'protectedParentIframes',
|
|
918
|
+
'redirectSecret',
|
|
919
|
+
'extensionFrame',
|
|
920
|
+
'frameRead',
|
|
921
|
+
'bfcacheRestore',
|
|
922
|
+
].includes(probe) &&
|
|
908
923
|
out.ok &&
|
|
909
924
|
out.result?.incomplete
|
|
910
925
|
)
|
|
@@ -1000,9 +1015,15 @@ class Execution {
|
|
|
1000
1015
|
return;
|
|
1001
1016
|
}
|
|
1002
1017
|
if (step.client) return outcome(this.exit());
|
|
1003
|
-
if (
|
|
1004
|
-
|
|
1005
|
-
|
|
1018
|
+
if (
|
|
1019
|
+
step.redirectSecret ||
|
|
1020
|
+
step.errorPage ||
|
|
1021
|
+
step.closeProtectedShape ||
|
|
1022
|
+
step.protectedParentIframes ||
|
|
1023
|
+
step.spawnWorker ||
|
|
1024
|
+
step.probeSessions
|
|
1025
|
+
)
|
|
1026
|
+
return this.stepProbe(step, session);
|
|
1006
1027
|
if (step.bfcacheRestore)
|
|
1007
1028
|
return outcome(
|
|
1008
1029
|
bfcacheRestore({
|
|
@@ -1143,6 +1164,14 @@ class Execution {
|
|
|
1143
1164
|
}
|
|
1144
1165
|
}
|
|
1145
1166
|
|
|
1167
|
+
stepProbe(step, session) {
|
|
1168
|
+
if (step.redirectSecret || step.errorPage) return this.originProbe(step, session);
|
|
1169
|
+
if (step.closeProtectedShape) return this.runCloseProtectedShape(step);
|
|
1170
|
+
if (step.protectedParentIframes) return this.runProtectedParentIframes(step);
|
|
1171
|
+
if (step.spawnWorker) return this.spawnWorker(step);
|
|
1172
|
+
return this.probeSessions(step);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1146
1175
|
async originProbe(step, session) {
|
|
1147
1176
|
if (step.redirectSecret) {
|
|
1148
1177
|
this.internalSessions.add(session);
|
|
@@ -1187,6 +1216,44 @@ class Execution {
|
|
|
1187
1216
|
);
|
|
1188
1217
|
}
|
|
1189
1218
|
|
|
1219
|
+
runCloseProtectedShape(step) {
|
|
1220
|
+
return outcome(
|
|
1221
|
+
closeProtectedShape({
|
|
1222
|
+
client: this.client,
|
|
1223
|
+
reader: this.reader,
|
|
1224
|
+
ordinary: this.vars.ordinary,
|
|
1225
|
+
protectedOrigin: this.vars.target,
|
|
1226
|
+
shape: step.closeProtectedShape,
|
|
1227
|
+
tag: this.vars.tag,
|
|
1228
|
+
internalSessions: this.internalSessions,
|
|
1229
|
+
notes: this.notes,
|
|
1230
|
+
budget: this.bound(5000),
|
|
1231
|
+
releaseSlow: this.options.fixture.releaseSlow,
|
|
1232
|
+
})
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
async runProtectedParentIframes(step) {
|
|
1237
|
+
const out = await outcome(
|
|
1238
|
+
protectedParentIframes({
|
|
1239
|
+
client: this.client,
|
|
1240
|
+
reader: this.reader,
|
|
1241
|
+
protectedOrigin: this.vars.target,
|
|
1242
|
+
cross: this.options.targets?.cross,
|
|
1243
|
+
ordinary: this.vars.ordinary,
|
|
1244
|
+
mode: step.protectedParentIframes,
|
|
1245
|
+
tag: this.vars.tag,
|
|
1246
|
+
internalSessions: this.internalSessions,
|
|
1247
|
+
notes: this.notes,
|
|
1248
|
+
budget: this.bound(5000),
|
|
1249
|
+
discovery: supported(['discovery'], this.options.capabilities),
|
|
1250
|
+
eventStart: this.eventStart,
|
|
1251
|
+
})
|
|
1252
|
+
);
|
|
1253
|
+
if (out.ok && out.result?.pageId) this.targets.push(out.result.pageId);
|
|
1254
|
+
return out;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1190
1257
|
spawnWorkerKind() {
|
|
1191
1258
|
return allSteps([...(this.vector.setup ?? []), ...(this.vector.trigger ?? [])]).find(
|
|
1192
1259
|
(step) => step.spawnWorker
|
|
@@ -2173,10 +2240,15 @@ const observerRequired = (check) => {
|
|
|
2173
2240
|
return check?.not ? observerRequired(check.not) : false;
|
|
2174
2241
|
};
|
|
2175
2242
|
const needsObserver = (vector) =>
|
|
2176
|
-
vector.control.via === 'unguarded' ||
|
|
2177
2243
|
allSteps([...(vector.setup ?? []), ...(vector.trigger ?? [])]).some(
|
|
2178
|
-
(step) =>
|
|
2244
|
+
(step) =>
|
|
2245
|
+
step.isolatedWorld ||
|
|
2246
|
+
step.observer ||
|
|
2247
|
+
step.spawnWorker ||
|
|
2248
|
+
step.closeProtectedShape ||
|
|
2249
|
+
step.protectedParentIframes
|
|
2179
2250
|
) ||
|
|
2251
|
+
vector.control.via === 'unguarded' ||
|
|
2180
2252
|
observerRequired(vector.liveCheck) ||
|
|
2181
2253
|
Object.values(vector.observe ?? {}).some(
|
|
2182
2254
|
(observation) => observerRequired(observation.check) || observerRequired(observation.eitherWhen)
|