-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathstates.mjs
More file actions
5673 lines (5518 loc) · 312 KB
/
Copy pathstates.mjs
File metadata and controls
5673 lines (5518 loc) · 312 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bun
// The E2E "states" — the single source of truth for what the verify loop drives
// and asserts. Each state is data + a run() that interacts with the LIVE side
// panel through one ctx (the harness). The single-Chrome verify runner
// (run-e2e-verify.mjs) executes every state against ONE Chrome — reset the
// session, swap the model responder, run — so a full pass is ~1 launch, not N.
//
// A state:
// { name, kind: 'functional'|'visual', phase: 'pre-unlock'|'post-unlock',
// responder, async run(ctx, rec) }
// - responder: the per-call model behaviour (swapped in before run)
// - run(ctx, rec): drives the panel and records via the recorder:
// rec.check(name, pass, detail) — a functional assertion
// rec.shot(label) — a screenshot artifact (Claude can read)
// rec.visual(name, opts) — capture + baseline pixel-compare
//
// The recorder is what makes the loop legible to an agent: every state leaves a
// screenshot to look at and a structured pass/fail with the "why".
import { createServer } from 'node:http';
import { createSocket } from 'node:dgram';
import {
rpc, evalIn, waitFor, sseText, sseToolCall, sseToolCalls, openExtPage, openWidePage, attach,
sleep, setEmulatedTheme, PASSPHRASE, PANEL_METRICS, NARROW_PANEL_METRICS,
NETWORK_GUARD_CONTROLLER_PORT,
} from './e2e-harness.mjs';
// A compact transcript probe shared by the functional states.
const probe = (ctx) => evalIn(ctx.page, `(() => {
const u = document.querySelector('.message-user');
const b = document.querySelector('.message-assistant .bubble');
const err = document.querySelector('.error-line');
const goalBar = !!document.querySelector('.goal-bar');
const stopChip = !!document.querySelector('.stop-chip');
const busy = !!(document.querySelector('.message-assistant.streaming') || document.querySelector('form.input-bar button.stop'));
const capped = /hit the .*limit/i.test(document.body.innerText);
return {
userText: u ? u.textContent.trim() : null,
assistantText: b ? b.textContent.trim() : null,
errorText: err ? err.textContent.trim() : null,
goalBar, stopChip, busy, capped,
};
})()`);
const SMOKE_TEXT = 'e2e-smoke-ok';
const TRANSFER_EXPORT_VERSION = 2;
const auditEntries = async (ctx, limit = 800) => {
const audit = await rpc(ctx.page, { type: 'audit/list', limit });
return (audit && audit.entries) || [];
};
const actorIsolationEvidence = (entries) => {
const isolated = entries.filter((entry) => entry.type === 'actor_ran_isolated');
return {
isolated,
exactProof: isolated.length > 0 && isolated.every((entry) =>
entry.details?.workerType === 'dedicated'
&& entry.details?.realmVerified === true),
backgroundRefused: entries.some((entry) => entry.type === 'actor_background_turn_refused'),
isolationFailed: entries.some((entry) => entry.type === 'actor_isolation_failure'),
};
};
// Transfer routes require the exact options-page channel. Keep the live E2E on
// that production boundary instead of calling the generic dispatcher.
const privateTransferRpc = (page, message) => evalIn(page, `(async () => {
const { callPrivateTransfer } = await import('/options/private-transfer-session.js');
return callPrivateTransfer(${JSON.stringify(message)});
})()`, true);
// The raw CDP handle's close() disconnects the debugger but does not close the
// tab. Navigate private-transfer fixtures away first so the next exact-client
// assertion cannot inherit an old options document from an earlier state.
const retirePrivateTransferPage = async (page) => {
await page.send('Page.navigate', { url: 'about:blank' });
const retired = await waitFor(() => evalIn(page, `location.href === 'about:blank'`),
{ budgetMs: 5_000, pollMs: 50 });
if (!retired) throw new Error('private transfer fixture did not retire');
try { page.close(); } catch { /* */ }
};
// The local-first personal-data agent, end to end through the REAL stack: the
// faked model calls script, the sealed worker builds an on-device index in OPFS
// and queries it, and the agent reports the answer — every byte computed on
// device (the realm seal makes the worker incapable of egress).
const PDA_SCRIPT = `
const records = [
{ id: 'amazon:o1', date: '2025-02-03', merchant: 'Amazon', amount: 12.5 },
{ id: 'amazon:o2', date: '2025-06-20', merchant: 'Amazon', amount: 7.5 },
{ id: 'amazon:o3', date: '2025-11-03', merchant: 'Amazon', amount: 30 },
];
await peerd.self.writeFile('records/orders.jsonl', records.map((r) => JSON.stringify(r)).join('\\n'));
const text = await peerd.self.readFile('records/orders.jsonl');
const rows = text.split('\\n').filter(Boolean).map((l) => JSON.parse(l));
const total = rows.reduce((a, r) => a + r.amount, 0);
return { total, count: rows.length, source: 'on-device OPFS index' };
`;
// Captures the model's SECOND request body (which carries the script tool result
// back to the model) so the state can prove the sealed worker REALLY computed the
// answer — not that the faked final turn merely claims it.
let pdaToolResultBody = '';
// Per-call capture for the actor-delegation probes. The ONE shared responder
// serves BOTH orchestrator and actor model calls, so we record each call's
// system-prompt markers to PROVE the cross-process flow (orchestrator delegate
// -> web-actor sub-loop -> async fenced reply re-entering the orchestrator).
// `delegates` is responder-side because the delegate is in the RESPONSE, not the
// request — and after the ack tool_result the orchestrator loop CONTINUES, so a
// real model delegates once then ends its turn (the ack says the reply lands
// later). We mirror that: delegate once, then return plain text.
let actorState = { delegates: 0, seen: [] };
let actorBoundaryState = { delegates: 0 };
let scriptFanState = { scripts: 0, seen: [] };
let dwebActorState = { delegates: 0, actorCalls: 0 };
let a2aState = { delegates: 0, actorCalls: 0 };
// heap-split phase 1: the offscreen pure-reasoning actor state.
let reasoningState = { spawned: 0, childCalls: 0 };
let actorChannelTargetState = { spawned: 0, childCalls: 0 };
// heap-split phase 4: the offscreen TOOL-BEARING actor state.
let actorToolsState = { spawned: 0, childCalls: 0 };
// issue #324: an offscreen actor delegating FROM its granted script surface.
let actorCodeDelegatesState = {
spawned: 0, childCalls: 0, webCalls: 0, sawComposedResult: false,
};
// heap-split phase 4: an offscreen actor DELEGATING to its own web actor.
let actorDelegatesState = { spawned: 0, childCalls: 0, webCalls: 0 };
let actorFabricHierarchyState = {
spawned: 0, nestedCalls: 0, siblingCalls: 0, webCalls: 0,
};
let actorOverviewState = { alphaSpawned: 0, betaSpawned: 0 };
// heap-split phase 4: an offscreen actor BUILDING an app (create + delegate).
let actorAppState = { spawned: 0, childCalls: 0, appCalls: 0, appId: null };
let actorAppProbeUrl = '';
let actorAppStunPort = 0;
const toolResultsIn = (postData) => {
try {
const body = JSON.parse(postData);
return (body.messages ?? [])
.filter((message) => message?.role === 'tool')
.map((message) => typeof message.content === 'string'
? message.content
: JSON.stringify(message.content));
} catch { return []; }
};
// --- harvest: the FULL personal-data flow, incl. reading a real page ---------
// An order page served locally through a reserved public-looking .test name.
// The order lines are anchor text so the web actor returns them as visible text.
const ORDERS_HTML = [
'<!doctype html><html><head><title>My Orders</title></head><body>',
'<h1>My Orders</h1><ul>',
'<li><a href="/o/1001">Order #1001 - Coffee Mug - $12.00</a></li>',
'<li><a href="/o/1002">Order #1002 - Notebook - $8.50</a></li>',
'<li><a href="/o/1003">Order #1003 - Pen Set - $15.00</a></li>',
'</ul></body></html>',
].join('\n');
// The append+query the agent runs AFTER reading the page (records shaped from the
// harvested orders; total = 12 + 8.50 + 15 = 35.50).
const HARVEST_SCRIPT = `
const records = [
{ id: 'order:1001', item: 'Coffee Mug', amount: 12 },
{ id: 'order:1002', item: 'Notebook', amount: 8.5 },
{ id: 'order:1003', item: 'Pen Set', amount: 15 },
];
await peerd.self.writeFile('records/orders.jsonl', records.map((r) => JSON.stringify(r)).join('\\n'));
const rows = (await peerd.self.readFile('records/orders.jsonl')).split('\\n').filter(Boolean).map((l) => JSON.parse(l));
return { total: rows.reduce((a, r) => a + r.amount, 0), count: rows.length, source: 'harvested on-device index' };
`;
// harvest sequencing (post-#61 actor flow). The orchestrator delegates the read
// to the WEB ACTOR via message_actor; the web actor OWNS a tab, opens the fixture
// itself and reads
// it (read_page). We capture the actor request that carries the read_page RESULT
// to PROVE the actor genuinely read the live page, and sequence the actor's
// navigate→read→report turns and the orchestrator's post-reply index→answer turns
// independently (interleaving slots make callIndex fragile).
let harvestActorSawPage = '';
let harvestActorTurn = 0;
let harvestOrchTurn = 0;
let harvestDelegated = false;
let harvestActorUsedCode = false;
let harvestFixtureUrl = '';
let numericTabAuthorityState = {
addressed: false,
tabId: null, refusalBody: '', actorCallsAfterAddress: 0,
};
let numericTabAuthorityRequestBodies = [];
let numericTabAuthorityRedirectUrl = '';
let idpTransitState = { addressed: 0, siteRefusal: '', bareRefusal: '', actorCalls: 0 };
let idpTransitRequestBodies = [];
let networkGuardActorTurn = 0;
let networkGuardDelegated = false;
let networkGuardActorReady = false;
let networkGuardActorResult = '';
let networkGuardFixtureUrl = '';
let networkGuardActorTask = 'open';
let networkGuardTrustedBurstComplete = false;
let networkGuardWakeSettled = false;
// --- issue 251: the origin lock, end to end --------------------------------
//
// The unit tiers can prove the RULE and the STORE. What only this tier can prove
// is that a roaming web actor really is stopped by the live stack — real service
// worker, real actor loop, real tab, real DOM walk — and that the orchestrator is
// told something it can act on.
//
// The fixture is a SIGN-IN page, and that is the point rather than set dressing:
// nothing marks its origin sensitive up front. The actor walks the page, the walk
// sees `input[type=password]`, the classifier learns the origin, and the NEXT
// landing check hands off. So this state exercises the learned signal and the
// enforcement together — which is the only way to find out whether they agree.
//
// The harness maps reserved `*.peerd.test` names to loopback. This keeps the
// fixture public under the product's lexical host policy while remaining local.
const LOGIN_HTML = `<!doctype html><html><head><title>Acme — Sign in</title></head><body>
<h1>Sign in to Acme</h1>
<form><label>Email <input type="email" name="email"></label>
<label>Password <input type="password" name="password"></label>
<button type="submit">Sign in</button></form>
</body></html>`;
const PLAIN_HTML = `<!doctype html><html><head><title>Acme — Public docs</title></head><body>
<h1>Public docs</h1><p>Nothing here needs an account.</p></body></html>`;
let siteActorTurn = 0;
let siteDelegated = false;
let siteReplyBody = '';
let siteActorSawPage = '';
let siteFixtureUrl = '';
let siteFixtureOrigin = '';
let siteNumericTarget = null;
let siteNumericAddressed = false;
let siteNumericRefusalBody = '';
let siteNumericActorCalls = 0;
let lockActorTurn = 0;
let lockDelegated = false;
let lockReportBody = '';
let lockFixtureUrl = '';
const captureHomeLibraryGit = async (ctx, rec, { visualName, metrics, revealPanel = false }) => {
const imported = await evalIn(ctx.page, `(async () => {
const { buildAppExport } = await import('/peerd-engine/index.js');
const envelope = await buildAppExport({
record: { name: 'Versioned App', entryFile: 'index.html', tags: ['visual-fixture'] },
files: { 'index.html': '<!doctype html><title>Versioned App</title><main>Hello</main>' },
});
return chrome.runtime.sendMessage({ type: 'import/apply', envelope });
})()`, true);
rec.check('visual fixture App imported with a Git repository', imported?.ok && imported?.kind === 'app', JSON.stringify(imported));
const appId = imported?.id ?? '';
const cardSelector = `.library-card[data-app-id="${appId}"]`;
let page = null;
try {
const branched = appId ? await evalIn(ctx.page,
`chrome.runtime.sendMessage({ type: 'apps/repository/branch', appId: ${JSON.stringify(appId)}, name: 'feature/visual', checkout: true })`, true) : null;
rec.check('visual fixture exposes existing-branch switching', branched?.ok === true, JSON.stringify(branched));
page = await openWidePage(ctx, 'home/home.html#library', { metrics });
const libraryReady = await waitFor(() => evalIn(page, `
document.querySelector('[data-home-view="library"]')?.getAttribute('aria-current') === 'page'
&& !!document.querySelector('.library-grid')
`), { budgetMs: 15_000, pollMs: 80 });
rec.check('visual fixture opens the Library route', !!libraryReady);
const appReady = await waitFor(() => evalIn(page,
`!!document.querySelector(${JSON.stringify(cardSelector)})`),
{ budgetMs: 20_000, pollMs: 80 });
rec.check('visual fixture App appears in the Library', !!appReady);
await evalIn(page, `document.querySelector(${JSON.stringify(cardSelector)})?.querySelector('.library-kebab')?.click()`);
const historyActionReady = await waitFor(() => evalIn(page, `!![...document.querySelector(${JSON.stringify(cardSelector)})?.querySelectorAll('.library-menu-item') ?? []].find((button) => button.textContent === 'History & Git')`),
{ budgetMs: 5_000, pollMs: 50 });
rec.check('visual fixture exposes the History and Git action', !!historyActionReady);
await evalIn(page, `[...document.querySelector(${JSON.stringify(cardSelector)})?.querySelectorAll('.library-menu-item') ?? []].find((button) => button.textContent === 'History & Git')?.click()`);
const historyReady = await waitFor(() => evalIn(page,
`!!document.querySelector(${JSON.stringify(`${cardSelector} .library-repository .library-commit`)})`),
{ budgetMs: 20_000, pollMs: 80 });
rec.check('visual fixture renders repository history', !!historyReady);
// Git commit IDs include the commit timestamp, and the rows carry RELATIVE
// times, so this visual fixture must normalize both before capture. The
// surrounding branch, history, controls, and layout remain
// production-rendered; only the inherently run-specific values are replaced.
const pinVisualState = () => evalIn(page, `(() => {
const card = document.querySelector(${JSON.stringify(cardSelector)});
const fixedOid = '0000000000';
const fixedWhen = 'just now';
const head = card?.querySelector('.library-repository-head .muted');
if (head) head.textContent = head.textContent.replace(/[0-9a-f]{10}$/i, fixedOid);
for (const oid of card?.querySelectorAll('.library-commit code') ?? []) oid.textContent = fixedOid;
// why: fmtWhen rounds to the nearest minute, so 'just now' becomes '1m ago'
// at 30s, well inside this state's ~60s of waitFor budget. Unpinned, it
// flips either between runs (both themes drift) or between the two shots
// ~100ms apart (dark alone drifts, and the state flaps on dark forever).
for (const when of card?.querySelectorAll('.library-commit > span.muted') ?? []) when.textContent = fixedWhen;
// The app row renders fmtWhen as a bare leading text node followed by an
// optional ' · source' sibling, so replace that node rather than the box.
const meta = card?.querySelector('.library-meta');
const metaWhen = meta?.firstChild;
if (metaWhen?.nodeType === 3 && metaWhen.nodeValue.trim()) metaWhen.nodeValue = fixedWhen;
const commit = card?.querySelector('.library-commit');
const scroller = card?.closest('.home-content');
if (${JSON.stringify(revealPanel)} && commit && scroller) {
const commitRect = commit.getBoundingClientRect();
const scrollerRect = scroller.getBoundingClientRect();
scroller.scrollTop += commitRect.top - scrollerRect.top
- Math.max(0, (scroller.clientHeight - commitRect.height) / 2);
}
const commitRect = commit?.getBoundingClientRect();
const scrollerRect = scroller?.getBoundingClientRect();
return {
commitTop: commitRect?.top ?? null,
commitBottom: commitRect?.bottom ?? null,
scrollerTop: scrollerRect?.top ?? null,
scrollerBottom: scrollerRect?.bottom ?? null,
};
})()`);
const settleNarrowCamera = async () => {
await pinVisualState();
await sleep(80);
return pinVisualState();
};
const visualState = revealPanel ? await settleNarrowCamera() : await pinVisualState();
if (revealPanel) {
// why the explicit number guard: the rects are `?? null` on a miss, and
// `null >= null` coerces to `0 >= 0`, i.e. true. Without it a missing
// commit row or scroller passes this check silently.
const framed = [visualState?.commitTop, visualState?.commitBottom,
visualState?.scrollerTop, visualState?.scrollerBottom].every((v) => typeof v === 'number');
rec.check('visual fixture keeps the narrow commit row in frame',
framed
&& visualState.commitTop >= visualState.scrollerTop
&& visualState.commitBottom <= visualState.scrollerBottom,
JSON.stringify(visualState));
}
// why: a peer notification landing mid-capture leaks an unread badge into the
// top bar. That is global chrome, nothing to do with this fixture, and it is
// exactly the drift that reaches the SECOND shot alone. home-fulltab already
// quiets them the same way before each theme.
const quietNotifications = async () => {
await evalIn(page, `import('/shared/peer-notifications.js')
.then(({ peerNotifications }) => peerNotifications.clear())`, true);
await waitFor(() => evalIn(page, `!document.querySelector('.notif-badge, .notif-banner')`),
{ budgetMs: 2_000, pollMs: 25 });
};
// why beforeShot on BOTH paths: the two theme captures are ~100ms apart, so
// the pinned oid and relative times have to be re-applied for the second one
// or whatever moved in that window lands in the dark shot alone.
const beforeShot = async () => {
await quietNotifications();
return revealPanel ? settleNarrowCamera() : pinVisualState();
};
await rec.visualPage(visualName, page, { beforeShot });
} finally {
try { page?.close(); } catch { /* */ }
if (appId) {
const deleted = await evalIn(ctx.page,
`chrome.runtime.sendMessage({ type: 'apps/delete', appId: ${JSON.stringify(appId)} })`, true)
.catch(() => null);
rec.check('visual fixture App removed after capture', deleted?.ok === true, JSON.stringify(deleted));
}
}
};
export const STATES = [
// --- visual: the pre-unlock setup screen (must capture BEFORE unlock) -------
{
name: 'initial-screen', kind: 'visual', phase: 'pre-unlock',
responder: null,
async run(ctx, rec) { await rec.visual('initial-screen'); },
},
// --- functional: one full happy-path turn ----------------------------------
{
name: 'smoke', kind: 'functional', phase: 'post-unlock',
responder: () => ({ sse: sseText(SMOKE_TEXT) }),
async run(ctx, rec) {
const sent = await rpc(ctx.page, { type: 'agent/send', text: 'ping from e2e' });
rec.check('agent/send accepted', !!sent?.ok, JSON.stringify(sent));
let out = {};
await waitFor(async () => { out = await probe(ctx); return out.assistantText && !out.busy; }, { budgetMs: 25_000 });
rec.check('model call intercepted (no real egress)', ctx.modelCallCount() > 0);
rec.check('user message round-trips', !!out.userText && out.userText.includes('ping from e2e'), JSON.stringify(out.userText));
rec.check('assistant turn renders the streamed text', out.assistantText === SMOKE_TEXT, JSON.stringify(out.assistantText));
rec.check('turn reaches a terminal/idle state', out.busy === false);
await rec.shot('final');
},
},
// --- functional: Chrome accepts and installs the private-network floor ---
{
name: 'browser-network-rules', kind: 'functional', phase: 'post-unlock',
responder: null,
async run(ctx, rec) {
const result = await evalIn(ctx.page, `(async () => {
const rules = await import(chrome.runtime.getURL('peerd-egress/denylist/dnr-rules.js'));
const validations = await Promise.all(
rules.PRIVATE_NETWORK_REGEX_RULES.map(async ({ id, regex }) => ({
id,
...await chrome.declarativeNetRequest.isRegexSupported({
regex,
isCaseSensitive: false,
}),
})),
);
const tab = await chrome.tabs.getCurrent();
const testIdOffset = 1000;
const candidates = rules.buildPrivateNetworkBlockRules({
tabIds: [tab.id],
resourceTypes: rules.CHROME_DNR_RESOURCE_TYPES,
})
.map((rule) => ({ ...rule, id: rule.id + testIdOffset }));
const testRuleIds = candidates.map((rule) => rule.id);
try {
await chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: testRuleIds,
addRules: candidates,
});
const installed = await chrome.declarativeNetRequest.getSessionRules();
return {
validations,
expectedRuleIds: testRuleIds,
privateRuleIds: installed
.filter((rule) => testRuleIds.includes(rule.id))
.map((rule) => rule.id)
.sort((left, right) => left - right),
};
} finally {
await chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: testRuleIds,
});
}
})()`, true);
const evaluationDetail = JSON.stringify(result);
rec.check('Chrome accepts every private-network request regex',
result?.validations?.every(({ isSupported }) => isSupported === true) === true,
JSON.stringify(result?.validations) ?? evaluationDetail);
rec.check('all private-network session rules are installed',
JSON.stringify(result?.privateRuleIds) === JSON.stringify(result?.expectedRuleIds), evaluationDetail);
},
},
// --- functional: private targets never receive a driven-tab request -----
{
name: 'browser-network-floor', kind: 'functional', phase: 'post-unlock',
responder: (_callIndex, request) => {
const body = request?.postData ?? '';
if (body.includes('<actor_agent>')) {
if (networkGuardActorTurn > 0) {
const results = toolResultsIn(body).join('\n');
networkGuardActorResult = results;
if (networkGuardActorTask === 'open' && results.includes('network-guard-controller')) {
networkGuardActorReady = true;
}
if (networkGuardActorTask === 'trusted-blank-burst' && results.includes('clicked')) {
networkGuardTrustedBurstComplete = true;
}
}
const turn = networkGuardActorTurn++;
if (body.includes('tools: page_code')) {
if (turn === 0) {
return { sse: sseToolCall('page_code', {
code: networkGuardActorTask === 'trusted-blank-burst'
? 'await page.snapshot(); return await page.click("@e1");'
: `await page.goto(${JSON.stringify(networkGuardFixtureUrl)}); return await page.content();`,
}) };
}
return { sse: sseText('The network guard controller is ready.') };
}
if (turn === 0) return { sse: sseToolCall('navigate', { url: networkGuardFixtureUrl }) };
if (turn === 1) return { sse: sseToolCall('read_page', {}) };
return { sse: sseText('The network guard controller is ready.') };
}
if (networkGuardActorReady) networkGuardWakeSettled = true;
if (!networkGuardDelegated) {
networkGuardDelegated = true;
return { sse: sseToolCall('message_actor', {
to: 'web',
message: networkGuardActorTask === 'trusted-blank-burst'
? 'Click the only button on the current controller page.'
: `Open ${networkGuardFixtureUrl} and report when the controller is ready.`,
}) };
}
return { sse: sseText('The browser network test is delegated.') };
},
async run(ctx, rec) {
networkGuardActorTurn = 0;
networkGuardDelegated = false;
networkGuardActorReady = false;
networkGuardActorResult = '';
networkGuardActorTask = 'open';
networkGuardTrustedBurstComplete = false;
networkGuardWakeSettled = false;
let probeConnections = 0;
let probeRequests = [];
let controllerRequests = 0;
const controllerAttempts = new Set();
const probeServer = createServer((req, res) => {
probeRequests.push(req.url ?? '/');
res.writeHead(204, { connection: 'close' });
res.end();
});
probeServer.on('connection', () => { probeConnections += 1; });
probeServer.on('upgrade', (req, socket) => {
probeRequests.push(req.url ?? '/');
socket.destroy();
});
const controllerServer = createServer((req, res) => {
controllerRequests += 1;
const requestUrl = new URL(req.url ?? '/', 'http://orders.peerd.test');
if (requestUrl.pathname === '/attempt') {
controllerAttempts.add(requestUrl.searchParams.get('vector') ?? '');
res.writeHead(204);
res.end();
return;
}
if (requestUrl.pathname === '/worker.js') {
res.writeHead(200, {
'content-type': 'application/javascript',
'service-worker-allowed': '/',
'cache-control': 'no-store',
});
res.end(`self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', (event) => event.waitUntil(self.clients.claim()));
self.addEventListener('message', (event) => {
const { fetchUrl, socketUrl, token } = event.data || {};
event.waitUntil((async () => {
await fetch('/attempt?vector=worker-' + encodeURIComponent(token)
+ '-websocket-' + typeof WebSocket, { cache: 'no-store' });
const fetchDone = fetch(fetchUrl, { mode: 'no-cors', cache: 'no-store' }).catch(() => {});
const socketDone = new Promise((resolve) => {
let socket;
const timer = setTimeout(resolve, 4_000);
const finish = () => { clearTimeout(timer); try { socket?.close(); } catch {} resolve(); };
try {
socket = new WebSocket(socketUrl);
socket.addEventListener('open', finish, { once: true });
socket.addEventListener('error', finish, { once: true });
} catch { finish(); }
});
await Promise.all([fetchDone, socketDone]);
event.source?.postMessage({ peerdNetworkGuardToken: token });
})());
});`);
return;
}
res.setHeader('content-type', 'text/html');
res.setHeader('connection', 'close');
if (requestUrl.pathname === '/redirect') {
const target = `http://127.0.0.1:${probePort}/probe?vector=redirect`;
res.writeHead(302, { location: target });
res.end();
return;
}
if (requestUrl.pathname === '/meta') {
const target = `http://127.0.0.1:${probePort}/probe?vector=meta`;
res.end(`<!doctype html><meta http-equiv="refresh" content="0;url=${target}">`);
return;
}
if (requestUrl.pathname === '/script') {
const target = `http://127.0.0.1:${probePort}/probe?vector=script`;
res.end(`<!doctype html><script>location.href=${JSON.stringify(target)}<\/script>`);
return;
}
if (requestUrl.pathname === '/cross-frame-popup') {
const target = `http://127.0.0.1:${probePort}/probe?vector=cross-frame-popup`;
res.end(`<!doctype html><script>
navigator.sendBeacon('/attempt?vector=cross-frame-popup');
const link = document.createElement('a');
link.href = ${JSON.stringify(target)};
link.target = '_blank';
document.body.append(link);
link.click();
<\/script>`);
return;
}
if (requestUrl.pathname === '/cross-frame-blank') {
const target = `http://127.0.0.1:${probePort}/probe?vector=cross-frame-blank`;
res.end(`<!doctype html><script>
const name = 'private-child-' + Math.random();
const link = document.createElement('a');
link.href = 'about:blank';
link.target = name;
document.body.append(link);
link.click();
const child = window.open('', name);
if (child) {
navigator.sendBeacon('/attempt?vector=cross-frame-blank');
child.fetch(${JSON.stringify(target)}, { mode: 'no-cors' }).catch(() => {});
}
<\/script>`);
return;
}
const trustedTarget = `http://127.0.0.1:${probePort}/probe?vector=trusted-click-blank`;
res.end(`<!doctype html><title>network-guard-controller</title>
<h1>network-guard-controller</h1>
<button id="trusted-blank-burst">Open child</button>
<script>
navigator.serviceWorker.register('/worker.js');
document.querySelector('#trusted-blank-burst').addEventListener('click', () => {
const child = window.open(${JSON.stringify(trustedTarget)}, 'trusted-private-child');
if (!child) return;
navigator.sendBeacon('/attempt?vector=trusted-click-blank');
child.fetch(${JSON.stringify(trustedTarget)}, { mode: 'no-cors' }).catch(() => {});
});
<\/script>`);
});
await Promise.all([
new Promise((resolve) => probeServer.listen(0, '127.0.0.1', resolve)),
new Promise((resolve, reject) => controllerServer
.once('error', reject)
.listen(NETWORK_GUARD_CONTROLLER_PORT, '127.0.0.1', resolve)),
]);
const probePort = /** @type {{ port: number }} */ (probeServer.address()).port;
networkGuardFixtureUrl = `http://orders.peerd.test:${NETWORK_GUARD_CONTROLLER_PORT}/`;
const resetProbe = async () => {
await sleep(100);
probeConnections = 0;
probeRequests = [];
};
try {
const fixtureTab = await evalIn(ctx.page, `(async () => {
const tab = await chrome.tabs.create({ active: false });
try {
const updated = await chrome.tabs.update(tab.id, {
url: ${JSON.stringify(networkGuardFixtureUrl)},
});
return { tab, updated };
} catch (error) {
return { tab, error: String(error) };
}
})()`, true);
// Cold Chrome can spend several seconds resolving the synthetic .test
// host before the first byte reaches this fixture. This probes routing,
// not latency; keep the budget above the measured cold-start tail.
await waitFor(() => controllerRequests > 0, { budgetMs: 15_000, pollMs: 25 });
rec.check('the public-looking controller fixture resolves locally',
controllerRequests > 0, JSON.stringify({ controllerRequests, fixtureTab }));
if (typeof fixtureTab?.tab?.id === 'number') {
await evalIn(ctx.page, `chrome.tabs.remove(${fixtureTab.tab.id})`, true).catch(() => {});
}
const sent = await rpc(ctx.page, {
type: 'agent/send',
text: 'Open the browser network test controller and wait.',
});
rec.check('agent/send accepted', !!sent?.ok, JSON.stringify(sent));
const actorReady = await waitFor(() => networkGuardActorReady, {
budgetMs: 30_000, pollMs: 100,
});
rec.check('the web actor loaded the public controller',
actorReady === true, networkGuardActorResult.slice(0, 2000));
await waitFor(async () => networkGuardWakeSettled && !(await probe(ctx)).busy,
{ budgetMs: 15_000, pollMs: 50 });
const tabs = await evalIn(ctx.page, `chrome.tabs.query({}).then((items) => items.map(({ id, url, openerTabId }) => ({ id, url, openerTabId })))`, true);
const drivenTab = tabs.find((tab) => tab.url?.startsWith(networkGuardFixtureUrl));
const networkGuardDiagnostics = typeof drivenTab?.id === 'number'
? null
: await evalIn(ctx.page, `(async () => {
const definitions = await import(chrome.runtime.getURL('peerd-egress/denylist/dnr-rules.js'));
const rules = (await chrome.declarativeNetRequest.getSessionRules())
.filter((rule) => definitions.PRIVATE_NETWORK_RULE_IDS.includes(rule.id));
const guardedTabIds = [...new Set(rules.flatMap((rule) => rule.condition?.tabIds ?? []))];
const matches = [];
for (const tabId of guardedTabIds) {
matches.push({
tabId,
outcome: await chrome.declarativeNetRequest.testMatchOutcome({
url: ${JSON.stringify(networkGuardFixtureUrl)},
type: 'main_frame',
tabId,
}),
});
}
return { guardedTabIds, matches, rules };
})()`, true).catch((error) => ({ error: String(error) }));
rec.check('the controller is owned by the production web actor',
typeof drivenTab?.id === 'number', JSON.stringify({ tabs, networkGuardDiagnostics }));
if (typeof drivenTab?.id !== 'number') throw new Error('driven controller tab not found');
const productionRules = await evalIn(ctx.page, `(async () => {
const rules = await import(chrome.runtime.getURL('peerd-egress/denylist/dnr-rules.js'));
const installed = await chrome.declarativeNetRequest.getSessionRules();
return {
expected: rules.PRIVATE_NETWORK_RULE_IDS,
scoped: installed
.filter((rule) => rules.PRIVATE_NETWORK_RULE_IDS.includes(rule.id)
&& rule.condition?.tabIds?.includes(${drivenTab.id}))
.map((rule) => rule.id)
.sort((left, right) => left - right),
};
})()`, true);
rec.check('production private-network rules are scoped to the driven tab',
JSON.stringify(productionRules.scoped) === JSON.stringify(productionRules.expected),
JSON.stringify(productionRules));
const baselineUrl = `http://127.0.0.1:${probePort}/probe?vector=user-tab`;
const userTab = await evalIn(ctx.page, `chrome.tabs.create({ url: ${JSON.stringify(baselineUrl)}, active: false })`, true);
await waitFor(() => probeRequests.length > 0, { budgetMs: 5_000, pollMs: 25 });
rec.check('an ordinary user tab can still reach the private probe',
probeRequests.length > 0 && probeConnections > 0,
JSON.stringify({ probeConnections, probeRequests }));
if (typeof userTab?.id === 'number') {
await evalIn(ctx.page, `chrome.tabs.remove(${userTab.id})`, true).catch(() => {});
}
await resetProbe();
networkGuardActorTask = 'trusted-blank-burst';
networkGuardActorTurn = 0;
networkGuardDelegated = false;
networkGuardActorResult = '';
networkGuardTrustedBurstComplete = false;
networkGuardWakeSettled = false;
const burstTabIdsBefore = new Set((await evalIn(ctx.page,
'chrome.tabs.query({}).then((tabs) => tabs.map((tab) => tab.id))', true))
.filter((id) => typeof id === 'number'));
const burstSent = await rpc(ctx.page, {
type: 'agent/send',
text: 'Click the controller button once.',
});
rec.check('trusted child-burst turn accepted', !!burstSent?.ok, JSON.stringify(burstSent));
const burstComplete = await waitFor(() => networkGuardTrustedBurstComplete, {
budgetMs: 30_000, pollMs: 100,
});
await sleep(800);
const burstTabs = await evalIn(ctx.page, `chrome.tabs.query({}).then((tabs) =>
tabs.map(({ id, openerTabId, url, pendingUrl, status }) => ({ id, openerTabId, url, pendingUrl, status })))`, true);
const burstObserved = {
completed: burstComplete === true,
attempted: controllerAttempts.has('trusted-click-blank'),
connections: probeConnections,
requests: [...probeRequests],
tabs: burstTabs,
};
rec.check('the trusted click reaches its about:blank child action',
burstObserved.completed && burstObserved.attempted, JSON.stringify(burstObserved));
const expectedRaceRequests = burstObserved.requests.filter((request) => request.includes('trusted-click-blank'));
rec.check('Chrome immediate-child outcome stays inside the documented race envelope',
burstObserved.requests.length === 0
|| expectedRaceRequests.length === burstObserved.requests.length,
JSON.stringify(burstObserved));
rec.check('the protected child is closed instead of left as a blank tab',
!burstTabs.some((tab) => !burstTabIdsBefore.has(tab.id) && tab.openerTabId === drivenTab.id),
JSON.stringify(burstTabs));
rec.check('the source actor receives the fixed child policy outcome',
networkGuardActorResult.includes('protected_child_navigation')
&& networkGuardActorResult.includes('closed')
&& !networkGuardActorResult.includes(`127.0.0.1:${probePort}`),
networkGuardActorResult.slice(0, 2000));
const runVector = async (vector) => {
await resetProbe();
const target = `${vector === 'websocket' ? 'ws' : 'http'}://127.0.0.1:${probePort}/probe?vector=${vector}`;
await evalIn(ctx.page, `(async () => chrome.scripting.executeScript({
target: { tabId: ${drivenTab.id} },
world: 'MAIN',
func: (kind, privateTarget, publicBase) => {
const frame = (url, name = '') => {
const node = document.createElement('iframe');
if (name) node.name = name;
node.hidden = true;
node.src = url;
document.body.append(node);
return node;
};
if (kind === 'fetch') fetch(privateTarget).catch(() => {});
if (kind === 'websocket') new WebSocket(privateTarget);
if (kind === 'image') {
const image = new Image();
image.src = privateTarget;
document.body.append(image);
}
if (kind === 'form') {
const name = 'private-probe-frame';
frame('about:blank', name);
const form = document.createElement('form');
form.method = 'post';
form.action = privateTarget;
form.target = name;
document.body.append(form);
form.submit();
}
if (['redirect', 'meta', 'script'].includes(kind)) {
frame(publicBase + kind);
}
if (kind === 'popup') {
const link = document.createElement('a');
link.href = privateTarget;
link.target = '_blank';
document.body.append(link);
link.click();
}
if (kind === 'cross-frame-popup') {
const crossOrigin = publicBase.replace('orders.peerd.test', 'acct.peerd.test');
frame(crossOrigin + 'cross-frame-popup');
}
if (kind === 'cross-frame-blank') {
const crossOrigin = publicBase.replace('orders.peerd.test', 'acct.peerd.test');
frame(crossOrigin + 'cross-frame-blank');
}
if (kind === 'location') location.href = privateTarget;
},
args: [${JSON.stringify(vector)}, ${JSON.stringify(target)}, ${JSON.stringify(networkGuardFixtureUrl)}],
}))()`, true);
await sleep(800);
const observed = {
connections: probeConnections,
requests: [...probeRequests],
attempted: controllerAttempts.has(vector),
};
if (['popup', 'cross-frame-popup', 'cross-frame-blank'].includes(vector)) {
const children = await evalIn(ctx.page, `chrome.tabs.query({}).then((items) => items.filter((tab) => tab.openerTabId === ${drivenTab.id}).map((tab) => tab.id))`, true);
for (const childId of children) {
await evalIn(ctx.page, `chrome.tabs.remove(${childId})`, true).catch(() => {});
}
}
return observed;
};
for (const vector of [
'fetch', 'websocket', 'image', 'form', 'redirect', 'meta', 'script',
'popup', 'cross-frame-popup', 'cross-frame-blank', 'location',
]) {
const observed = await runVector(vector);
if (vector === 'cross-frame-popup') {
rec.check(`${vector} reaches its cross-origin action`, observed.attempted === true,
JSON.stringify(observed));
}
rec.check(`${vector} causes no private TCP or HTTP side effect`,
observed.connections === 0 && observed.requests.length === 0,
JSON.stringify(observed));
}
// A blocked top-level navigation can leave Chrome displaying its
// network error document. Return to the controlled fixture before the
// worker lane so the test exercises the page worker, not an error page.
await evalIn(ctx.page,
`chrome.tabs.update(${drivenTab.id}, { url: ${JSON.stringify(networkGuardFixtureUrl)} })`, true);
await waitFor(() => evalIn(ctx.page, `chrome.tabs.get(${drivenTab.id}).then((tab) =>
tab.status === 'complete' && tab.url === ${JSON.stringify(networkGuardFixtureUrl)})`, true), {
budgetMs: 5_000, pollMs: 25,
});
const workerRuleShape = await evalIn(ctx.page, `(async () => {
const policy = await import(chrome.runtime.getURL('peerd-egress/index.js'));
const rules = await chrome.declarativeNetRequest.getSessionRules();
return rules.filter((rule) => policy.PRIVATE_NETWORK_INITIATOR_RULE_IDS.includes(rule.id));
})()`, true);
rec.check('the worker fetch floor is no-tab and limited to a visited page domain',
workerRuleShape.length > 0 && workerRuleShape.every((rule) =>
JSON.stringify(rule.condition?.tabIds) === JSON.stringify([-1])
&& JSON.stringify(rule.condition?.initiatorDomains) === JSON.stringify(['orders.peerd.test'])),
JSON.stringify(workerRuleShape));
const initiatorOutcomes = await evalIn(ctx.page, `Promise.all([
chrome.declarativeNetRequest.testMatchOutcome({
url: ${JSON.stringify(`http://127.0.0.1:${probePort}/probe?vector=dnr-match`)},
type: 'xmlhttprequest', tabId: -1,
initiator: ${JSON.stringify(new URL(networkGuardFixtureUrl).origin)},
}),
chrome.declarativeNetRequest.testMatchOutcome({
url: ${JSON.stringify(`http://127.0.0.1:${probePort}/probe?vector=dnr-miss`)},
type: 'xmlhttprequest', tabId: -1,
initiator: ${JSON.stringify(new URL(networkGuardFixtureUrl.replace('orders.peerd.test', 'acct.peerd.test')).origin)},
}),
chrome.declarativeNetRequest.testMatchOutcome({
url: ${JSON.stringify(`ws://127.0.0.1:${probePort}/probe?vector=dnr-socket-match`)},
type: 'websocket', tabId: -1,
initiator: ${JSON.stringify(new URL(networkGuardFixtureUrl).origin)},
}),
])`, true).catch((error) => ({ error: String(error) }));
rec.check('Chrome matches the no-tab rule only for the custodied initiator',
Array.isArray(initiatorOutcomes)
&& initiatorOutcomes[0]?.matchedRules?.length > 0
&& initiatorOutcomes[1]?.matchedRules?.length === 0
&& initiatorOutcomes[2]?.matchedRules?.some(({ ruleId }) => ruleId >= 100),
JSON.stringify(initiatorOutcomes));
const attachWorkerMonitor = async (origin) => {
const target = await waitFor(async () => {
const targets = await fetch(`http://127.0.0.1:${ctx.port}/json/list`).then((response) => response.json());
return targets.find((candidate) => candidate.type === 'service_worker'
&& candidate.url === `${origin}/worker.js`);
}, { budgetMs: 5_000, pollMs: 25 });
if (!target) return null;
const events = [];
const requests = new Map();
const connection = await attach(target.webSocketDebuggerUrl, (method, params) => {
if (method === 'Network.requestWillBeSent') {
requests.set(params.requestId, params.request?.url ?? '');
}
if (method === 'Network.loadingFailed') {
events.push({
url: requests.get(params.requestId) ?? '',
blockedReason: params.blockedReason ?? '',
errorText: params.errorText ?? '',
});
}
if (method === 'Network.webSocketCreated') {
events.push({
url: params.url ?? '',
webSocketCreated: true,
initiator: params.initiator ?? null,
});
}
});
await connection.send('Network.enable');
return { connection, events };
};
const networkFailureFor = (monitor, token) => monitor?.events
.find((event) => event.url.includes(token));
const ordersWorkerMonitor = await attachWorkerMonitor(new URL(networkGuardFixtureUrl).origin);
rec.check('Chrome exposes the fixture service worker to the network test',
ordersWorkerMonitor !== null, JSON.stringify({ monitored: ordersWorkerMonitor !== null }));
const triggerWorker = async (tabId, token) => evalIn(ctx.page, `(async () => {
const [injection] = await chrome.scripting.executeScript({
target: { tabId: ${tabId} },
world: 'MAIN',
func: async (fetchUrl, socketUrl, workerToken) => {
const registration = await navigator.serviceWorker.ready;
const completed = new Promise((resolve) => {
const finish = (value) => {
clearTimeout(timer);
navigator.serviceWorker.removeEventListener('message', onMessage);
resolve(value);
};
const onMessage = (event) => {
if (event.data?.peerdNetworkGuardToken === workerToken) finish(true);
};
const timer = setTimeout(() => finish(false), 6_000);
navigator.serviceWorker.addEventListener('message', onMessage);
});
registration.active.postMessage({ fetchUrl, socketUrl, token: workerToken });
return { secure: isSecureContext, active: !!registration.active, completed: await completed };
},
args: [
${JSON.stringify(`http://127.0.0.1:${probePort}/probe?vector=worker-fetch-${token}`)},
${JSON.stringify(`ws://127.0.0.1:${probePort}/probe?vector=worker-websocket-${token}`)},
${JSON.stringify(token)},
],
});
return injection?.result;
})()`, true);
await resetProbe();
const guardedWorker = await triggerWorker(drivenTab.id, 'guarded');
await waitFor(() => [...controllerAttempts]
.some((value) => value === 'worker-guarded-websocket-function'), {
budgetMs: 5_000, pollMs: 25,
});
await waitFor(() => networkFailureFor(ordersWorkerMonitor, 'worker-fetch-guarded')
&& probeRequests.some((request) => request.includes('worker-websocket-guarded')), {
budgetMs: 5_000, pollMs: 25,
});
const guardedNetworkFailure = networkFailureFor(ordersWorkerMonitor, 'worker-fetch-guarded');
rec.check('the public fixture has an active service worker with WebSocket support',
guardedWorker?.secure === true && guardedWorker?.active === true
&& controllerAttempts.has('worker-guarded-websocket-function'),
JSON.stringify({ guardedWorker, attempts: [...controllerAttempts] }));
rec.check('the custodied page worker fetch causes no private-network side effect',
!probeRequests.some((request) => request.includes('worker-fetch-guarded')),
JSON.stringify({ probeConnections, probeRequests, events: ordersWorkerMonitor?.events }));
rec.check('Chrome reports the custodied worker request as browser-policy blocked',
guardedNetworkFailure?.errorText === 'net::ERR_BLOCKED_BY_CLIENT',
JSON.stringify(guardedNetworkFailure));
rec.check('Chrome worker WebSocket bypass remains visible to the regression test',
probeRequests.some((request) => request.includes('worker-websocket-guarded')),
JSON.stringify({ probeConnections, probeRequests, events: ordersWorkerMonitor?.events }));
// Characterize the browser boundary directly. If even an unscoped
// WebSocket rule does not see this request, adding wider peerd custody
// cannot close the gap and would only disrupt unrelated browsing.
await evalIn(ctx.page, `chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [4999],
addRules: [{
id: 4999,
priority: 10,
action: { type: 'block' },
condition: {
regexFilter: ${JSON.stringify('^wss?://(?:[^/]+@)?127\\.')},
resourceTypes: ['websocket'],
},
}],
})`, true);
await resetProbe();
await triggerWorker(drivenTab.id, 'unscoped-diagnostic');
const unscopedAttempted = await waitFor(() => controllerAttempts
.has('worker-unscoped-diagnostic-websocket-function'), {
budgetMs: 5_000, pollMs: 25,
});
const unscopedReached = await waitFor(() => probeRequests
.some((request) => request.includes('worker-websocket-unscoped-diagnostic')), {
budgetMs: 5_000, pollMs: 25,
});
// This is a browser-characterization probe, not a peerd invariant.
// Chrome 151 defers this service-worker socket until the unscoped rule
// is removed; older lanes let it through. The strict product assertions
// above and below remain scoped-rule isolation and unrelated browsing.
rec.check('Chrome unscoped worker-WebSocket behavior is explicitly classified',
unscopedAttempted === true,
JSON.stringify({ mode: unscopedReached ? 'bypassed' : 'blocked-or-deferred',
probeConnections, probeRequests, events: ordersWorkerMonitor?.events }));