2026-10-13 10:33:31 +02:00
const { app , BrowserWindow , webContents } = require ( 'electron' ); const assert = require ( 'node:assert/strict' ), fs = require ( 'node:fs' ), path = require ( 'node:path' );
app . setPath ( 'userData' , process . env . SAFETYNET_TABS_USERDATA ); app . disableHardwareAcceleration (); require ( '../apps/browser/main.cjs' );
function finish ( code ){ for ( const contents of webContents . getAllWebContents ()){ if ( contents . getURL (). endsWith ( 'index.html' )) continue ; require ( '../apps/browser/page-clipboard-guard.cjs' ). disposePageClipboardGuard ( contents );} app . exit ( code );}
const wait = ms => new Promise ( r => setTimeout ( r , ms )); async function until ( fn , label ){ for ( let i = 0 ; i < 150 ; i ++ ){ if ( await fn ()) return ; await wait ( 100 );} throw Error ( label + ' timed out' );}
app . whenReady (). then ( async ()=>{ try {
let win ; await until (()=>{ win = BrowserWindow . getAllWindows ()[ 0 ]; return win &&! win . webContents . isLoading () && win . webContents . getURL (). endsWith ( 'index.html' );}, 'Shell' );
const run = code => win . webContents . executeJavaScript ( code ), state = async ()=>{ const r = await run ( 'window.safetynet.state()' ); assert . equal ( r . ok , true , r . error ); return r . data ;};
const invoke = async ( method , value )=>{ const r = await run ( `window.safetynet[ ${ JSON . stringify ( method ) } ]( ${ JSON . stringify ( value ) } )` ); assert . equal ( r . ok , true , r . error ); return r . data ;};
await invoke ( 'enroll' ,{ server : process . env . SAFETYNET_TABS_SERVER , code : process . env . SAFETYNET_TABS_CODE }); const base = process . env . SAFETYNET_TABS_SINK ;
await until ( async ()=>{ const s = await state (); return s . tabs ? . length === 1 && s . tabs [ 0 ]. url === base + '/' ;}, 'Initial managed native tab' );
const aId = ( await state ()). activeTabId ; const a = webContents . getAllWebContents (). find ( w => w . getURL () === base + '/' ); assert . ok ( a ); await until (()=> ! a . isLoading (), 'Initial page' );
assert . deepEqual ( await a . executeJavaScript ( '({node:typeof require,bridge:typeof window.safetynet})' ),{ node : 'undefined' , bridge : 'undefined' });
await a . executeJavaScript ( "document.getElementById('remember').value='A survives';window.scrollTo(0,800)" );
const before = ( await ( await fetch ( base + '/counts' )). json ()). requests [ '/' ];
await run ( "document.getElementById('new-tab').click()" ); await until ( async ()=>{ const s = await state (); return s . tabs . length === 2 && s . activeTabId !== aId ;}, 'New tab UI' );
const bId = ( await state ()). activeTabId ; assert . equal (( await state ()). tabs . find ( t => t . id === bId ). startPage , true );
await run ( `document.getElementById('address').value= ${ JSON . stringify ( base + '/b' ) } ;document.getElementById('address-form').requestSubmit()` );
await until ( async ()=>{ const s = await state (); return s . tabs . find ( t => t . id === bId ) ? . url === base + '/b' ;}, 'Second native page' ); const b = webContents . getAllWebContents (). find ( w => w . getURL () === base + '/b' ); assert . ok ( b ); await until (()=> ! b . isLoading (), 'Second page' ); assert . notEqual ( a . id , b . id );
await b . executeJavaScript ( "document.getElementById('remember').value='B survives'" );
const clickTab = id => run ( `(()=>{const element=[...document.querySelectorAll('#tab-strip .browser-tab')].find(el=>el.dataset.tabId=== ${ JSON . stringify ( id ) } );if(!element)throw Error('Tab UI missing');element.querySelector('.tab-label').click()})()` );
await clickTab ( aId ); await until ( async ()=>( await state ()). activeTabId === aId , 'Switch A' ); assert . equal ( await a . executeJavaScript ( "document.getElementById('remember').value" ), 'A survives' ); assert . equal ( await a . executeJavaScript ( 'Math.round(scrollY)' ), 800 ); assert . equal (( await ( await fetch ( base + '/counts' )). json ()). requests [ '/' ], before , 'Selecting a tab reloaded it' );
await a . executeJavaScript ( "document.getElementById('next').click()" , true ); await until (()=> a . getURL () === base + '/a-two' &&! a . isLoading (), 'A history' ); await invoke ( 'back' ); await until (()=> a . getURL () === base + '/' &&! a . isLoading (), 'A back' );
await clickTab ( bId ); await until ( async ()=>( await state ()). activeTabId === bId , 'Switch B' ); assert . equal ( await b . executeJavaScript ( "document.getElementById('remember').value" ), 'B survives' ); await b . executeJavaScript ( "document.getElementById('next').click()" , true ); await until (()=> b . getURL () === base + '/b-two' &&! b . isLoading (), 'B history' ); await invoke ( 'back' ); await until (()=> b . getURL () === base + '/b' &&! b . isLoading (), 'B back' ); assert . equal ( a . getURL (), base + '/' , 'B history changed A' );
for ( const page of [ a , b ]){ const blocked = await page . executeJavaScript ( `fetch( ${ JSON . stringify ( base + '/blocked' ) } ,{method:'POST',body:'TAB_FIXTURE_SECRET'}).then(()=>false).catch(()=>true)` ); assert . equal ( blocked , true , 'Inactive/active tab DLP bypass' ); assert . deepEqual ( await page . executeJavaScript ( '({node:typeof require,bridge:typeof window.safetynet})' ),{ node : 'undefined' , bridge : 'undefined' }); assert . equal ( await page . executeJavaScript ( 'navigator.clipboard.writeText("TAB_FIXTURE_SECRET").then(()=>false).catch(()=>true)' ), true , 'Site clipboard bypass' );}
fs . mkdirSync ( 'artifacts' ,{ recursive : true }); fs . writeFileSync ( 'artifacts/native-tabs.png' ,( await win . webContents . capturePage ()). toPNG ());
await invoke ( 'closeTab' , aId ); assert . equal ( a . isDestroyed (), true ); await invoke ( 'closeTab' , bId ); assert . equal ( b . isDestroyed (), true ); const final = await state (); assert . equal ( final . tabs . length , 1 ); assert . equal ( final . tabs [ 0 ]. startPage , true ); assert . notEqual ( final . activeTabId , bId ); await until ( async ()=> await run ( "!document.getElementById('start-page').hidden" ), 'Fresh start page' );
const fresh = webContents . getAllWebContents (). find ( w => w !== win . webContents && w . getURL () === 'about:blank' ); assert . ok ( fresh , 'Closing last tab did not create native replacement' ); assert . deepEqual ( await fresh . executeJavaScript ( '({node:typeof require,bridge:typeof window.safetynet})' ),{ node : 'undefined' , bridge : 'undefined' });
const revokedA = final . activeTabId ; await invoke ( 'navigate' ,{ url : base + '/revoke-a' , tabId : revokedA }); await until (()=> webContents . getAllWebContents (). some ( w => w . getURL () === base + '/revoke-a' &&! w . isLoading ()), 'Revocation A' ); const revokePageA = webContents . getAllWebContents (). find ( w => w . getURL () === base + '/revoke-a' );
await invoke ( 'createTab' ,{}); const revokedB = ( await state ()). activeTabId ; await invoke ( 'navigate' ,{ url : base + '/revoke-b' , tabId : revokedB }); await until (()=> webContents . getAllWebContents (). some ( w => w . getURL () === base + '/revoke-b' &&! w . isLoading ()), 'Revocation B' ); const revokePageB = webContents . getAllWebContents (). find ( w => w . getURL () === base + '/revoke-b' );
const collectorStatus = async ()=> await ( await fetch ( process . env . SAFETYNET_TABS_COLLECTOR + '/status' )). json (); await until ( async ()=>{ const status = await collectorStatus (); return [ 'browser.ipc' , 'browser.navigation' , 'browser.dns' , 'browser.request' ]. every ( name => status . names . includes ( name )) && status . logs > 0 && status . parentChild && status . correlatedLogs ;}, 'Actual native diagnostics export' );
const enrolled = ( await state ()). deviceId ; const response = await fetch ( ` ${ process . env . SAFETYNET_TABS_SERVER } /api/admin/devices/ ${ enrolled } /revoke` ,{ method : 'POST' , headers : { Authorization : `Bearer ${ process . env . SAFETYNET_TABS_ADMIN } ` , 'content-type' : 'application/json' }, body : '{}' }); assert . equal ( response . status , 200 ); await until ( async ()=> ! ( await state ()). ready , 'Central revocation' );
for ( const page of [ revokePageA , revokePageB ]){ assert . equal ( page . isDestroyed (), false , 'Revocation proof lost native view before request assertion' ); assert . equal ( await page . executeJavaScript ( `fetch( ${ JSON . stringify ( base + '/revoked-public' ) } ,{method:'POST',body:'Public after revocation'}).then(()=>false).catch(()=>true)` ), true , 'Revoked background/foreground page sent request' );}
2026-10-13 18:04:02 +02:00
await wait ( 500 ); const stoppedExports = await collectorStatus (); for ( let i = 0 ; i < 5 ; i ++ ) await state (); await wait ( 600 ); const afterRevocation = await collectorStatus (); assert . ok ( stoppedExports . exportsByService . browser > 0 , 'Browser export attribution missing' ); assert . equal ( afterRevocation . exportsByService . browser , stoppedExports . exportsByService . browser , 'Revoked browser continued diagnostic export' ); fs . writeFileSync ( 'artifacts/native-tabs-revocation-diagnostics.json' , JSON . stringify ({ before : stoppedExports . exportsByService , after : afterRevocation . exportsByService , aggregateBefore : stoppedExports . exports , aggregateAfter : afterRevocation . exports }, null , 2 ));
2026-10-13 10:33:31 +02:00
const report = { actualNativeWebContents : true , actualChromeUi : true , independentDomAndScroll : true , noReloadOnSwitch : true , independentHistory : true , allTabsDlp : true , siteClipboardDenied : true , remoteNodeUnavailable : true , closedContentsDestroyed : true , lastTabFreshGuard : true , centralRevocationAllTabs : true , actualBrowserDiagnostics : true , diagnosticsStoppedOnRevocation : true }; fs . mkdirSync ( 'artifacts' ,{ recursive : true }); fs . writeFileSync ( 'artifacts/native-tabs-proof.json' , JSON . stringify ( report , null , 2 )); console . log ( 'NATIVE_TABS_PROOF_OK ' + JSON . stringify ( report )); finish ( 0 );
} catch ( error ){ console . error ( 'NATIVE_TABS_PROOF_FAILED ' + error . stack ); finish ( 1 );}});