Files
safetynet/tests/native-transport-observer.test.mjs
T

37 lines
8.5 KiB
JavaScript

import test from 'node:test';import assert from 'node:assert/strict';import {EventEmitter} from 'node:events';import {createRequire} from 'node:module';
const {createNativeTransportObserver}=createRequire(import.meta.url)('../apps/browser/native-transport-observer.cjs');
const id='00000000-0000-4000-8000-000000000001';
function fixture(options={}){const debug=new EventEmitter(),commands=[],events=[],failures=[];let attached=true,owner={deviceId:id,policyVersion:1,sessionId:id,documentGeneration:1};debug.isAttached=()=>attached;debug.attach=()=>{attached=true;};debug.detach=()=>{attached=false;debug.emit('detach',{},'target_closed');};debug.sendCommand=async(method,params,sessionId)=>{commands.push({method,params,sessionId});return {};};const channel={isHealthy:()=>attached,enableNetwork:sessionId=>debug.sendCommand('Network.enable',{maxPostDataSize:0},sessionId),autoAttachTargets:sessionId=>debug.sendCommand('Target.setAutoAttach',{autoAttach:true,waitForDebuggerOnStart:true,flatten:true},sessionId),resumeTarget:sessionId=>debug.sendCommand('Runtime.runIfWaitingForDebugger',{},sessionId),subscribeMessage:fn=>{debug.on('message',fn);return ()=>debug.removeListener('message',fn);},subscribeDetach:fn=>{debug.on('detach',fn);return ()=>debug.removeListener('detach',fn);},release:()=>{}};const observer=createNativeTransportObserver({channel,getContext:()=>owner,isCurrent:c=>c.deviceId===owner.deviceId&&c.sessionId===owner.sessionId,audit:async(facts,context)=>events.push({facts,context}),onFailure:async reason=>failures.push(reason),onEstablished:()=>{},...options});return {debug,commands,events,failures,observer,setOwner:value=>owner=value,owner:()=>owner,emit:(method,params,sessionId)=>debug.emit('message',{},method,params,sessionId)};}
test('fixed private commands emit only bounded own-ID lifecycle metadata and retain creation policy across navigation',async()=>{
const f=fixture();await f.observer.install();assert.deepEqual(f.commands.map(x=>x.method),['Network.enable','Target.setAutoAttach']);f.emit('Network.requestWillBeSent',{request:{url:'https://private.example/?secret=private',postData:'private body'}});
f.emit('Network.webTransportCreated',{transportId:'1.1',url:'https://receiver.example/path?token=private',initiator:{stack:'private'}});f.setOwner({...f.owner(),policyVersion:2,documentGeneration:2});f.emit('Network.webTransportClosed',{transportId:'1.1'});await f.observer.flush();assert.equal(f.events.length,2);assert.equal(f.events[0].facts.transportId,f.events[1].facts.transportId);assert.match(f.events[0].facts.transportId,/^[a-f0-9-]{36}$/);assert.equal(f.events[1].context.policyVersion,1);assert.equal(f.events[1].context.documentGeneration,1);assert.equal(f.events[1].facts.connectionEstablished,false);assert.equal(f.events[0].facts.host,'receiver.example');assert.equal(JSON.stringify(f.events).includes('private'),false);assert.equal(JSON.stringify(f.events).includes('1.1'),false);f.observer.dispose();assert.equal(f.failures.length,0);assert.equal(f.debug.listenerCount('message'),0);assert.equal(f.debug.isAttached(),true,'Observer must preserve clipboard-owned debugger');
});
test('fresh workers queue observation and recursive attachment before startup resume',async()=>{
const f=fixture();await f.observer.install();f.emit('Target.attachedToTarget',{sessionId:'worker-1',targetInfo:{type:'worker',url:'blob:https://private.example/private'}});await f.observer.flush();assert.deepEqual(f.commands.slice(2).map(x=>x.method),['Network.enable','Target.setAutoAttach','Runtime.runIfWaitingForDebugger']);f.emit('Network.webTransportCreated',{transportId:'2.1',url:'https://receiver.example/'},'worker-1');f.emit('Network.webTransportConnectionEstablished',{transportId:'2.1'},'worker-1');f.emit('Network.webTransportClosed',{transportId:'2.1'},'worker-1');await f.observer.flush();assert.deepEqual(f.events.map(x=>x.facts.phase),['created','established','closed']);assert.ok(f.events.every(x=>x.facts.realm==='worker'));assert.equal(f.events[1].facts.connectionEstablished,true);assert.equal(f.events[2].facts.connectionEstablished,true);f.observer.dispose();
});
test('unknown session, overflow, unexpected detach and audit outage fail once without releasing protocol access',async()=>{
for(const type of ['unknown','overflow','detach','audit']){const f=fixture(type==='overflow'?{maxAttempts:1}:type==='audit'?{audit:async()=>{throw Error('private payload');}}:{});await f.observer.install();if(type==='unknown')f.emit('Network.webTransportCreated',{transportId:'1.1',url:'https://receiver.example/'},'unknown-session');else if(type==='detach')f.debug.detach();else{f.emit('Network.webTransportCreated',{transportId:'1.1',url:'https://receiver.example/'});if(type==='overflow')f.emit('Network.webTransportCreated',{transportId:'1.2',url:'https://receiver.example/'});}await f.observer.flush();assert.equal(f.observer.status().failed,true,type);const expected={unknown:'transport-observer-unavailable',overflow:'transport-observer-overflow',detach:'transport-observer-detached',audit:'transport-observer-audit-failed'};assert.deepEqual(f.failures,[expected[type]]);if(type==='overflow')assert.equal(f.events.length,1,'Previously captured attempt was erased by overflow');assert.equal(JSON.stringify(f.failures).includes('private payload'),false);f.observer.dispose();}
});
test('command timeout and unhealthy private channel never silently replaces clipboard ownership',async()=>{
const f=fixture({commandTimeoutMs:50});f.debug.sendCommand=()=>new Promise(()=>{});await assert.rejects(f.observer.install());assert.equal(f.observer.status().failed,true);f.observer.dispose();const g=fixture();g.debug.detach();await assert.rejects(g.observer.install());assert.equal(g.commands.length,0);g.observer.dispose();assert.equal(g.debug.isAttached(),false);
});
test('invalid URL metadata and changed device cannot enqueue a successful observed attempt',async()=>{
const f=fixture();await f.observer.install();f.emit('Network.webTransportCreated',{transportId:'1.1',url:'https://user:[email protected]/'});await f.observer.flush();assert.equal(f.events.length,0);assert.equal(f.observer.status().failed,true);f.observer.dispose();const g=fixture({isCurrent:()=>false});await g.observer.install();g.emit('Network.webTransportCreated',{transportId:'1.1',url:'https://receiver.example/'});await g.observer.flush();assert.equal(g.events.length,0);g.observer.dispose();
});
test('IPv6 remains canonical and worker late close preserves captured lifecycle after target detach',async()=>{
const f=fixture();await f.observer.install();f.emit('Target.attachedToTarget',{sessionId:'worker-1',targetInfo:{type:'worker'}});await f.observer.flush();f.emit('Network.webTransportCreated',{transportId:'1.1',url:'https://[::1]:443/private?secret=never-log'},'worker-1');f.emit('Target.detachedFromTarget',{sessionId:'worker-1'});f.emit('Network.webTransportClosed',{transportId:'1.1'},'worker-1');await f.observer.flush();assert.equal(f.events.length,2);assert.equal(f.events[0].facts.host,'[::1]');assert.equal(f.events[1].facts.transportId,f.events[0].facts.transportId);assert.equal(f.failures.length,0);f.observer.dispose();
});
test('captured trusted audit drains after intentional teardown instead of erasing authorized event history',async()=>{
const f=fixture();await f.observer.install();f.emit('Network.webTransportCreated',{transportId:'1.1',url:'https://receiver.example/'});f.observer.dispose();await f.observer.flush();assert.equal(f.events.length,1);assert.equal(f.events[0].facts.phase,'created');assert.equal(f.failures.length,0);
});
test('observer requires a private fixed channel and cannot consume arbitrary debugger authority',()=>{assert.throws(()=>createNativeTransportObserver({contents:{debugger:new EventEmitter()},getContext:()=>null,isCurrent:()=>true,audit:async()=>{},onFailure:()=>{}}),TypeError);});
test('establishment containment is synchronous even while earlier Created persistence is held',async()=>{
let release;const gate=new Promise(resolve=>{release=resolve;});const phases=[],alarms=[];
const f=fixture({audit:async facts=>{phases.push(facts.phase);if(facts.phase==='created')await gate;},onEstablished:context=>alarms.push(context)});await f.observer.install();
f.emit('Network.webTransportCreated',{transportId:'1.1',url:'https://receiver.example/'});await Promise.resolve();
f.emit('Network.webTransportConnectionEstablished',{transportId:'1.1'});assert.equal(alarms.length,1);assert.deepEqual(phases,['created']);
f.emit('Network.webTransportClosed',{transportId:'1.1'});release();await f.observer.flush();assert.deepEqual(phases,['created','established','closed']);assert.equal(alarms.length,1);f.observer.dispose();
});