first commit

This commit is contained in:
Karl committed 2026-10-13 10:34:48 +02:00
commit ff482ebe23
251 files changed
+19126

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
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');}
await wait(500);const stoppedExports=(await collectorStatus()).exports;for(let i=0;i<5;i++)await state();await wait(600);assert.equal((await collectorStatus()).exports,stoppedExports,'Revoked browser continued diagnostic export');
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);}});