Files
safetynet/tests/clipboard-subframes.fixture.cjs
T
2026-10-13 10:34:48 +02:00

47 lines
6.2 KiB
JavaScript

// Isolated native proof fixture; never loaded by the application.
const {app,BrowserWindow,ipcMain,clipboard,ClipboardItem}=require('electron');
const fs=require('node:fs'),path=require('node:path'),os=require('node:os'),http=require('node:http');
const {attachPageClipboardGuard,disposePageClipboardGuard}=require('../apps/browser/page-clipboard-guard.cjs');
const profile=fs.mkdtempSync(path.join(os.tmpdir(),'safetynet-frame-proof-profile-'));
app.setPath('userData',profile);app.setPath('sessionData',profile);
app.enableSandbox();app.disableHardwareAcceleration();
const temporary=path.join(os.tmpdir(),`safetynet-frame-proof-${process.pid}.cjs`),records=[];
const control=process.env.SAFETYNET_CLIP_PROOF_CONTROL==='1';
const hooks=control?'':fs.readFileSync(path.resolve('apps/browser/site-preload.cjs'),'utf8');
fs.writeFileSync(temporary,hooks+`\nlet fsDenied=false;try{require('node:fs');}catch{fsDenied=true;}require('electron').ipcRenderer.send('frame-proof',{main:process.isMainFrame,sandboxed:process.sandboxed,contextIsolated:process.contextIsolated,fsDenied,url:location.href});`);
ipcMain.on('managed:visual-policy',event=>{event.returnValue={enabled:false};});
ipcMain.on('frame-proof',(event,value)=>records.push({...value,processId:event.senderFrame.processId}));
const timeout=setTimeout(()=>app.exit(1),15000);
app.whenReady().then(async()=>{let win,server,original;try{
original=[];for(const item of await clipboard.read()){const data={};for(const type of item.types)data[type]=await item.getType(type);original.push(new ClipboardItem(data));}await clipboard.writeText('frame-proof-sentinel');
server=http.createServer((req,res)=>{res.setHeader('content-type','text/html');res.end(req.url==='/child'?'<textarea id="a">frame-proof-secret</textarea>':`<h1>Frame proof</h1><iframe id="child" srcdoc="<p>Frame text</p>"></iframe><iframe id="cross" src="http://localhost:${server.address().port}/child"></iframe>`);});await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
win=new BrowserWindow({show:true,webPreferences:{sandbox:true,contextIsolation:true,nodeIntegration:false,nodeIntegrationInSubFrames:true,preload:temporary}});await win.loadURL('about:blank');
if(control){win.webContents.debugger.attach('1.3');await win.webContents.debugger.sendCommand('Page.enable',{});await win.webContents.debugger.sendCommand('Page.addScriptToEvaluateOnNewDocument',{source:'void 0;'});}
else await attachPageClipboardGuard(win.webContents,()=>{throw Error('Proof guard detached');});
await win.loadURL(`http://127.0.0.1:${server.address().port}/`);win.focus();win.webContents.focus();await new Promise(resolve=>setTimeout(resolve,300));
win.show();win.focus();win.webContents.focus();await new Promise(resolve=>setTimeout(resolve,500));
const srcdoc=await win.webContents.executeJavaScript(`(()=>{const frame=document.getElementById('child'),d=frame.contentDocument;d.body.innerHTML='<textarea id="a">frame-proof-secret</textarea>';const a=d.getElementById('a');a.focus();a.select();return {copied:d.execCommand('copy'),node:typeof frame.contentWindow.require,process:typeof frame.contentWindow.process,bridge:typeof frame.contentWindow.safetynet};})()`,true);
const srcdocFrame=win.webContents.mainFrame.frames.find(frame=>frame.url==='about:srcdoc');
const srcdocActivated=await srcdocFrame.executeJavaScript(`(()=>{const a=document.getElementById('a');a.focus();a.select();return {copied:document.execCommand('copy')};})()`,true);
await new Promise(resolve=>setTimeout(resolve,300));
const srcdocSafe=await clipboard.readText()==='frame-proof-sentinel';
await clipboard.writeText('frame-proof-sentinel');
const blank=await win.webContents.executeJavaScript(`(()=>{const frame=document.createElement('iframe');document.body.append(frame);const d=frame.contentDocument;d.body.innerHTML='<textarea id="a">frame-proof-secret</textarea>';const a=d.getElementById('a');a.focus();a.select();return {copied:d.execCommand('copy'),node:typeof frame.contentWindow.require,process:typeof frame.contentWindow.process};})()`,true);
const blankFrame=win.webContents.mainFrame.frames.at(-1);
const blankActivated=await blankFrame.executeJavaScript(`(()=>{const a=document.getElementById('a');a.focus();a.select();return {copied:document.execCommand('copy'),node:typeof require,process:typeof process};})()`,true);
await new Promise(resolve=>setTimeout(resolve,300));
const blankSafe=await clipboard.readText()==='frame-proof-sentinel';await new Promise(resolve=>setTimeout(resolve,100));
await clipboard.writeText('frame-proof-sentinel');
const crossFrame=win.webContents.mainFrame.frames.find(frame=>frame.url.includes('localhost'));
win.show();win.focus();win.webContents.focus();await new Promise(resolve=>setTimeout(resolve,500));
const cross=await crossFrame.executeJavaScript(`(()=>{const a=document.getElementById('a');a.focus();a.select();return {copied:document.execCommand('copy'),node:typeof require,process:typeof process,bridge:typeof safetynet};})()`,true);
await new Promise(resolve=>setTimeout(resolve,300));
const crossSafe=await clipboard.readText()==='frame-proof-sentinel';
const mainProcess=records.find(r=>r.main)?.processId,crossProcess=records.find(r=>r.url.includes('localhost'))?.processId;
const oopif=Number.isInteger(mainProcess)&&Number.isInteger(crossProcess)&&crossProcess!==mainProcess;
console.log(JSON.stringify({control,records,srcdoc,srcdocActivated,srcdocSafe,blank,blankActivated,blankSafe,cross,crossSafe,oopif}));
const protectedExpected=srcdocSafe&&blankSafe&&crossSafe;
const controlExpected=srcdocActivated.copied===true&&blankActivated.copied===true&&cross.copied===true&&!srcdocSafe&&!blankSafe&&!crossSafe;
if(original.length)await clipboard.write(original);else await clipboard.clear();if(!control)disposePageClipboardGuard(win.webContents);win.destroy();server.close();clearTimeout(timeout);fs.unlinkSync(temporary);app.exit((control?controlExpected:protectedExpected)&&oopif&&records.every(r=>r.sandboxed&&r.contextIsolated&&r.fsDenied)?0:1);
}catch(error){console.log(JSON.stringify({fixtureError:error.name,message:error.message}));if(original?.length)await clipboard.write(original).catch(()=>{});if(win&&!control)disposePageClipboardGuard(win.webContents);win?.destroy();server?.close();clearTimeout(timeout);try{fs.unlinkSync(temporary);}catch{}app.exit(1);}});